initial migration to TS
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import js from '@eslint/js';
|
||||
import react from 'eslint-plugin-react';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
files: ['src/**/*.{js,jsx}'],
|
||||
files: ['src/**/*.{js,jsx,ts,tsx}'],
|
||||
plugins: {
|
||||
react,
|
||||
'react-hooks': reactHooks,
|
||||
@@ -13,6 +15,7 @@ export default [
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
parser: tseslint.parser,
|
||||
parserOptions: { ecmaFeatures: { jsx: true } },
|
||||
globals: {
|
||||
// Browser APIs
|
||||
@@ -77,7 +80,10 @@ export default [
|
||||
'react/react-in-jsx-scope': 'off', // not needed with React 17+ JSX transform
|
||||
'react/prop-types': 'off', // no PropTypes in this codebase
|
||||
'react/no-unknown-property': 'off', // false positives with Three.js / custom attrs
|
||||
'no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'no-empty': 'off',
|
||||
'no-prototype-builtins': 'off',
|
||||
},
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
452
frontend/package-lock.json
generated
452
frontend/package-lock.json
generated
@@ -15,11 +15,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/three": "^0.183.1",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"c8": "^10.1.3",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"typescript": "^6.0.2",
|
||||
"typescript-eslint": "^8.58.0",
|
||||
"vite": "^5.4.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -319,6 +324,13 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@dimforge/rapier3d-compat": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
|
||||
"integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
@@ -1414,6 +1426,13 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@tweenjs/tween.js": {
|
||||
"version": "23.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
|
||||
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||
@@ -1529,6 +1548,312 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/stats.js": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
|
||||
"integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/three": {
|
||||
"version": "0.183.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.183.1.tgz",
|
||||
"integrity": "sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dimforge/rapier3d-compat": "~0.12.0",
|
||||
"@tweenjs/tween.js": "~23.1.3",
|
||||
"@types/stats.js": "*",
|
||||
"@types/webxr": ">=0.5.17",
|
||||
"@webgpu/types": "*",
|
||||
"fflate": "~0.8.2",
|
||||
"meshoptimizer": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/webxr": {
|
||||
"version": "0.5.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
||||
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz",
|
||||
"integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.58.0",
|
||||
"@typescript-eslint/type-utils": "8.58.0",
|
||||
"@typescript-eslint/utils": "8.58.0",
|
||||
"@typescript-eslint/visitor-keys": "8.58.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.58.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
|
||||
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz",
|
||||
"integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.58.0",
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"@typescript-eslint/typescript-estree": "8.58.0",
|
||||
"@typescript-eslint/visitor-keys": "8.58.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz",
|
||||
"integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.58.0",
|
||||
"@typescript-eslint/types": "^8.58.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz",
|
||||
"integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"@typescript-eslint/visitor-keys": "8.58.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz",
|
||||
"integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz",
|
||||
"integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"@typescript-eslint/typescript-estree": "8.58.0",
|
||||
"@typescript-eslint/utils": "8.58.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz",
|
||||
"integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz",
|
||||
"integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.58.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.58.0",
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"@typescript-eslint/visitor-keys": "8.58.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz",
|
||||
"integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.58.0",
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"@typescript-eslint/typescript-estree": "8.58.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz",
|
||||
"integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
|
||||
"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": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
||||
@@ -1550,6 +1875,13 @@
|
||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@webgpu/types": {
|
||||
"version": "0.1.69",
|
||||
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz",
|
||||
"integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@xyflow/react": {
|
||||
"version": "12.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.1.tgz",
|
||||
@@ -2186,6 +2518,13 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
@@ -2978,6 +3317,31 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
@@ -4156,6 +4520,13 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/meshoptimizer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.0.1.tgz",
|
||||
"integrity": "sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
||||
@@ -4495,6 +4866,19 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
@@ -5262,6 +5646,36 @@
|
||||
"integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"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": {
|
||||
"node": ">=18.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@@ -5353,6 +5767,44 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz",
|
||||
"integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz",
|
||||
"integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.58.0",
|
||||
"@typescript-eslint/parser": "8.58.0",
|
||||
"@typescript-eslint/typescript-estree": "8.58.0",
|
||||
"@typescript-eslint/utils": "8.58.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
||||
|
||||
@@ -24,11 +24,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/three": "^0.183.1",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"c8": "^10.1.3",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"typescript": "^6.0.2",
|
||||
"typescript-eslint": "^8.58.0",
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
measureAngleDegrees,
|
||||
moveAngleWidget,
|
||||
round3,
|
||||
} from './angleMeasureGeometry.js';
|
||||
} from './angleMeasureGeometry';
|
||||
|
||||
function clamp01(value) {
|
||||
return Math.max(0, Math.min(1, Number(value) || 0));
|
||||
@@ -20,7 +20,7 @@ import { hydrateWorkflowState } from './workflowHydration';
|
||||
import useUndoRedo from './useUndoRedo';
|
||||
import { packWorkflow, unpackWorkflow } from './workflowPacking';
|
||||
import { serializeWorkflowState } from './workflowSerialization';
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy.js';
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy';
|
||||
import {
|
||||
buildNodeClipboardPayload,
|
||||
buildNodeClipboardPayloadForIds,
|
||||
@@ -38,8 +38,8 @@ import {
|
||||
beginTrackedNodeRequest,
|
||||
isTrackedNodeRequestCurrent,
|
||||
resolveLoadNodeChannelPath,
|
||||
} from './loadNodeOutputs.js';
|
||||
import { buildDefaultWidgetValues } from './nodeWidgetDefaults.js';
|
||||
} from './loadNodeOutputs';
|
||||
import { buildDefaultWidgetValues } from './nodeWidgetDefaults';
|
||||
import {
|
||||
getHandleType,
|
||||
getInputName,
|
||||
@@ -52,7 +52,7 @@ import {
|
||||
outputTypeCanConnectToTarget,
|
||||
resolveOutputTypeForTarget,
|
||||
checkConnectionValid,
|
||||
} from './connectionUtils.js';
|
||||
} from './connectionUtils';
|
||||
|
||||
import {
|
||||
getSpecTypeAndOptions,
|
||||
@@ -2624,7 +2624,7 @@ function Flow() {
|
||||
|
||||
let nextNodes = currentNodes;
|
||||
let changed = false;
|
||||
let structureChanged = false;
|
||||
const structureChanged = false;
|
||||
|
||||
nextNodes = nextNodes.map((candidate) => {
|
||||
const candidateId = String(candidate.id);
|
||||
@@ -18,9 +18,9 @@ import TextNoteNode from './TextNoteNode';
|
||||
import {
|
||||
getSpecTypeAndOptions, isDataSocketSpec, SOCKET_WIDGET_TYPES, TYPE_COLORS, CAT_COLORS,
|
||||
} from './constants';
|
||||
import { getGroupMinimumSize } from './groupSizing.js';
|
||||
import { buildCombinedInputNameByWidgetName, formatUiLabel } from './nodeWidgetLayout.js';
|
||||
import { applySIPrefix, formatNumericCell, formatTableRowCell, getTableColumns, parseNumberWithUnit } from './valueFormatting.js';
|
||||
import { getGroupMinimumSize } from './groupSizing';
|
||||
import { buildCombinedInputNameByWidgetName, formatUiLabel } from './nodeWidgetLayout';
|
||||
import { applySIPrefix, formatNumericCell, formatTableRowCell, getTableColumns, parseNumberWithUnit } from './valueFormatting';
|
||||
|
||||
// ── Context (provided by App) ─────────────────────────────────────────
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
parseMarkupShapes,
|
||||
sanitizeMarkupColor,
|
||||
sanitizeMarkupShape,
|
||||
} from './markupShapeGeometry.js';
|
||||
} from './markupShapeGeometry';
|
||||
|
||||
function clampFraction(value) {
|
||||
const numeric = Number(value);
|
||||
@@ -1,31 +1,32 @@
|
||||
const EXCLUDED_CANVAS_TARGETS = '.context-menu, .react-flow__node, .react-flow__edge, .react-flow__controls, .react-flow__minimap, .surface-view-container';
|
||||
const CANVAS_AREA_TARGETS = '.react-flow, .react-flow__renderer, .react-flow__viewport, .react-flow__pane, .react-flow__background, .react-flow__selectionpane';
|
||||
|
||||
function getTargetElement(target) {
|
||||
function getTargetElement(target: EventTarget | null): Element | null {
|
||||
if (!target) return null;
|
||||
if (typeof target.closest === 'function') return target;
|
||||
if (target.parentElement && typeof target.parentElement.closest === 'function') {
|
||||
return target.parentElement;
|
||||
if (typeof (target as Element).closest === 'function') return target as Element;
|
||||
const parent = (target as Node).parentElement;
|
||||
if (parent && typeof parent.closest === 'function') {
|
||||
return parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function supportsClosest(target) {
|
||||
function supportsClosest(target: EventTarget | null): boolean {
|
||||
return !!getTargetElement(target);
|
||||
}
|
||||
|
||||
function matchesClosest(target, selector) {
|
||||
function matchesClosest(target: EventTarget | null, selector: string): boolean {
|
||||
const element = getTargetElement(target);
|
||||
return !!element && element.closest(selector) !== null;
|
||||
}
|
||||
|
||||
export function isEditableInteractionTarget(target) {
|
||||
export function isEditableInteractionTarget(target: EventTarget | null): boolean {
|
||||
if (!supportsClosest(target)) return false;
|
||||
if (matchesClosest(target, 'input, textarea, select')) return true;
|
||||
return matchesClosest(target, '[contenteditable="true"]');
|
||||
}
|
||||
|
||||
export function canStartCanvasRightDragZoomTarget(target) {
|
||||
export function canStartCanvasRightDragZoomTarget(target: EventTarget | null): boolean {
|
||||
if (!supportsClosest(target)) return false;
|
||||
if (isEditableInteractionTarget(target)) return false;
|
||||
if (matchesClosest(target, EXCLUDED_CANVAS_TARGETS)) {
|
||||
@@ -34,7 +35,7 @@ export function canStartCanvasRightDragZoomTarget(target) {
|
||||
return matchesClosest(target, CANVAS_AREA_TARGETS);
|
||||
}
|
||||
|
||||
export function canOpenCanvasContextMenuTarget(target) {
|
||||
export function canOpenCanvasContextMenuTarget(target: EventTarget | null): boolean {
|
||||
if (!supportsClosest(target)) return false;
|
||||
if (isEditableInteractionTarget(target)) return false;
|
||||
if (matchesClosest(target, EXCLUDED_CANVAS_TARGETS)) {
|
||||
@@ -43,7 +44,7 @@ export function canOpenCanvasContextMenuTarget(target) {
|
||||
return matchesClosest(target, CANVAS_AREA_TARGETS);
|
||||
}
|
||||
|
||||
export function isSecondaryCanvasContextEvent(event) {
|
||||
export function isSecondaryCanvasContextEvent(event: MouseEvent | null): boolean {
|
||||
if (!event || typeof event.button !== 'number') return false;
|
||||
return event.button === 2 || (event.button === 0 && !!event.ctrlKey);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// ── Connection utility functions ───────────────────────────────────────
|
||||
// Pure functions extracted from App.jsx so they can be independently tested.
|
||||
|
||||
import { socketSpecAcceptsType } from './constants.js';
|
||||
import { socketSpecAcceptsType } from './constants.ts';
|
||||
|
||||
// ── Handle ID helpers ─────────────────────────────────────────────────
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { InputSpec, InputOptions } from './types.ts';
|
||||
|
||||
// ── Shared type & color constants ─────────────────────────────────────
|
||||
|
||||
export const DATA_TYPES = new Set([
|
||||
@@ -8,7 +10,7 @@ export const DATA_TYPES = new Set([
|
||||
|
||||
export const SOCKET_WIDGET_TYPES = new Set(['FLOAT', 'INT']);
|
||||
|
||||
export const TYPE_COLORS = {
|
||||
export const TYPE_COLORS: Record<string, string> = {
|
||||
DATA_FIELD: '#3a7abf',
|
||||
IMAGE: '#00ff08a0',
|
||||
LINE: '#ffbe5c',
|
||||
@@ -26,7 +28,7 @@ export const TYPE_COLORS = {
|
||||
DIRECTORY: '#f97316',
|
||||
};
|
||||
|
||||
export const CAT_COLORS = {
|
||||
export const CAT_COLORS: Record<string, string> = {
|
||||
Input: '#37474f',
|
||||
Display: '#212121',
|
||||
Overlay: '#0f766e',
|
||||
@@ -39,34 +41,34 @@ export const CAT_COLORS = {
|
||||
Grains: '#bf360c',
|
||||
};
|
||||
|
||||
export const SOCKET_COMPATIBILITY = {
|
||||
export const SOCKET_COMPATIBILITY: Record<string, Set<string>> = {
|
||||
FLOAT: new Set(['INT']),
|
||||
INT: new Set(['FLOAT']),
|
||||
LINE: new Set(['COORDPAIR']),
|
||||
};
|
||||
|
||||
const EMPTY_SOCKET_TYPE_SET = new Set();
|
||||
const EMPTY_SOCKET_TYPE_SET: Set<string> = new Set();
|
||||
|
||||
export function getSpecTypeAndOptions(spec) {
|
||||
export function getSpecTypeAndOptions(spec: InputSpec): [string | string[], InputOptions] {
|
||||
if (Array.isArray(spec)) {
|
||||
return [spec[0], spec[1] || {}];
|
||||
return [spec[0], (spec[1] || {}) as InputOptions];
|
||||
}
|
||||
return [spec, {}];
|
||||
}
|
||||
|
||||
export function isDataSocketType(type) {
|
||||
export function isDataSocketType(type: unknown): boolean {
|
||||
return typeof type === 'string' && DATA_TYPES.has(type);
|
||||
}
|
||||
|
||||
export function isDataSocketSpec(spec) {
|
||||
export function isDataSocketSpec(spec: InputSpec): boolean {
|
||||
const [type] = getSpecTypeAndOptions(spec);
|
||||
return isDataSocketType(type);
|
||||
}
|
||||
|
||||
export function getAcceptedSocketTypes(specOrType) {
|
||||
export function getAcceptedSocketTypes(specOrType: InputSpec | string): Set<string> {
|
||||
const [type, opts] = Array.isArray(specOrType)
|
||||
? getSpecTypeAndOptions(specOrType)
|
||||
: [specOrType, {}];
|
||||
? getSpecTypeAndOptions(specOrType as InputSpec)
|
||||
: [specOrType, {} as InputOptions];
|
||||
if (typeof type !== 'string') {
|
||||
return EMPTY_SOCKET_TYPE_SET;
|
||||
}
|
||||
@@ -89,7 +91,7 @@ export function getAcceptedSocketTypes(specOrType) {
|
||||
return accepted;
|
||||
}
|
||||
|
||||
export function socketSpecAcceptsType(sourceType, targetSpecOrType) {
|
||||
export function socketSpecAcceptsType(sourceType: string, targetSpecOrType: InputSpec | string): boolean {
|
||||
if (typeof sourceType !== 'string' || !sourceType) return false;
|
||||
return getAcceptedSocketTypes(targetSpecOrType).has(sourceType);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { extractWorkflow } from './pngMetadata.js';
|
||||
import { extractWorkflow } from './pngMetadata.ts';
|
||||
|
||||
const DEFAULT_WORKFLOW_CANDIDATES = [
|
||||
{ path: '/default-workflow.json', type: 'json' },
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.js';
|
||||
import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.ts';
|
||||
|
||||
const OMITTED_WIDGET_INPUTS_BY_CLASS = {
|
||||
View3D: new Set([
|
||||
@@ -1,6 +1,18 @@
|
||||
export const GROUP_DRAG_RELEASE_DISTANCE = 18;
|
||||
|
||||
export function getPointDistanceOutsideRect(rect, point) {
|
||||
interface Rect {
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export function getPointDistanceOutsideRect(rect: Rect | null, point: Point | null): number {
|
||||
if (!rect || !point) return Infinity;
|
||||
|
||||
const dx = point.x < rect.left
|
||||
@@ -13,6 +25,6 @@ export function getPointDistanceOutsideRect(rect, point) {
|
||||
return Math.hypot(dx, dy);
|
||||
}
|
||||
|
||||
export function shouldReleaseFromGroup(rect, point, threshold = GROUP_DRAG_RELEASE_DISTANCE) {
|
||||
export function shouldReleaseFromGroup(rect: Rect | null, point: Point | null, threshold = GROUP_DRAG_RELEASE_DISTANCE): boolean {
|
||||
return getPointDistanceOutsideRect(rect, point) >= threshold;
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
const DEFAULT_CHILD_WIDTH = 200;
|
||||
const DEFAULT_CHILD_HEIGHT = 120;
|
||||
|
||||
function getNodeSize(node, axis) {
|
||||
interface SizableNode {
|
||||
position?: { x: number; y: number };
|
||||
measured?: { width?: number; height?: number };
|
||||
width?: number;
|
||||
height?: number;
|
||||
style?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
function getNodeSize(node: SizableNode | null | undefined, axis: 'width' | 'height'): number {
|
||||
const fallback = axis === 'width' ? DEFAULT_CHILD_WIDTH : DEFAULT_CHILD_HEIGHT;
|
||||
const measured = Number(node?.measured?.[axis]);
|
||||
if (Number.isFinite(measured) && measured > 0) return measured;
|
||||
@@ -12,7 +20,7 @@ function getNodeSize(node, axis) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function getGroupMinimumSize(memberNodes, {
|
||||
export function getGroupMinimumSize(memberNodes: SizableNode[] | null | undefined, {
|
||||
minWidth = 260,
|
||||
minHeight = 180,
|
||||
paddingX = 24,
|
||||
@@ -1,9 +1,9 @@
|
||||
export function resolveLoadNodeChannelPath({
|
||||
explicitPath = null,
|
||||
resolvedPathInput = null,
|
||||
explicitPath = null as string | null,
|
||||
resolvedPathInput = null as string | null,
|
||||
className = '',
|
||||
widgetValues = {},
|
||||
} = {}) {
|
||||
widgetValues = {} as Record<string, unknown>,
|
||||
} = {}): string {
|
||||
if (typeof explicitPath === 'string' && explicitPath) {
|
||||
return explicitPath;
|
||||
}
|
||||
@@ -19,12 +19,12 @@ export function resolveLoadNodeChannelPath({
|
||||
return '';
|
||||
}
|
||||
|
||||
export function beginTrackedNodeRequest(requestVersions, nodeId) {
|
||||
export function beginTrackedNodeRequest(requestVersions: Map<string, number>, nodeId: string): number {
|
||||
const nextVersion = (requestVersions.get(nodeId) || 0) + 1;
|
||||
requestVersions.set(nodeId, nextVersion);
|
||||
return nextVersion;
|
||||
}
|
||||
|
||||
export function isTrackedNodeRequestCurrent(requestVersions, nodeId, version) {
|
||||
export function isTrackedNodeRequestCurrent(requestVersions: Map<string, number>, nodeId: string, version: number): boolean {
|
||||
return requestVersions.get(nodeId) === version;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy.js';
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy.ts';
|
||||
|
||||
export const NODE_CLIPBOARD_KIND = 'tono/node-selection';
|
||||
export const NODE_CLIPBOARD_MIME = 'application/x-tono-node-selection';
|
||||
@@ -1,12 +1,18 @@
|
||||
export function sortNodesForParentOrder(nodes) {
|
||||
interface NodeLike {
|
||||
id: string | number;
|
||||
parentId?: string | number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export function sortNodesForParentOrder<T extends NodeLike>(nodes: T[]): T[] {
|
||||
const list = Array.isArray(nodes) ? nodes.filter(Boolean) : [];
|
||||
const entries = list.map((node) => ({ id: String(node.id), node }));
|
||||
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
||||
const visiting = new Set();
|
||||
const visited = new Set();
|
||||
const ordered = [];
|
||||
const visiting = new Set<string>();
|
||||
const visited = new Set<string>();
|
||||
const ordered: T[] = [];
|
||||
|
||||
function visit(entry) {
|
||||
function visit(entry: { id: string; node: T } | undefined) {
|
||||
if (!entry) return;
|
||||
const { id, node } = entry;
|
||||
if (visited.has(id) || visiting.has(id)) return;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.js';
|
||||
import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.ts';
|
||||
|
||||
export function getDefaultWidgetValue(spec) {
|
||||
const [type, opts] = getSpecTypeAndOptions(spec);
|
||||
@@ -1,4 +1,4 @@
|
||||
export function formatUiLabel(text) {
|
||||
export function formatUiLabel(text: unknown): string {
|
||||
return String(text ?? '')
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
@@ -6,7 +6,7 @@ export function formatUiLabel(text) {
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function normalizeInputNames(raw) {
|
||||
function normalizeInputNames(raw: unknown): string[] {
|
||||
if (!raw) return [];
|
||||
return (Array.isArray(raw) ? raw : [raw])
|
||||
.map((value) => String(value))
|
||||
@@ -1,4 +1,4 @@
|
||||
export function sanitizeRuntimeValuesForPersistence(className, runtimeValues) {
|
||||
export function sanitizeRuntimeValuesForPersistence(className: string | undefined, runtimeValues: Record<string, unknown> | undefined): Record<string, unknown> {
|
||||
if (!runtimeValues || typeof runtimeValues !== 'object' || Array.isArray(runtimeValues)) {
|
||||
return {};
|
||||
}
|
||||
235
frontend/src/types.ts
Normal file
235
frontend/src/types.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import type { Node, Edge } from '@xyflow/react';
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
// ── Input Specifications ─────────────────────────────────────────────
|
||||
|
||||
export interface InputOptions {
|
||||
label?: string;
|
||||
hidden?: boolean;
|
||||
socket_only?: boolean;
|
||||
accepted_types?: string[];
|
||||
default?: unknown;
|
||||
placeholder?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
slider?: boolean;
|
||||
min_widget?: string;
|
||||
max_widget?: string;
|
||||
text_input?: boolean;
|
||||
color_picker?: boolean;
|
||||
colormap_stops?: boolean;
|
||||
set_widgets?: Record<string, unknown>;
|
||||
show_when_source_type?: Record<string, string[]>;
|
||||
show_when_widget_value?: Record<string, unknown[]>;
|
||||
show_when_input_visible?: string | string[];
|
||||
hide_when_input_connected?: string | string[];
|
||||
choices_by_source_type?: Record<string, string[]>;
|
||||
choices_from_table_input?: string;
|
||||
choices_from_measure_input?: string;
|
||||
inline_with_input?: string | [string];
|
||||
source_type_input?: string;
|
||||
placement?: 'top';
|
||||
}
|
||||
|
||||
/** An input spec is either a bare type string, or a [type, opts] tuple. */
|
||||
export type InputSpec = string | [type: string | string[], opts?: InputOptions];
|
||||
|
||||
// ── Node Definition (from GET /nodes) ────────────────────────────────
|
||||
|
||||
export interface NodeDefinition {
|
||||
input: {
|
||||
required: Record<string, InputSpec>;
|
||||
optional: Record<string, InputSpec>;
|
||||
};
|
||||
output: string[];
|
||||
output_name: string[];
|
||||
output_paths?: string[];
|
||||
category: string;
|
||||
manual_trigger?: boolean;
|
||||
}
|
||||
|
||||
export type NodeDefsRegistry = Record<string, NodeDefinition>;
|
||||
|
||||
// ── Overlay Types ────────────────────────────────────────────────────
|
||||
|
||||
export interface OverlayData {
|
||||
kind: string;
|
||||
image?: string;
|
||||
image_width?: number;
|
||||
image_height?: number;
|
||||
x1?: number;
|
||||
y1?: number;
|
||||
x2?: number;
|
||||
y2?: number;
|
||||
xm?: number;
|
||||
ym?: number;
|
||||
a_locked?: boolean;
|
||||
b_locked?: boolean;
|
||||
section_title?: string;
|
||||
line?: number[];
|
||||
shape?: string;
|
||||
stroke_color?: string;
|
||||
stroke_width?: number;
|
||||
angle_deg?: number;
|
||||
color?: string;
|
||||
label_dx?: number;
|
||||
label_dy?: number;
|
||||
line_thickness?: number;
|
||||
histogram?: unknown;
|
||||
}
|
||||
|
||||
// ── Preview Image Types ──────────────────────────────────────────────
|
||||
|
||||
export interface PreviewPanel {
|
||||
kind: 'image' | 'line_plot';
|
||||
title?: string;
|
||||
image?: string;
|
||||
line?: number[];
|
||||
fallback_image?: string;
|
||||
}
|
||||
|
||||
export interface PreviewPayload {
|
||||
kind: 'image' | 'line_plot' | 'layer_gallery' | 'panels';
|
||||
image?: string;
|
||||
line?: number[];
|
||||
layers?: Array<{ name?: string; image: string }>;
|
||||
fallback_image?: string;
|
||||
panels?: PreviewPanel[];
|
||||
}
|
||||
|
||||
export type PreviewImage = string | PreviewPayload;
|
||||
|
||||
// ── Node Data (attached to each ReactFlow node) ──────────────────────
|
||||
|
||||
export interface GroupProxy {
|
||||
handleId: string;
|
||||
type: string;
|
||||
label: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface NodeData extends Record<string, unknown> {
|
||||
label: string;
|
||||
className: string;
|
||||
definition?: NodeDefinition | null;
|
||||
widgetValues: Record<string, unknown>;
|
||||
runtimeValues?: Record<string, unknown>;
|
||||
|
||||
// Execution results
|
||||
previewImage?: PreviewImage | null;
|
||||
tableRows?: Array<Record<string, unknown>> | null;
|
||||
scalarValue?: number | { value: number | string; unit?: string } | null;
|
||||
meshData?: unknown;
|
||||
overlay?: OverlayData | null;
|
||||
|
||||
// Status
|
||||
error?: string | null;
|
||||
warning?: string | null;
|
||||
processingTimeMs?: number | null;
|
||||
|
||||
// Group node fields
|
||||
proxyInputs?: GroupProxy[];
|
||||
proxyOutputs?: GroupProxy[];
|
||||
childCount?: number;
|
||||
collapsed?: boolean;
|
||||
expandedSize?: { width: number; height: number };
|
||||
|
||||
// Serialization extras
|
||||
extraData?: Record<string, unknown>;
|
||||
output?: string[];
|
||||
output_name?: string[];
|
||||
}
|
||||
|
||||
// ── ReactFlow Node & Edge ────────────────────────────────────────────
|
||||
|
||||
export type TonoNode = Node<NodeData, 'custom'>;
|
||||
export type TonoEdge = Edge<{
|
||||
groupProxyOwner?: string;
|
||||
groupProxyOriginal?: {
|
||||
source?: string;
|
||||
sourceHandle?: string;
|
||||
target?: string;
|
||||
targetHandle?: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
// ── Serialized Workflow ──────────────────────────────────────────────
|
||||
|
||||
export interface SerializedNode {
|
||||
id: string;
|
||||
type?: string;
|
||||
position: { x: number; y: number };
|
||||
width?: number;
|
||||
height?: number;
|
||||
className?: string;
|
||||
parentId?: string;
|
||||
extent?: [[number, number], [number, number]];
|
||||
hidden?: boolean;
|
||||
style?: CSSProperties;
|
||||
dragHandle?: string;
|
||||
data: {
|
||||
label: string;
|
||||
className: string;
|
||||
widgetValues: Record<string, unknown>;
|
||||
runtimeValues?: Record<string, unknown>;
|
||||
extraData?: Record<string, unknown>;
|
||||
output?: string[];
|
||||
output_name?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SerializedEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
sourceHandle?: string;
|
||||
target: string;
|
||||
targetHandle?: string;
|
||||
style?: CSSProperties;
|
||||
hidden?: boolean;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SerializedWorkflow {
|
||||
version: number;
|
||||
nodes: SerializedNode[];
|
||||
edges: SerializedEdge[];
|
||||
packed?: boolean;
|
||||
packedFiles?: Record<string, { filename: string; data: string }>;
|
||||
}
|
||||
|
||||
// ── WebSocket Messages ───────────────────────────────────────────────
|
||||
|
||||
export type WsMessage =
|
||||
| { type: 'execution_start'; data: { prompt_id: string } }
|
||||
| { type: 'executing'; data: { node: string; prompt_id: string } }
|
||||
| { type: 'execution_complete'; data: { prompt_id: string } }
|
||||
| { type: 'execution_error'; data: { node_id: string; message: string } }
|
||||
| { type: 'preview'; data: { node_id: string; image: PreviewImage } }
|
||||
| { type: 'table'; data: { node_id: string; rows: Array<Record<string, unknown>> } }
|
||||
| { type: 'scalar'; data: { node_id: string; value: number | string; unit?: string } }
|
||||
| { type: 'node_timing'; data: { node_id: string; elapsed_ms: number } }
|
||||
| { type: 'mesh3d'; data: { node_id: string; mesh: unknown } }
|
||||
| { type: 'overlay'; data: { node_id: string; overlay: OverlayData } }
|
||||
| { type: 'node_warning'; data: { node_id: string; message: string } }
|
||||
| { type: 'nodes_updated'; data: Record<string, never> };
|
||||
|
||||
// ── Widget description (used by nodeWidgetLayout) ────────────────────
|
||||
|
||||
export interface WidgetDescriptor {
|
||||
name: string;
|
||||
type: string | string[];
|
||||
opts: InputOptions;
|
||||
socketType?: string;
|
||||
}
|
||||
|
||||
// ── Node Context (provided by App to CustomNode) ─────────────────────
|
||||
|
||||
export interface NodeContextValue {
|
||||
executingNodeId: string | null;
|
||||
onWidgetChange: (nodeId: string, name: string, value: unknown) => void;
|
||||
openFileBrowser: (callback: (files: File[]) => void, options?: unknown) => void;
|
||||
openHelp: (label: string) => void;
|
||||
getTableColumns: (nodeId: string, inputName: string) => string[];
|
||||
getMeasurementChoices: (nodeId: string, inputName: string) => string[];
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { toBlob } from 'html-to-image';
|
||||
import { CANVAS_COLORS } from './constants.js';
|
||||
import { CANVAS_COLORS } from './constants.ts';
|
||||
import { CAPTURE_SELECTOR as linePlotSelector } from './LinePlotOverlay';
|
||||
import { CAPTURE_SELECTOR as thresholdSelector } from './ThresholdHistogram';
|
||||
import { CAPTURE_SELECTOR as csSelector } from './CrossSectionOverlay';
|
||||
@@ -1,5 +1,5 @@
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy.js';
|
||||
import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.js';
|
||||
import { sortNodesForParentOrder } from './nodeHierarchy.ts';
|
||||
import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.ts';
|
||||
|
||||
function mergeDefinition(nodeData, defs) {
|
||||
const savedData = nodeData || {};
|
||||
@@ -5,7 +5,7 @@
|
||||
* portable across machines and sessions.
|
||||
*/
|
||||
|
||||
import * as api from './api';
|
||||
import * as api from './api.ts';
|
||||
|
||||
const MAX_PACKED_BYTES = 100 * 1024 * 1024; // 100 MB
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.js';
|
||||
import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.ts';
|
||||
|
||||
export function serializeWorkflowState(nodes, edges) {
|
||||
const compactObject = (value) => {
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
getAngleLabelPosition,
|
||||
measureAngleDegrees,
|
||||
moveAngleWidget,
|
||||
} from '../src/angleMeasureGeometry.js';
|
||||
} from '../src/angleMeasureGeometry.ts';
|
||||
|
||||
test('measureAngleDegrees returns the included angle', () => {
|
||||
assert.equal(measureAngleDegrees(0, 1, 0, 0, 1, 0), 90);
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
canStartCanvasRightDragZoomTarget,
|
||||
isEditableInteractionTarget,
|
||||
isSecondaryCanvasContextEvent,
|
||||
} from '../src/canvasInteractionTargets.js';
|
||||
} from '../src/canvasInteractionTargets.ts';
|
||||
|
||||
function makeTarget(activeSelectors = []) {
|
||||
const selectorSet = new Set(activeSelectors);
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
outputTypeCanConnectToTarget,
|
||||
resolveOutputTypeForTarget,
|
||||
checkConnectionValid,
|
||||
} from '../src/connectionUtils.js';
|
||||
} from '../src/connectionUtils.ts';
|
||||
|
||||
// ── Handle ID helpers ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
getAcceptedSocketTypes,
|
||||
isDataSocketSpec,
|
||||
socketSpecAcceptsType,
|
||||
} from '../src/constants.js';
|
||||
} from '../src/constants.ts';
|
||||
|
||||
test('intrinsic socket compatibility still allows INT to connect to FLOAT sockets', () => {
|
||||
assert.equal(socketSpecAcceptsType('INT', 'FLOAT'), true);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { embedWorkflow } from '../src/pngMetadata.js';
|
||||
import { loadDefaultWorkflowAsset } from '../src/defaultWorkflow.js';
|
||||
import { embedWorkflow } from '../src/pngMetadata.ts';
|
||||
import { loadDefaultWorkflowAsset } from '../src/defaultWorkflow.ts';
|
||||
|
||||
const PNG_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aF9sAAAAASUVORK5CYII=';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
serializeExecutionGraph,
|
||||
getAutoRunnableNodes,
|
||||
hasBlockingAutoRunInput,
|
||||
} from '../src/executionGraph.js';
|
||||
} from '../src/executionGraph.ts';
|
||||
|
||||
test('serializeExecutionGraph excludes isolated nodes from the backend prompt', () => {
|
||||
const nodes = [
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
GROUP_DRAG_RELEASE_DISTANCE,
|
||||
getPointDistanceOutsideRect,
|
||||
shouldReleaseFromGroup,
|
||||
} from '../src/groupDrag.js';
|
||||
} from '../src/groupDrag.ts';
|
||||
|
||||
test('getPointDistanceOutsideRect returns zero inside the rect', () => {
|
||||
const rect = { left: 10, top: 20, right: 110, bottom: 120 };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { getGroupMinimumSize } from '../src/groupSizing.js';
|
||||
import { getGroupMinimumSize } from '../src/groupSizing.ts';
|
||||
|
||||
test('getGroupMinimumSize keeps the base minimum for empty groups', () => {
|
||||
assert.deepEqual(getGroupMinimumSize([]), { width: 260, height: 180 });
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
beginTrackedNodeRequest,
|
||||
isTrackedNodeRequestCurrent,
|
||||
resolveLoadNodeChannelPath,
|
||||
} from '../src/loadNodeOutputs.js';
|
||||
} from '../src/loadNodeOutputs.ts';
|
||||
|
||||
test('resolveLoadNodeChannelPath can resolve a new ImageDemo node from its explicit selection before mount', () => {
|
||||
const resolvedPath = resolveLoadNodeChannelPath({
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
getMarkupPreviewStrokeWidth,
|
||||
sanitizeMarkupColor,
|
||||
sanitizeMarkupShape,
|
||||
} from '../src/markupShapeGeometry.js';
|
||||
} from '../src/markupShapeGeometry.ts';
|
||||
|
||||
test('markup defaults use arrow and red', () => {
|
||||
assert.equal(MARKUP_DEFAULT_SHAPE, 'arrow');
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
instantiateNodeClipboardPayload,
|
||||
NODE_CLIPBOARD_KIND,
|
||||
parseNodeClipboardPayload,
|
||||
} from '../src/nodeClipboard.js';
|
||||
} from '../src/nodeClipboard.ts';
|
||||
|
||||
test('buildNodeClipboardPayload keeps only selected nodes and internal edges', () => {
|
||||
const nodes = [
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { sortNodesForParentOrder } from '../src/nodeHierarchy.js';
|
||||
import { hydrateWorkflowState } from '../src/workflowHydration.js';
|
||||
import { instantiateNodeClipboardPayload, NODE_CLIPBOARD_KIND } from '../src/nodeClipboard.js';
|
||||
import { sortNodesForParentOrder } from '../src/nodeHierarchy.ts';
|
||||
import { hydrateWorkflowState } from '../src/workflowHydration.ts';
|
||||
import { instantiateNodeClipboardPayload, NODE_CLIPBOARD_KIND } from '../src/nodeClipboard.ts';
|
||||
|
||||
test('sortNodesForParentOrder places parents before descendants', () => {
|
||||
const nodes = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { buildDefaultWidgetValues, getDefaultWidgetValue } from '../src/nodeWidgetDefaults.js';
|
||||
import { buildDefaultWidgetValues, getDefaultWidgetValue } from '../src/nodeWidgetDefaults.ts';
|
||||
|
||||
test('enum widget defaults honor opts.default instead of the first option', () => {
|
||||
assert.equal(
|
||||
|
||||
@@ -4,7 +4,7 @@ import assert from 'node:assert/strict';
|
||||
import {
|
||||
buildCombinedInputNameByWidgetName,
|
||||
getWidgetCombinedInputName,
|
||||
} from '../src/nodeWidgetLayout.js';
|
||||
} from '../src/nodeWidgetLayout.ts';
|
||||
|
||||
test('getWidgetCombinedInputName pairs same-label hide_when_input_connected widgets with their matching input', () => {
|
||||
const dataInputByName = new Map([
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { embedWorkflow, extractWorkflow } from '../src/pngMetadata.js';
|
||||
import { embedWorkflow, extractWorkflow } from '../src/pngMetadata.ts';
|
||||
|
||||
const PNG_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aF9sAAAAASUVORK5CYII=';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
formatDisplayUnit,
|
||||
formatTableRowCell,
|
||||
getTableColumns,
|
||||
} from '../src/valueFormatting.js';
|
||||
} from '../src/valueFormatting.ts';
|
||||
|
||||
test('getTableColumns hides companion record-table unit columns', () => {
|
||||
const columns = getTableColumns([
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { OVERLAY_CAPTURE_SELECTORS, captureViewportBlob } from '../src/workflowCapture.js';
|
||||
import { OVERLAY_CAPTURE_SELECTORS, captureViewportBlob } from '../src/workflowCapture.ts';
|
||||
|
||||
function makeElement(name, { tagName = 'DIV', width = 160, height = 90, src = '' } = {}) {
|
||||
return {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { hydrateWorkflowState } from '../src/workflowHydration.js';
|
||||
import { serializeWorkflowState } from '../src/workflowSerialization.js';
|
||||
import { hydrateWorkflowState } from '../src/workflowHydration.ts';
|
||||
import { serializeWorkflowState } from '../src/workflowSerialization.ts';
|
||||
|
||||
test('serializeWorkflowState keeps only stable workflow fields needed for reload', () => {
|
||||
const nodes = [
|
||||
|
||||
19
frontend/tsconfig.json
Normal file
19
frontend/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
"strict": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"rewriteRelativeImportExtensions": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user