mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2025-02-21 06:29:02 +00:00
removed script platform
This commit is contained in:
parent
fbd9089a8d
commit
a2d818c3e5
134
package-lock.json
generated
134
package-lock.json
generated
@ -9,27 +9,20 @@
|
||||
"version": "3.11.0",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@types/chroma-js": "^2.1.3",
|
||||
"@types/dompurify": "^2.3.4",
|
||||
"@types/emscripten": "^1.39.5",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"atob": "^2.1.x",
|
||||
"binaryen": "^101.0.0",
|
||||
"btoa": "^1.2.x",
|
||||
"chroma-js": "^2.1.2",
|
||||
"clipboard": "^2.0.6",
|
||||
"dompurify": "^2.4.0",
|
||||
"error-stack-parser": "^2.0.6",
|
||||
"fast-png": "^5.0.4",
|
||||
"file-saver": "^2.0.5",
|
||||
"jquery": "^3.6.3",
|
||||
"jszip": "^3.7.0",
|
||||
"localforage": "^1.9.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"octokat": "^0.10.0",
|
||||
"preact": "^10.5.14",
|
||||
"split.js": "^1.6.2",
|
||||
"yufka": "^2.0.1"
|
||||
"split.js": "^1.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bootbox": "^5.1.3",
|
||||
@ -45,6 +38,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"esbuild": "^0.12.29",
|
||||
"express": "^4.18.2",
|
||||
"fast-png": "^5.0.4",
|
||||
"jsdom": "^21.1.0",
|
||||
"lzg": "^1.0.x",
|
||||
"mocha": "^9.2.0",
|
||||
@ -865,11 +859,6 @@
|
||||
"integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/chroma-js": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.5.tgz",
|
||||
"integrity": "sha512-LnJmElng1zoH7GOYqIo/EuL7L0/vEh5rc+fKaF4rsylJyjwOkX0pXeBemH25FQAWHifKJWqaRwR0EhC+yDod9A=="
|
||||
},
|
||||
"node_modules/@types/dompurify": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.4.0.tgz",
|
||||
@ -962,11 +951,6 @@
|
||||
"@types/sizzle": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/js-yaml": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
|
||||
"integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA=="
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.200",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz",
|
||||
@ -988,7 +972,8 @@
|
||||
"node_modules/@types/pako": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.4.tgz",
|
||||
"integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA=="
|
||||
"integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/selenium-webdriver": {
|
||||
"version": "4.1.15",
|
||||
@ -1084,6 +1069,7 @@
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@ -1735,11 +1721,6 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chroma-js": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
|
||||
"integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
|
||||
},
|
||||
"node_modules/chromedriver": {
|
||||
"version": "119.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-119.0.0.tgz",
|
||||
@ -2624,6 +2605,7 @@
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
|
||||
"integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"stackframe": "^1.3.4"
|
||||
}
|
||||
@ -2851,6 +2833,7 @@
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-5.0.4.tgz",
|
||||
"integrity": "sha512-vTNj6yixRnclW6sTlCeH6sNRLBOhM5ITmlo1LSU5ojKEc2e9kZkqXPo2xzBxKb61MBCXRXBcr8qJztOHr2O6WQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/pako": "^1.0.1",
|
||||
"iobuffer": "^5.0.2",
|
||||
@ -2860,7 +2843,8 @@
|
||||
"node_modules/fast-png/node_modules/pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
@ -3617,7 +3601,8 @@
|
||||
"node_modules/iobuffer": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.3.2.tgz",
|
||||
"integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw=="
|
||||
"integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ip-regex": {
|
||||
"version": "4.3.0",
|
||||
@ -4668,14 +4653,6 @@
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
|
||||
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
|
||||
"dependencies": {
|
||||
"sourcemap-codec": "^1.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
@ -6174,15 +6151,6 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.12.1",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
|
||||
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
@ -6903,12 +6871,6 @@
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sourcemap-codec": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||
"deprecated": "Please use @jridgewell/sourcemap-codec instead"
|
||||
},
|
||||
"node_modules/spawn-wrap": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz",
|
||||
@ -7002,7 +6964,8 @@
|
||||
"node_modules/stackframe": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
|
||||
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
|
||||
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/stacktrace-parser": {
|
||||
"version": "0.1.10",
|
||||
@ -7983,18 +7946,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/yufka": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yufka/-/yufka-2.1.1.tgz",
|
||||
"integrity": "sha512-/8gX42mxcoJ21pdGbEg2w/6l0xlL7XFkFarcLmq2upxt6PTf7ehHsPnQQibGd6yLP1odMh0F04eZMh/gZhQHIQ==",
|
||||
"dependencies": {
|
||||
"acorn": "^8.3.0",
|
||||
"magic-string": "^0.25.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yup": {
|
||||
"version": "0.32.11",
|
||||
"resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz",
|
||||
@ -8692,11 +8643,6 @@
|
||||
"integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==",
|
||||
"optional": true
|
||||
},
|
||||
"@types/chroma-js": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.5.tgz",
|
||||
"integrity": "sha512-LnJmElng1zoH7GOYqIo/EuL7L0/vEh5rc+fKaF4rsylJyjwOkX0pXeBemH25FQAWHifKJWqaRwR0EhC+yDod9A=="
|
||||
},
|
||||
"@types/dompurify": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.4.0.tgz",
|
||||
@ -8788,11 +8734,6 @@
|
||||
"@types/sizzle": "*"
|
||||
}
|
||||
},
|
||||
"@types/js-yaml": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
|
||||
"integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA=="
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.200",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz",
|
||||
@ -8814,7 +8755,8 @@
|
||||
"@types/pako": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.4.tgz",
|
||||
"integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA=="
|
||||
"integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/selenium-webdriver": {
|
||||
"version": "4.1.15",
|
||||
@ -8906,7 +8848,8 @@
|
||||
"acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw=="
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"devOptional": true
|
||||
},
|
||||
"acorn-globals": {
|
||||
"version": "7.0.1",
|
||||
@ -9400,11 +9343,6 @@
|
||||
"readdirp": "~3.6.0"
|
||||
}
|
||||
},
|
||||
"chroma-js": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
|
||||
"integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
|
||||
},
|
||||
"chromedriver": {
|
||||
"version": "119.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-119.0.0.tgz",
|
||||
@ -10096,6 +10034,7 @@
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
|
||||
"integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"stackframe": "^1.3.4"
|
||||
}
|
||||
@ -10270,6 +10209,7 @@
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-5.0.4.tgz",
|
||||
"integrity": "sha512-vTNj6yixRnclW6sTlCeH6sNRLBOhM5ITmlo1LSU5ojKEc2e9kZkqXPo2xzBxKb61MBCXRXBcr8qJztOHr2O6WQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/pako": "^1.0.1",
|
||||
"iobuffer": "^5.0.2",
|
||||
@ -10279,7 +10219,8 @@
|
||||
"pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -10859,7 +10800,8 @@
|
||||
"iobuffer": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.3.2.tgz",
|
||||
"integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw=="
|
||||
"integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw==",
|
||||
"dev": true
|
||||
},
|
||||
"ip-regex": {
|
||||
"version": "4.3.0",
|
||||
@ -11707,14 +11649,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
|
||||
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
|
||||
"requires": {
|
||||
"sourcemap-codec": "^1.4.8"
|
||||
}
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
@ -12871,11 +12805,6 @@
|
||||
"integrity": "sha512-8xCNE/aT/EXKenuMDZ+xTVwkT8gsoHN2z/Q29l80u0ppGEXVvsKRzNMbtKhg8LS8k1tJLAHHylf6p4VFmP6XUQ==",
|
||||
"dev": true
|
||||
},
|
||||
"preact": {
|
||||
"version": "10.12.1",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
|
||||
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg=="
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
@ -13463,11 +13392,6 @@
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"sourcemap-codec": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
|
||||
},
|
||||
"spawn-wrap": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz",
|
||||
@ -13556,7 +13480,8 @@
|
||||
"stackframe": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
|
||||
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
|
||||
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
|
||||
"optional": true
|
||||
},
|
||||
"stacktrace-parser": {
|
||||
"version": "0.1.10",
|
||||
@ -14309,15 +14234,6 @@
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"devOptional": true
|
||||
},
|
||||
"yufka": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yufka/-/yufka-2.1.1.tgz",
|
||||
"integrity": "sha512-/8gX42mxcoJ21pdGbEg2w/6l0xlL7XFkFarcLmq2upxt6PTf7ehHsPnQQibGd6yLP1odMh0F04eZMh/gZhQHIQ==",
|
||||
"requires": {
|
||||
"acorn": "^8.3.0",
|
||||
"magic-string": "^0.25.2"
|
||||
}
|
||||
},
|
||||
"yup": {
|
||||
"version": "0.32.11",
|
||||
"resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz",
|
||||
|
10
package.json
10
package.json
@ -10,27 +10,20 @@
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@types/chroma-js": "^2.1.3",
|
||||
"@types/dompurify": "^2.3.4",
|
||||
"@types/emscripten": "^1.39.5",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"atob": "^2.1.x",
|
||||
"binaryen": "^101.0.0",
|
||||
"btoa": "^1.2.x",
|
||||
"chroma-js": "^2.1.2",
|
||||
"clipboard": "^2.0.6",
|
||||
"dompurify": "^2.4.0",
|
||||
"error-stack-parser": "^2.0.6",
|
||||
"fast-png": "^5.0.4",
|
||||
"file-saver": "^2.0.5",
|
||||
"jquery": "^3.6.3",
|
||||
"jszip": "^3.7.0",
|
||||
"localforage": "^1.9.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"octokat": "^0.10.0",
|
||||
"preact": "^10.5.14",
|
||||
"split.js": "^1.6.2",
|
||||
"yufka": "^2.0.1"
|
||||
"split.js": "^1.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bootbox": "^5.1.3",
|
||||
@ -46,6 +39,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"esbuild": "^0.12.29",
|
||||
"express": "^4.18.2",
|
||||
"fast-png": "^5.0.4",
|
||||
"jsdom": "^21.1.0",
|
||||
"lzg": "^1.0.x",
|
||||
"mocha": "^9.2.0",
|
||||
|
@ -1,294 +0,0 @@
|
||||
|
||||
import { WorkerError } from "../workertypes";
|
||||
import ErrorStackParser = require("error-stack-parser");
|
||||
import yufka from 'yufka';
|
||||
import * as bitmap from "./lib/bitmap";
|
||||
import * as io from "./lib/io";
|
||||
import * as output from "./lib/output";
|
||||
import * as color from "./lib/color";
|
||||
import * as scriptui from "./lib/scriptui";
|
||||
|
||||
export const PROP_CONSTRUCTOR_NAME = "$$consname";
|
||||
|
||||
export interface Cell {
|
||||
id: string;
|
||||
object?: any;
|
||||
}
|
||||
|
||||
export interface RunResult {
|
||||
cells: Cell[];
|
||||
state: {};
|
||||
}
|
||||
|
||||
const IMPORTS = {
|
||||
'bitmap': bitmap,
|
||||
'io': io,
|
||||
'output': output,
|
||||
'color': color,
|
||||
'ui': scriptui,
|
||||
}
|
||||
|
||||
const LINE_NUMBER_OFFSET = 3; // TODO: shouldnt need?
|
||||
|
||||
const GLOBAL_BADLIST = [
|
||||
'eval'
|
||||
]
|
||||
|
||||
const GLOBAL_GOODLIST = [
|
||||
'eval', // 'eval' can't be defined or assigned to in strict mode code
|
||||
'Math', 'JSON',
|
||||
'parseFloat', 'parseInt', 'isFinite', 'isNaN',
|
||||
'String', 'Symbol', 'Number', 'Object', 'Boolean', 'NaN', 'Infinity', 'Date', 'BigInt',
|
||||
'Set', 'Map', 'RegExp', 'Array', 'ArrayBuffer', 'DataView',
|
||||
'Float32Array', 'Float64Array',
|
||||
'Int8Array', 'Int16Array', 'Int32Array',
|
||||
'Uint8Array', 'Uint16Array', 'Uint32Array', 'Uint8ClampedArray',
|
||||
]
|
||||
|
||||
class RuntimeError extends Error {
|
||||
constructor(public loc: acorn.SourceLocation, msg: string) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function setConstructorName(o: object) : void {
|
||||
let name = Object.getPrototypeOf(o)?.constructor?.name;
|
||||
if (name != null && name != 'Object') {
|
||||
o[PROP_CONSTRUCTOR_NAME] = name;
|
||||
}
|
||||
}
|
||||
|
||||
export class Environment {
|
||||
preamble: string;
|
||||
postamble: string;
|
||||
obj: {};
|
||||
seq: number;
|
||||
declvars : {[name : string] : acorn.Node};
|
||||
builtins : {}
|
||||
|
||||
constructor(
|
||||
public readonly globalenv: any,
|
||||
public readonly path: string
|
||||
) {
|
||||
var badlst = Object.getOwnPropertyNames(this.globalenv).filter(name => GLOBAL_GOODLIST.indexOf(name) < 0);
|
||||
this.builtins = {
|
||||
print: (...args) => this.print(args),
|
||||
...IMPORTS
|
||||
}
|
||||
this.preamble = `'use strict';var ${badlst.join(',')};`;
|
||||
for (var impname in this.builtins) {
|
||||
this.preamble += `var ${impname}=$$.${impname};`
|
||||
}
|
||||
this.preamble += '{\n';
|
||||
this.postamble = '\n}';
|
||||
}
|
||||
error(varname: string, msg: string) {
|
||||
let obj = this.declvars && this.declvars[varname];
|
||||
console.log('ERROR', varname, obj, this);
|
||||
throw new RuntimeError(obj && obj.loc, msg);
|
||||
}
|
||||
print(args: any[]) {
|
||||
if (args && args.length > 0 && args[0] != null) {
|
||||
this.obj[`$print__${this.seq++}`] = args.length == 1 ? args[0] : args;
|
||||
}
|
||||
}
|
||||
preprocess(code: string): string {
|
||||
this.declvars = {};
|
||||
this.seq = 0;
|
||||
let options = {
|
||||
// https://www.npmjs.com/package/magic-string#sgeneratemap-options-
|
||||
sourceMap: {
|
||||
file: this.path,
|
||||
source: this.path,
|
||||
hires: false,
|
||||
includeContent: false
|
||||
},
|
||||
// https://github.com/acornjs/acorn/blob/master/acorn/README.md
|
||||
acorn: {
|
||||
ecmaVersion: 6 as any,
|
||||
locations: true,
|
||||
allowAwaitOutsideFunction: true,
|
||||
allowReturnOutsideFunction: true,
|
||||
allowReserved: true,
|
||||
}
|
||||
};
|
||||
const result = yufka(code, options, (node, { update, source, parent }) => {
|
||||
const isTopLevel = () => {
|
||||
return parent() && parent().type === 'ExpressionStatement' && parent(2) && parent(2).type === 'Program';
|
||||
}
|
||||
const convertTopToPrint = () => {
|
||||
if (isTopLevel()) {
|
||||
let printkey = `$print__${this.seq++}`;
|
||||
update(`this.${printkey} = io.data.load(${source()}, ${JSON.stringify(printkey)})`);
|
||||
//update(`print(${source()});`)
|
||||
}
|
||||
}
|
||||
const left = node['left'];
|
||||
switch (node.type) {
|
||||
// add preamble, postamble
|
||||
case 'Program':
|
||||
update(`${this.preamble}${source()}${this.postamble}`)
|
||||
break;
|
||||
// error on forbidden keywords
|
||||
case 'Identifier':
|
||||
if (GLOBAL_BADLIST.indexOf(source()) >= 0) {
|
||||
update(`__FORBIDDEN__KEYWORD__${source()}__`) // TODO? how to preserve line number?
|
||||
} else {
|
||||
convertTopToPrint();
|
||||
}
|
||||
break;
|
||||
// x = expr --> var x = expr (first use)
|
||||
case 'AssignmentExpression':
|
||||
if (isTopLevel()) {
|
||||
if (left && left.type === 'Identifier') {
|
||||
if (!this.declvars[left.name]) {
|
||||
update(`var ${left.name}=io.data.load(this.${source()}, ${JSON.stringify(left.name)})`)
|
||||
this.declvars[left.name] = left;
|
||||
} else {
|
||||
update(`${left.name}=this.${source()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
// convert lone expressions to print()
|
||||
case 'UnaryExpression':
|
||||
case 'BinaryExpression':
|
||||
case 'CallExpression':
|
||||
case 'MemberExpression':
|
||||
convertTopToPrint();
|
||||
break;
|
||||
// literal comments
|
||||
case 'Literal':
|
||||
if (typeof node['value'] === 'string' && isTopLevel()) {
|
||||
update(`this.$doc__${this.seq++} = { literaltext: ${source()} };`);
|
||||
} else {
|
||||
convertTopToPrint();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
return result.toString();
|
||||
}
|
||||
async run(code: string): Promise<void> {
|
||||
// TODO: split into cells based on "--" linebreaks?
|
||||
code = this.preprocess(code);
|
||||
this.obj = {};
|
||||
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
|
||||
const fn = new AsyncFunction('$$', code).bind(this.obj, this.builtins);
|
||||
await fn.call(this);
|
||||
this.checkResult(this.obj, new Set(), []);
|
||||
}
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
|
||||
// TODO: return initial location of thingie
|
||||
checkResult(o, checked: Set<object>, fullkey: string[]) {
|
||||
if (o == null) return;
|
||||
if (checked.has(o)) return;
|
||||
if (typeof o === 'object') {
|
||||
setConstructorName(o);
|
||||
delete o.$$callback; // clear callbacks (TODO? put somewhere else?)
|
||||
if (o.length > 100) return; // big array, don't bother
|
||||
if (o.BYTES_PER_ELEMENT > 0) return; // typed array, don't bother
|
||||
checked.add(o); // so we don't recurse if cycle
|
||||
function prkey() { return fullkey.join('.') }
|
||||
// go through all object properties recursively
|
||||
for (var [key, value] of Object.entries(o)) {
|
||||
if (value == null && fullkey.length == 0 && !key.startsWith("$")) {
|
||||
this.error(key, `"${key}" has no value.`)
|
||||
}
|
||||
fullkey.push(key);
|
||||
if (typeof value === 'function') {
|
||||
if (fullkey.length == 1)
|
||||
this.error(fullkey[0], `"${prkey()}" is a function. Did you forget to pass parameters?`); // TODO? did you mean (needs to see entire expr)
|
||||
else
|
||||
this.error(fullkey[0], `This expression may be incomplete, or it contains a function object: ${prkey()}`); // TODO? did you mean (needs to see entire expr)
|
||||
}
|
||||
if (typeof value === 'symbol') {
|
||||
this.error(fullkey[0], `"${prkey()}" is a Symbol, and can't be used.`) // TODO?
|
||||
}
|
||||
if (value instanceof Promise) {
|
||||
this.error(fullkey[0], `"${prkey()}" is unresolved. Use "await" before expression.`) // TODO?
|
||||
}
|
||||
this.checkResult(value, checked, fullkey);
|
||||
fullkey.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
render(): Cell[] {
|
||||
var cells = [];
|
||||
for (var [key, value] of Object.entries(this.obj)) {
|
||||
if (typeof value === 'function') {
|
||||
// TODO: find other values, functions embedded in objects?
|
||||
} else {
|
||||
var cell: Cell = { id: key, object: value };
|
||||
cells.push(cell);
|
||||
}
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
extractErrors(e: Error): WorkerError[] {
|
||||
let loc = e['loc'];
|
||||
if (loc && loc.start && loc.end) {
|
||||
return [{
|
||||
path: this.path,
|
||||
msg: e.message,
|
||||
line: loc.start.line,
|
||||
start: loc.start.column,
|
||||
end: loc.end.line,
|
||||
}]
|
||||
}
|
||||
if (loc && loc.line != null) {
|
||||
return [{
|
||||
path: this.path,
|
||||
msg: e.message,
|
||||
line: loc.line,
|
||||
start: loc.column,
|
||||
}]
|
||||
}
|
||||
// TODO: Cannot parse given Error object?
|
||||
let frames = ErrorStackParser.parse(e);
|
||||
let frame = frames.findIndex(f => f.functionName === 'anonymous');
|
||||
let errors = [];
|
||||
// if ErrorStackParser fails, resort to regex
|
||||
if (frame < 0 && e.stack != null) {
|
||||
let m = /.anonymous.:(\d+):(\d+)/g.exec(e.stack);
|
||||
if (m != null) {
|
||||
errors.push( {
|
||||
path: this.path,
|
||||
msg: e.message,
|
||||
line: parseInt(m[1]) - LINE_NUMBER_OFFSET,
|
||||
});
|
||||
}
|
||||
}
|
||||
// otherwise iterate thru all the frames
|
||||
while (frame >= 0) {
|
||||
console.log(frames[frame]);
|
||||
if (frames[frame].fileName.endsWith('Function')) {
|
||||
// TODO: use source map
|
||||
errors.push( {
|
||||
path: this.path,
|
||||
msg: e.message,
|
||||
line: frames[frame].lineNumber - LINE_NUMBER_OFFSET,
|
||||
//start: frames[frame].columnNumber,
|
||||
} );
|
||||
}
|
||||
--frame;
|
||||
}
|
||||
// if no stack frames parsed, last resort error msg
|
||||
if (errors.length == 0) {
|
||||
errors.push( {
|
||||
path: this.path,
|
||||
msg: e.message,
|
||||
line: 0
|
||||
} );
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
commitLoadableState() {
|
||||
// TODO: visit children?
|
||||
for (let [key, value] of Object.entries(this.obj)) {
|
||||
let loadable = <any>value as io.Loadable;
|
||||
io.data.save(loadable, key);
|
||||
}
|
||||
return io.$$getData();
|
||||
}
|
||||
}
|
@ -1,474 +0,0 @@
|
||||
|
||||
// TODO: dynamic import
|
||||
import * as fastpng from 'fast-png';
|
||||
import { Palette } from './color';
|
||||
import * as io from './io'
|
||||
import * as color from './color'
|
||||
import { coerceToArray, findIntegerFactors, RGBA } from '../../util';
|
||||
|
||||
export type PixelMapFunction = (x: number, y: number) => number;
|
||||
|
||||
export abstract class AbstractBitmap<T> {
|
||||
aspect? : number; // aspect ratio, null == default == 1:1
|
||||
style? : {} = {}; // CSS styles (TODO: other elements?)
|
||||
|
||||
constructor(
|
||||
public readonly width: number,
|
||||
public readonly height: number,
|
||||
) {
|
||||
}
|
||||
abstract blank(width: number, height: number) : AbstractBitmap<T>;
|
||||
abstract setarray(arr: ArrayLike<number>) : void;
|
||||
abstract set(x: number, y: number, val: number) : void;
|
||||
abstract get(x: number, y: number): number;
|
||||
abstract getrgba(x: number, y: number): number;
|
||||
|
||||
inbounds(x: number, y: number): boolean {
|
||||
return (x >= 0 && x < this.width && y >= 0 && y < this.height);
|
||||
}
|
||||
assign(fn: ArrayLike<number> | PixelMapFunction) : void {
|
||||
if (typeof fn === 'function') {
|
||||
for (let y=0; y<this.height; y++) {
|
||||
for (let x=0; x<this.width; x++) {
|
||||
this.set(x, y, fn(x, y));
|
||||
}
|
||||
}
|
||||
} else if (fn && fn['length'] != null) {
|
||||
this.setarray(fn);
|
||||
} else {
|
||||
throw new Error(`Illegal argument to assign(): ${fn}`)
|
||||
}
|
||||
}
|
||||
clone() : AbstractBitmap<T> {
|
||||
let bmp = this.blank(this.width, this.height);
|
||||
bmp.assign((x,y) => this.get(x,y));
|
||||
return bmp;
|
||||
}
|
||||
crop(srcx: number, srcy: number, width: number, height: number) {
|
||||
let dest = this.blank(width, height);
|
||||
dest.assign((x, y) => this.get(x + srcx, y + srcy));
|
||||
return dest;
|
||||
}
|
||||
blit(src: BitmapType,
|
||||
destx: number, desty: number,
|
||||
srcx: number, srcy: number)
|
||||
{
|
||||
destx |= 0;
|
||||
desty |= 0;
|
||||
srcx |= 0;
|
||||
srcy |= 0;
|
||||
for (var y=0; y<src.height; y++) {
|
||||
for (var x=0; x<src.width; x++) {
|
||||
let rgba = src.getrgba(x+srcx, y+srcy);
|
||||
this.set(x+destx, y+desty, rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
fill(destx: number, desty: number, width:number, height:number, value:number) {
|
||||
for (var y=0; y<height; y++) {
|
||||
for (var x=0; x<width; x++) {
|
||||
this.set(x+destx, y+desty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class RGBABitmap extends AbstractBitmap<RGBABitmap> {
|
||||
public readonly rgba: Uint32Array
|
||||
|
||||
constructor(
|
||||
width: number,
|
||||
height: number,
|
||||
initial?: Uint32Array | PixelMapFunction
|
||||
) {
|
||||
super(width, height);
|
||||
this.rgba = new Uint32Array(this.width * this.height);
|
||||
if (initial) this.assign(initial);
|
||||
}
|
||||
setarray(arr: ArrayLike<number>) {
|
||||
this.rgba.set(arr);
|
||||
}
|
||||
set(x: number, y: number, rgba: number) {
|
||||
if (this.inbounds(x,y)) this.rgba[y * this.width + x] = rgba;
|
||||
}
|
||||
get(x: number, y: number): number {
|
||||
return this.inbounds(x,y) ? this.rgba[y * this.width + x] : 0;
|
||||
}
|
||||
getrgba(x: number, y: number): number {
|
||||
return this.get(x, y);
|
||||
}
|
||||
blank(width?: number, height?: number) : RGBABitmap {
|
||||
return new RGBABitmap(width || this.width, height || this.height);
|
||||
}
|
||||
clone() : RGBABitmap {
|
||||
let bitmap = this.blank(this.width, this.height);
|
||||
bitmap.rgba.set(this.rgba);
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class MappedBitmap extends AbstractBitmap<MappedBitmap> {
|
||||
public readonly pixels: Uint8Array
|
||||
|
||||
constructor(
|
||||
width: number,
|
||||
height: number,
|
||||
public readonly bpp: number,
|
||||
initial?: Uint8Array | PixelMapFunction
|
||||
) {
|
||||
super(width, height);
|
||||
if (bpp != 1 && bpp != 2 && bpp != 4 && bpp != 8)
|
||||
throw new Error(`Invalid bits per pixel: ${bpp}`);
|
||||
this.pixels = new Uint8Array(this.width * this.height);
|
||||
if (initial) this.assign(initial);
|
||||
}
|
||||
setarray(arr: ArrayLike<number>) {
|
||||
this.pixels.set(arr);
|
||||
}
|
||||
set(x: number, y: number, index: number) {
|
||||
if (this.inbounds(x,y)) this.pixels[y * this.width + x] = index;
|
||||
}
|
||||
get(x: number, y: number): number {
|
||||
return this.inbounds(x,y) ? this.pixels[y * this.width + x] : 0;
|
||||
}
|
||||
}
|
||||
|
||||
function getbpp(x : number | Palette) : number {
|
||||
if (typeof x === 'number') return x;
|
||||
if (x instanceof Palette) {
|
||||
if (x.colors.length <= 2) return 1;
|
||||
else if (x.colors.length <= 4) return 2;
|
||||
else if (x.colors.length <= 16) return 4;
|
||||
}
|
||||
return 8;
|
||||
}
|
||||
|
||||
export class IndexedBitmap extends MappedBitmap {
|
||||
public palette: Palette;
|
||||
|
||||
constructor(
|
||||
width: number,
|
||||
height: number,
|
||||
bppOrPalette: number | Palette,
|
||||
initial?: Uint8Array | PixelMapFunction
|
||||
) {
|
||||
super(width, height, getbpp(bppOrPalette), initial);
|
||||
this.palette = bppOrPalette instanceof Palette
|
||||
? bppOrPalette
|
||||
: color.palette.colors(1 << this.bpp);
|
||||
}
|
||||
getrgba(x: number, y: number): number {
|
||||
return this.palette && this.palette.colors[this.get(x, y)];
|
||||
}
|
||||
blank(width?: number, height?: number, newPalette?: Palette) : IndexedBitmap {
|
||||
let bitmap = new IndexedBitmap(width || this.width, height || this.height, newPalette || this.palette);
|
||||
return bitmap;
|
||||
}
|
||||
clone() : IndexedBitmap {
|
||||
let bitmap = this.blank(this.width, this.height);
|
||||
bitmap.pixels.set(this.pixels);
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
export function rgba(width: number, height: number, initial?: Uint32Array | PixelMapFunction) {
|
||||
return new RGBABitmap(width, height, initial);
|
||||
}
|
||||
|
||||
export function indexed(width: number, height: number, bpp: number, initial?: Uint8Array | PixelMapFunction) {
|
||||
return new IndexedBitmap(width, height, bpp, initial);
|
||||
}
|
||||
|
||||
export type BitmapType = RGBABitmap | IndexedBitmap;
|
||||
|
||||
// TODO: check arguments
|
||||
export function decode(arr: Uint8Array, fmt: PixelEditorImageFormat) {
|
||||
var pixels = convertWordsToImages(arr, fmt);
|
||||
// TODO: guess if missing w/h/count?
|
||||
// TODO: reverse mapping
|
||||
// TODO: maybe better composable functions
|
||||
let bpp = (fmt.bpp||1) * (fmt.np||1);
|
||||
return pixels.map(data => new IndexedBitmap(fmt.w, fmt.h, bpp, data));
|
||||
}
|
||||
|
||||
export interface BitmapAnalysis {
|
||||
min: {w: number, h: number};
|
||||
max: {w: number, h: number};
|
||||
}
|
||||
|
||||
export function analyze(bitmaps: BitmapType[]) {
|
||||
bitmaps = coerceToArray(bitmaps);
|
||||
let r = {min:{w:0,h:0}, max:{w:0,h:0}};
|
||||
for (let bmp of bitmaps) {
|
||||
if (!(bmp instanceof AbstractBitmap)) return null;
|
||||
r.min.w = Math.min(bmp.width);
|
||||
r.max.w = Math.max(bmp.width);
|
||||
r.min.h = Math.min(bmp.height);
|
||||
r.max.h = Math.max(bmp.height);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
export interface MontageOptions {
|
||||
analysis?: BitmapAnalysis;
|
||||
gap?: number;
|
||||
aspect?: number;
|
||||
}
|
||||
|
||||
export function montage(bitmaps: BitmapType[], options?: MontageOptions) {
|
||||
bitmaps = coerceToArray(bitmaps);
|
||||
let minmax = (options && options.analysis) || analyze(bitmaps);
|
||||
if (minmax == null) throw new Error(`Expected an array of bitmaps`);
|
||||
let hitrects = [];
|
||||
let aspect = (options && options.aspect) || 1;
|
||||
let gap = (options && options.gap) || 0;
|
||||
if (minmax.min.w == minmax.max.w && minmax.min.h == minmax.max.h) {
|
||||
let totalPixels = minmax.min.w * minmax.min.h * bitmaps.length;
|
||||
let factors = findIntegerFactors(totalPixels, minmax.max.w, minmax.max.h, aspect);
|
||||
let columns = Math.ceil(factors.a / minmax.min.w); // TODO: rounding?
|
||||
let rows = Math.ceil(factors.b / minmax.min.h);
|
||||
let result = new RGBABitmap(factors.a + gap * (columns-1), factors.b + gap * (rows-1));
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
bitmaps.forEach((bmp) => {
|
||||
result.blit(bmp, x, y, 0, 0);
|
||||
hitrects.push({x, y, w: bmp.width, h: bmp.height })
|
||||
x += bmp.width + gap;
|
||||
if (x >= result.width) {
|
||||
x = 0;
|
||||
y += bmp.height + gap;
|
||||
}
|
||||
})
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(`combine() only supports uniformly-sized images right now`); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
export namespace png {
|
||||
export function read(url: string): BitmapType {
|
||||
return decode(io.readbin(url));
|
||||
}
|
||||
export function decode(data: Uint8Array): BitmapType {
|
||||
let png = fastpng.decode(data);
|
||||
return convertToBitmap(png);
|
||||
}
|
||||
function convertToBitmap(png: fastpng.IDecodedPNG): BitmapType {
|
||||
if (png.palette && png.depth <= 8) {
|
||||
return convertIndexedToBitmap(png);
|
||||
} else {
|
||||
return convertRGBAToBitmap(png);
|
||||
}
|
||||
}
|
||||
function convertIndexedToBitmap(png: fastpng.IDecodedPNG): IndexedBitmap {
|
||||
var palarr = <any>png.palette as [number, number, number, number][];
|
||||
var palette = new Palette(palarr);
|
||||
let bitmap = new IndexedBitmap(png.width, png.height, png.depth);
|
||||
if (png.depth == 8) {
|
||||
bitmap.pixels.set(png.data);
|
||||
} else {
|
||||
let pixperbyte = Math.floor(8 / png.depth);
|
||||
let mask = (1 << png.depth) - 1;
|
||||
for (let i = 0; i < bitmap.pixels.length; i++) {
|
||||
var bofs = (i % pixperbyte) * png.depth;
|
||||
let val = png.data[Math.floor(i / pixperbyte)];
|
||||
bitmap.pixels[i] = (val >> bofs) & mask;
|
||||
}
|
||||
}
|
||||
bitmap.palette = palette;
|
||||
// TODO: aspect etc
|
||||
return bitmap;
|
||||
}
|
||||
function convertRGBAToBitmap(png: fastpng.IDecodedPNG): RGBABitmap {
|
||||
const bitmap = new RGBABitmap(png.width, png.height);
|
||||
const rgba : [number,number,number,number] = [0, 0, 0, 0];
|
||||
for (let i = 0; i < bitmap.rgba.length; i++) {
|
||||
for (let j = 0; j < 4; j++)
|
||||
rgba[j] = png.data[i * 4 + j];
|
||||
bitmap.rgba[i] = color.rgba(rgba);
|
||||
}
|
||||
// TODO: aspect etc
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace font {
|
||||
interface Font {
|
||||
maxheight: number;
|
||||
glyphs: { [code: number]: Glyph };
|
||||
properties: {};
|
||||
}
|
||||
class Glyph extends IndexedBitmap {
|
||||
constructor(width: number, height: number, bpp: number,
|
||||
public readonly code: number,
|
||||
public readonly yoffset: number) {
|
||||
super(width, height, bpp);
|
||||
}
|
||||
}
|
||||
export function read(url: string) {
|
||||
if (url.endsWith('.yaff')) return decodeyafflines(io.readlines(url));
|
||||
if (url.endsWith('.draw')) return decodedrawlines(io.readlines(url));
|
||||
throw new Error(`Can't figure out font format for "${url}"`);
|
||||
}
|
||||
export function decodeglyph(glines: string[], curcode: number, yoffset: number): Glyph {
|
||||
let width = 0;
|
||||
for (var gline of glines) width = Math.max(width, gline.length);
|
||||
let g = new Glyph(width, glines.length, 1, curcode, yoffset);
|
||||
for (var y = 0; y < glines.length; y++) {
|
||||
let gline = glines[y];
|
||||
for (var x = 0; x < gline.length; x++) {
|
||||
let ch = gline[x];
|
||||
g.set(x, y, ch==='@' || ch==='#' ? 1 : 0); // TODO: provide mapping
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
||||
// https://github.com/robhagemans/monobit
|
||||
export function decodeyafflines(lines: string[]): Font {
|
||||
let maxheight = 0;
|
||||
let properties = {};
|
||||
let glyphs = {};
|
||||
let yoffset = 0;
|
||||
let curcode = -1;
|
||||
let curglyph: string[] = [];
|
||||
const re_prop = /^([\w-]+):\s+(.+)/i;
|
||||
const re_label = /^0x([0-9a-f]+):|u[+]([0-9a-f]+):|(\w+):/i;
|
||||
const re_gline = /^\s+([.@]+)/
|
||||
function addfont() {
|
||||
if (curcode >= 0 && curglyph.length) {
|
||||
glyphs[curcode] = decodeglyph(curglyph, curcode, yoffset);
|
||||
curcode = -1;
|
||||
curglyph = [];
|
||||
}
|
||||
}
|
||||
for (let line of lines) {
|
||||
let m: RegExpExecArray;
|
||||
if (m = re_prop.exec(line)) {
|
||||
properties[m[1]] = m[2];
|
||||
if (m[1] === 'bottom') yoffset = parseInt(m[2]);
|
||||
if (m[1] === 'size') maxheight = parseInt(m[2]);
|
||||
} else if (m = re_label.exec(line)) {
|
||||
addfont();
|
||||
if (m[1] != null) curcode = parseInt(m[1], 16);
|
||||
else if (m[2] != null) curcode = parseInt(m[2], 16);
|
||||
else if (m[3] != null) curcode = null; // text labels not supported
|
||||
} else if (m = re_gline.exec(line)) {
|
||||
curglyph.push(m[1]);
|
||||
}
|
||||
if (isNaN(curcode + yoffset + maxheight))
|
||||
throw new Error(`couldn't decode .yaff: ${JSON.stringify(line)}`)
|
||||
}
|
||||
addfont();
|
||||
return { maxheight, properties, glyphs };
|
||||
}
|
||||
// https://github.com/robhagemans/monobit
|
||||
export function decodedrawlines(lines: string[]): Font {
|
||||
let maxheight = 0;
|
||||
let properties = {};
|
||||
let glyphs = {};
|
||||
let curcode = -1;
|
||||
let curglyph: string[] = [];
|
||||
const re_gline = /^([0-9a-f]+)?[:]?\s*([-#]+)/i;
|
||||
function addfont() {
|
||||
if (curcode >= 0 && curglyph.length) {
|
||||
glyphs[curcode] = decodeglyph(curglyph, curcode, 0);
|
||||
maxheight = Math.max(maxheight, curglyph.length);
|
||||
curcode = -1;
|
||||
curglyph = [];
|
||||
}
|
||||
}
|
||||
for (let line of lines) {
|
||||
let m: RegExpExecArray;
|
||||
if (m = re_gline.exec(line)) {
|
||||
if (m[1] != null) {
|
||||
addfont();
|
||||
curcode = parseInt(m[1], 16);
|
||||
if (isNaN(curcode))
|
||||
throw new Error(`couldn't decode .draw: ${JSON.stringify(line)}`)
|
||||
}
|
||||
curglyph.push(m[2]);
|
||||
}
|
||||
}
|
||||
addfont();
|
||||
return { maxheight, properties, glyphs };
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: merge w/ pixeleditor
|
||||
|
||||
export type PixelEditorImageFormat = {
|
||||
w:number
|
||||
h:number
|
||||
count?:number
|
||||
bpp?:number
|
||||
np?:number
|
||||
bpw?:number
|
||||
sl?:number
|
||||
pofs?:number
|
||||
remap?:number[]
|
||||
reindex?:number[]
|
||||
brev?:boolean
|
||||
flip?:boolean
|
||||
destfmt?:PixelEditorImageFormat
|
||||
xform?:string
|
||||
skip?:number
|
||||
aspect?:number
|
||||
};
|
||||
|
||||
function remapBits(x:number, arr:number[]) : number {
|
||||
if (!arr) return x;
|
||||
var y = 0;
|
||||
for (var i=0; i<arr.length; i++) {
|
||||
var s = arr[i];
|
||||
if (s < 0) {
|
||||
s = -s-1;
|
||||
y ^= 1 << s;
|
||||
}
|
||||
if (x & (1 << i)) {
|
||||
y ^= 1 << s;
|
||||
}
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
export function convertWordsToImages(words:ArrayLike<number>, fmt:PixelEditorImageFormat) : Uint8Array[] {
|
||||
var width = fmt.w;
|
||||
var height = fmt.h;
|
||||
var count = fmt.count || 1;
|
||||
var bpp = fmt.bpp || 1;
|
||||
var nplanes = fmt.np || 1;
|
||||
var bitsperword = fmt.bpw || 8;
|
||||
var wordsperline = fmt.sl || Math.ceil(width * bpp / bitsperword);
|
||||
var mask = (1 << bpp)-1;
|
||||
var pofs = fmt.pofs || wordsperline*height*count;
|
||||
var skip = fmt.skip || 0;
|
||||
var images = [];
|
||||
for (var n=0; n<count; n++) {
|
||||
var imgdata = [];
|
||||
for (var y=0; y<height; y++) {
|
||||
var yp = fmt.flip ? height-1-y : y;
|
||||
var ofs0 = n*wordsperline*height + yp*wordsperline;
|
||||
var shift = 0;
|
||||
for (var x=0; x<width; x++) {
|
||||
var color = 0;
|
||||
var ofs = remapBits(ofs0, fmt.remap);
|
||||
// TODO: if (fmt.reindex) { [ofs, shift] = reindexMask(x, fmt.reindex); ofs += ofs0; }
|
||||
for (var p=0; p<nplanes; p++) {
|
||||
var byte = words[ofs + p*pofs + skip];
|
||||
color |= ((fmt.brev ? byte>>(bitsperword-shift-bpp) : byte>>shift) & mask) << (p*bpp);
|
||||
}
|
||||
imgdata.push(color);
|
||||
shift += bpp;
|
||||
if (shift >= bitsperword && !fmt.reindex) {
|
||||
ofs0 += 1;
|
||||
shift = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
images.push(new Uint8Array(imgdata));
|
||||
}
|
||||
return images;
|
||||
}
|
||||
|
@ -1,163 +0,0 @@
|
||||
|
||||
import _chroma from 'chroma-js'
|
||||
import { isArray, rgb2bgr } from '../../util';
|
||||
|
||||
export type Chroma = { _rgb: [number,number,number,number] };
|
||||
|
||||
export type ColorSource = number | [number,number,number] | [number,number,number,number] | string | Chroma;
|
||||
|
||||
function checkCount(count) {
|
||||
if (count < 0 || count > 65536) {
|
||||
throw new Error("Palettes cannot have more than 2^16 (65536) colors.");
|
||||
}
|
||||
}
|
||||
|
||||
export function isPalette(object): object is Palette {
|
||||
return object['colors'] instanceof Uint32Array;
|
||||
}
|
||||
|
||||
export function isChroma(object): object is Chroma {
|
||||
return object['_rgb'] instanceof Array;
|
||||
}
|
||||
|
||||
export class Palette {
|
||||
readonly colors: Uint32Array;
|
||||
|
||||
constructor(arg: number | any[] | Uint32Array) {
|
||||
// TODO: more array types
|
||||
if (typeof arg === 'number') {
|
||||
checkCount(arg);
|
||||
this.colors = new Uint32Array(arg);
|
||||
} else if (arg instanceof Uint32Array) {
|
||||
this.colors = new Uint32Array(arg);
|
||||
} else if (isArray(arg)) {
|
||||
this.colors = new Uint32Array(arg.map(rgb));
|
||||
} else
|
||||
throw new Error(`Invalid Palette constructor`)
|
||||
}
|
||||
get(index: number) {
|
||||
return this.colors[index];
|
||||
}
|
||||
chromas() {
|
||||
return Array.from(this.colors).map((rgba) => from(rgba & 0xffffff));
|
||||
}
|
||||
}
|
||||
|
||||
export const chroma = _chroma;
|
||||
|
||||
export function from(obj: ColorSource) {
|
||||
if (typeof obj === 'number')
|
||||
return _chroma(rgb2bgr(obj & 0xffffff));
|
||||
else
|
||||
return _chroma(obj as any);
|
||||
}
|
||||
|
||||
export function rgb(obj: ColorSource) : number;
|
||||
export function rgb(r: number, g: number, b: number) : number;
|
||||
|
||||
export function rgb(obj: any, g?: number, b?: number) : number {
|
||||
return rgba(obj, g, b, 0xff) | 0xff000000;
|
||||
}
|
||||
|
||||
export function rgba(obj: ColorSource) : number;
|
||||
export function rgba(r: number, g: number, b: number, a: number) : number;
|
||||
|
||||
export function rgba(obj: ColorSource, g?: number, b?: number, a?: number) : number {
|
||||
if (isChroma(obj)) {
|
||||
return rgba(obj._rgb[0], obj._rgb[1], obj._rgb[2], obj._rgb[3]);
|
||||
}
|
||||
if (typeof obj === 'number') {
|
||||
let r = obj;
|
||||
if (typeof g === 'number' && typeof b === 'number')
|
||||
return ((r & 0xff) << 0) | ((g & 0xff) << 8) | ((b & 0xff) << 16) | ((a & 0xff) << 24);
|
||||
else
|
||||
return obj;
|
||||
}
|
||||
if (typeof obj !== 'string' && isArray(obj) && typeof obj[0] === 'number') {
|
||||
let arr = obj;
|
||||
let v = 0;
|
||||
v |= (arr[0] & 0xff) << 0;
|
||||
v |= (arr[1] & 0xff) << 8;
|
||||
v |= (arr[2] & 0xff) << 16;
|
||||
v |= (arr[3] & 0xff) << 24;
|
||||
return v;
|
||||
}
|
||||
return rgba(from(obj).rgb());
|
||||
}
|
||||
|
||||
export function rgba2arr(v: number): number[] {
|
||||
return [
|
||||
(v >> 0) & 0xff,
|
||||
(v >> 8) & 0xff,
|
||||
(v >> 16) & 0xff,
|
||||
(v >> 24) & 0xff,
|
||||
]
|
||||
}
|
||||
|
||||
export function rgb2arr(v: number): number[] {
|
||||
return rgba2arr(v).slice(0,3);
|
||||
}
|
||||
|
||||
type ColorGenFunc = (index: number) => number;
|
||||
|
||||
export namespace palette {
|
||||
export function from(obj: number | any[] | Uint32Array | ColorGenFunc, count?: number) {
|
||||
checkCount(count);
|
||||
if (typeof obj === 'function') {
|
||||
if (!count) throw new Error(`You must also pass the number of colors to generate.`)
|
||||
var pal = new Palette(count);
|
||||
for (var i = 0; i < pal.colors.length; i++) {
|
||||
pal.colors[i] = rgba(obj(i));
|
||||
}
|
||||
return pal;
|
||||
} else {
|
||||
return new Palette(obj);
|
||||
}
|
||||
}
|
||||
export function mono() {
|
||||
return greys(2);
|
||||
}
|
||||
function rgb2() {
|
||||
return new Palette([
|
||||
rgb(0, 0, 0),
|
||||
rgb(0, 0, 255),
|
||||
rgb(255, 0, 0),
|
||||
rgb(0, 255, 0),
|
||||
]);
|
||||
}
|
||||
function rgb3() {
|
||||
return new Palette([
|
||||
rgb(0, 0, 0),
|
||||
rgb(0, 0, 255),
|
||||
rgb(255, 0, 0),
|
||||
rgb(255, 0, 255),
|
||||
rgb(0, 255, 0),
|
||||
rgb(0, 255, 255),
|
||||
rgb(255, 255, 0),
|
||||
rgb(255, 255, 255),
|
||||
]);
|
||||
}
|
||||
export function greys(count: number) {
|
||||
return from((i) => {
|
||||
let v = 255 * i / (count - 1);
|
||||
return rgb(v,v,v);
|
||||
}, count);
|
||||
}
|
||||
export function colors(count: number) {
|
||||
switch (count) {
|
||||
case 2: return mono();
|
||||
case 4: return rgb2();
|
||||
case 8: return rgb3();
|
||||
default: return factors(count); // TODO
|
||||
}
|
||||
}
|
||||
export function helix(count: number) {
|
||||
checkCount(count);
|
||||
return new Palette(chroma.cubehelix().scale().colors(count));
|
||||
}
|
||||
export function factors(count: number, mult?: number) {
|
||||
mult = mult || 0x031f0f;
|
||||
return from((i) => rgb(i * mult), count);
|
||||
}
|
||||
// TODO: https://www.iquilezles.org/www/articles/palettes/palettes.htm
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
|
||||
import { FileDataCache } from "../../util";
|
||||
import { FileData, WorkingStore } from "../../workertypes";
|
||||
|
||||
// remote resource cache
|
||||
var $$cache = new FileDataCache(); // TODO: better cache?
|
||||
// file read/write interface
|
||||
var $$store: WorkingStore;
|
||||
// backing store for data
|
||||
var $$data: {} = {};
|
||||
// module cache
|
||||
var $$modules: Map<string,{}> = new Map();
|
||||
|
||||
export function $$setupFS(store: WorkingStore) {
|
||||
$$store = store;
|
||||
}
|
||||
export function $$getData() {
|
||||
return $$data;
|
||||
}
|
||||
export function $$loadData(data: {}) {
|
||||
Object.assign($$data, data);
|
||||
}
|
||||
|
||||
// object that can load state from backing store
|
||||
export interface Loadable {
|
||||
// called during script, from io.data.load()
|
||||
$$setstate?(newstate: {}) : void;
|
||||
// called after script, from io.data.save()
|
||||
$$getstate() : {};
|
||||
}
|
||||
|
||||
export namespace data {
|
||||
export function load(object: Loadable, key: string): Loadable {
|
||||
if (object == null) return object;
|
||||
let override = $$data && $$data[key];
|
||||
if (override && object.$$setstate) {
|
||||
object.$$setstate(override);
|
||||
} else if (override) {
|
||||
Object.assign(object, override);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
export function save(object: Loadable, key: string): Loadable {
|
||||
if ($$data && object && object.$$getstate) {
|
||||
$$data[key] = object.$$getstate();
|
||||
}
|
||||
return object;
|
||||
}
|
||||
export function get(key: string) {
|
||||
return $$data && $$data[key];
|
||||
}
|
||||
export function set(key: string, value: object) {
|
||||
if ($$data) {
|
||||
$$data[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class IOWaitError extends Error {
|
||||
}
|
||||
|
||||
export function canonicalurl(url: string) : string {
|
||||
// get raw resource URL for github
|
||||
if (url.startsWith('https://github.com/')) {
|
||||
let toks = url.split('/');
|
||||
if (toks[5] === 'blob') {
|
||||
return `https://raw.githubusercontent.com/${toks[3]}/${toks[4]}/${toks.slice(6).join('/')}`
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
export function fetchurl(url: string, type?: 'binary' | 'text'): FileData {
|
||||
// TODO: only works in web worker
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.responseType = type === 'text' ? 'text' : 'arraybuffer';
|
||||
xhr.open("GET", url, false); // synchronous request
|
||||
xhr.send(null);
|
||||
if (xhr.response != null && xhr.status == 200) {
|
||||
if (type === 'text') {
|
||||
return xhr.response as string;
|
||||
} else {
|
||||
return new Uint8Array(xhr.response);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`The resource at "${url}" responded with status code of ${xhr.status}.`)
|
||||
}
|
||||
}
|
||||
|
||||
export function readnocache(url: string, type?: 'binary' | 'text'): FileData {
|
||||
if (url.startsWith('http:') || url.startsWith('https:')) {
|
||||
return fetchurl(url, type);
|
||||
}
|
||||
if ($$store) {
|
||||
return $$store.getFileData(url);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: read files too
|
||||
export function read(url: string, type?: 'binary' | 'text'): FileData {
|
||||
// canonical-ize url
|
||||
url = canonicalurl(url);
|
||||
// check cache first
|
||||
let cachekey = url;
|
||||
let data = $$cache.get(cachekey);
|
||||
if (data != null) return data;
|
||||
// not in cache, read it
|
||||
data = readnocache(url, type);
|
||||
if (data == null) throw new Error(`Cannot find resource "${url}"`);
|
||||
if (type === 'text' && typeof data !== 'string') throw new Error(`Resource "${url}" is not a string`);
|
||||
if (type === 'binary' && !(data instanceof Uint8Array)) throw new Error(`Resource "${url}" is not a binary file`);
|
||||
// store in cache
|
||||
$$cache.put(cachekey, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function readbin(url: string): Uint8Array {
|
||||
var data = read(url, 'binary');
|
||||
if (data instanceof Uint8Array)
|
||||
return data;
|
||||
else
|
||||
throw new Error(`The resource at "${url}" is not a binary file.`);
|
||||
}
|
||||
|
||||
export function readlines(url: string) : string[] {
|
||||
return (read(url, 'text') as string).split('\n');
|
||||
}
|
||||
|
||||
export function splitlines(text: string) : string[] {
|
||||
return text.split(/\n|\r\n/g);
|
||||
}
|
||||
|
||||
export function module(url: string) {
|
||||
// find module in cache?
|
||||
let key = `${url}::${url.length}`;
|
||||
let exports = $$modules.get(key);
|
||||
if (exports == null) {
|
||||
let code = readnocache(url, 'text') as string;
|
||||
let func = new Function('exports', 'module', code);
|
||||
let module = {}; // TODO?
|
||||
exports = {};
|
||||
func(exports, module);
|
||||
$$modules.set(key, exports);
|
||||
}
|
||||
return exports;
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
// TODO: what if this isn't top level?
|
||||
export class Mutable<T> implements Loadable {
|
||||
value : T;
|
||||
constructor(initial : T) {
|
||||
this.value = initial;
|
||||
}
|
||||
$$setstate(newstate) {
|
||||
this.value = newstate.value;
|
||||
}
|
||||
$$getstate() {
|
||||
return { value: this.value };
|
||||
}
|
||||
}
|
||||
|
||||
export function mutable<T>(obj: object) : object {
|
||||
Object.defineProperty(obj, '$$setstate', {
|
||||
value: function(newstate) {
|
||||
Object.assign(this, newstate);
|
||||
},
|
||||
enumerable: false
|
||||
});
|
||||
Object.defineProperty(obj, '$$getstate', {
|
||||
value: function() {
|
||||
return this;
|
||||
},
|
||||
enumerable: false
|
||||
});
|
||||
return obj;
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
|
||||
enum DataType {
|
||||
unknown,
|
||||
u8,
|
||||
s8,
|
||||
u16,
|
||||
s16,
|
||||
u32,
|
||||
s32,
|
||||
f32,
|
||||
f64,
|
||||
};
|
||||
|
||||
function getArrayDataType(value: any) : DataType {
|
||||
if (value instanceof Uint8Array) {
|
||||
return DataType.u8;
|
||||
} else if (value instanceof Int8Array) {
|
||||
return DataType.s8;
|
||||
} else if (value instanceof Uint16Array) {
|
||||
return DataType.u16;
|
||||
} else if (value instanceof Int16Array) {
|
||||
return DataType.s16;
|
||||
} else if (value instanceof Uint32Array) {
|
||||
return DataType.u32;
|
||||
} else if (value instanceof Int32Array) {
|
||||
return DataType.s32;
|
||||
} else if (value instanceof Float32Array) {
|
||||
return DataType.f32;
|
||||
} else if (value instanceof Float64Array) {
|
||||
return DataType.f64;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class OutputFile {
|
||||
constructor(
|
||||
public readonly path : string,
|
||||
public readonly decls : {}
|
||||
) {
|
||||
}
|
||||
abstract declToText(label: string, value: any) : string;
|
||||
toString() : string {
|
||||
return Object.entries(this.decls).map(entry => this.declToText(entry[0],entry[1])).join('\n\n');
|
||||
}
|
||||
}
|
||||
|
||||
export class COutputFile extends OutputFile {
|
||||
toString() : string {
|
||||
return `#include <stdint.h>\n\n${super.toString()}\n`;
|
||||
}
|
||||
dataTypeToString(dtype: DataType) {
|
||||
switch (dtype) {
|
||||
case DataType.u8: return 'uint8_t';
|
||||
case DataType.s8: return 'int8_t';
|
||||
case DataType.u16: return 'uint16_t';
|
||||
case DataType.s16: return 'int16_t';
|
||||
case DataType.u32: return 'uint32_t';
|
||||
case DataType.s32: return 'int32_t';
|
||||
case DataType.f32: return 'float';
|
||||
case DataType.f64: return 'double';
|
||||
default:
|
||||
throw new Error('Cannot convert data type'); // TODO
|
||||
}
|
||||
}
|
||||
valueToString(value, atype: DataType) : string {
|
||||
// TODO: round, check value
|
||||
return value+"";
|
||||
}
|
||||
declToText(label: string, value: any) : string {
|
||||
if (Array.isArray(value) || value['BYTES_PER_ELEMENT']) {
|
||||
let atype = getArrayDataType(value);
|
||||
if (atype != null) {
|
||||
let dtypestr = this.dataTypeToString(atype);
|
||||
let dtext = value.map(elem => this.valueToString(elem, atype)).join(',');
|
||||
let len = value.length;
|
||||
return `${dtypestr} ${label}[${len}] = { ${dtext} };`;
|
||||
}
|
||||
}
|
||||
throw new Error(`Cannot convert array "${label}"`); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: header file, auto-detect tool?
|
||||
export function file(path: string, decls: {}) {
|
||||
return new COutputFile(path, decls);
|
||||
}
|
@ -1,193 +0,0 @@
|
||||
|
||||
import * as io from "./io";
|
||||
|
||||
// sequence counter
|
||||
var $$seq : number = 0;
|
||||
|
||||
// if an event is specified, it goes here
|
||||
export const EVENT_KEY = "$$event";
|
||||
|
||||
// an object that can become interactive, identified by ID
|
||||
export interface Interactive {
|
||||
$$interact: InteractionRecord;
|
||||
}
|
||||
|
||||
export interface InteractEvent {
|
||||
interactid : number;
|
||||
type: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
button?: boolean;
|
||||
}
|
||||
|
||||
export type InteractCallback = (event: InteractEvent) => void;
|
||||
|
||||
// InteractionRecord maps a target object to an interaction ID
|
||||
// the $$callback is used once per script eval, then gets nulled
|
||||
// whether or not it's invoked
|
||||
// event comes from $$data.$$event
|
||||
export class InteractionRecord implements io.Loadable {
|
||||
readonly interacttarget: Interactive;
|
||||
interactid : number;
|
||||
lastevent : {} = null;
|
||||
constructor(
|
||||
interacttarget: Interactive,
|
||||
private $$callback: InteractCallback
|
||||
) {
|
||||
this.interacttarget = interacttarget || (<any>this as Interactive);
|
||||
this.interactid = ++$$seq;
|
||||
}
|
||||
$$setstate(newstate: {interactid: number}) {
|
||||
this.interactid = newstate.interactid;
|
||||
this.interacttarget.$$interact = this;
|
||||
let event : InteractEvent = io.data.get(EVENT_KEY);
|
||||
if (event && event.interactid == this.interactid) {
|
||||
if (this.$$callback) {
|
||||
this.$$callback(event);
|
||||
}
|
||||
this.lastevent = event;
|
||||
io.data.set(EVENT_KEY, null);
|
||||
}
|
||||
this.$$callback = null;
|
||||
}
|
||||
$$getstate() {
|
||||
//TODO: this isn't always cleared before we serialize (e.g. if exception or move element)
|
||||
//and we do it in checkResult() too
|
||||
this.$$callback = null;
|
||||
return {interactid: this.interactid};
|
||||
}
|
||||
}
|
||||
|
||||
export function isInteractive(obj: object): obj is Interactive {
|
||||
return !!((obj as Interactive).$$interact);
|
||||
}
|
||||
|
||||
export function interact(object: any, callback) : InteractionRecord {
|
||||
// TODO: limit to Bitmap, etc
|
||||
if (typeof object === 'object') {
|
||||
return new InteractionRecord(object, callback);
|
||||
}
|
||||
throw new Error(`This object is not capable of interaction.`);
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
export interface ScriptUIType {
|
||||
uitype : string;
|
||||
}
|
||||
|
||||
export class ScriptUISliderType implements ScriptUIType {
|
||||
readonly uitype = 'slider';
|
||||
value: number;
|
||||
constructor(
|
||||
readonly min: number,
|
||||
readonly max: number,
|
||||
readonly step: number
|
||||
) {
|
||||
this.value = min;
|
||||
}
|
||||
}
|
||||
|
||||
export class ScriptUISlider extends ScriptUISliderType implements io.Loadable {
|
||||
initial(value: number) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
$$getstate() {
|
||||
return { value: this.value };
|
||||
}
|
||||
}
|
||||
|
||||
export function slider(min: number, max: number, step?: number) {
|
||||
return new ScriptUISlider(min, max, step || 1);
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
export class ScriptUISelectType<T> implements ScriptUIType {
|
||||
readonly uitype = 'select';
|
||||
value: T;
|
||||
index: number;
|
||||
constructor(
|
||||
readonly options: T[]
|
||||
) {
|
||||
this.index = 0;
|
||||
this.value = this.options[this.index];
|
||||
}
|
||||
}
|
||||
|
||||
export class ScriptUISelect<T> extends ScriptUISelectType<T> implements io.Loadable {
|
||||
initial(index: number) {
|
||||
this.index = index;
|
||||
this.value = this.options[index];
|
||||
return this;
|
||||
}
|
||||
$$getstate() {
|
||||
return { value: this.value, index: this.index };
|
||||
}
|
||||
}
|
||||
|
||||
export function select(options: any[]) {
|
||||
return new ScriptUISelect(options);
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
export class ScriptUIButtonType extends InteractionRecord implements ScriptUIType, Interactive {
|
||||
readonly uitype = 'button';
|
||||
$$interact: InteractionRecord;
|
||||
enabled?: boolean;
|
||||
|
||||
constructor(
|
||||
readonly label: string,
|
||||
callback: InteractCallback
|
||||
) {
|
||||
super(null, callback);
|
||||
this.$$interact = this;
|
||||
}
|
||||
}
|
||||
|
||||
export class ScriptUIButton extends ScriptUIButtonType {
|
||||
}
|
||||
|
||||
export function button(name: string, callback: InteractCallback) {
|
||||
return new ScriptUIButton(name, callback);
|
||||
}
|
||||
|
||||
export class ScriptUIToggle extends ScriptUIButton implements io.Loadable {
|
||||
// share with InteractionRecord
|
||||
$$getstate() {
|
||||
let state = super.$$getstate() as any;
|
||||
state.enabled = this.enabled;
|
||||
return state;
|
||||
}
|
||||
$$setstate(newstate: any) {
|
||||
this.enabled = newstate.enabled;
|
||||
super.$$setstate(newstate);
|
||||
}
|
||||
}
|
||||
|
||||
export function toggle(name: string) {
|
||||
return new ScriptUIToggle(name, function(e) {
|
||||
this.enabled = !this.enabled;
|
||||
});
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
export class ScriptUIShortcut extends InteractionRecord implements ScriptUIType, Interactive {
|
||||
readonly uitype = 'shortcut';
|
||||
$$interact: InteractionRecord;
|
||||
|
||||
constructor(
|
||||
readonly key: string,
|
||||
callback: InteractCallback
|
||||
) {
|
||||
super(null, callback);
|
||||
this.$$interact = this;
|
||||
}
|
||||
}
|
||||
|
||||
export function key(key: string, callback: InteractCallback) {
|
||||
return new ScriptUIShortcut(key, callback);
|
||||
}
|
@ -1,479 +0,0 @@
|
||||
|
||||
import { Component, render, h, createRef, VNode } from 'preact';
|
||||
import { Cell, PROP_CONSTRUCTOR_NAME } from "../env";
|
||||
import { findIntegerFactors, hex, isArray, rgb2bgr } from "../../util";
|
||||
import { dumpRAM } from "../../emu";
|
||||
import { current_project } from "../../../ide/ui";
|
||||
// TODO: can't call methods from this end (e.g. Palette, Bitmap)
|
||||
import * as bitmap from "../lib/bitmap";
|
||||
import * as color from "../lib/color";
|
||||
import * as scriptui from "../lib/scriptui";
|
||||
|
||||
const MAX_STRING_LEN = 100;
|
||||
const DEFAULT_ASPECT = 1;
|
||||
|
||||
function sendInteraction(iobj: scriptui.Interactive, type: string, event: Event, xtraprops: {}) {
|
||||
let irec = iobj.$$interact;
|
||||
let ievent : scriptui.InteractEvent = {interactid: irec.interactid, type, ...xtraprops};
|
||||
if (event instanceof PointerEvent) {
|
||||
const canvas = event.target as HTMLCanvasElement;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const scaleX = canvas.width / rect.width;
|
||||
const scaleY = canvas.height / rect.height;
|
||||
const x = (event.clientX - rect.left) * scaleX;
|
||||
const y = (event.clientY - rect.top) * scaleY;
|
||||
ievent.x = Math.floor(x);
|
||||
ievent.y = Math.floor(y);
|
||||
// TODO: pressure, etc.
|
||||
} else {
|
||||
console.log("Unknown event type", event);
|
||||
}
|
||||
// TODO: add events to queue?
|
||||
current_project.updateDataItems([{
|
||||
key: scriptui.EVENT_KEY,
|
||||
value: ievent
|
||||
}]);
|
||||
}
|
||||
|
||||
interface ColorComponentProps {
|
||||
rgbavalue: number;
|
||||
}
|
||||
|
||||
class ColorComponent extends Component<ColorComponentProps> {
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
let rgb = this.props.rgbavalue & 0xffffff;
|
||||
let bgr = rgb2bgr(rgb);
|
||||
var htmlcolor = `#${hex(bgr,6)}`;
|
||||
var textcolor = (rgb & 0x008000) ? '#222' : '#ddd';
|
||||
var printcolor = hex(rgb & 0xffffff, 6); // TODO: show index instead?
|
||||
return h('div', {
|
||||
class: 'scripting-item scripting-color',
|
||||
style: `background-color: ${htmlcolor}; color: ${textcolor}`,
|
||||
alt: htmlcolor, // TODO
|
||||
}, [
|
||||
//h('span', { }, printcolor )
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
interface BitmapComponentProps {
|
||||
bitmap: bitmap.BitmapType;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
class BitmapComponent extends Component<BitmapComponentProps> {
|
||||
ref = createRef(); // TODO: can we use the ref?
|
||||
canvas: HTMLCanvasElement;
|
||||
ctx: CanvasRenderingContext2D;
|
||||
imageData: ImageData;
|
||||
datau32: Uint32Array;
|
||||
pressed = false;
|
||||
|
||||
constructor(props: BitmapComponentProps) {
|
||||
super(props);
|
||||
}
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
let props = {
|
||||
class: 'scripting-item',
|
||||
ref: this.ref,
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
style: this.props.bitmap.style
|
||||
}
|
||||
let obj : any = this.props.bitmap;
|
||||
if (scriptui.isInteractive(obj)) {
|
||||
return h('canvas', {
|
||||
onPointerMove: (e: PointerEvent) => {
|
||||
sendInteraction(obj, 'move', e, { pressed: this.pressed });
|
||||
},
|
||||
onPointerDown: (e: PointerEvent) => {
|
||||
this.pressed = true;
|
||||
this.canvas.setPointerCapture(e.pointerId);
|
||||
sendInteraction(obj, 'down', e, { pressed: true });
|
||||
},
|
||||
onPointerUp: (e: PointerEvent) => {
|
||||
this.pressed = false;
|
||||
sendInteraction(obj, 'up', e, { pressed: false });
|
||||
},
|
||||
onPointerOut: (e: PointerEvent) => {
|
||||
this.pressed = false;
|
||||
sendInteraction(obj, 'out', e, { pressed: false });
|
||||
},
|
||||
...props
|
||||
});
|
||||
} else {
|
||||
return h('canvas', props);
|
||||
}
|
||||
}
|
||||
componentDidMount() {
|
||||
this.refresh();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.canvas = null;
|
||||
this.imageData = null;
|
||||
this.datau32 = null;
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
this.refresh();
|
||||
}
|
||||
prepare() {
|
||||
this.canvas = this.base as HTMLCanvasElement;
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.imageData = this.ctx.createImageData(this.canvas.width, this.canvas.height);
|
||||
this.datau32 = new Uint32Array(this.imageData.data.buffer);
|
||||
}
|
||||
refresh() {
|
||||
// preact can reuse this component but it can change shape :^P
|
||||
if (this.canvas !== this.base
|
||||
|| this.imageData.width != this.props.width
|
||||
|| this.imageData.height != this.props.height) {
|
||||
this.prepare();
|
||||
}
|
||||
this.updateCanvas(this.datau32, this.props.bitmap);
|
||||
this.ctx.putImageData(this.imageData, 0, 0);
|
||||
}
|
||||
updateCanvas(vdata: Uint32Array, bmp: bitmap.BitmapType) {
|
||||
if (bmp['palette']) {
|
||||
this.updateCanvasIndexed(vdata, bmp as bitmap.IndexedBitmap);
|
||||
}
|
||||
if (bmp['rgba']) {
|
||||
this.updateCanvasRGBA(vdata, bmp as bitmap.RGBABitmap);
|
||||
}
|
||||
}
|
||||
updateCanvasRGBA(vdata: Uint32Array, bmp: bitmap.RGBABitmap) {
|
||||
vdata.set(bmp.rgba);
|
||||
}
|
||||
updateCanvasIndexed(vdata: Uint32Array, bmp: bitmap.IndexedBitmap) {
|
||||
let pal = bmp.palette.colors;
|
||||
for (var i = 0; i < bmp.pixels.length; i++) {
|
||||
vdata[i] = pal[bmp.pixels[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ObjectTreeComponentProps {
|
||||
name?: string;
|
||||
object: {} | [];
|
||||
objpath: string;
|
||||
}
|
||||
|
||||
interface ObjectTreeComponentState {
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
class ObjectKeyValueComponent extends Component<ObjectTreeComponentProps, ObjectTreeComponentState> {
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
let expandable = typeof this.props.object === 'object';
|
||||
let hdrclass = '';
|
||||
if (expandable) hdrclass = this.state.expanded ? 'tree-expanded' : 'tree-collapsed'
|
||||
let propName = this.props.name || null;
|
||||
return h('div', {
|
||||
class: 'tree-content',
|
||||
key: `${this.props.objpath}__tree`
|
||||
}, [
|
||||
h('div', {
|
||||
class: 'tree-header ' + hdrclass,
|
||||
onClick: expandable ? () => this.toggleExpand() : null
|
||||
}, [
|
||||
propName != null ? h('span', { class: 'tree-key' }, [ propName, expandable ]) : null,
|
||||
h('span', { class: 'tree-value scripting-item' }, [
|
||||
getShortName(this.props.object)
|
||||
])
|
||||
]),
|
||||
this.state.expanded ? objectToContentsDiv(this.props.object, this.props.objpath) : null
|
||||
]);
|
||||
}
|
||||
toggleExpand() {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
}
|
||||
}
|
||||
|
||||
function getShortName(object: any) {
|
||||
if (typeof object === 'object') {
|
||||
try {
|
||||
var s = object[PROP_CONSTRUCTOR_NAME] || Object.getPrototypeOf(object).constructor.name;
|
||||
if (object.length > 0) {
|
||||
s += `[${object.length}]`
|
||||
}
|
||||
return s;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return e + "";
|
||||
}
|
||||
} else {
|
||||
return primitiveToString(object);
|
||||
}
|
||||
}
|
||||
|
||||
function primitiveToString(obj) {
|
||||
var text = "";
|
||||
// is it a function? call it first, if we are expanded
|
||||
// TODO: only call functions w/ signature
|
||||
if (obj && obj.$$ && typeof obj.$$ == 'function' && this._content != null) {
|
||||
obj = obj.$$();
|
||||
}
|
||||
// check null first
|
||||
if (obj == null) {
|
||||
text = obj + "";
|
||||
// primitive types
|
||||
} else if (typeof obj == 'number') {
|
||||
if (obj != (obj | 0)) text = obj.toString(); // must be a float
|
||||
else text = obj + "\t($" + hex(obj) + ")";
|
||||
} else if (typeof obj == 'string') {
|
||||
text = obj;
|
||||
} else {
|
||||
text = JSON.stringify(obj);
|
||||
}
|
||||
if (text.length > MAX_STRING_LEN)
|
||||
text = text.substring(0, MAX_STRING_LEN) + "...";
|
||||
return text;
|
||||
}
|
||||
|
||||
function isIndexedBitmap(object): object is bitmap.IndexedBitmap {
|
||||
return object['bpp'] && object['pixels'] && object['palette'];
|
||||
}
|
||||
function isRGBABitmap(object): object is bitmap.RGBABitmap {
|
||||
return object['rgba'] instanceof Uint32Array;
|
||||
}
|
||||
|
||||
function objectToChildren(object: any) : any[] {
|
||||
if (color.isPalette(object)) {
|
||||
return new color.Palette(object.colors).chromas();
|
||||
} else if (isArray(object)) {
|
||||
return Array.from(object);
|
||||
} else if (object != null) {
|
||||
return [ object ]
|
||||
} else {
|
||||
return [ ]
|
||||
}
|
||||
}
|
||||
|
||||
function objectToChild(object: any, index: number) : any {
|
||||
if (color.isPalette(object)) {
|
||||
return color.from(object.colors[index]);
|
||||
} else if (isArray(object)) {
|
||||
return object[index];
|
||||
} else if (object != null) {
|
||||
return object
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function objectToDiv(object: any, name: string, objpath: string): VNode<any> {
|
||||
// don't view any keys that start with "$"
|
||||
if (name && name.startsWith("$")) {
|
||||
// don't view any values that start with "$$"
|
||||
if (name.startsWith("$$")) return;
|
||||
// don't print if null or undefined
|
||||
if (object == null) return;
|
||||
// don't print key in any case
|
||||
name = null;
|
||||
}
|
||||
// TODO: limit # of items
|
||||
// TODO: detect table
|
||||
if (object == null) {
|
||||
return h('span', { }, object + "");
|
||||
} else if (object['uitype']) {
|
||||
let cons = UI_COMPONENTS[object['uitype']];
|
||||
if (!cons) throw new Error(`Unknown UI component type: ${object['uitype']}`);
|
||||
return h(cons, { iokey: objpath, uiobject: object });
|
||||
} else if (object['literaltext']) {
|
||||
return h("pre", { }, [ object['literaltext'] ]);
|
||||
} else if (isIndexedBitmap(object) || isRGBABitmap(object)) {
|
||||
return h(BitmapComponent, { bitmap: object, width: object.width, height: object.height });
|
||||
} else if (color.isChroma(object)) {
|
||||
return h(ColorComponent, { rgbavalue: color.rgb(object) });
|
||||
} else if (color.isPalette(object)) {
|
||||
// TODO?
|
||||
if (object.colors.length <= 256) {
|
||||
let children = [];
|
||||
let props = { class: '', key: `${objpath}__obj` };
|
||||
props.class += ' scripting-flex ';
|
||||
object.colors.forEach((val) => {
|
||||
children.push(h(ColorComponent, { rgbavalue: val }));
|
||||
})
|
||||
return h('div', props, children);
|
||||
} else {
|
||||
let {a,b} = findIntegerFactors(object.colors.length, 1, 1, DEFAULT_ASPECT);
|
||||
return objectToDiv({ rgba: object.colors, width: a, height: b }, name, objpath);
|
||||
}
|
||||
} else {
|
||||
return h(ObjectKeyValueComponent, { name, object, objpath }, []);
|
||||
}
|
||||
}
|
||||
|
||||
function fixedArrayToDiv(tyarr: Array<number>, bpel: number, objpath: string) {
|
||||
const maxBytes = 0x100;
|
||||
if (tyarr.length <= maxBytes) {
|
||||
// TODO
|
||||
let dumptext = dumpRAM(tyarr, 0, tyarr.length);
|
||||
return h('pre', {}, dumptext);
|
||||
} else {
|
||||
let children = [];
|
||||
for (var ofs = 0; ofs < tyarr.length; ofs += maxBytes) {
|
||||
children.push(objectToDiv(tyarr.slice(ofs, ofs + maxBytes), '$' + hex(ofs), `${objpath}.${ofs}`));
|
||||
}
|
||||
return h('div', {}, children);
|
||||
}
|
||||
}
|
||||
|
||||
function objectToContentsDiv(object: {} | [], objpath: string) {
|
||||
// is typed array?
|
||||
let bpel = object['BYTES_PER_ELEMENT'];
|
||||
if (typeof bpel === 'number') {
|
||||
return fixedArrayToDiv(object as Array<number>, bpel, objpath);
|
||||
}
|
||||
let objectEntries = Object.entries(object);
|
||||
let objectDivs = objectEntries.map(entry => objectToDiv(entry[1], entry[0], `${objpath}.${entry[1]}`));
|
||||
return h('div', { class: 'scripting-flex' }, objectDivs);
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
interface UIComponentProps {
|
||||
iokey: string;
|
||||
uiobject: scriptui.ScriptUIType;
|
||||
dropdown?: boolean;
|
||||
}
|
||||
|
||||
class UISliderComponent extends Component<UIComponentProps> {
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
let slider = this.props.uiobject as scriptui.ScriptUISliderType;
|
||||
return h('div', {}, [
|
||||
this.props.iokey,
|
||||
h('input', {
|
||||
type: 'range',
|
||||
min: slider.min / slider.step,
|
||||
max: slider.max / slider.step,
|
||||
value: slider.value / slider.step,
|
||||
onInput: (ev) => {
|
||||
let target = ev.target as HTMLInputElement; // TODO
|
||||
let newUIValue = { value: parseFloat(target.value) * slider.step };
|
||||
this.setState(this.state);
|
||||
current_project.updateDataItems([{key: this.props.iokey, value: newUIValue}]);
|
||||
}
|
||||
}),
|
||||
h('span', { }, getShortName(slider.value)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class UISelectComponent extends Component<UIComponentProps, ObjectTreeComponentState> {
|
||||
ref = createRef();
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
let select = this.props.uiobject as scriptui.ScriptUISelectType<any>;
|
||||
let children = objectToChildren(select.options);
|
||||
this.props.dropdown = children.length > 16;
|
||||
let showselections = !this.props.dropdown || this.state.expanded;
|
||||
let seldiv = null;
|
||||
if (showselections) {
|
||||
seldiv = h('div', {
|
||||
class: 'scripting-select scripting-flex',
|
||||
ref: this.ref,
|
||||
onClick: (e) => {
|
||||
// select object -- iterate parents until we find select div, then find index of child
|
||||
let target = e.target as HTMLElement;
|
||||
while (target.parentElement && target.parentElement != this.ref.current) {
|
||||
target = target.parentElement;
|
||||
}
|
||||
if (target.parentElement) {
|
||||
const selindex = Array.from(target.parentElement.children).indexOf(target);
|
||||
if (selindex >= 0 && selindex < children.length) {
|
||||
let newUIValue = { value: children[selindex], index: selindex };
|
||||
this.setState({ expanded: false });
|
||||
current_project.updateDataItems([{key: this.props.iokey, value: newUIValue}]);
|
||||
} else {
|
||||
throw new Error(`Could not find click target of ${this.props.iokey}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
children.map((child, index) => {
|
||||
let div = objectToDiv(child, null, `${this.props.iokey}__select_${index}`);
|
||||
let selected = (index == select.index);
|
||||
return h('div', { class: selected ? 'scripting-selected' : '' }, [ div ]);
|
||||
}));
|
||||
}
|
||||
if (this.props.dropdown) {
|
||||
let selectedDiv = objectToDiv(objectToChild(select.options, select.index), null, `${this.props.iokey}__selected`);
|
||||
return h('div', {
|
||||
class: 'tree-content',
|
||||
key: `${this.props.iokey}__tree`
|
||||
}, [
|
||||
h('div', {
|
||||
class: 'tree-header ' + (this.state.expanded ? 'tree-expanded' : 'tree-collapsed'),
|
||||
onClick: () => this.toggleExpand()
|
||||
}, [
|
||||
this.props.iokey,
|
||||
h('span', { class: 'tree-value scripting-item' }, [
|
||||
selectedDiv,
|
||||
//getShortName(select.options)
|
||||
])
|
||||
]),
|
||||
seldiv
|
||||
]);
|
||||
} else {
|
||||
return seldiv;
|
||||
}
|
||||
}
|
||||
toggleExpand() {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
}
|
||||
}
|
||||
|
||||
class UIButtonComponent extends Component<UIComponentProps> {
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
let button = this.props.uiobject as scriptui.ScriptUIButtonType;
|
||||
return h('button', {
|
||||
class: button.enabled ? 'scripting-button scripting-enabled' : 'scripting-button',
|
||||
onClick: (e: MouseEvent) => {
|
||||
sendInteraction(button, 'click', e, { });
|
||||
},
|
||||
}, [
|
||||
button.label
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
class UIShortcutComponent extends Component<UIComponentProps> {
|
||||
render(virtualDom, containerNode, replaceNode) {
|
||||
let shortcut = this.props.uiobject as scriptui.ScriptUIShortcut;
|
||||
// TODO: needs to fire on container node
|
||||
return h('div', {
|
||||
onKeyDown: (e: KeyboardEvent) => {
|
||||
sendInteraction(shortcut, 'key', e, { });
|
||||
},
|
||||
}, [ ])
|
||||
}
|
||||
}
|
||||
|
||||
const UI_COMPONENTS = {
|
||||
'slider': UISliderComponent,
|
||||
'select': UISelectComponent,
|
||||
'button': UIButtonComponent,
|
||||
'shortcut': UIShortcutComponent,
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
export class Notebook {
|
||||
constructor(
|
||||
public readonly maindoc: HTMLDocument,
|
||||
public readonly maindiv: HTMLElement
|
||||
) {
|
||||
maindiv.classList.add('vertical-scroll');
|
||||
}
|
||||
updateCells(cells: Cell[]) {
|
||||
let hTree = cells.map(cell => {
|
||||
return h('div', {
|
||||
class: 'scripting-cell',
|
||||
key: `${cell.id}__cell`
|
||||
}, [
|
||||
objectToDiv(cell.object, cell.id, cell.id)
|
||||
])
|
||||
});
|
||||
render(hTree, this.maindiv);
|
||||
}
|
||||
}
|
@ -165,13 +165,7 @@ export class CodeProject {
|
||||
parseIncludeDependencies(text:string):string[] {
|
||||
let files = [];
|
||||
let m;
|
||||
if (this.platform_id.startsWith('script')) { // TODO
|
||||
let re1 = /\b\w+[.]read\(["'](.+?)["']/gmi;
|
||||
while (m = re1.exec(text)) {
|
||||
if (m[1] && m[1].indexOf(':/') < 0) // TODO: ignore URLs
|
||||
this.pushAllFiles(files, m[1]);
|
||||
}
|
||||
} else if (this.platform_id.startsWith('verilog')) {
|
||||
if (this.platform_id.startsWith('verilog')) {
|
||||
// include verilog includes
|
||||
let re1 = /^\s*(`include|[.]include)\s+"(.+?)"/gmi;
|
||||
while (m = re1.exec(text)) {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { hex } from "chroma-js";
|
||||
import { MasterAudio, WorkerSoundChannel } from "../common/audio";
|
||||
import { MemoryBus } from "../common/baseplatform";
|
||||
import { CPU6809 } from "../common/cpu/6809";
|
||||
|
@ -19,7 +19,6 @@ export function importPlatform(name: string) : Promise<any> {
|
||||
case "mw8080bw": return import("../platform/mw8080bw");
|
||||
case "nes": return import("../platform/nes");
|
||||
case "pce": return import("../platform/pce");
|
||||
case "script": return import("../platform/script");
|
||||
case "sms": return import("../platform/sms");
|
||||
case "sound_konami": return import("../platform/sound_konami");
|
||||
case "sound_williams": return import("../platform/sound_williams");
|
||||
|
@ -1,46 +0,0 @@
|
||||
|
||||
import { PLATFORMS } from "../common/emu";
|
||||
import { Platform } from "../common/baseplatform";
|
||||
import { RunResult } from "../common/script/env";
|
||||
import { Notebook } from "../common/script/ui/notebook";
|
||||
|
||||
class ScriptingPlatform implements Platform {
|
||||
mainElement: HTMLElement;
|
||||
iframe: HTMLIFrameElement;
|
||||
notebook: Notebook;
|
||||
|
||||
constructor(mainElement: HTMLElement) {
|
||||
this.mainElement = mainElement;
|
||||
this.notebook = new Notebook(document, mainElement);
|
||||
}
|
||||
start() {
|
||||
}
|
||||
reset() {
|
||||
}
|
||||
pause() {
|
||||
}
|
||||
resume() {
|
||||
}
|
||||
loadROM(title, run: RunResult) {
|
||||
this.notebook.updateCells(run.cells);
|
||||
// TODO: save state file
|
||||
}
|
||||
isRunning() {
|
||||
return true;
|
||||
}
|
||||
isDebugging(): boolean {
|
||||
return false;
|
||||
}
|
||||
getToolForFilename(fn: string): string {
|
||||
return "js";
|
||||
}
|
||||
getDefaultExtension(): string {
|
||||
return ".js";
|
||||
}
|
||||
getPresets() {
|
||||
return [
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
PLATFORMS['script'] = ScriptingPlatform;
|
@ -1,46 +0,0 @@
|
||||
|
||||
import { BuildStep, BuildStepResult, emglobal, store } from "../workermain";
|
||||
import { Environment, RunResult } from "../../common/script/env";
|
||||
import * as io from "../../common/script/lib/io";
|
||||
import { createNewPersistentStore } from "../../ide/project";
|
||||
|
||||
// cache environments
|
||||
var environments: { [path: string]: Environment } = {};
|
||||
|
||||
function getEnv(path: string): Environment {
|
||||
var env = environments[path];
|
||||
if (!env) {
|
||||
env = environments[path] = new Environment(emglobal, path);
|
||||
// TODO: load environment from store?
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
export async function runJavascript(step: BuildStep): Promise<BuildStepResult> {
|
||||
var env = getEnv(step.path);
|
||||
var code = store.getFileAsString(step.path);
|
||||
var lstore = createNewPersistentStore(step.platform + "//items");
|
||||
// load items from persistent storage (TODO)
|
||||
const itemskey = step.path;
|
||||
if (store.items == null) {
|
||||
store.items = (await lstore.getItem(itemskey)) || {}; // TODO
|
||||
console.log(store.items);
|
||||
}
|
||||
io.$$setupFS(store);
|
||||
io.$$loadData(store.items);
|
||||
try {
|
||||
await env.run(code);
|
||||
let cells = env.render();
|
||||
let state = env.commitLoadableState(); // TODO: doesn't work
|
||||
let output : RunResult = { cells, state };
|
||||
// save items to persistent storage (TODO)
|
||||
lstore.setItem(itemskey, state); // TODO
|
||||
store.items = state; // TODO: why????
|
||||
return { output: output };
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return { errors: env.extractErrors(e) };
|
||||
} finally {
|
||||
io.$$setupFS(null);
|
||||
}
|
||||
}
|
@ -1133,7 +1133,6 @@ import * as m6502 from './tools/m6502'
|
||||
import * as z80 from './tools/z80'
|
||||
import * as x86 from './tools/x86'
|
||||
import * as arm from './tools/arm'
|
||||
import * as script from './tools/script'
|
||||
import * as ecs from './tools/ecs'
|
||||
import * as remote from './tools/remote'
|
||||
|
||||
@ -1171,7 +1170,6 @@ var TOOLS = {
|
||||
'wiz': misc.compileWiz,
|
||||
'armips': arm.assembleARMIPS,
|
||||
'vasmarm': arm.assembleVASMARM,
|
||||
//'js': script.runJavascript,
|
||||
'ecs': ecs.assembleECS,
|
||||
'remote': remote.buildRemote
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user