diff --git a/copy-and-watch.mjs b/copy-and-watch.mjs deleted file mode 100644 index 65fe5b5..0000000 --- a/copy-and-watch.mjs +++ /dev/null @@ -1,57 +0,0 @@ -import fs from 'fs'; -import path from 'path'; - -// custom plugin to copy files and watch them -export default function copyAndWatch(config) { - const resolvedConfig = { - targets: [] - }; - - // resolve source directories into files - config.targets.forEach((target) => { - const readRec = (pathname) => { - if (!fs.existsSync(pathname)) { - console.log(`skipping missing file ${target.src}`); - } else { - if (fs.lstatSync(pathname).isDirectory()) { - const children = fs.readdirSync(pathname); - children.forEach((childPath) => { - readRec(path.join(pathname, childPath)); - }); - } else { - let dest; - if (fs.lstatSync(target.src).isDirectory()) { - dest = path.join(target.dest || '', path.basename(target.destFilename || target.src), path.relative(target.src, pathname)); - } else { - dest = path.join(target.dest || '', path.basename(target.destFilename || target.src)); - } - resolvedConfig.targets.push({ - src: pathname, - dest: dest, - transform: target.transform - }); - } - } - }; - readRec(target.src); - }); - - return { - name: 'copy-and-watch', - async buildStart() { - resolvedConfig.targets.forEach((target) => { - this.addWatchFile(target.src); - }); - }, - async generateBundle() { - resolvedConfig.targets.forEach((target) => { - const contents = fs.readFileSync(target.src); - this.emitFile({ - type: 'asset', - fileName: target.dest, - source: target.transform ? target.transform(contents, target.src) : contents - }) - }); - } - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs index 3cc441a..d6fb70c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,16 +1,51 @@ import playcanvasConfig from '@playcanvas/eslint-config'; +import tsPlugin from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; import globals from 'globals'; export default [ ...playcanvasConfig, + { + files: ['**/*.ts'], + languageOptions: { + parser: tsParser, + globals: { + ...globals.browser + } + }, + plugins: { + '@typescript-eslint': tsPlugin + }, + settings: { + 'import/resolver': { + typescript: {} + } + }, + rules: { + ...tsPlugin.configs.recommended.rules, + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + 'import/no-unresolved': 'off', + 'jsdoc/require-param-type': 'off', + 'jsdoc/require-returns-type': 'off', + 'lines-between-class-members': 'off', + 'no-duplicate-imports': 'off', + 'import/order': 'off' + } + }, + { + files: ['**/*.d.ts'], + rules: { + 'no-multiple-empty-lines': 'off' + } + }, { files: ['**/*.js', '**/*.mjs'], languageOptions: { - ecmaVersion: 2022, - sourceType: 'module', globals: { - ...globals.browser, - 'JSZip': 'readonly' + ...globals.node } }, rules: { @@ -23,4 +58,3 @@ export default [ ] } ]; - diff --git a/package-lock.json b/package-lock.json index 0b25f50..f115e14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,14 +15,55 @@ "@rollup/plugin-alias": "6.0.0", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "16.0.3", + "@rollup/plugin-typescript": "12.1.4", + "@types/react": "19.2.7", + "@typescript-eslint/eslint-plugin": "8.51.0", + "@typescript-eslint/parser": "8.51.0", "concurrently": "9.2.1", "cross-env": "10.1.0", "eslint": "9.39.2", + "eslint-import-resolver-typescript": "4.4.4", "globals": "17.0.0", "playcanvas": "2.14.4", - "rollup": "4.54.0", + "rollup": "4.55.1", "rollup-plugin-sass": "1.15.3", - "serve": "14.2.5" + "serve": "14.2.5", + "tslib": "2.8.1", + "typescript": "5.9.3" + } + }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@epic-web/invariant": { @@ -258,6 +299,19 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", @@ -688,6 +742,33 @@ } } }, + "node_modules/@rollup/plugin-typescript": { + "version": "12.1.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.4.tgz", + "integrity": "sha512-s5Hx+EtN60LMlDBvl5f04bEiFZmAepk27Q+mr85L/00zPDn1jtzlTV6FWn81MaIwqfWzKxmOJrBWHU6vtQyedQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", @@ -705,16 +786,698 @@ "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "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.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", + "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/type-utils": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.2.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.51.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.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.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz", + "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "debug": "^4.3.4" + }, + "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", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", + "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.51.0", + "@typescript-eslint/types": "^8.51.0", + "debug": "^4.3.4" + }, + "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.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", + "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.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.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", + "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", + "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.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", + "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.2.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", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", + "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", + "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.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", + "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.51.0", + "@typescript-eslint/tsconfig-utils": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.2.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.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "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==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz", + "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.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", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", + "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", - "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", "cpu": [ "arm" ], @@ -725,10 +1488,10 @@ "android" ] }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", - "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", "cpu": [ "arm64" ], @@ -739,10 +1502,10 @@ "android" ] }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", - "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", "cpu": [ "arm64" ], @@ -753,10 +1516,10 @@ "darwin" ] }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", - "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", "cpu": [ "x64" ], @@ -767,24 +1530,10 @@ "darwin" ] }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", - "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", - "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", "cpu": [ "x64" ], @@ -795,10 +1544,10 @@ "freebsd" ] }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", - "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", "cpu": [ "arm" ], @@ -809,10 +1558,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", - "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", "cpu": [ "arm" ], @@ -823,10 +1572,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", - "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", "cpu": [ "arm64" ], @@ -837,10 +1586,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", - "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", "cpu": [ "arm64" ], @@ -851,24 +1600,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", - "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", - "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", "cpu": [ "ppc64" ], @@ -879,10 +1614,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", - "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", "cpu": [ "riscv64" ], @@ -893,10 +1628,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", - "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", "cpu": [ "riscv64" ], @@ -907,10 +1642,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", - "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", "cpu": [ "s390x" ], @@ -921,10 +1656,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", - "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", "cpu": [ "x64" ], @@ -935,10 +1670,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", - "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", "cpu": [ "x64" ], @@ -949,24 +1684,27 @@ "linux" ] }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", - "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", "cpu": [ - "arm64" + "wasm32" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "openharmony" - ] + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", - "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", "cpu": [ "arm64" ], @@ -977,10 +1715,10 @@ "win32" ] }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", - "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", "cpu": [ "ia32" ], @@ -991,24 +1729,10 @@ "win32" ] }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", - "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", - "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", "cpu": [ "x64" ], @@ -1019,62 +1743,6 @@ "win32" ] }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true, - "license": "MIT" - }, - "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/types": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", - "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", - "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/@webgpu/types": { "version": "0.1.68", "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.68.tgz", @@ -1967,6 +2635,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==", + "dev": true, + "license": "MIT" + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -2415,6 +3090,31 @@ } } }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -2437,6 +3137,41 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz", + "integrity": "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.2.0", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, "node_modules/eslint-module-utils": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", @@ -2726,6 +3461,24 @@ "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/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2961,6 +3714,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -3296,6 +4062,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -3957,6 +4746,22 @@ "license": "MIT", "optional": true }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4633,10 +5438,20 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/rollup": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", - "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", "dev": true, "license": "MIT", "dependencies": { @@ -4650,28 +5465,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.54.0", - "@rollup/rollup-android-arm64": "4.54.0", - "@rollup/rollup-darwin-arm64": "4.54.0", - "@rollup/rollup-darwin-x64": "4.54.0", - "@rollup/rollup-freebsd-arm64": "4.54.0", - "@rollup/rollup-freebsd-x64": "4.54.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", - "@rollup/rollup-linux-arm-musleabihf": "4.54.0", - "@rollup/rollup-linux-arm64-gnu": "4.54.0", - "@rollup/rollup-linux-arm64-musl": "4.54.0", - "@rollup/rollup-linux-loong64-gnu": "4.54.0", - "@rollup/rollup-linux-ppc64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-musl": "4.54.0", - "@rollup/rollup-linux-s390x-gnu": "4.54.0", - "@rollup/rollup-linux-x64-gnu": "4.54.0", - "@rollup/rollup-linux-x64-musl": "4.54.0", - "@rollup/rollup-openharmony-arm64": "4.54.0", - "@rollup/rollup-win32-arm64-msvc": "4.54.0", - "@rollup/rollup-win32-ia32-msvc": "4.54.0", - "@rollup/rollup-win32-x64-gnu": "4.54.0", - "@rollup/rollup-win32-x64-msvc": "4.54.0", + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" } }, @@ -4777,9 +5595,9 @@ } }, "node_modules/sass": { - "version": "1.97.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz", - "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==", + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz", + "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", "dev": true, "license": "MIT", "dependencies": { @@ -5171,6 +5989,16 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -5383,6 +6211,23 @@ "node": ">=6" } }, + "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/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5407,6 +6252,19 @@ "tree-kill": "cli.js" } }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -5545,6 +6403,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -5564,6 +6436,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, "node_modules/update-check": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", diff --git a/package.json b/package.json index 94829a6..f1470ca 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "webgpu" ], "license": "MIT", - "main": "src/index.js", + "main": "src/index.ts", "bugs": { "url": "https://github.com/playcanvas/texture-tool/issues" }, @@ -28,14 +28,21 @@ "@rollup/plugin-alias": "6.0.0", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "16.0.3", + "@rollup/plugin-typescript": "12.1.4", + "@types/react": "19.2.7", + "@typescript-eslint/eslint-plugin": "8.51.0", + "@typescript-eslint/parser": "8.51.0", "concurrently": "9.2.1", "cross-env": "10.1.0", "eslint": "9.39.2", + "eslint-import-resolver-typescript": "4.4.4", "globals": "17.0.0", "playcanvas": "2.14.4", - "rollup": "4.54.0", + "rollup": "4.55.1", "rollup-plugin-sass": "1.15.3", - "serve": "14.2.5" + "serve": "14.2.5", + "tslib": "2.8.1", + "typescript": "5.9.3" }, "scripts": { "build": "rollup -c", diff --git a/plugins/copy-and-watch.mjs b/plugins/copy-and-watch.mjs new file mode 100644 index 0000000..9250642 --- /dev/null +++ b/plugins/copy-and-watch.mjs @@ -0,0 +1,65 @@ +import fs from 'fs'; +import path from 'path'; + +const isDir = src => fs.lstatSync(src).isDirectory(); + +/** + * Copy files and directories to the output directory and watch for changes. + * + * @param {object[]} targets - The array of objects with src, dest, and transform properties. + * @param {string} targets.src - The source file or directory. + * @param {string} targets.dest - The destination directory. + * @param {function} targets.transform - Optional function to transform file contents. + * @returns {import('rollup').Plugin} - The rollup plugin. + */ +export function copyAndWatch(targets = []) { + const resolvedTargets = []; + + // resolve source directories into files + targets.forEach((target) => { + const readTargets = (pathname) => { + if (!fs.existsSync(pathname)) { + console.log(`skipping missing file ${target.src}`); + return; + } + + if (isDir(pathname)) { + fs.readdirSync(pathname).forEach((childPath) => { + readTargets(path.join(pathname, childPath)); + }); + return; + } + + let dest = path.join(target.dest || '', path.basename(target.src)); + if (isDir(target.src)) { + dest = path.join(dest, path.relative(target.src, pathname)); + } + + resolvedTargets.push({ + src: pathname, + dest: dest, + transform: target.transform + }); + }; + readTargets(target.src); + }); + + return { + name: 'copy-and-watch', + buildStart() { + resolvedTargets.forEach((target) => { + this.addWatchFile(target.src); + }); + }, + generateBundle() { + resolvedTargets.forEach((target) => { + const contents = fs.readFileSync(target.src); + this.emitFile({ + type: 'asset', + fileName: target.dest, + source: target.transform ? target.transform(contents, target.src) : contents + }); + }); + } + }; +} diff --git a/rollup.config.mjs b/rollup.config.mjs index e3f8b58..354ec64 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,9 +1,10 @@ import path from 'path'; -import copyAndWatch from "./copy-and-watch.mjs"; +import { copyAndWatch } from './plugins/copy-and-watch.mjs'; import resolve from '@rollup/plugin-node-resolve'; import alias from '@rollup/plugin-alias'; import json from '@rollup/plugin-json'; import sass from 'rollup-plugin-sass'; +import typescript from '@rollup/plugin-typescript'; const PROD_BUILD = process.env.BUILD_TYPE === 'prod'; const HREF = process.env.BASE_HREF || ''; @@ -20,8 +21,21 @@ const aliasEntries = { 'pcui': PCUI_PATH }; +const TARGETS = [ + { + src: 'src/index.html', + transform: (contents) => { + return contents.toString().replace('__BASE_HREF__', HREF); + } + }, + { src: 'static/playcanvas-logo.png' }, + { src: 'static/lib' }, + { src: 'static/textures' }, + { src: 'src/fonts.css' } +]; + export default { - input: 'src/index.js', + input: 'src/index.ts', output: { dir: 'dist', format: 'es', @@ -31,25 +45,17 @@ export default { include: 'src/**' }, plugins: [ - copyAndWatch({ - targets: [{ - src: 'src/index.html', - dest: '', - transform: (contents, filename) => { - return contents.toString().replace('__BASE_HREF__', HREF); - } - }, - { src: 'static/playcanvas-logo.png' }, - { src: 'static/lib' }, - { src: 'static/textures' }, - { src: 'src/fonts.css' } - ]}), + copyAndWatch(TARGETS), alias({ entries: aliasEntries }), resolve(), + typescript({ + tsconfig: './tsconfig.json' + }), sass({ insert: false, output: 'dist/style.css', - outputStyle: 'compressed' + outputStyle: 'compressed', + api: 'modern' }), json() ] diff --git a/src/const.js b/src/const.ts similarity index 94% rename from src/const.js rename to src/const.ts index a2a66e1..b73a11e 100644 --- a/src/const.js +++ b/src/const.ts @@ -37,7 +37,7 @@ import { TEXTURETYPE_SWIZZLEGGGR } from 'playcanvas'; -const PixelFormatTable = { +const PixelFormatTable: { [key: number]: string } = { [PIXELFORMAT_A8]: 'A8', [PIXELFORMAT_L8]: 'L8', [PIXELFORMAT_L8_A8]: 'LA8', @@ -71,13 +71,13 @@ const PixelFormatTable = { [PIXELFORMAT_ATC_RGBA]: 'ATC_A' }; -const TextureTypeTable = { +const TextureTypeTable: { [key: number]: string } = { [TEXTURETYPE_DEFAULT]: 'default', [TEXTURETYPE_RGBM]: 'rgbm', [TEXTURETYPE_RGBE]: 'rgbe', [TEXTURETYPE_RGBP]: 'rgbp', [TEXTURETYPE_SWIZZLEGGGR]: 'swizzleGGGR' -}; +} as { [key: number]: string }; export { PixelFormatTable, diff --git a/src/declaration.d.ts b/src/declaration.d.ts new file mode 100644 index 0000000..b10fef2 --- /dev/null +++ b/src/declaration.d.ts @@ -0,0 +1,32 @@ +// File System Access API types +interface FileSystemHandle { + kind: 'file' | 'directory'; + name: string; +} + +interface FileSystemFileHandle extends FileSystemHandle { + kind: 'file'; + getFile(): Promise; +} + +interface FileSystemDirectoryHandle extends FileSystemHandle { + kind: 'directory'; + values(): AsyncIterable; +} + +interface Window { + showOpenFilePicker?: (options?: { + multiple?: boolean; + types?: Array<{ + description?: string; + accept?: Record; + }>; + }) => Promise; + showDirectoryPicker?: () => Promise; +} + +// DataTransferItem extensions +interface DataTransferItem { + getAsFileSystemHandle?: () => Promise; +} + diff --git a/src/drop-handler.js b/src/drop-handler.ts similarity index 54% rename from src/drop-handler.js rename to src/drop-handler.ts index fa49c60..9cb588d 100644 --- a/src/drop-handler.js +++ b/src/drop-handler.ts @@ -1,9 +1,12 @@ import { Events } from '@playcanvas/observer'; +import type { TextureManager } from './texture-manager'; // handle file drag/drop // fires 'load' event class DropHandler extends Events { - constructor(dom, textureManager) { + textureManager: TextureManager; + + constructor(dom: HTMLElement, textureManager: TextureManager) { super(); this.textureManager = textureManager; @@ -11,18 +14,20 @@ class DropHandler extends Events { // handle drop target for env maps and models const transferType = 'Files'; - dom.addEventListener('dragover', (ev) => { - if (ev.dataTransfer.types.includes(transferType)) { + dom.addEventListener('dragover', (ev: DragEvent) => { + if (ev.dataTransfer && ev.dataTransfer.types.includes(transferType)) { ev.preventDefault(); ev.stopPropagation(); ev.dataTransfer.dropEffect = 'copy'; } }); - dom.addEventListener('drop', (ev) => { + dom.addEventListener('drop', (ev: DragEvent) => { ev.preventDefault(); ev.stopPropagation(); - this.emit('filesDropped', ev.dataTransfer.items); + if (ev.dataTransfer) { + this.emit('filesDropped', ev.dataTransfer.items); + } }, false); } } diff --git a/src/export-panel.js b/src/export-panel.ts similarity index 69% rename from src/export-panel.js rename to src/export-panel.ts index 073ce18..9f555d5 100644 --- a/src/export-panel.js +++ b/src/export-panel.ts @@ -1,18 +1,27 @@ import { Button, Panel, Container } from 'pcui'; -import { RenderTarget } from 'playcanvas'; - -import { HdrExporter } from './hdr-exporter.js'; -import { Helpers } from './helpers.js'; -import { PngExporter } from './png-exporter.js'; +import { RenderTarget, Texture, WebglGraphicsDevice } from 'playcanvas'; +import type { EventHandle } from '@playcanvas/observer'; + +import { HdrExporter } from './hdr-exporter'; +import { Helpers } from './helpers'; +import { PngExporter } from './png-exporter'; +import type { TextureManager } from './texture-manager'; +import type { TextureDoc } from './texture-doc'; + +interface Exporter { + run(words: Uint32Array, width: number, height: number): Promise | Uint8Array; + extension: string; +} -const readPixels = (texture, face) => { - const rt = new RenderTarget({ colorBuffer: texture, depth: false, face: face }); +const readPixels = (texture: Texture, face: number | null): Uint32Array => { + const rt = new RenderTarget({ colorBuffer: texture, depth: false, face: face ?? undefined }); const data = new Uint8ClampedArray(texture.width * texture.height * 4); - const device = texture.device; + const device = texture.device as WebglGraphicsDevice; + const gl = device.gl; - device.setFramebuffer(rt._glFrameBuffer); + device.setFramebuffer(rt.impl._glFrameBuffer); device.initRenderTarget(rt); - device.gl.readPixels(0, 0, texture.width, texture.height, device.gl.RGBA, device.gl.UNSIGNED_BYTE, data); + gl.readPixels(0, 0, texture.width, texture.height, gl.RGBA, gl.UNSIGNED_BYTE, data); rt.destroy(); @@ -20,8 +29,8 @@ const readPixels = (texture, face) => { }; // download the data uri -const download = (filename, data) => { - const blob = new Blob([data], { type: 'octet/stream' }); +const download = (filename: string, data: Uint8Array): void => { + const blob = new Blob([data.buffer as ArrayBuffer], { type: 'octet/stream' }); const url = window.URL.createObjectURL(blob); const lnk = document.createElement('a'); @@ -35,20 +44,20 @@ const download = (filename, data) => { 0, 0, 0, 0, 0, false, false, false, false, 0, null); lnk.dispatchEvent(e); - } else if (lnk.fireEvent) { - lnk.fireEvent('onclick'); + } else if ((lnk as any).fireEvent) { + (lnk as any).fireEvent('onclick'); } window.URL.revokeObjectURL(url); }; class TextureExportPanel extends Panel { - constructor(textureManager, args = { }) { + constructor(textureManager: TextureManager, args: Record = {}) { Object.assign(args, { id: 'texture-export-pane', headerText: 'Export', collapsible: true, - flexGrow: 1 + flexGrow: '1' }); super(args); @@ -85,15 +94,15 @@ class TextureExportPanel extends Panel { this.append(exportToPngContainer); this.append(exportToHdrContainer); - const doExport = async (exporter, texture) => { - const t = texture.resource; + const doExport = async (exporter: Exporter, texture: TextureDoc): Promise => { + const t = texture.resource!; this.enabled = false; if (t.cubemap) { const faceNames = ['posx', 'negx', 'posy', 'negy', 'posz', 'negz']; for (let face = 0; face < 6; ++face) { - // eslint-disable-next-line + // eslint-disable-next-line no-await-in-loop download(`${Helpers.removeExtension(texture.filename)}_${faceNames[face]}.${exporter.extension}`, await exporter.run(readPixels(t, face), t.width, t.height)); } } else { @@ -103,8 +112,8 @@ class TextureExportPanel extends Panel { this.enabled = true; }; - const events = []; - textureManager.on('textureDocSelected', (texture) => { + const events: EventHandle[] = []; + textureManager.on('textureDocSelected', (texture: TextureDoc) => { // unregister preview events events.forEach(ev => ev.unbind()); events.length = 0; @@ -127,7 +136,7 @@ class TextureExportPanel extends Panel { })); exportToPng.enabled = !!texture.resource; - exportToHdr.enabled = texture.resource && texture.resource.encoding === 'rgbe'; + exportToHdr.enabled = !!(texture.resource && (texture.resource as any).encoding === 'rgbe'); this.enabled = !!texture.resource; }); diff --git a/src/feedback-panel.js b/src/feedback-panel.ts similarity index 80% rename from src/feedback-panel.js rename to src/feedback-panel.ts index 59a73dd..6339912 100644 --- a/src/feedback-panel.js +++ b/src/feedback-panel.ts @@ -1,7 +1,7 @@ import { Container, Button } from 'pcui'; class FeedbackPanel extends Container { - constructor(args = { }) { + constructor(args: Record = {}) { Object.assign(args, { id: 'feedback-pane' }); @@ -13,11 +13,12 @@ class FeedbackPanel extends Container { flexDirection: 'row' }); + // flexGrow missing from ButtonArgs (PCUI bug) const githubButton = new Button({ text: 'GITHUB', icon: '\E259', - flexGrow: 1 - }); + flexGrow: '1' + } as any); githubButton.on('click', () => { window.open('https://github.com/playcanvas/texture-tool'); diff --git a/src/file-tabs.js b/src/file-tabs.ts similarity index 62% rename from src/file-tabs.js rename to src/file-tabs.ts index f7d7dee..8890914 100644 --- a/src/file-tabs.js +++ b/src/file-tabs.ts @@ -1,7 +1,12 @@ import { Container, Label } from 'pcui'; +import type { TextureManager } from './texture-manager'; +import type { TextureDoc } from './texture-doc'; class FileTabs extends Container { - constructor(textureManager, args = {}) { + textureManager: TextureManager; + buttons: Map; + + constructor(textureManager: TextureManager, args: Record = {}) { Object.assign(args, { id: 'file-tabs-container', flex: true, @@ -12,12 +17,12 @@ class FileTabs extends Container { this.textureManager = textureManager; this.buttons = new Map(); - textureManager.on('textureDocAdded', texture => this.onTextureDocAdded(texture)); - textureManager.on('textureDocRemoved', texture => this.onTextureDocRemoved(texture)); - textureManager.on('textureDocSelected', texture => this.onTextureDocSelected(texture)); + textureManager.on('textureDocAdded', (texture: TextureDoc) => this.onTextureDocAdded(texture)); + textureManager.on('textureDocRemoved', (texture: TextureDoc) => this.onTextureDocRemoved(texture)); + textureManager.on('textureDocSelected', (texture: TextureDoc) => this.onTextureDocSelected(texture)); } - onTextureDocAdded(texture) { + onTextureDocAdded(texture: TextureDoc): void { const tab = new Container({ class: 'file-tab', flex: true, @@ -50,12 +55,15 @@ class FileTabs extends Container { button.dom.scrollIntoView(); } - onTextureDocRemoved(texture) { - this.remove(this.buttons.get(texture)); + onTextureDocRemoved(texture: TextureDoc): void { + const tab = this.buttons.get(texture); + if (tab) { + this.remove(tab); + } this.buttons.delete(texture); } - onTextureDocSelected(texture) { + onTextureDocSelected(texture: TextureDoc): void { this.buttons.forEach((b, t) => { if (t === texture) { b.dom.classList.add('selected'); diff --git a/src/files-browser-panel.js b/src/files-browser-panel.ts similarity index 63% rename from src/files-browser-panel.js rename to src/files-browser-panel.ts index 5be899b..12249b6 100644 --- a/src/files-browser-panel.js +++ b/src/files-browser-panel.ts @@ -1,65 +1,96 @@ import { Panel, Container, Button, TreeView, TreeViewItem, TextInput } from 'pcui'; import { path } from 'playcanvas'; +import type { TextureManager } from './texture-manager'; +import type { TextureDoc } from './texture-doc'; +import type { DropHandler } from './drop-handler'; + +interface FileSource { + handle?: FileSystemFileHandle; + entry?: FileSystemEntry; + file?: File; + url?: string; +} + +interface DirectorySource { + handle?: FileSystemDirectoryHandle; + entry?: FileSystemDirectoryEntry; +} + +type NodeType = 'file' | 'directory'; + +interface TreeViewItemWithNode extends TreeViewItem { + node: FileNode | DirectoryNode; +} class FileNode { - constructor(name, source) { + name: string; + source: FileSource; + hidden: boolean; + texture: TextureDoc | null; + + constructor(name: string, source: FileSource) { this.name = name; this.source = source; this.hidden = false; this.texture = null; } - get type() { + get type(): NodeType { return 'file'; } - async getUrl() { + async getUrl(): Promise { const source = this.source; if (source.handle) { const file = await source.handle.getFile(); return URL.createObjectURL(file); } else if (source.entry) { return new Promise((resolve, reject) => { - source.entry.file((file) => { + (source.entry as FileSystemFileEntry).file((file: File) => { resolve(URL.createObjectURL(file)); - }, (err) => { + }, (err: Error) => { reject(err); }); }); } else if (source.file) { return URL.createObjectURL(source.file); } - return source.url; + return source.url!; } } class DirectoryNode { - constructor(name, source) { + name: string; + source: DirectorySource | null; + children: (FileNode | DirectoryNode)[]; + hidden: boolean; + + constructor(name: string, source: DirectorySource | null = null) { this.name = name; this.source = source; this.children = []; this.hidden = false; } - add(node) { + add(node: FileNode | DirectoryNode): void { this.children.push(node); } - get type() { + get type(): NodeType { return 'directory'; } - async mountHandle(handle) { - let result; + async mountHandle(handle: FileSystemHandle): Promise { + let result: FileNode | DirectoryNode; if (handle.kind === 'file') { result = new FileNode(handle.name, { - handle: handle + handle: handle as FileSystemFileHandle }); - } else if (handle.kind === 'directory') { + } else { result = new DirectoryNode(handle.name, { - handle: handle + handle: handle as FileSystemDirectoryHandle }); - for await (const childHandle of handle.values()) { + for await (const childHandle of (handle as FileSystemDirectoryHandle).values()) { await result.mountHandle(childHandle); } } @@ -67,25 +98,26 @@ class DirectoryNode { return result; } - async mountEntry(entry) { - let result; + async mountEntry(entry: FileSystemEntry): Promise { + let result: FileNode | DirectoryNode; if (entry.isFile) { result = new FileNode(entry.fullPath.substring(1), { entry: entry }); - } else if (entry.isDirectory) { + } else { result = new DirectoryNode(entry.fullPath.substring(1), { - entry: entry + entry: entry as FileSystemDirectoryEntry }); - const reader = entry.createReader(); - await new Promise((resolve, reject) => { + const reader = (entry as FileSystemDirectoryEntry).createReader(); + const dirResult = result as DirectoryNode; + await new Promise((resolve) => { const recurse = () => { - reader.readEntries(async (entries) => { + reader.readEntries(async (entries: FileSystemEntry[]) => { if (entries.length) { for (let i = 0; i < entries.length; ++i) { // eslint-disable-next-line no-await-in-loop - await result.mountEntry(entries[i]); + await dirResult.mountEntry(entries[i]); } recurse(); } else { @@ -100,19 +132,19 @@ class DirectoryNode { return result; } - mountFile(filename, file) { + mountFile(filename: string, file: File): void { this.add(new FileNode(filename, { file: file })); } - mountUrl(filename, url) { + mountUrl(filename: string, url: string): void { this.add(new FileNode(filename, { url: url })); } - sort() { + sort(): void { this.children.sort((a, b) => { if (a.type !== b.type) { return a.type === 'directory' ? -1 : 1; @@ -122,19 +154,25 @@ class DirectoryNode { this.children.forEach((c) => { if (c.type === 'directory') { - c.sort(); + (c as DirectoryNode).sort(); } }); } } class FilesBrowserPanel extends Panel { - constructor(textureManager, dropHandler, args = {}) { + textureManager: TextureManager; + root: DirectoryNode; + treeView: TreeView; + textureToElement: Map | null; + nodeToElement: Map | null; + + constructor(textureManager: TextureManager, dropHandler: DropHandler, args: Record = {}) { Object.assign(args, { id: 'files-browser', headerText: 'TEXTURE TOOL', flex: true, - flexGrow: 1 + flexGrow: '1' }); super(args); @@ -152,29 +190,30 @@ class FilesBrowserPanel extends Panel { class: 'files-browser-group', flex: true, flexDirection: 'row', - flexGrow: 0, - flexShrink: 0 + flexGrow: '0', + flexShrink: '0' }); // files input element const filesInputElement = document.createElement('input'); filesInputElement.type = 'file'; filesInputElement.multiple = true; - filesInputElement.onchange = (e) => { - const files = Array.from(filesInputElement.files); + filesInputElement.onchange = () => { + const files = Array.from(filesInputElement.files || []); files.forEach((f) => { this.root.mountFile(f.name, f); }); this.rebuildTreeUI(); }; + // flexGrow/flexShrink missing from ButtonArgs (PCUI bug) const filesButton = new Button({ class: 'files-browser-button', text: 'Add Files...', - flexGrow: 1, - flexShrink: 1, + flexGrow: '1', + flexShrink: '1', width: 50 - }); + } as any); // open file picker filesButton.on('click', async () => { @@ -189,7 +228,7 @@ class FilesBrowserPanel extends Panel { this.rebuildTreeUI(); nodes.forEach((node) => { - const ui = this.nodeToElement.get(node); + const ui = this.nodeToElement?.get(node); if (ui) { this.treeView.deselect(); ui.selected = true; @@ -203,37 +242,38 @@ class FilesBrowserPanel extends Panel { }); // directory input element - const directoryInputElement = document.createElement('input'); + const directoryInputElement = document.createElement('input') as HTMLInputElement & { webkitdirectory: boolean }; directoryInputElement.type = 'file'; directoryInputElement.multiple = true; directoryInputElement.webkitdirectory = true; - directoryInputElement.onchange = (e) => { - const files = Array.from(directoryInputElement.files); + directoryInputElement.onchange = () => { + const files = Array.from(directoryInputElement.files || []); files.forEach((f) => { // split the full path into parts and add to the directory tree - const path = f.webkitRelativePath.split('/'); - let node = this.root; - for (let i = 0; i < path.length - 1; ++i) { - const p = path[i]; + const pathParts = f.webkitRelativePath.split('/'); + let node: DirectoryNode = this.root; + for (let i = 0; i < pathParts.length - 1; ++i) { + const p = pathParts[i]; let child = node.children.find(v => v.name === p); if (!child || child.type !== 'directory') { child = new DirectoryNode(p, null); node.add(child); } - node = child; + node = child as DirectoryNode; } node.mountFile(f.name, f); }); this.rebuildTreeUI(); }; + // flexGrow/flexShrink missing from ButtonArgs (PCUI bug) const directoryButton = new Button({ class: 'files-browser-button', text: 'Mount Folder...', - flexGrow: 1, - flexShrink: 1, + flexGrow: '1', + flexShrink: '1', width: 50 - }); + } as any); directoryButton.on('click', async () => { if (window.showDirectoryPicker) { @@ -249,8 +289,8 @@ class FilesBrowserPanel extends Panel { const treeViewContainer = new Container({ id: 'files-browser-tree-view-container', - flexGrow: 1, - flexShrink: 1 + flexGrow: '1', + flexShrink: '1' }); const treeView = new TreeView({ @@ -260,7 +300,7 @@ class FilesBrowserPanel extends Panel { allowRenaming: false }); - treeView.on('select', item => this.onItemSelected(item)); + treeView.on('select', (item: TreeViewItemWithNode) => this.onItemSelected(item)); treeViewContainer.append(treeView); @@ -268,24 +308,26 @@ class FilesBrowserPanel extends Panel { const urlGroup = new Container({ flex: true, flexDirection: 'row', - flexGrow: 0, - flexShrink: 0 + flexGrow: '0', + flexShrink: '0' }); + // flexGrow missing from TextInputArgs (PCUI bug) const urlInput = new TextInput({ placeholder: 'url', - flexGrow: 1 - }); + flexGrow: '1' + } as any); + // flexGrow/flexShrink missing from ButtonArgs (PCUI bug) const urlAddButton = new Button({ id: 'browser-panel-entry-button', text: '', icon: '\E120', - flexGrow: 0, - flexShrink: 0, + flexGrow: '0', + flexShrink: '0', width: 30, height: 24 - }); + } as any); urlAddButton.on('click', () => { const url = urlInput.value; @@ -304,20 +346,21 @@ class FilesBrowserPanel extends Panel { this.append(treeViewContainer); // handle drag and drop - dropHandler.on('filesDropped', async (fileItems) => { // eslint-disable-line require-await - const itemPromises = []; + dropHandler.on('filesDropped', (fileItems: DataTransferItemList) => { + const itemPromises: Promise[] = []; for (let i = 0; i < fileItems.length; ++i) { const item = fileItems[i]; if (item.getAsFileSystemHandle) { itemPromises.push(item.getAsFileSystemHandle()); } else if (item.webkitGetAsEntry) { - itemPromises.push(item.webkitGetAsEntry()); + itemPromises.push(Promise.resolve(item.webkitGetAsEntry())); } } Promise.all(itemPromises).then((items) => { const nodePromises = items.map((item) => { - return item.kind ? this.root.mountHandle(item) : this.root.mountEntry(item); + if (!item) return Promise.resolve(null); + return (item as any).kind ? this.root.mountHandle(item as FileSystemHandle) : this.root.mountEntry(item as FileSystemEntry); }); Promise.all(nodePromises).then((nodes) => { @@ -325,11 +368,11 @@ class FilesBrowserPanel extends Panel { // if user dragged in images, select each in turn nodes.forEach((node) => { - if (node.type === 'file') { - const ui = this.nodeToElement.get(node); + if (node && node.type === 'file') { + const ui = this.nodeToElement?.get(node); if (ui) { this.treeView.deselect(); - ui.selected = true; + ui.selected = true; ui.dom.scrollIntoView(); } } @@ -340,7 +383,7 @@ class FilesBrowserPanel extends Panel { this.textureToElement = null; this.nodeToElement = null; - textureManager.on('textureDocSelected', (texture) => { + textureManager.on('textureDocSelected', (texture: TextureDoc) => { if (this.textureToElement) { const element = this.textureToElement.get(texture); if (element) { @@ -351,11 +394,11 @@ class FilesBrowserPanel extends Panel { } }); - textureManager.on('textureDocRemoved', (texture) => { + textureManager.on('textureDocRemoved', (texture: TextureDoc) => { if (this.textureToElement) { const element = this.textureToElement.get(texture); - if (element) { - element.node.texture = null; + if (element && element.node.type === 'file') { + (element.node as FileNode).texture = null; this.textureToElement.delete(texture); } } @@ -366,26 +409,27 @@ class FilesBrowserPanel extends Panel { } // called when an item is selected - async onItemSelected(item) { + async onItemSelected(item: TreeViewItemWithNode): Promise { const node = item.node; if (node.type === 'file') { - if (!node.texture) { - const url = await node.getUrl(); - this.textureManager.addTextureDocByUrl(url, node.name, (err, texture) => { + const fileNode = node as FileNode; + if (!fileNode.texture) { + const url = await fileNode.getUrl(); + this.textureManager.addTextureDocByUrl(url, fileNode.name, (err, texture) => { URL.revokeObjectURL(url); - if (!err) { - node.texture = texture; + if (!err && texture) { + fileNode.texture = texture; this.textureManager.selectTextureDoc(texture); - this.textureToElement.set(texture, item); + this.textureToElement?.set(texture, item); } }); } else { - this.textureManager.selectTextureDoc(node.texture); + this.textureManager.selectTextureDoc(fileNode.texture); } } } - isImageFilename(filename) { + isImageFilename(filename: string): boolean { const extensions = ['.dds', '.png', '.jpg', '.jpeg', '.basis', '.ktx', '.ktx2', '.hdr', '.pvr']; for (let i = 0; i < extensions.length; ++i) { if (filename.endsWith(extensions[i])) { @@ -395,15 +439,16 @@ class FilesBrowserPanel extends Panel { return false; } - updateHiddenFlags() { - const recurse = (node) => { - let hidden; + updateHiddenFlags(): void { + const recurse = (node: FileNode | DirectoryNode): boolean => { + let hidden: boolean; if (node.type === 'file') { hidden = !this.isImageFilename(node.name); } else { hidden = true; - for (let i = 0; i < node.children.length; ++i) { - hidden = recurse(node.children[i]) && hidden; // NOTE: don't short-circuit + const dirNode = node as DirectoryNode; + for (let i = 0; i < dirNode.children.length; ++i) { + hidden = recurse(dirNode.children[i]) && hidden; // NOTE: don't short-circuit } } node.hidden = hidden; @@ -413,26 +458,26 @@ class FilesBrowserPanel extends Panel { recurse(this.root); } - rebuildTreeUI() { + rebuildTreeUI(): void { this.textureToElement = new Map(); this.nodeToElement = new Map(); - const recurse = (ui, children) => { + const recurse = (ui: TreeView | TreeViewItem, children: (FileNode | DirectoryNode)[]) => { children.forEach((node) => { if (!node.hidden) { const treeViewItem = new TreeViewItem({ text: node.name, class: node.type === 'file' ? 'files-browser-tree-view-file-item' : 'files-browser-tree-view-directory-item', open: false - }); - if (node.texture) { - this.textureToElement.set(node.texture, treeViewItem); + }) as TreeViewItemWithNode; + if ((node as FileNode).texture) { + this.textureToElement!.set((node as FileNode).texture!, treeViewItem); } - this.nodeToElement.set(node, treeViewItem); + this.nodeToElement!.set(node, treeViewItem); treeViewItem.node = node; ui.append(treeViewItem); if (node.type === 'directory') { - recurse(treeViewItem, node.children); + recurse(treeViewItem, (node as DirectoryNode).children); } } }); diff --git a/src/hdr-exporter.js b/src/hdr-exporter.ts similarity index 77% rename from src/hdr-exporter.js rename to src/hdr-exporter.ts index 68a6b05..614d407 100644 --- a/src/hdr-exporter.js +++ b/src/hdr-exporter.ts @@ -1,9 +1,11 @@ class HdrExporter { + encoder: TextEncoder; + constructor() { this.encoder = new TextEncoder(); } - run(words, width, height) { + run(words: Uint32Array, width: number, height: number): Uint8Array { const header = this.encoder.encode(`#?RADIANCE\nFORMAT=32-bit_rle_rgbe\n\n-Y ${height} +X ${width}\n`); const result = new Uint8Array(width * height * 4 + header.length); result.set(header); @@ -11,7 +13,7 @@ class HdrExporter { return result; } - get extension() { + get extension(): string { return 'hdr'; } } diff --git a/src/helpers.js b/src/helpers.js deleted file mode 100644 index 6a840d4..0000000 --- a/src/helpers.js +++ /dev/null @@ -1,167 +0,0 @@ -import { path } from 'playcanvas'; - -class Helpers { - // deep compare of two json objects - static cmp(value0, value1) { - if (value0 === null) { - return value1 === null; - } else if (value1 === null) { - return false; - } - - // neither are null - if (typeof value0 !== typeof value1) { - return false; - } - - // types match - if (typeof value0 === 'object') { - // cmp objects - for (const k in value0) { - if (value0.hasOwnProperty(k)) { - if (!value1.hasOwnProperty(k) || !Helpers.cmp(value0[k], value1[k])) { - return false; - } - } - } - return true; - } else if (value0.constructor === Array) { - // cmp arrays - if (value0.length !== value1.length) { - return false; - } - for (let i = 0; i < value0.length; ++i) { - if (!Helpers.cmp(value0[i], value1[i])) { - return false; - } - } - return true; - } - - // cmp atomic types - return value0 === value1; - } - - // create a deep clone of a simple object - static clone(object) { - if (object === null || object === undefined) { - // null - return null; - } else if (object instanceof Array) { - // array - const res = []; - for (let i = 0; i < object.length; ++i) { - res[i] = Helpers.clone(object[i]); - } - return res; - } else if (typeof object === 'object') { - // object - const res = { }; - for (const key in object) { - if (object.hasOwnProperty(key)) { - res[key] = Helpers.clone(object[key]); - } - } - return res; - } - // everything else - return object; - } - - // map, but on objects instead of arrays - static map(object, callback) { - const result = { }; - for (const k in object) { - if (object.hasOwnProperty(k)) { - const [nk, nv] = callback.call(this, object[k], k); - result[nk] = nv; - } - } - return result; - } - - // forEach, but on objects instead of arrays - static forEach(object, callback) { - for (const k in object) { - if (object.hasOwnProperty(k)) { - callback.call(this, object[k], k); - } - } - } - - static find(object, callback) { - for (const k in object) { - if (object.hasOwnProperty(k)) { - const value = object[k]; - if (callback.call(this, value, k)) { - return value; - } - } - } - return undefined; - } - - static isImageFilename(filename) { - const imageExtensions = ['.png', '.jpg', '.hdr', '.dds', '.ktx', '.ktx2', '.webp']; - return imageExtensions.indexOf(path.getExtension(filename).toLowerCase()) !== -1; - } - - static valueFromArray(value) { - if (Array.isArray(value)) { - const names = ['x', 'y', 'z', 'w']; - const result = { }; - value.forEach((v, i) => { - result[names[i]] = v; - }); - return result; - } - return value; - } - - static valueToArray(value) { - if (typeof value === 'object') { - if (value.hasOwnProperty('w')) { - return [value.x, value.y, value.z, value.w]; - } else if (value.hasOwnProperty('z')) { - return [value.x, value.y, value.z]; - } else if (value.hasOwnProperty('y')) { - return [value.x, value.y]; - } - } - return value; - } - - static offByScope(eventHandler, scope) { - for (const name in eventHandler._callbacks) { - const events = eventHandler._callbacks[name]; - for (let i = 0; i < events.length; ++i) { - const event = events[i]; - if (event.scope === scope) { - eventHandler.off(name, event.callback, event.scope); - } - } - } - } - - static downloadBlob(name, blob) { - const a = document.createElement('a'); - a.href = URL.createObjectURL(blob); - a.download = name; - a.click(); - } - - static downloadTextFile(name, text) { - const type = name.split('.').pop(); - this.downloadBlob(name, new Blob([text], { - type: `text/${type === 'txt' ? 'plain' : type}` - })); - } - - static removeExtension(filename) { - return filename.substring(0, filename.length - path.getExtension(filename).length); - } -} - -export { - Helpers -}; diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 0000000..bc402ee --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,11 @@ +import { path } from 'playcanvas'; + +class Helpers { + static removeExtension(filename: string): string { + return filename.substring(0, filename.length - path.getExtension(filename).length); + } +} + +export { + Helpers +}; diff --git a/src/index.js b/src/index.ts similarity index 95% rename from src/index.js rename to src/index.ts index 5d7eed0..cf577c4 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,4 +1,4 @@ -import './main.js'; +import './main'; import './style.scss'; import { version as pcuiVersion, revision as pcuiRevision } from 'pcui'; diff --git a/src/info-panel.js b/src/info-panel.ts similarity index 72% rename from src/info-panel.js rename to src/info-panel.ts index 6237733..5101663 100644 --- a/src/info-panel.js +++ b/src/info-panel.ts @@ -1,13 +1,21 @@ import { Panel, Label } from 'pcui'; -import { PixelFormatTable, TextureTypeTable } from './const.js'; +import { PixelFormatTable, TextureTypeTable } from './const'; +import type { TextureManager } from './texture-manager'; +import type { TextureDoc } from './texture-doc'; class InfoPanel extends Panel { - constructor(textureManager, args = { }) { + textureStructure: Label; + textureDims: Label; + texturePixelFormat: Label; + textureType: Label; + cursorTexel: Label; + + constructor(textureManager: TextureManager, args: Record = {}) { Object.assign(args, { headerText: '-', - flexGrow: 0, - flexShrink: 0 + flexGrow: '0', + flexShrink: '0' }); super(args); @@ -43,12 +51,12 @@ class InfoPanel extends Panel { this.header.append(this.textureType); this.header.append(this.cursorTexel); - textureManager.on('textureDocSelected', (texture) => { + textureManager.on('textureDocSelected', (texture: TextureDoc) => { this.setTexture(texture); }); } - setTexture(texture) { + setTexture(texture: TextureDoc): void { // filename this.headerText = texture.url.startsWith('blob:') ? texture.filename : texture.url; @@ -59,10 +67,10 @@ class InfoPanel extends Panel { this.textureDims.text = `${texture.width}x${texture.height}`; // pixel format - this.texturePixelFormat.text = `${PixelFormatTable[texture.format] || '???'}`; + this.texturePixelFormat.text = `${PixelFormatTable[texture.format!] || '???'}`; // texture type - this.textureType.text = `${TextureTypeTable[texture.type] || '???'}`; + this.textureType.text = `${TextureTypeTable[texture.type!] || '???'}`; } } diff --git a/src/inspector-panel.js b/src/inspector-panel.ts similarity index 61% rename from src/inspector-panel.js rename to src/inspector-panel.ts index a4043da..fb68ae5 100644 --- a/src/inspector-panel.js +++ b/src/inspector-panel.ts @@ -1,12 +1,13 @@ import { Container } from 'pcui'; -import { TextureExportPanel } from './export-panel.js'; -import { FeedbackPanel } from './feedback-panel.js'; -import { ReprojectPanel } from './reproject-panel.js'; -import { ShowPanel } from './show-panel.js'; +import { TextureExportPanel } from './export-panel'; +import { FeedbackPanel } from './feedback-panel'; +import { ReprojectPanel } from './reproject-panel'; +import { ShowPanel } from './show-panel'; +import type { TextureManager } from './texture-manager'; class InspectorPanel extends Container { - constructor(textureManager, args = {}) { + constructor(textureManager: TextureManager, args: Record = {}) { Object.assign(args, { class: 'inspector-panel-container', resizable: 'left', @@ -14,7 +15,7 @@ class InspectorPanel extends Container { resizeMax: 1000, flex: true, flexDirection: 'column', - flexGrow: 1 + flexGrow: '1' }); super(args); diff --git a/src/main.js b/src/main.ts similarity index 75% rename from src/main.js rename to src/main.ts index 44fdf4f..0acecce 100644 --- a/src/main.js +++ b/src/main.ts @@ -1,27 +1,32 @@ import { Container } from 'pcui'; import { path } from 'playcanvas'; -import { DropHandler } from './drop-handler.js'; -import { FileTabs } from './file-tabs.js'; -import { FilesBrowserPanel } from './files-browser-panel.js'; -import { InspectorPanel } from './inspector-panel.js'; -import { Renderer } from './renderer.js'; -import { TextureManager } from './texture-manager.js'; -import { ViewportPanel } from './viewport-panel.js'; +import { DropHandler } from './drop-handler'; +import { FileTabs } from './file-tabs'; +import { FilesBrowserPanel } from './files-browser-panel'; +import { InspectorPanel } from './inspector-panel'; +import { Renderer } from './renderer'; +import { TextureManager } from './texture-manager'; +import { ViewportPanel } from './viewport-panel'; +import { TextureDoc } from './texture-doc'; // globals const renderer = new Renderer(); const textureManager = new TextureManager(renderer.app.assets); // disable global drag/drop -window.addEventListener('dragover', (ev) => { +window.addEventListener('dragover', (ev: DragEvent) => { ev.preventDefault(); - ev.dataTransfer.dropEffect = 'none'; + if (ev.dataTransfer) { + ev.dataTransfer.dropEffect = 'none'; + } }, false); -window.addEventListener('drop', (ev) => { +window.addEventListener('drop', (ev: DragEvent) => { ev.preventDefault(); - ev.dataTransfer.dropEffect = 'none'; + if (ev.dataTransfer) { + ev.dataTransfer.dropEffect = 'none'; + } }, false); // construct application containers @@ -67,7 +72,7 @@ setTimeout(() => { const keys = Array.from(url.searchParams.keys()); const values = Array.from(url.searchParams.values()); let i = 0; - let activeTexture = null; + let activeTexture: TextureDoc | null = null; const handleNextParam = () => { if (i === keys.length) { @@ -77,7 +82,7 @@ setTimeout(() => { const param = keys[i]; const value = values[i++]; if (param === 'load') { - textureManager.addTextureDocByUrl(value, path.getBasename(value).split('?')[0], (err, texture) => { + textureManager.addTextureDocByUrl(value, path.getBasename(value).split('?')[0], (err: Error | null, texture: TextureDoc | null) => { if (!err && texture) { textureManager.selectTextureDoc(texture); } diff --git a/src/png-exporter.js b/src/png-exporter.ts similarity index 59% rename from src/png-exporter.js rename to src/png-exporter.ts index cfbf300..90c1b23 100644 --- a/src/png-exporter.js +++ b/src/png-exporter.ts @@ -1,14 +1,15 @@ -function PngExportWorker(href) { - const initLodepng = () => { - return new Promise((resolve, reject) => { - self.importScripts(`${href}lib/lodepng/lodepng.js`); - resolve(self.lodepng({ +// Worker function - kept as a string template for blob creation +function PngExportWorker(href: string): void { + const initLodepng = (): Promise => { + return new Promise((resolve) => { + (self as any).importScripts(`${href}lib/lodepng/lodepng.js`); + resolve((self as any).lodepng({ locateFile: () => `${href}lib/lodepng/lodepng.wasm` })); }); }; - const compress = (lodepng, words, width, height) => { + const compress = (lodepng: any, words: Uint32Array, width: number, height: number): Uint8Array => { const resultDataPtrPtr = lodepng._malloc(4); const resultSizePtr = lodepng._malloc(4); const imageData = lodepng._malloc(width * height * 4); @@ -32,48 +33,59 @@ function PngExportWorker(href) { return result; }; - const main = async () => { + const main = async (): Promise => { const lodepng = await initLodepng(); - self.onmessage = (message) => { + self.onmessage = (message: MessageEvent) => { const data = message.data; // compress const result = compress(lodepng, data.words, data.width, data.height); // return - self.postMessage({ result: result }, [result.buffer]); + (self as any).postMessage({ result: result }, [result.buffer]); }; }; main(); } +interface PngExportMessage { + data: { + result: Uint8Array; + }; +} + class PngExporter { + worker: Worker; + promiseFunc: (resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => void; + constructor() { - let receiver; + let receiver: ((message: PngExportMessage) => void) | null; this.worker = PngExporter.createWorker(); - this.worker.addEventListener('message', (message) => { - receiver(message); + this.worker.addEventListener('message', (message: MessageEvent) => { + if (receiver) { + receiver(message as PngExportMessage); + } }); - this.promiseFunc = (resolve, reject) => { - receiver = (message) => { + this.promiseFunc = (resolve) => { + receiver = (message: PngExportMessage) => { resolve(message.data.result); receiver = null; }; }; } - static createWorker() { + static createWorker(): Worker { const workerBlob = new Blob([`(${PngExportWorker.toString()})('${window.location.href.split('?')[0]}')\n\n`], { type: 'application/javascript' }); return new Worker(URL.createObjectURL(workerBlob)); } - run(words, width, height) { + run(words: Uint32Array, width: number, height: number): Promise { this.worker.postMessage({ words: words, width: width, @@ -83,7 +95,7 @@ class PngExporter { return new Promise(this.promiseFunc); } - get extension() { + get extension(): string { return 'png'; } } diff --git a/src/render-canvas.js b/src/render-canvas.ts similarity index 71% rename from src/render-canvas.js rename to src/render-canvas.ts index 564a28c..bf86880 100644 --- a/src/render-canvas.js +++ b/src/render-canvas.ts @@ -1,18 +1,30 @@ import { Container } from 'pcui'; +import type { LayerComposition } from 'playcanvas'; +import type { Renderer } from './renderer'; // implements a pcui canvas element with rendering class RenderCanvas extends Container { - constructor(renderer, composition = null, args = {}) { + renderer: Renderer; + composition: LayerComposition; + renderRequest: number | null; + canvas: HTMLCanvasElement; + needsResize: boolean; + canvasWidth: number; + canvasHeight: number; + observer: ResizeObserver; + animationFrame: (timeStamp: number) => void; + + constructor(renderer: Renderer, composition: LayerComposition | null = null, args: Record = {}) { Object.assign(args, { class: 'render-canvas-container', flex: true, - flexGrow: 1, - flexShrink: 1 + flexGrow: '1', + flexShrink: '1' }); super(args); this.renderer = renderer; - this.composition = composition || this.renderer.app.defaultLayerComposition; + this.composition = composition || this.renderer.app.scene.layers; this.renderRequest = null; // canvas @@ -25,7 +37,7 @@ class RenderCanvas extends Container { this.canvasHeight = 150; // resize observer - this.observer = new ResizeObserver((entries) => { + this.observer = new ResizeObserver(() => { const rect = this.dom.getBoundingClientRect(); const width = Math.floor(rect.width); const height = Math.floor(rect.height); @@ -34,14 +46,14 @@ class RenderCanvas extends Container { this.canvasWidth = width; this.canvasHeight = height; this.canvas.style.width = `${width}px`; - this.canvas.style.height = `${height}py`; + this.canvas.style.height = `${height}px`; this.emit('resize', width, height); } }); this.observer.observe(this.dom); // animation frame - this.animationFrame = (timeStamp) => { + this.animationFrame = () => { // clear request this.renderRequest = null; @@ -60,7 +72,7 @@ class RenderCanvas extends Container { }; } - render() { + render(): void { if (!this.renderRequest) { this.renderRequest = window.requestAnimationFrame(this.animationFrame); } diff --git a/src/renderer.js b/src/renderer.ts similarity index 76% rename from src/renderer.js rename to src/renderer.ts index 969c1a3..f2bf6ee 100644 --- a/src/renderer.js +++ b/src/renderer.ts @@ -1,8 +1,8 @@ -import { basisInitialize, Application } from 'playcanvas'; +import { basisInitialize, Application, LayerComposition } from 'playcanvas'; +import type { CameraComponent, Layer } from 'playcanvas'; // initialize basis - -const getAssetPath = (assetPath) => { +const getAssetPath = (assetPath: string): string => { return assetPath; }; @@ -14,6 +14,10 @@ basisInitialize({ }); class Renderer { + private _canvas: HTMLCanvasElement; + private _app: Application; + private _renderRequest: number | null; + constructor() { // create the canvas (which will remain invisible) this._canvas = document.createElement('canvas'); @@ -26,7 +30,7 @@ class Renderer { } }); - this._app.loader.getHandler('texture').imgParser.crossOrigin = 'anonymous'; + (this._app.loader.getHandler('texture') as any).imgParser.crossOrigin = 'anonymous'; // taken from Application.start() this._app.systems.fire('initialize', this._app.root); @@ -35,11 +39,11 @@ class Renderer { this._renderRequest = null; } - get app() { + get app(): Application { return this._app; } - render(canvas, composition) { + render(canvas: HTMLCanvasElement, composition?: LayerComposition): void { // ensure back buffer is large enough if (canvas.width > this._canvas.width || canvas.height > this._canvas.height) { @@ -48,14 +52,14 @@ class Renderer { } // default to app scene - composition = composition || this._app.scene.layers; + const comp = composition || this._app.scene.layers; // step through all cameras in the composition and set their viewport // based on the device backbuffer. // NOTE: we can't rely on composition.cameras here because that list // only gets populated during renderComposition. - const seen = new Set(); - for (const layer of composition.layerList) { + const seen = new Set(); + for (const layer of comp.layerList as Layer[]) { for (const camera of layer.cameras) { if (!seen.has(camera)) { seen.add(camera); @@ -77,11 +81,11 @@ class Renderer { // render this._app.graphicsDevice.frameStart(); this._app.batcher.updateAll(); - this._app.renderComposition(composition); + this._app.renderComposition(comp); this._app.graphicsDevice.frameEnd(); // copy the result to the target canvas - const context = canvas.getContext('2d'); + const context = canvas.getContext('2d')!; context.globalCompositeOperation = 'copy'; context.drawImage(this._canvas, 0, this._canvas.height - canvas.height, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); } diff --git a/src/reproject-panel.js b/src/reproject-panel.ts similarity index 82% rename from src/reproject-panel.js rename to src/reproject-panel.ts index 54ae477..4f6aac3 100644 --- a/src/reproject-panel.js +++ b/src/reproject-panel.ts @@ -6,12 +6,14 @@ import { FILTER_NEAREST, FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, ADDRESS_REPEAT, ADDRESS_CLAMP_TO_EDGE, EnvLighting } from 'playcanvas'; +import type { EventHandle } from '@playcanvas/observer'; -import { Helpers } from './helpers.js'; -import { TextureDoc } from './texture-doc.js'; +import { Helpers } from './helpers'; +import { TextureDoc } from './texture-doc'; +import type { TextureManager } from './texture-manager'; class ReprojectPanel extends Panel { - constructor(textureManager, args = { }) { + constructor(textureManager: TextureManager, args: Record = {}) { Object.assign(args, { id: 'texture-reproject-pane', headerText: 'Reproject', @@ -20,21 +22,21 @@ class ReprojectPanel extends Panel { super(args); - const projections = { + const projections: Record = { 0: 'cube', 1: 'equirect', 2: 'octahedral', 3: 'envAtlas' }; - const pindices = { + const pindices: Record = { cube: '0', equirect: '1', octahedral: '2', none: '1' }; - const encodings = { + const encodings: Record = { 0: 'rgbm', 1: 'rgbe', 2: 'rgbp', @@ -42,7 +44,7 @@ class ReprojectPanel extends Panel { 4: 'srgb' }; - const eindices = { + const eindices: Record = { rgbm: '0', rgbe: '1', rgbp: '2', @@ -128,8 +130,8 @@ class ReprojectPanel extends Panel { this.append(new LabelGroup({ text: 'height', field: height })); this.append(buttonContainer); - const events = []; - textureManager.on('textureDocSelected', (texture) => { + const events: EventHandle[] = []; + textureManager.on('textureDocSelected', (texture: TextureDoc) => { // unregister preview events events.forEach(ev => ev.unbind()); events.length = 0; @@ -139,15 +141,15 @@ class ReprojectPanel extends Panel { if (!t) { source.options = []; target.options = []; - width.value = ''; - height.value = ''; + width.value = 0; + height.value = 0; this.enabled = false; return; } const onTargetProjectionChanged = () => { if (projections[target.value] === 'envAtlas') { - encoding.value = 2; + encoding.value = '2'; width.value = 512; } height.enabled = ['cube', 'envAtlas'].indexOf(projections[target.value]) === -1; @@ -157,13 +159,13 @@ class ReprojectPanel extends Panel { }; source.options = t.cubemap ? sourceCubemapProjections : sourceTextureProjections; - source.value = t.cubemap ? pindices.cube : pindices[t.projection]; + source.value = t.cubemap ? pindices.cube : pindices[(t as any).projection] || pindices.equirect; target.options = targetProjections; target.value = t.cubemap ? pindices.equirect : pindices.cube; target.on('change', onTargetProjectionChanged); - encoding.value = eindices[t.encoding]; + encoding.value = eindices[(t as any).encoding] || '0'; width.value = t.width; width.on('change', () => { @@ -179,45 +181,45 @@ class ReprojectPanel extends Panel { const targetProjection = projections[target.value]; const targetEncoding = encodings[encoding.value]; - const format = { + const format: Record = { 'rgbm': PIXELFORMAT_R8_G8_B8_A8, 'rgbe': PIXELFORMAT_R8_G8_B8_A8, 'rgbp': PIXELFORMAT_R8_G8_B8_A8, 'linear': PIXELFORMAT_RGBA16F, 'srgb': PIXELFORMAT_R8_G8_B8_A8 - }[targetEncoding]; + }; - const type = { + const typeMap: Record = { 'rgbm': 'rgbm', 'rgbe': 'rgbe', 'rgbp': 'rgbp', 'linear': 'default', 'srgb': 'default' - }[targetEncoding]; + }; // create target texture const targetTexture = new Texture(t.device, { cubemap: targetProjection === 'cube', - width: width.value, - height: height.value, - format: format, - type: type, + width: width.value as number, + height: height.value as number, + format: format[targetEncoding], + type: typeMap[targetEncoding] as any, mipmaps: false, - projection: targetProjection, + projection: targetProjection as any, anisotropy: t.device.maxAnisotropy }); // reprojectTexture function uses the texture's own setup so apply view settings to the texture - t.projection = sourceProjection; - t.magFilter = t.encoding === 'rgbe' ? FILTER_NEAREST : FILTER_LINEAR; - t.minFilter = t.encoding === 'rgbe' ? FILTER_NEAREST : FILTER_LINEAR_MIPMAP_LINEAR; + (t as any).projection = sourceProjection; + t.magFilter = (t as any).encoding === 'rgbe' ? FILTER_NEAREST : FILTER_LINEAR; + t.minFilter = (t as any).encoding === 'rgbe' ? FILTER_NEAREST : FILTER_LINEAR_MIPMAP_LINEAR; t.addressU = ADDRESS_REPEAT; t.addressV = sourceProjection === 'equirect' ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT; switch (texture.settings.get('view.type')) { case 'rgbm': t.type = TEXTURETYPE_RGBM; break; case 'rgbe': t.type = TEXTURETYPE_RGBE; break; case 'rgbp': t.type = TEXTURETYPE_RGBP; break; - default: t.type = TEXTURETYPE_DEFAULT; break; + default: t.type = TEXTURETYPE_DEFAULT; break; } t.anisotropy = t.device.maxAnisotropy; @@ -226,7 +228,7 @@ class ReprojectPanel extends Panel { const sameDims = width.value === t.width && height.value === t.height; // check if source texture is capable of reprojection as-is - if (sameProjection && (sameDims || (t.encoding !== 'rgbe' && t.mipmaps && t._levels.length > 1))) { + if (sameProjection && (sameDims || ((t as any).encoding !== 'rgbe' && t.mipmaps && (t as any)._levels.length > 1))) { reprojectTexture(t, targetTexture, { numSamples: 1 }); } else { const tmp = new Texture(t.device, { @@ -235,7 +237,7 @@ class ReprojectPanel extends Panel { height: t.height, format: PIXELFORMAT_RGBA32F, type: TEXTURETYPE_DEFAULT, - projection: sourceProjection, + projection: sourceProjection as any, addressU: ADDRESS_REPEAT, addressV: sourceProjection === 'equirect' ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT, mipmaps: true, @@ -259,7 +261,7 @@ class ReprojectPanel extends Panel { tmp.destroy(); } - const asset = new Asset(`${Helpers.removeExtension(texture.asset.name)}-${targetProjection}`, targetProjection === 'cube' ? 'cubemap' : 'texture', { + const asset = new Asset(`${Helpers.removeExtension(texture.asset!.name)}-${targetProjection}`, targetProjection === 'cube' ? 'cubemap' : 'texture', { filename: `${Helpers.removeExtension(texture.filename)}-${targetProjection}`, url: '' }, null); @@ -278,10 +280,6 @@ class ReprojectPanel extends Panel { this.enabled = false; } - - generateEnvAtlas(source, target) { - - } } export { diff --git a/src/show-panel.js b/src/show-panel.ts similarity index 82% rename from src/show-panel.js rename to src/show-panel.ts index 7c44f4a..46e3904 100644 --- a/src/show-panel.js +++ b/src/show-panel.ts @@ -1,12 +1,15 @@ import { Panel, Button, Container, LabelGroup, SelectInput, SliderInput, BooleanInput } from 'pcui'; +import type { EventHandle } from '@playcanvas/observer'; +import type { TextureManager } from './texture-manager'; +import type { TextureDoc } from './texture-doc'; class ShowPanel extends Panel { - constructor(textureManager, args = { }) { + constructor(textureManager: TextureManager, args: Record = {}) { Object.assign(args, { id: 'show-panel', headerText: 'Show', collapsible: true, - flexGrow: 0 + flexGrow: '0' }); super(args); @@ -19,7 +22,7 @@ class ShowPanel extends Panel { id: 'cubemap-face-buttons-container' }); - const faceButtons = { + const faceButtons: Record = { 0: new Button({ id: 'cubemap-face-px', class: 'cubemap-face', text: '+x' }), 1: new Button({ id: 'cubemap-face-nx', class: 'cubemap-face', text: '-x' }), 2: new Button({ id: 'cubemap-face-py', class: 'cubemap-face', text: '+y' }), @@ -35,13 +38,14 @@ class ShowPanel extends Panel { this.append(faceButtonsContainer); // mipmap select + // flexGrow missing from SelectInputArgs (PCUI bug) const mipmapSelect = new SelectInput({ value: '0', options: [], - flexGrow: 1 - }); + flexGrow: '1' + } as any); - // texture type + // flexGrow missing from SelectInputArgs (PCUI bug) const textureTypeSelect = new SelectInput({ value: 'gamma', options: [ @@ -52,8 +56,8 @@ class ShowPanel extends Panel { { v: 'rgbp', t: 'rgbp' }, { v: 'a', t: 'a' } ], - flexGrow: 1 - }); + flexGrow: '1' + } as any); // alpha const alphaToggle = new BooleanInput(); @@ -67,7 +71,7 @@ class ShowPanel extends Panel { min: -5, max: 5, precision: 1, - flexGrow: 1 + flexGrow: '1' }); // label groups @@ -77,12 +81,12 @@ class ShowPanel extends Panel { this.append(new LabelGroup({ text: 'filter', field: filterToggle })); this.append(new LabelGroup({ text: 'exposure', field: exposureSlider })); - const getTextureType = (texture) => { - const encoding = texture?.asset?.resource?.encoding || 'gamma'; + const getTextureType = (texture: TextureDoc): string => { + const encoding = (texture?.asset?.resource as any)?.encoding || 'gamma'; return encoding === 'srgb' ? 'gamma' : encoding; }; - textureManager.on('textureDocAdded', (doc) => { + textureManager.on('textureDocAdded', (doc: TextureDoc) => { doc.settings.patch({ view: { filter: false, @@ -98,14 +102,14 @@ class ShowPanel extends Panel { }); }); - const events = []; - textureManager.on('textureDocSelected', (texture) => { + const events: EventHandle[] = []; + textureManager.on('textureDocSelected', (texture: TextureDoc) => { // unregister preview events events.forEach(ev => ev.unbind()); events.length = 0; // register change events - events.push(texture.settings.on('view.face:set', (value) => { + events.push(texture.settings.on('view.face:set', (value: string) => { Object.keys(faceButtons).forEach((face) => { if (face === value) { faceButtons[face].dom.classList.add('depressed'); @@ -114,19 +118,19 @@ class ShowPanel extends Panel { } }); })); - events.push(texture.settings.on('view.mipmap:set', (value) => { + events.push(texture.settings.on('view.mipmap:set', (value: string) => { mipmapSelect.value = value; })); - events.push(texture.settings.on('view.type:set', (value) => { + events.push(texture.settings.on('view.type:set', (value: string) => { textureTypeSelect.value = value; })); - events.push(texture.settings.on('view.alpha:set', (value) => { + events.push(texture.settings.on('view.alpha:set', (value: boolean) => { alphaToggle.value = !!value; })); - events.push(texture.settings.on('view.filter:set', (value) => { + events.push(texture.settings.on('view.filter:set', (value: boolean) => { filterToggle.value = !!value; })); - events.push(texture.settings.on('view.exposure:set', (value) => { + events.push(texture.settings.on('view.exposure:set', (value: number) => { exposureSlider.value = value; })); @@ -154,14 +158,14 @@ class ShowPanel extends Panel { // mipmap select const numMipmaps = texture.numMipmaps; - const mips = []; + const mips: { v: string; t: string }[] = []; for (let i = 0; i < numMipmaps; ++i) { mips.push({ v: `${i}`, t: `${i}` }); } mipmapSelect.options = mips; // face select - faceButtonsContainer.enabled = texture.cubemap; + faceButtonsContainer.enabled = !!texture.cubemap; this.enabled = !!texture.resource; }); diff --git a/src/style.scss b/src/style.scss index 28a7ed2..c891dc2 100644 --- a/src/style.scss +++ b/src/style.scss @@ -1,4 +1,4 @@ -@import '../node_modules/@playcanvas/pcui/dist/pcui-theme-grey.scss'; +@use '../node_modules/@playcanvas/pcui/dist/pcui-theme-grey.scss' as *; /* texture-tool stylesheet */ diff --git a/src/texture-doc.js b/src/texture-doc.js deleted file mode 100644 index 9c430f4..0000000 --- a/src/texture-doc.js +++ /dev/null @@ -1,62 +0,0 @@ -import { Observer } from '@playcanvas/observer'; - -class TextureDoc { - constructor(asset) { - this.asset = asset; - - // observable/editable state - this.settings = new Observer(); - } - - get filename() { - return this.asset ? this.asset.file.filename : ''; - } - - get url() { - return this.asset ? this.asset.file.url : ''; - } - - get resource() { - return this.asset ? this.asset.resource : null; - } - - get id() { - return this.asset ? this.asset.id : 0; - } - - get width() { - return this.resource ? this.resource.width : null; - } - - get height() { - return this.resource ? this.resource.height : null; - } - - get type() { - return this.resource ? this.resource.type : null; - } - - get format() { - return this.resource ? this.resource.format : null; - } - - get mipmaps() { - return this.resource ? this.resource.mipmaps : null; - } - - get cubemap() { - return this.resource ? this.resource.cubemap : null; - } - - get levels() { - return this.resource ? this.resource._levels : null; - } - - get numMipmaps() { - return this.levels ? this.levels.length : 0; - } -} - -export { - TextureDoc -}; diff --git a/src/texture-doc.ts b/src/texture-doc.ts new file mode 100644 index 0000000..c24ebf7 --- /dev/null +++ b/src/texture-doc.ts @@ -0,0 +1,71 @@ +import { Observer } from '@playcanvas/observer'; +import type { Asset, Texture } from 'playcanvas'; + +interface AssetFile { + filename: string; + url: string; +} + +class TextureDoc { + asset: Asset | null; + settings: Observer; + + constructor(asset: Asset | null) { + this.asset = asset; + + // observable/editable state + this.settings = new Observer(); + } + + get filename(): string { + return this.asset ? (this.asset.file as AssetFile).filename : ''; + } + + get url(): string { + return this.asset ? (this.asset.file as AssetFile).url : ''; + } + + get resource(): Texture | null { + return this.asset ? this.asset.resource as Texture : null; + } + + get id(): number { + return this.asset ? this.asset.id : 0; + } + + get width(): number | null { + return this.resource ? this.resource.width : null; + } + + get height(): number | null { + return this.resource ? this.resource.height : null; + } + + get type(): number | null { + return this.resource ? (this.resource.type as unknown as number) : null; + } + + get format(): number | null { + return this.resource ? this.resource.format : null; + } + + get mipmaps(): boolean | null { + return this.resource ? this.resource.mipmaps : null; + } + + get cubemap(): boolean | null { + return this.resource ? this.resource.cubemap : null; + } + + get levels(): any[] | null { + return this.resource ? (this.resource as any)._levels : null; + } + + get numMipmaps(): number { + return this.levels ? this.levels.length : 0; + } +} + +export { + TextureDoc +}; diff --git a/src/texture-manager.js b/src/texture-manager.ts similarity index 68% rename from src/texture-manager.js rename to src/texture-manager.ts index 2b64c4b..0619a42 100644 --- a/src/texture-manager.js +++ b/src/texture-manager.ts @@ -1,9 +1,17 @@ import { Events } from '@playcanvas/observer'; +import type { AssetRegistry, Asset } from 'playcanvas'; -import { TextureDoc } from './texture-doc.js'; +import { TextureDoc } from './texture-doc'; + +type TextureDocCallback = (err: Error | null, textureDoc: TextureDoc | null) => void; class TextureManager extends Events { - constructor(assets) { + assets: AssetRegistry; + emptyTextureDoc: TextureDoc; + textureDocs: Map; + selectedTextureDoc: TextureDoc | null; + + constructor(assets: AssetRegistry) { super(); this.assets = assets; @@ -13,20 +21,20 @@ class TextureManager extends Events { } // add a texture asset - addTextureDocByUrl(url, filename, callback) { - this.assets.loadFromUrlAndFilename(url, filename, 'texture', (err, asset) => { + addTextureDocByUrl(url: string, filename: string, callback?: TextureDocCallback): void { + this.assets.loadFromUrlAndFilename(url, filename, 'texture', (err: string | null, asset?: Asset) => { if (err) { console.error(err); if (callback) { - callback(err, null); + callback(new Error(err), null); } - } else { + } else if (asset) { this.addTextureDoc(new TextureDoc(asset), callback); } }); } - addTextureDoc(textureDoc, callback) { + addTextureDoc(textureDoc: TextureDoc, callback?: TextureDocCallback): void { this.textureDocs.set(textureDoc.id, textureDoc); this.emit('textureDocAdded', textureDoc); if (callback) { @@ -34,7 +42,7 @@ class TextureManager extends Events { } } - removeTextureDoc(textureDoc) { + removeTextureDoc(textureDoc: TextureDoc): void { const id = textureDoc.id; if (!this.textureDocs.has(id)) { console.error('invalid texture'); @@ -47,25 +55,27 @@ class TextureManager extends Events { } else { // find another texture in the list const idx = ids.indexOf(textureDoc.id) + 1; - this.selectTextureDoc(this.getTextureDoc(ids[idx === ids.length ? idx - 2 : idx])); + this.selectTextureDoc(this.getTextureDoc(ids[idx === ids.length ? idx - 2 : idx])!); } } this.emit('textureDocRemoved', textureDoc); const asset = textureDoc.asset; - asset.unload(); - this.assets.remove(id); + if (asset) { + asset.unload(); + this.assets.remove(asset); + } this.textureDocs.delete(id); } } - selectTextureDoc(textureDoc) { + selectTextureDoc(textureDoc: TextureDoc): void { if (textureDoc !== this.selectedTextureDoc) { this.selectedTextureDoc = textureDoc; this.emit('textureDocSelected', textureDoc); // fire changed values on everything - const recurse = (json, path) => { + const recurse = (json: Record, path: string) => { Object.keys(json).forEach((key) => { const p = `${path}${path.length > 0 ? '.' : ''}${key}`; if (typeof json[key] === 'object') { @@ -80,11 +90,11 @@ class TextureManager extends Events { } } - get textureDocIds() { + get textureDocIds(): number[] { return Array.from(this.textureDocs.keys()); } - getTextureDoc(id) { + getTextureDoc(id: number): TextureDoc | null { return this.textureDocs.get(id) || null; } } diff --git a/src/texture-view.js b/src/texture-view.ts similarity index 82% rename from src/texture-view.js rename to src/texture-view.ts index d997fb7..47e7cdc 100644 --- a/src/texture-view.js +++ b/src/texture-view.ts @@ -17,9 +17,43 @@ import { ShaderChunks, SHADERLANGUAGE_GLSL } from 'playcanvas'; +import type { RenderCanvas } from './render-canvas'; +import type { TextureDoc } from './texture-doc'; + +interface TexelCoord { + u: number; + v: number; +} + +interface PixelCoord { + x: number; + y: number; +} class TextureView { - constructor(canvas) { + canvas: RenderCanvas; + composition: LayerComposition; + layer: Layer; + root: Entity; + material: ShaderMaterial; + render: Entity; + camera: Entity; + defaultTex: Texture; + rebuildMaterial: boolean; + texture: TextureDoc | null; + textureType: string; + alpha: boolean; + face: number; + filter: boolean; + mipmap: number; + exposure: number; + offsetX: number; + offsetY: number; + scale: number; + viewportW: number; + viewportH: number; + + constructor(canvas: RenderCanvas) { const device = canvas.renderer.app.graphicsDevice; this.canvas = canvas; @@ -30,12 +64,12 @@ class TextureView { this.layer = new Layer({ id: -2, enabled: true, - opaqueSortMost: 2, + opaqueSortMode: 2, transparentSortMode: 3 }); this.composition.push(this.layer); - this.canvas.composition = this.composition; + (this.canvas as any).composition = this.composition; // root entity this.root = new Entity(); @@ -78,11 +112,11 @@ class TextureView { layers: [] }); this.camera.setLocalPosition(0, 0, 2.2); - this.camera.camera.layers = [this.layer.id]; + this.camera.camera!.layers = [this.layer.id]; this.root.addChild(this.camera); - this.layer.addMeshInstances(this.render.render.meshInstances); - this.layer.addCamera(this.camera.camera); + this.layer.addMeshInstances(this.render.render!.meshInstances); + this.layer.addCamera(this.camera.camera!); this.root.syncHierarchy(); @@ -93,7 +127,7 @@ class TextureView { }); // handle renders - this.canvas.on('prerender', (frameTime) => { + this.canvas.on('prerender', () => { // set filtering if (this.texture && this.texture.resource) { this.texture.resource.magFilter = this.filter ? FILTER_LINEAR : FILTER_NEAREST; @@ -103,7 +137,7 @@ class TextureView { }); // create default texture - const data = new Uint8ClampedArray([64, 64, 64, 255]); + const data = new Uint8Array([64, 64, 64, 255]); this.defaultTex = new Texture(device, { cubemap: false, width: 1, @@ -128,10 +162,10 @@ class TextureView { this.viewportH = 150; } - setTexture(texture) { + setTexture(texture: TextureDoc): void { if (texture !== this.texture) { this.texture = texture; - this.scale = Math.min(this.viewportW / this.texture.width, this.viewportH / this.texture.height); + this.scale = Math.min(this.viewportW / this.texture.width!, this.viewportH / this.texture.height!); this.offsetX = 0; this.offsetY = 0; this.clamp(); @@ -140,14 +174,14 @@ class TextureView { } } - setFace(value) { + setFace(value: number): void { if (value !== this.face) { this.face = value; this.canvas.render(); } } - setTextureType(value) { + setTextureType(value: string): void { if (value !== this.textureType) { this.textureType = value; this.rebuildMaterial = true; @@ -155,7 +189,7 @@ class TextureView { } } - setAlpha(value) { + setAlpha(value: boolean): void { if (value !== this.alpha) { this.alpha = value; this.rebuildMaterial = true; @@ -163,34 +197,34 @@ class TextureView { } } - setFilter(value) { + setFilter(value: boolean): void { if (value !== this.filter) { this.filter = value; this.canvas.render(); } } - setMipmap(value) { + setMipmap(value: number): void { if (value !== this.mipmap) { this.mipmap = value; this.canvas.render(); } } - setExposure(value) { + setExposure(value: number): void { if (value !== this.exposure) { this.exposure = value; this.canvas.render(); } } - setViewport(width, height) { + setViewport(width: number, height: number): void { this.viewportW = width; this.viewportH = height; this.clamp(); } - setScale(scale, pixelX, pixelY) { + setScale(scale: number, pixelX: number, pixelY: number): void { const tex = this.pixelToTexel(pixelX, this.viewportH - pixelY); // update scale @@ -204,27 +238,27 @@ class TextureView { this.clamp(); } - setOffset(offsetX, offsetY) { + setOffset(offsetX: number, offsetY: number): void { this.offsetX = offsetX; this.offsetY = offsetY; this.clamp(); } - pixelToTexel(x, y) { + pixelToTexel(x: number, y: number): TexelCoord { return { u: (x - this.offsetX) / this.scale, v: (y - this.offsetY) / this.scale }; } - texelToPixel(u, v) { + texelToPixel(u: number, v: number): PixelCoord { return { x: u * this.scale + this.offsetX, y: v * this.scale + this.offsetY }; } - clamp() { + clamp(): void { if (this.texture) { // clamp scale const minScale = 0.2; // Math.min(this.viewportW / this.texture.width, this.viewportH / this.texture.height); @@ -232,11 +266,11 @@ class TextureView { this.scale = Math.max(minScale, Math.min(maxScale, this.scale)); // clamp offsetX - const diffX = this.scale * this.texture.width - this.viewportW; + const diffX = this.scale * this.texture.width! - this.viewportW; this.offsetX = diffX < 0 ? diffX * -0.5 : Math.max(-diffX, Math.min(0, this.offsetX)); // clamp offsetY - const diffY = this.scale * this.texture.height - this.viewportH; + const diffY = this.scale * this.texture.height! - this.viewportH; this.offsetY = diffY < 0 ? diffY * -0.5 : Math.max(-diffY, Math.min(0, this.offsetY)); this.canvas.render(); @@ -245,8 +279,8 @@ class TextureView { // Build the fragment shader source with current settings. // Original shader logic from src/chunks/texture.frag.js, adapted for ShaderMaterial API. - buildFragmentShader() { - const decodeFunc = { + buildFragmentShader(): string { + const decodeFunc: Record = { 'gamma': 'decodeGamma', 'linear': 'decodeLinear', 'rgbm': 'decodeRGBM', @@ -334,7 +368,7 @@ void main() { } // prepare the material for rendering - prepare() { + prepare(): void { if (this.rebuildMaterial) { this.rebuildMaterial = false; diff --git a/src/viewport-panel.js b/src/viewport-panel.ts similarity index 67% rename from src/viewport-panel.js rename to src/viewport-panel.ts index f029898..8da6055 100644 --- a/src/viewport-panel.js +++ b/src/viewport-panel.ts @@ -1,16 +1,25 @@ import { Container } from 'pcui'; +import type { EventHandle } from '@playcanvas/observer'; -import { InfoPanel } from './info-panel.js'; -import { RenderCanvas } from './render-canvas.js'; -import { TextureView } from './texture-view.js'; +import { InfoPanel } from './info-panel'; +import { RenderCanvas } from './render-canvas'; +import { TextureView } from './texture-view'; +import type { Renderer } from './renderer'; +import type { TextureManager } from './texture-manager'; +import type { TextureDoc } from './texture-doc'; class ViewportPanel extends Container { - constructor(renderer, textureManager, args = {}) { + renderer: Renderer; + canvas: RenderCanvas; + view: TextureView; + texture: TextureDoc | null; + + constructor(renderer: Renderer, textureManager: TextureManager, args: Record = {}) { Object.assign(args, { class: 'texture-2d-panel', flex: true, - flexGrow: 1, - flexShrink: 1 + flexGrow: '1', + flexShrink: '1' }); super(args); @@ -23,8 +32,8 @@ class ViewportPanel extends Container { this.append(new InfoPanel(textureManager)); // handle mouse events - this.dom.addEventListener('wheel', (event) => { - const rect = event.target.getBoundingClientRect(); + this.dom.addEventListener('wheel', (event: WheelEvent) => { + const rect = (event.target as HTMLElement).getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; this.view.setScale(this.view.scale - event.deltaY * 0.01, x * window.devicePixelRatio, y * window.devicePixelRatio); @@ -34,14 +43,14 @@ class ViewportPanel extends Container { let my = 0; let dragging = false; - this.dom.addEventListener('mousedown', (event) => { + this.dom.addEventListener('mousedown', (event: MouseEvent) => { event.preventDefault(); mx = event.offsetX; my = event.offsetY; dragging = true; }); - this.dom.addEventListener('mousemove', (event) => { + this.dom.addEventListener('mousemove', (event: MouseEvent) => { const ratio = window.devicePixelRatio; if (dragging) { @@ -56,9 +65,9 @@ class ViewportPanel extends Container { if (t) { const uv = this.view.pixelToTexel(event.offsetX * ratio, this.view.viewportH - event.offsetY * ratio); - if (uv.u >= 0 && uv.v >= 0 && uv.u < t.width && uv.v < t.height) { + if (uv.u >= 0 && uv.v >= 0 && uv.u < t.width! && uv.v < t.height!) { uv.u = Math.floor(uv.u / Math.pow(2, this.view.mipmap)); - uv.v = Math.floor((t.height - uv.v) / Math.pow(2, this.view.mipmap)); + uv.v = Math.floor((t.height! - uv.v) / Math.pow(2, this.view.mipmap)); // this.cursorTexel.text = `${uv.u.toFixed(0)},${uv.v.toFixed(0)}`; } else { // this.cursorTexel.text = `-`; @@ -66,38 +75,38 @@ class ViewportPanel extends Container { } }); - this.dom.addEventListener('mouseup', (event) => { + this.dom.addEventListener('mouseup', () => { dragging = false; }); - textureManager.on('textureDocAdded', (doc) => { + textureManager.on('textureDocAdded', () => { // doc.settings.patch({ // viewport: { // } // }); }); - const events = []; - textureManager.on('textureDocSelected', (texture) => { + const events: EventHandle[] = []; + textureManager.on('textureDocSelected', (texture: TextureDoc) => { events.forEach(ev => ev.unbind()); events.length = 0; - events.push(texture.settings.on('view.filter:set', (value) => { + events.push(texture.settings.on('view.filter:set', (value: boolean) => { this.view.setFilter(value); })); - events.push(texture.settings.on('view.face:set', (value) => { + events.push(texture.settings.on('view.face:set', (value: string) => { this.view.setFace(parseInt(value, 10)); })); - events.push(texture.settings.on('view.mipmap:set', (value) => { + events.push(texture.settings.on('view.mipmap:set', (value: string) => { this.view.setMipmap(parseInt(value, 10)); })); - events.push(texture.settings.on('view.type:set', (value) => { + events.push(texture.settings.on('view.type:set', (value: string) => { this.view.setTextureType(value); })); - events.push(texture.settings.on('view.alpha:set', (value) => { + events.push(texture.settings.on('view.alpha:set', (value: boolean) => { this.view.setAlpha(value); })); - events.push(texture.settings.on('view.exposure:set', (value) => { + events.push(texture.settings.on('view.exposure:set', (value: string) => { this.view.setExposure(Math.pow(2, parseInt(value, 10))); })); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..335fef1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "allowJs": true, + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "esModuleInterop": true, + "inlineSources": true, + "lib": ["es2022", "dom", "dom.iterable"], + "types": ["@webgpu/types"], + "moduleResolution": "node", + "noEmit": true, + "noImplicitAny": false, + "resolveJsonModule": true, + "sourceMap": true, + "target": "es2022", + "skipLibCheck": true, + "paths": { + "pcui": ["node_modules/@playcanvas/pcui/types/index.d.ts"], + "playcanvas": ["node_modules/playcanvas/build/playcanvas.d.ts"] + } + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +}