mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 02:37:41 -05:00
Compare commits
16 Commits
2025.2.1
...
event-refi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00f6cfbd28 | ||
|
|
b911ea99fb | ||
|
|
46ca9154c4 | ||
|
|
afd7a8eb00 | ||
|
|
b22f384d73 | ||
|
|
caeab77a8e | ||
|
|
f2bbc6ed5f | ||
|
|
1d666b92a2 | ||
|
|
290eb03388 | ||
|
|
55031f2347 | ||
|
|
8168383653 | ||
|
|
3ff2c171bc | ||
|
|
edbac13713 | ||
|
|
46239caa19 | ||
|
|
0366e48fad | ||
|
|
b13317d32f |
@@ -28,19 +28,26 @@ repos:
|
||||
rev: 25.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
exclude: core/tests
|
||||
args: ["-l", "88", "--skip-string-normalization"]
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.4.1
|
||||
hooks:
|
||||
- id: codespell # See 'pyproject.toml' for args
|
||||
exclude: \.js\.map$
|
||||
exclude: fs\.py|\.js\.map$
|
||||
additional_dependencies:
|
||||
- tomli
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.9.6
|
||||
hooks:
|
||||
- id: ruff
|
||||
exclude: core/tests
|
||||
|
||||
- repo: https://github.com/hoodmane/pyscript-prettier-precommit
|
||||
rev: "v3.0.0-alpha.6"
|
||||
hooks:
|
||||
- id: prettier
|
||||
exclude: core/test|core/dist|core/types|core/src/stdlib/pyscript.js|pyscript\.sw/|core/src/3rd-party
|
||||
exclude: core/tests|core/dist|core/types|core/src/stdlib/pyscript.js|pyscript\.sw/|core/src/3rd-party
|
||||
args: [--tab-width, "4"]
|
||||
|
||||
4
Makefile
4
Makefile
@@ -41,8 +41,8 @@ check-python:
|
||||
# Check the environment, install the dependencies.
|
||||
setup: check-node check-npm check-python
|
||||
cd core && npm ci && cd ..
|
||||
ifeq ($(VIRTUAL_ENV),)
|
||||
echo "\n\n\033[0;31mCannot install Python dependencies. Your virtualenv is not activated.\033[0m"
|
||||
ifeq (,$(VIRTUAL_ENV)$(CONDA_PREFIX))
|
||||
echo "\n\n\033[0;31mCannot install Python dependencies. Your virtualenv or conda env is not activated.\033[0m"
|
||||
false
|
||||
else
|
||||
python -m pip install -r requirements.txt
|
||||
|
||||
@@ -83,3 +83,12 @@ documentation for more information on how to setup your development environment.
|
||||
|
||||
The [PyScript organization governance](https://github.com/pyscript/governance)
|
||||
is documented in a separate repository.
|
||||
|
||||
## Supporters
|
||||
|
||||
PyScript is an independent open source project.
|
||||
|
||||
However, PyScript was born at [Anaconda Inc](https://anaconda.com/) and its
|
||||
core contributors are currently employed by Anaconda to work on PyScript. We
|
||||
would like to acknowledge and celebrate Anaconda's continued support of this
|
||||
project. Thank you [Anaconda Inc](https://anaconda.com/)!
|
||||
|
||||
511
core/package-lock.json
generated
511
core/package-lock.json
generated
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.6.25",
|
||||
"version": "0.6.39",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.6.25",
|
||||
"version": "0.6.39",
|
||||
"license": "APACHE-2.0",
|
||||
"dependencies": {
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
"@webreflection/idb-map": "^0.3.2",
|
||||
"add-promise-listener": "^0.1.3",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"polyscript": "^0.16.10",
|
||||
"polyscript": "^0.16.22",
|
||||
"sabayon": "^0.6.6",
|
||||
"sticky-module": "^0.1.1",
|
||||
"to-json-callback": "^0.1.1",
|
||||
@@ -22,29 +22,29 @@
|
||||
"devDependencies": {
|
||||
"@codemirror/commands": "^6.8.0",
|
||||
"@codemirror/lang-python": "^6.1.7",
|
||||
"@codemirror/language": "^6.10.8",
|
||||
"@codemirror/language": "^6.11.0",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/view": "^6.36.2",
|
||||
"@playwright/test": "1.45.3",
|
||||
"@rollup/plugin-commonjs": "^28.0.2",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
"@codemirror/view": "^6.36.4",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@webreflection/toml-j0.4": "^1.1.3",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"bun": "^1.2.2",
|
||||
"bun": "^1.2.5",
|
||||
"chokidar": "^4.0.3",
|
||||
"codedent": "^0.1.2",
|
||||
"codemirror": "^6.0.1",
|
||||
"eslint": "^9.19.0",
|
||||
"flatted": "^3.3.2",
|
||||
"rollup": "^4.34.3",
|
||||
"eslint": "^9.22.0",
|
||||
"flatted": "^3.3.3",
|
||||
"rollup": "^4.36.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
"static-handler": "^0.5.3",
|
||||
"string-width": "^7.2.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript": "^5.8.2",
|
||||
"xterm-readline": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -52,9 +52,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.18.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.4.tgz",
|
||||
"integrity": "sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==",
|
||||
"version": "6.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
|
||||
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -92,9 +92,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.8.tgz",
|
||||
"integrity": "sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz",
|
||||
"integrity": "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -119,9 +119,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/search": {
|
||||
"version": "6.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz",
|
||||
"integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==",
|
||||
"version": "6.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.10.tgz",
|
||||
"integrity": "sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -141,9 +141,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.2.tgz",
|
||||
"integrity": "sha512-DZ6ONbs8qdJK0fdN7AB82CgI6tYXf4HWk1wSVa0+9bhVznCuuvhQtX8bFBoy3dv8rZSQqUd8GvhVAcielcidrA==",
|
||||
"version": "6.36.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.4.tgz",
|
||||
"integrity": "sha512-ZQ0V5ovw/miKEXTvjgzRyjnrk9TwriUB1k4R5p7uNnHR9Hus+D1SXHGdJshijEzPFjU25xea/7nhIeSqYFKdbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -153,9 +153,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
|
||||
"integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz",
|
||||
"integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -209,10 +209,20 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-helpers": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz",
|
||||
"integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
|
||||
"integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
|
||||
"integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -223,9 +233,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
|
||||
"integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz",
|
||||
"integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -247,9 +257,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
|
||||
"integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
|
||||
"version": "9.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz",
|
||||
"integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -267,13 +277,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
|
||||
"integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz",
|
||||
"integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.10.0",
|
||||
"@eslint/core": "^0.12.0",
|
||||
"levn": "^0.4.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -333,9 +343,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/retry": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
|
||||
"integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
|
||||
"integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
@@ -438,9 +448,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/python": {
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.15.tgz",
|
||||
"integrity": "sha512-aVQ43m2zk4FZYedCqL0KHPEUsqZOrmAvRhkhHlVPnDD1HODDyyQv5BRIuod4DadkgBEZd53vQOtXTonNbEgjrQ==",
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.16.tgz",
|
||||
"integrity": "sha512-ievIWylIZA5rNgAyHgA06/Y76vMUISKaYL9WrtjU8rCTTEzyZYo2jz9ER2YBdnN6dxCyS7eaK4HJCzamoAMKZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -457,9 +467,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@oven/bun-darwin-aarch64": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.2.2.tgz",
|
||||
"integrity": "sha512-hCDvi6GGJvsKpfGcU9xdrIhshDtzkYcGiB5wnj0jq/QM3U85qmIe8QUs7tyse9T77aZNjFIXO0GirL+oZ7C+IQ==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.2.5.tgz",
|
||||
"integrity": "sha512-ggZfdpgUJ/OiWrfcfTgHeSTHcec5HAjkGrZHL9FJ/R60sydRKPYHgAgexdIoJAGfsCVAL+x7y8NSTRIAX8J4Ng==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -471,9 +481,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oven/bun-darwin-x64": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.2.2.tgz",
|
||||
"integrity": "sha512-W1MkLpfLMH4+aRoLNksLiODySVUlnKF5dTEmS2VlHxl4Mle+V/40/WSalpZVRPcztJMquwQy2VXox62WUmERXQ==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.2.5.tgz",
|
||||
"integrity": "sha512-4zqyQLJB33s99KcTxH6yQqH5EYBmF1qofQTtLsToIFbIZN1NqSp/aegYiGmxO5Kj/BuWsy8Wf8MS6vX2O0o2Lw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -485,9 +495,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oven/bun-darwin-x64-baseline": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.2.2.tgz",
|
||||
"integrity": "sha512-Q4gC6fB/6BwGc6QltAAlhugCdRRIraxbNYuA0cyuwUlFmMzQIqgO+iSCIaS2PLEwVEwVx8WF++YpU+dVGyNAvg==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.2.5.tgz",
|
||||
"integrity": "sha512-3W1RO3/D6Z1S79J47F/DLzmK+dgkYq5hS1ShOCSBAYTTA2b1ZuymaN8avGzSb9ed5W0QfxtyeAksfEY2xUBOqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -499,9 +509,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oven/bun-linux-aarch64": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.2.2.tgz",
|
||||
"integrity": "sha512-VW83fgwFAJyu76xMF2t6W1+VxcLaKJUMH+/k61PdwAulpR6M+aqf8vPQDpIx2vrs4BqR8DQ4eEspyb2/DpcuyQ==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.2.5.tgz",
|
||||
"integrity": "sha512-NQFtAVyQyJhLYrhFVxKdh6cqrDNc60pBnBGLQSO8PU+oyFyiJ3e3gGXjLzMbxd6cJxNIK5FZ0JIq96WljKAhlg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -513,9 +523,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oven/bun-linux-aarch64-musl": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.2.2.tgz",
|
||||
"integrity": "sha512-73srKJaPf3fKUuST4Xd+CO52qCKVMtXUICkTddZ+6itnNNrBDBw4AoayrlNImg7swL8wIZonGGYZHpsvyjmlcg==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.2.5.tgz",
|
||||
"integrity": "sha512-URlISBOE2HQi8qdru691OYywJRwChxMfXFbk26tCgdZ01LgGAKsIjAYylefuSsPuA697imDN3Pel3D7rveusmw==",
|
||||
"cpu": [
|
||||
"aarch64"
|
||||
],
|
||||
@@ -527,9 +537,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oven/bun-linux-x64": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.2.2.tgz",
|
||||
"integrity": "sha512-qoEkSdWGvTcX2/Iv3nUcwIcOk72L9Pg6X4ONUXV7luuOHsXktoQK5vuQeCC5FTxCEPjfoKy1GFAFomoU4qZDGQ==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.2.5.tgz",
|
||||
"integrity": "sha512-pa3kQ4cXNV0jk5aM8+Hdmxr+b4QoPVgeAIA454SN5l3hMGfNsHjczKpsz0ksInZ8506iMMTCPEBXpyQJcSme+Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -541,9 +551,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oven/bun-linux-x64-baseline": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.2.2.tgz",
|
||||
"integrity": "sha512-YvIilLbII9+sjwnZVumwItoUKSUHJHo9xYw5hcf6aeqDztK1ebGzNIJpE0RgjIT+GYqy504Yz5cK77CGpyVyJw==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.2.5.tgz",
|
||||
"integrity": "sha512-fCm/qp7e3VYlaoRs6NIEsKubPqyxjzLv8/qZkxeLLOlPd7CS8L26UY4KPOSjA+wrhPT+Nxsyvl/EEJq2R/iauA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -555,9 +565,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oven/bun-linux-x64-musl": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.2.2.tgz",
|
||||
"integrity": "sha512-6uyUXVNH5jUsvhyzD0uiQNe8VPujuGTsHHDTW1LvRR6Lgr6AVaYjdk45ypbDSauRQsABxDKFQdfQqemkM97sYw==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.2.5.tgz",
|
||||
"integrity": "sha512-DuU2kQnY48g9tNWjFrZqyG+U2emCBwlhOPxbuY/TMVVNSTMAcQbE/bb3s2pZdhZH5ssjc5SH/ZyWU1TePcYB2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -569,9 +579,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oven/bun-linux-x64-musl-baseline": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl-baseline/-/bun-linux-x64-musl-baseline-1.2.2.tgz",
|
||||
"integrity": "sha512-K2o2vSHSnGn+ayXUuFJ+7O5L5Mc+uOIydMc5WI9bMhvmEtRqlohrwQR56HPzeFZVPWvmHdxb34VIceEKXmig6Q==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl-baseline/-/bun-linux-x64-musl-baseline-1.2.5.tgz",
|
||||
"integrity": "sha512-H7tuJz7mZvOTPo4yLbIXIxkiDGWSGd2DbwGl4zNol/FURqGsKQVqpomv86yl9KCXsUUOm5FX2i5Ed+ro8N//Cg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -583,9 +593,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oven/bun-windows-x64": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.2.2.tgz",
|
||||
"integrity": "sha512-ZjxyIcS7kQFoGxyNBMZ9mqCxCkg7ZVG9P+/GTzMpUceFF/q88lUwsFsC7YwAe0ZubqcQLRSfdyjbKbC/HuqnFw==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.2.5.tgz",
|
||||
"integrity": "sha512-oNDdPmzsCyvCATiYgkKWgxOeEx2F7m/i2MGUba+YJAeVXJsJg9iPJrLVBtETvKoSAgkXViwoUEw2U25jRYsp4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -597,9 +607,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oven/bun-windows-x64-baseline": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.2.2.tgz",
|
||||
"integrity": "sha512-bYopMWSCjjjCKjANv7xxAXQoabVUxLZxTw0iC1bGYD9VZGo48nGaJXPn7DsPfeCXGyl+CY3Cy4QIEn+3gNRS2A==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.2.5.tgz",
|
||||
"integrity": "sha512-j5FxI8FeKfWI6rEXA+1O3ASBMTp5CFcZ7MR+/aCpiBKrDse32wLaZMVGnvqQqs4y0YHUvR8b7eXHHTboezjL1w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -611,13 +621,13 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.45.3",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.3.tgz",
|
||||
"integrity": "sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==",
|
||||
"version": "1.51.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.1.tgz",
|
||||
"integrity": "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.45.3"
|
||||
"playwright": "1.51.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -627,9 +637,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-commonjs": {
|
||||
"version": "28.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.2.tgz",
|
||||
"integrity": "sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==",
|
||||
"version": "28.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz",
|
||||
"integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -654,9 +664,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.0.tgz",
|
||||
"integrity": "sha512-0FPvAeVUT/zdWoO0jnb/V5BlBsUSNfkIOtFHzMO4H9MOklrmQFY6FduVHKucNb/aTFxvnGhj4MNj/T1oNdDfNg==",
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz",
|
||||
"integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -725,9 +735,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.3.tgz",
|
||||
"integrity": "sha512-8kq/NjMKkMTGKMPldWihncOl62kgnLYk7cW+/4NCUWfS70/wz4+gQ7rMxMMpZ3dIOP/xw7wKNzIuUnN/H2GfUg==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.36.0.tgz",
|
||||
"integrity": "sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -739,9 +749,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.3.tgz",
|
||||
"integrity": "sha512-1PqMHiuRochQ6++SDI7SaRDWJKr/NgAlezBi5nOne6Da6IWJo3hK0TdECBDwd92IUDPG4j/bZmWuwOnomNT8wA==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.36.0.tgz",
|
||||
"integrity": "sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -753,9 +763,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.3.tgz",
|
||||
"integrity": "sha512-fqbrykX4mGV3DlCDXhF4OaMGcchd2tmLYxVt3On5oOZWVDFfdEoYAV2alzNChl8OzNaeMAGqm1f7gk7eIw/uDg==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.36.0.tgz",
|
||||
"integrity": "sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -767,9 +777,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.3.tgz",
|
||||
"integrity": "sha512-8Wxrx/KRvMsTyLTbdrMXcVKfpW51cCNW8x7iQD72xSEbjvhCY3b+w83Bea3nQfysTMR7K28esc+ZFITThXm+1w==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.36.0.tgz",
|
||||
"integrity": "sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -781,9 +791,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.3.tgz",
|
||||
"integrity": "sha512-lpBmV2qSiELh+ATQPTjQczt5hvbTLsE0c43Rx4bGxN2VpnAZWy77we7OO62LyOSZNY7CzjMoceRPc+Lt4e9J6A==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.36.0.tgz",
|
||||
"integrity": "sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -795,9 +805,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.3.tgz",
|
||||
"integrity": "sha512-sNPvBIXpgaYcI6mAeH13GZMXFrrw5mdZVI1M9YQPRG2LpjwL8DSxSIflZoh/B5NEuOi53kxsR/S2GKozK1vDXA==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.36.0.tgz",
|
||||
"integrity": "sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -809,9 +819,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.3.tgz",
|
||||
"integrity": "sha512-MW6N3AoC61OfE1VgnN5O1OW0gt8VTbhx9s/ZEPLBM11wEdHjeilPzOxVmmsrx5YmejpGPvez8QwGGvMU+pGxpw==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.36.0.tgz",
|
||||
"integrity": "sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -823,9 +833,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.3.tgz",
|
||||
"integrity": "sha512-2SQkhr5xvatYq0/+H6qyW0zvrQz9LM4lxGkpWURLoQX5+yP8MsERh4uWmxFohOvwCP6l/+wgiHZ1qVwLDc7Qmw==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.36.0.tgz",
|
||||
"integrity": "sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -837,9 +847,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.3.tgz",
|
||||
"integrity": "sha512-R3JLYt8YoRwKI5shJsovLpcR6pwIMui/MGG/MmxZ1DYI3iRSKI4qcYrvYgDf4Ss2oCR3RL3F3dYK7uAGQgMIuQ==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.36.0.tgz",
|
||||
"integrity": "sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -851,9 +861,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.3.tgz",
|
||||
"integrity": "sha512-4XQhG8v/t3S7Rxs7rmFUuM6j09hVrTArzONS3fUZ6oBRSN/ps9IPQjVhp62P0W3KhqJdQADo/MRlYRMdgxr/3w==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.36.0.tgz",
|
||||
"integrity": "sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -865,9 +875,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.3.tgz",
|
||||
"integrity": "sha512-QlW1jCUZ1LHUIYCAK2FciVw1ptHsxzApYVi05q7bz2A8oNE8QxQ85NhM4arLxkAlcnS42t4avJbSfzSQwbIaKg==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.36.0.tgz",
|
||||
"integrity": "sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -879,9 +889,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.3.tgz",
|
||||
"integrity": "sha512-kMbLToizVeCcN69+nnm20Dh0hrRIAjgaaL+Wh0gWZcNt8e542d2FUGtsyuNsHVNNF3gqTJrpzUGIdwMGLEUM7g==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.36.0.tgz",
|
||||
"integrity": "sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -893,9 +903,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.3.tgz",
|
||||
"integrity": "sha512-YgD0DnZ3CHtvXRH8rzjVSxwI0kMTr0RQt3o1N92RwxGdx7YejzbBO0ELlSU48DP96u1gYYVWfUhDRyaGNqJqJg==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.36.0.tgz",
|
||||
"integrity": "sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -907,9 +917,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.3.tgz",
|
||||
"integrity": "sha512-dIOoOz8altjp6UjAi3U9EW99s8nta4gzi52FeI45GlPyrUH4QixUoBMH9VsVjt+9A2RiZBWyjYNHlJ/HmJOBCQ==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.36.0.tgz",
|
||||
"integrity": "sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -921,9 +931,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.3.tgz",
|
||||
"integrity": "sha512-lOyG3aF4FTKrhpzXfMmBXgeKUUXdAWmP2zSNf8HTAXPqZay6QYT26l64hVizBjq+hJx3pl0DTEyvPi9sTA6VGA==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.36.0.tgz",
|
||||
"integrity": "sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -935,9 +945,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.3.tgz",
|
||||
"integrity": "sha512-usztyYLu2i+mYzzOjqHZTaRXbUOqw3P6laNUh1zcqxbPH1P2Tz/QdJJCQSnGxCtsRQeuU2bCyraGMtMumC46rw==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.36.0.tgz",
|
||||
"integrity": "sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -949,9 +959,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.3.tgz",
|
||||
"integrity": "sha512-ojFOKaz/ZyalIrizdBq2vyc2f0kFbJahEznfZlxdB6pF9Do6++i1zS5Gy6QLf8D7/S57MHrmBLur6AeRYeQXSA==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.36.0.tgz",
|
||||
"integrity": "sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -963,9 +973,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.3.tgz",
|
||||
"integrity": "sha512-K/V97GMbNa+Da9mGcZqmSl+DlJmWfHXTuI9V8oB2evGsQUtszCl67+OxWjBKpeOnYwox9Jpmt/J6VhpeRCYqow==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.36.0.tgz",
|
||||
"integrity": "sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -977,9 +987,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.3.tgz",
|
||||
"integrity": "sha512-CUypcYP31Q8O04myV6NKGzk9GVXslO5EJNfmARNSzLF2A+5rmZUlDJ4et6eoJaZgBT9wrC2p4JZH04Vkic8HdQ==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.36.0.tgz",
|
||||
"integrity": "sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1085,9 +1095,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -1238,9 +1248,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bun": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/bun/-/bun-1.2.2.tgz",
|
||||
"integrity": "sha512-RUc8uVVTw8WoASUzXaEQJR1s7mnwoHm3P871qBUIqSaoOpuwcU+bSVX151/xoqDwnyv38SjOX7yQ3oO0IeT73g==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/bun/-/bun-1.2.5.tgz",
|
||||
"integrity": "sha512-fbQLt+DPiGUrPKdmsHRRT7cQAlfjdxPVFvLZrsUPmKiTdv+pU50ypdx9yRJluknSbyaZchFVV7Lx2KXikXKX2Q==",
|
||||
"cpu": [
|
||||
"arm64",
|
||||
"x64",
|
||||
@@ -1259,17 +1269,17 @@
|
||||
"bunx": "bin/bun.exe"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@oven/bun-darwin-aarch64": "1.2.2",
|
||||
"@oven/bun-darwin-x64": "1.2.2",
|
||||
"@oven/bun-darwin-x64-baseline": "1.2.2",
|
||||
"@oven/bun-linux-aarch64": "1.2.2",
|
||||
"@oven/bun-linux-aarch64-musl": "1.2.2",
|
||||
"@oven/bun-linux-x64": "1.2.2",
|
||||
"@oven/bun-linux-x64-baseline": "1.2.2",
|
||||
"@oven/bun-linux-x64-musl": "1.2.2",
|
||||
"@oven/bun-linux-x64-musl-baseline": "1.2.2",
|
||||
"@oven/bun-windows-x64": "1.2.2",
|
||||
"@oven/bun-windows-x64-baseline": "1.2.2"
|
||||
"@oven/bun-darwin-aarch64": "1.2.5",
|
||||
"@oven/bun-darwin-x64": "1.2.5",
|
||||
"@oven/bun-darwin-x64-baseline": "1.2.5",
|
||||
"@oven/bun-linux-aarch64": "1.2.5",
|
||||
"@oven/bun-linux-aarch64-musl": "1.2.5",
|
||||
"@oven/bun-linux-x64": "1.2.5",
|
||||
"@oven/bun-linux-x64-baseline": "1.2.5",
|
||||
"@oven/bun-linux-x64-musl": "1.2.5",
|
||||
"@oven/bun-linux-x64-musl-baseline": "1.2.5",
|
||||
"@oven/bun-windows-x64": "1.2.5",
|
||||
"@oven/bun-windows-x64-baseline": "1.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
@@ -1296,9 +1306,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001697",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001697.tgz",
|
||||
"integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==",
|
||||
"version": "1.0.30001706",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz",
|
||||
"integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1734,9 +1744,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.92",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.92.tgz",
|
||||
"integrity": "sha512-BeHgmNobs05N1HMmMZ7YIuHfYBGlq/UmvlsTgg+fsbFs9xVMj+xJHFg19GN04+9Q+r8Xnh9LXqaYIyEWElnNgQ==",
|
||||
"version": "1.5.120",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.120.tgz",
|
||||
"integrity": "sha512-oTUp3gfX1gZI+xfD2djr2rzQdHCwHzPQrrK0CD7WpTdF0nPdQ/INcRVjWgLdCT4a9W3jFObR9DAfsuyFQnI8CQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -1781,22 +1791,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.19.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
|
||||
"integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
|
||||
"version": "9.22.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz",
|
||||
"integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
"@eslint/config-array": "^0.19.0",
|
||||
"@eslint/core": "^0.10.0",
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "9.19.0",
|
||||
"@eslint/plugin-kit": "^0.2.5",
|
||||
"@eslint/config-array": "^0.19.2",
|
||||
"@eslint/config-helpers": "^0.1.0",
|
||||
"@eslint/core": "^0.12.0",
|
||||
"@eslint/eslintrc": "^3.3.0",
|
||||
"@eslint/js": "9.22.0",
|
||||
"@eslint/plugin-kit": "^0.2.7",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.4.1",
|
||||
"@humanwhocodes/retry": "^0.4.2",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"ajv": "^6.12.4",
|
||||
@@ -1804,7 +1815,7 @@
|
||||
"cross-spawn": "^7.0.6",
|
||||
"debug": "^4.3.2",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"eslint-scope": "^8.2.0",
|
||||
"eslint-scope": "^8.3.0",
|
||||
"eslint-visitor-keys": "^4.2.0",
|
||||
"espree": "^10.3.0",
|
||||
"esquery": "^1.5.0",
|
||||
@@ -1841,9 +1852,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
|
||||
"integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
|
||||
"integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
@@ -2029,9 +2040,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
|
||||
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
|
||||
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -2311,21 +2322,15 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/js-proxy": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/js-proxy/-/js-proxy-0.5.1.tgz",
|
||||
"integrity": "sha512-G1AswnGndelrmZ2tuJi5NWlXo28BucJdgO8aKP5U1NkxWxPvgFA510Ku6at+1A17Kh2ja7A/r4RDtd9Hdr6sOw==",
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/js-proxy/-/js-proxy-0.5.2.tgz",
|
||||
"integrity": "sha512-gywYozJo2nfzlnYtBZXZCzbcMX8TfEJJtuUcj/uIE6xwqx9UoiOUeZcBb1Gy1IFxs3Tf+1kos8Aiv45P9HAkyQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"gc-hook": "^0.3.1",
|
||||
"gc-hook": "^0.4.1",
|
||||
"proxy-target": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/js-proxy/node_modules/gc-hook": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/gc-hook/-/gc-hook-0.3.1.tgz",
|
||||
"integrity": "sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
@@ -2486,9 +2491,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -2715,13 +2720,13 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.45.3",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.3.tgz",
|
||||
"integrity": "sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww==",
|
||||
"version": "1.51.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz",
|
||||
"integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.3"
|
||||
"playwright-core": "1.51.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -2734,9 +2739,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.45.3",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.3.tgz",
|
||||
"integrity": "sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==",
|
||||
"version": "1.51.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz",
|
||||
"integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -2747,12 +2752,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/polyscript": {
|
||||
"version": "0.16.10",
|
||||
"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.16.10.tgz",
|
||||
"integrity": "sha512-KMzPsFqTJDYA1JHKVTpUZZ1TK6MIFRDjF6FgIgs6PQ/zzlIWc9LlM9HoeXD9521U6g8SfiKqVChvpxfG84azig==",
|
||||
"version": "0.16.22",
|
||||
"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.16.22.tgz",
|
||||
"integrity": "sha512-qMCi5ehkVwNc+Qq1kVcT413oLS7erh1nwjkTXCUigtFzS6w5kmqVOLj3UVPJBF39q8Xgu0HJStyl+37psBFwyg==",
|
||||
"license": "APACHE-2.0",
|
||||
"dependencies": {
|
||||
"@ungap/structured-clone": "^1.2.0",
|
||||
"@ungap/structured-clone": "^1.3.0",
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
"@webreflection/fetch": "^0.1.5",
|
||||
"@webreflection/idb-map": "^0.3.2",
|
||||
@@ -2767,9 +2772,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
|
||||
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3084,9 +3089,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz",
|
||||
"integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==",
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
|
||||
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3114,9 +3119,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz",
|
||||
"integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==",
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
|
||||
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3437,9 +3442,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz",
|
||||
"integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3482,9 +3487,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.34.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.3.tgz",
|
||||
"integrity": "sha512-ORCtU0UBJyiAIn9m0llUXJXAswG/68pZptCrqxHG7//Z2DDzAUeyyY5hqf4XrsGlUxscMr9GkQ2QI7KTLqeyPw==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.36.0.tgz",
|
||||
"integrity": "sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3498,25 +3503,25 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.34.3",
|
||||
"@rollup/rollup-android-arm64": "4.34.3",
|
||||
"@rollup/rollup-darwin-arm64": "4.34.3",
|
||||
"@rollup/rollup-darwin-x64": "4.34.3",
|
||||
"@rollup/rollup-freebsd-arm64": "4.34.3",
|
||||
"@rollup/rollup-freebsd-x64": "4.34.3",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.34.3",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.34.3",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.3",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.3",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.34.3",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.3",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.34.3",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.34.3",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.3",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.3",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.3",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.34.3",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.3",
|
||||
"@rollup/rollup-android-arm-eabi": "4.36.0",
|
||||
"@rollup/rollup-android-arm64": "4.36.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.36.0",
|
||||
"@rollup/rollup-darwin-x64": "4.36.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.36.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.36.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.36.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.36.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.36.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.36.0",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.36.0",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.36.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.36.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.36.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.36.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.36.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.36.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.36.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.36.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -3848,9 +3853,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.37.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
|
||||
"integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
|
||||
"version": "5.39.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
|
||||
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
@@ -3899,9 +3904,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -3913,9 +3918,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
|
||||
"integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.6.25",
|
||||
"version": "0.6.39",
|
||||
"type": "module",
|
||||
"description": "PyScript",
|
||||
"module": "./index.js",
|
||||
@@ -25,6 +25,10 @@
|
||||
"types": "./types/core.d.ts",
|
||||
"import": "./src/core.js"
|
||||
},
|
||||
"./js": {
|
||||
"types": "./types/core.d.ts",
|
||||
"import": "./dist/core.js"
|
||||
},
|
||||
"./css": {
|
||||
"import": "./dist/core.css"
|
||||
},
|
||||
@@ -43,7 +47,7 @@
|
||||
"build:3rd-party": "node rollup/3rd-party.cjs",
|
||||
"build:tests-index": "node rollup/build_test_index.cjs",
|
||||
"clean:3rd-party": "rm src/3rd-party/*.js && rm src/3rd-party/*.css",
|
||||
"test:integration": "npm run test:ws; static-handler --coi . 2>/dev/null & SH_PID=$!; EXIT_CODE=0; playwright test tests/js_tests.spec.js tests/py_tests.spec.js || EXIT_CODE=$?; kill $SH_PID 2>/dev/null; exit $EXIT_CODE",
|
||||
"test:integration": "npm run test:ws; static-handler --coi . 2>/dev/null & SH_PID=$!; EXIT_CODE=0; (playwright test tests/js_tests.spec.js && playwright test tests/py_tests.main.spec.js && playwright test tests/py_tests.worker.spec.js) || EXIT_CODE=$?; kill $SH_PID 2>/dev/null; exit $EXIT_CODE",
|
||||
"test:ws": "bun tests/javascript/ws/index.js & playwright test tests/javascript/ws/index.spec.js",
|
||||
"dev": "node dev.cjs",
|
||||
"release": "npm run build && npm run zip",
|
||||
@@ -62,7 +66,7 @@
|
||||
"@webreflection/idb-map": "^0.3.2",
|
||||
"add-promise-listener": "^0.1.3",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"polyscript": "^0.16.10",
|
||||
"polyscript": "^0.16.22",
|
||||
"sabayon": "^0.6.6",
|
||||
"sticky-module": "^0.1.1",
|
||||
"to-json-callback": "^0.1.1",
|
||||
@@ -71,29 +75,29 @@
|
||||
"devDependencies": {
|
||||
"@codemirror/commands": "^6.8.0",
|
||||
"@codemirror/lang-python": "^6.1.7",
|
||||
"@codemirror/language": "^6.10.8",
|
||||
"@codemirror/language": "^6.11.0",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/view": "^6.36.2",
|
||||
"@playwright/test": "1.45.3",
|
||||
"@rollup/plugin-commonjs": "^28.0.2",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
"@codemirror/view": "^6.36.4",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@webreflection/toml-j0.4": "^1.1.3",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"bun": "^1.2.2",
|
||||
"bun": "^1.2.5",
|
||||
"chokidar": "^4.0.3",
|
||||
"codedent": "^0.1.2",
|
||||
"codemirror": "^6.0.1",
|
||||
"eslint": "^9.19.0",
|
||||
"flatted": "^3.3.2",
|
||||
"rollup": "^4.34.3",
|
||||
"eslint": "^9.22.0",
|
||||
"flatted": "^3.3.3",
|
||||
"rollup": "^4.36.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
"static-handler": "^0.5.3",
|
||||
"string-width": "^7.2.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript": "^5.8.2",
|
||||
"xterm-readline": "^1.1.2"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
2
core/src/3rd-party/xterm_addon-fit.js
vendored
2
core/src/3rd-party/xterm_addon-fit.js
vendored
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Bundled by jsDelivr using Rollup v2.79.1 and Terser v5.19.2.
|
||||
* Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.37.0.
|
||||
* Original file: /npm/@xterm/addon-fit@0.10.0/lib/addon-fit.js
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
|
||||
2
core/src/3rd-party/xterm_addon-web-links.js
vendored
2
core/src/3rd-party/xterm_addon-web-links.js
vendored
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Bundled by jsDelivr using Rollup v2.79.1 and Terser v5.19.2.
|
||||
* Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.37.0.
|
||||
* Original file: /npm/@xterm/addon-web-links@0.11.0/lib/addon-web-links.js
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
|
||||
@@ -28,53 +28,34 @@ mpy-config {
|
||||
.py-editor-run-button,
|
||||
.mpy-editor-run-button {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
right: 0.5rem;
|
||||
bottom: 0.5rem;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s;
|
||||
z-index: 1;
|
||||
padding: 0;
|
||||
}
|
||||
.py-editor-box:hover .py-editor-run-button,
|
||||
.mpy-editor-box:hover .mpy-editor-run-button,
|
||||
.py-editor-run-button:focus,
|
||||
.py-editor-run-button:disabled,
|
||||
.py-editor-run-button.running,
|
||||
.mpy-editor-run-button:focus,
|
||||
.mpy-editor-run-button:disabled {
|
||||
.mpy-editor-run-button.running {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes spinner {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.py-editor-run-button:disabled > *,
|
||||
.mpy-editor-run-button:disabled > * {
|
||||
display: none; /* hide all the child elements of the run button when it is disabled */
|
||||
}
|
||||
.py-editor-run-button:disabled,
|
||||
.mpy-editor-run-button:disabled {
|
||||
border-width: 0;
|
||||
}
|
||||
.py-editor-run-button:disabled::before,
|
||||
.mpy-editor-run-button:disabled::before {
|
||||
content: "";
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 100%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: -23px; /* hardcoded value to center the spinner on the run button */
|
||||
margin-left: -26px; /* hardcoded value to center the spinner on the run button */
|
||||
border-radius: 50%;
|
||||
border: 2px solid #aaa;
|
||||
border-top-color: #000;
|
||||
background-color: #fff;
|
||||
animation: spinner 0.6s linear infinite;
|
||||
}
|
||||
|
||||
py-terminal span,
|
||||
mpy-terminal span {
|
||||
letter-spacing: 0 !important;
|
||||
}
|
||||
|
||||
dialog.pyscript-fs {
|
||||
border-radius: 8px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
dialog.pyscript-fs > div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
createFunction,
|
||||
inputFailure,
|
||||
} from "./hooks.js";
|
||||
import * as fs from "./fs.js";
|
||||
|
||||
import codemirror from "./plugins/codemirror.js";
|
||||
export { codemirror };
|
||||
@@ -167,6 +168,8 @@ for (const [TYPE, interpreter] of TYPES) {
|
||||
// enrich the Python env with some JS utility for main
|
||||
interpreter.registerJsModule("_pyscript", {
|
||||
PyWorker,
|
||||
fs,
|
||||
interpreter,
|
||||
js_import: (...urls) => Promise.all(urls.map((url) => import(url))),
|
||||
get target() {
|
||||
return isScript(currentElement)
|
||||
|
||||
81
core/src/fs.js
Normal file
81
core/src/fs.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import IDBMap from "@webreflection/idb-map";
|
||||
import { assign } from "polyscript/exports";
|
||||
import { $$ } from "basic-devtools";
|
||||
|
||||
const stop = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
};
|
||||
|
||||
// ⚠️ these two constants MUST be passed as `fs`
|
||||
// within the worker onBeforeRunAsync hook!
|
||||
export const NAMESPACE = "@pyscript.fs";
|
||||
export const ERROR = "storage permissions not granted";
|
||||
|
||||
export const idb = new IDBMap(NAMESPACE);
|
||||
|
||||
/**
|
||||
* Ask a user action via dialog and returns the directory handler once granted.
|
||||
* @param {{id?:string, mode?:"read"|"readwrite", hint?:"desktop"|"documents"|"downloads"|"music"|"pictures"|"videos"}} options
|
||||
* @returns {Promise<FileSystemDirectoryHandle>}
|
||||
*/
|
||||
export const getFileSystemDirectoryHandle = async (options) => {
|
||||
if (!("showDirectoryPicker" in globalThis)) {
|
||||
return Promise.reject(
|
||||
new Error("showDirectoryPicker is not supported"),
|
||||
);
|
||||
}
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
|
||||
const how = { id: "pyscript", mode: "readwrite", ...options };
|
||||
if (options.hint) how.startIn = options.hint;
|
||||
|
||||
const transient = async () => {
|
||||
try {
|
||||
/* eslint-disable */
|
||||
const handler = await showDirectoryPicker(how);
|
||||
/* eslint-enable */
|
||||
if ((await handler.requestPermission(how)) === "granted") {
|
||||
resolve(handler);
|
||||
return true;
|
||||
}
|
||||
} catch ({ message }) {
|
||||
console.warn(message);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// in case the user decided to attach the event itself
|
||||
// as opposite of relying our dialog walkthrough
|
||||
if (navigator.userActivation?.isActive) {
|
||||
if (!(await transient())) reject(new Error(ERROR));
|
||||
} else {
|
||||
const dialog = assign(document.createElement("dialog"), {
|
||||
className: "pyscript-fs",
|
||||
innerHTML: [
|
||||
"<strong>ℹ️ Persistent FileSystem</strong><hr>",
|
||||
"<p><small>PyScript would like to access a local folder.</small></p>",
|
||||
"<div><button title='ok'>✅ Authorize</button>",
|
||||
"<button title='cancel'>❌</button></div>",
|
||||
].join(""),
|
||||
});
|
||||
|
||||
const [ok, cancel] = $$("button", dialog);
|
||||
|
||||
ok.addEventListener("click", async (event) => {
|
||||
stop(event);
|
||||
if (await transient()) dialog.close();
|
||||
});
|
||||
|
||||
cancel.addEventListener("click", async (event) => {
|
||||
stop(event);
|
||||
reject(new Error(ERROR));
|
||||
dialog.close();
|
||||
});
|
||||
|
||||
document.body.appendChild(dialog).showModal();
|
||||
}
|
||||
|
||||
return promise;
|
||||
};
|
||||
@@ -88,7 +88,19 @@ export const hooks = {
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRun: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRunAsync: new SetFunction(),
|
||||
onBeforeRunAsync: new SetFunction([
|
||||
({ interpreter }) => {
|
||||
interpreter.registerJsModule("_pyscript", {
|
||||
// cannot be imported from fs.js
|
||||
// because this code is stringified
|
||||
fs: {
|
||||
ERROR: "storage permissions not granted",
|
||||
NAMESPACE: "@pyscript.fs",
|
||||
},
|
||||
interpreter,
|
||||
});
|
||||
},
|
||||
]),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRun: new SetFunction(),
|
||||
/** @type {Set<function>} */
|
||||
|
||||
@@ -4,13 +4,15 @@ import { TYPES, offline_interpreter, relative_url, stdlib } from "../core.js";
|
||||
import { notify } from "./error.js";
|
||||
import codemirror from "./codemirror.js";
|
||||
|
||||
const RUN_BUTTON = `<svg style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>`;
|
||||
const RUN_BUTTON = `<svg style="height:24px;width:24px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19,12a1,1,0,0,1-.55.89l-10,5A1,1,0,0,1,8,18a1,1,0,0,1-.53-.15A1,1,0,0,1,7,17V7a1,1,0,0,1,1.45-.89l10,5A1,1,0,0,1,19,12Z" fill="#464646"/></svg>`;
|
||||
const STOP_BUTTON = `<svg style="height:24px;width:24px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7 7h10v10H7z" style="fill:#464646;stroke:#464646;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;paint-order:normal"/></svg>`;
|
||||
|
||||
let id = 0;
|
||||
const getID = (type) => `${type}-editor-${id++}`;
|
||||
|
||||
const envs = new Map();
|
||||
const configs = new Map();
|
||||
const editors = new WeakMap();
|
||||
|
||||
const hooks = {
|
||||
worker: {
|
||||
@@ -30,12 +32,18 @@ const validate = (config, result) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const getRelatedScript = (target, type) => {
|
||||
const editor = target.closest(`.${type}-editor-box`);
|
||||
return editor?.parentNode?.previousElementSibling;
|
||||
};
|
||||
|
||||
async function execute({ currentTarget }) {
|
||||
const { env, pySrc, outDiv } = this;
|
||||
const hasRunButton = !!currentTarget;
|
||||
|
||||
if (hasRunButton) {
|
||||
currentTarget.disabled = true;
|
||||
currentTarget.classList.add("running");
|
||||
currentTarget.innerHTML = STOP_BUTTON;
|
||||
outDiv.innerHTML = "";
|
||||
}
|
||||
|
||||
@@ -82,8 +90,7 @@ async function execute({ currentTarget }) {
|
||||
// creation and destruction of editors on the fly
|
||||
if (hasRunButton) {
|
||||
for (const type of TYPES.keys()) {
|
||||
const editor = currentTarget.closest(`.${type}-editor-box`);
|
||||
const script = editor?.parentNode?.previousElementSibling;
|
||||
const script = getRelatedScript(currentTarget, type);
|
||||
if (script) {
|
||||
defineProperties(script, { xworker: { value: xworker } });
|
||||
break;
|
||||
@@ -116,7 +123,10 @@ async function execute({ currentTarget }) {
|
||||
};
|
||||
|
||||
const enable = () => {
|
||||
if (hasRunButton) currentTarget.disabled = false;
|
||||
if (hasRunButton) {
|
||||
currentTarget.classList.remove("running");
|
||||
currentTarget.innerHTML = RUN_BUTTON;
|
||||
}
|
||||
};
|
||||
const { sync } = xworker;
|
||||
sync.write = (str) => {
|
||||
@@ -144,6 +154,24 @@ const makeRunButton = (handler, type) => {
|
||||
runButton.innerHTML = RUN_BUTTON;
|
||||
runButton.setAttribute("aria-label", "Python Script Run Button");
|
||||
runButton.addEventListener("click", async (event) => {
|
||||
if (
|
||||
runButton.classList.contains("running") &&
|
||||
confirm("Stop evaluating this code?")
|
||||
) {
|
||||
const script = getRelatedScript(runButton, type);
|
||||
if (script) {
|
||||
const editor = editors.get(script);
|
||||
const content = editor.state.doc.toString();
|
||||
const clone = script.cloneNode(true);
|
||||
clone.type = `${type}-editor`;
|
||||
clone.textContent = content;
|
||||
script.xworker.terminate();
|
||||
script.nextElementSibling.remove();
|
||||
script.replaceWith(clone);
|
||||
editors.delete(script);
|
||||
}
|
||||
return;
|
||||
}
|
||||
runButton.blur();
|
||||
await handler.handleEvent(event);
|
||||
});
|
||||
@@ -387,6 +415,7 @@ const init = async (script, type, interpreter) => {
|
||||
doc,
|
||||
});
|
||||
|
||||
editors.set(script, editor);
|
||||
editor.focus();
|
||||
notifyEditor();
|
||||
};
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
import { dedent, define } from "polyscript/exports";
|
||||
import {
|
||||
dedent,
|
||||
define,
|
||||
createProgress,
|
||||
loadProgress,
|
||||
} from "polyscript/exports";
|
||||
|
||||
import { stdlib } from "../core.js";
|
||||
import { configDetails } from "../config.js";
|
||||
import { getText } from "../fetch.js";
|
||||
|
||||
const progress = createProgress("py-game");
|
||||
|
||||
const inputPatch = `
|
||||
import builtins
|
||||
def input(prompt=""):
|
||||
import js
|
||||
return js.prompt(prompt)
|
||||
|
||||
builtins.input = input
|
||||
del builtins
|
||||
del input
|
||||
`;
|
||||
|
||||
let toBeWarned = true;
|
||||
|
||||
const hooks = {
|
||||
@@ -13,10 +31,11 @@ const hooks = {
|
||||
toBeWarned = false;
|
||||
console.warn("⚠️ EXPERIMENTAL `py-game` FEATURE");
|
||||
}
|
||||
|
||||
let config = {};
|
||||
if (script.hasAttribute("config")) {
|
||||
const value = script.getAttribute("config");
|
||||
const { json, toml, text } = configDetails(value);
|
||||
let config = {};
|
||||
const { json, toml, text, url } = await configDetails(value);
|
||||
if (json) config = JSON.parse(text);
|
||||
else if (toml) {
|
||||
const { parse } = await import(
|
||||
@@ -25,12 +44,20 @@ const hooks = {
|
||||
config = parse(text);
|
||||
}
|
||||
if (config.packages) {
|
||||
await wrap.interpreter.loadPackage("micropip");
|
||||
const micropip = wrap.interpreter.pyimport("micropip");
|
||||
await micropip.install(config.packages, {
|
||||
keep_going: true,
|
||||
});
|
||||
micropip.destroy();
|
||||
}
|
||||
await loadProgress(
|
||||
"py-game",
|
||||
progress,
|
||||
wrap.interpreter,
|
||||
config,
|
||||
url ? new URL(url, location.href).href : location.href,
|
||||
);
|
||||
}
|
||||
|
||||
wrap.interpreter.registerJsModule("_pyscript", {
|
||||
@@ -47,6 +74,7 @@ const hooks = {
|
||||
});
|
||||
|
||||
await wrap.interpreter.runPythonAsync(stdlib);
|
||||
wrap.interpreter.runPython(inputPatch);
|
||||
|
||||
let code = dedent(script.textContent);
|
||||
if (script.src) code = await fetch(script.src).then(getText);
|
||||
@@ -54,6 +82,22 @@ const hooks = {
|
||||
const target = script.getAttribute("target") || "canvas";
|
||||
const canvas = document.getElementById(target);
|
||||
wrap.interpreter.canvas.setCanvas2D(canvas);
|
||||
|
||||
// allow 3rd party to hook themselves right before
|
||||
// the code gets executed
|
||||
const event = new CustomEvent("py-game", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: {
|
||||
canvas,
|
||||
code,
|
||||
config,
|
||||
wrap,
|
||||
},
|
||||
});
|
||||
script.dispatchEvent(event);
|
||||
// run only if the default was not prevented
|
||||
if (!event.defaultPrevented)
|
||||
await wrap.interpreter.runPythonAsync(code);
|
||||
},
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -73,14 +73,14 @@ def _eval_formatter(obj, print_method):
|
||||
"""
|
||||
if print_method == "__repr__":
|
||||
return repr(obj)
|
||||
elif hasattr(obj, print_method):
|
||||
if hasattr(obj, print_method):
|
||||
if print_method == "savefig":
|
||||
buf = io.BytesIO()
|
||||
obj.savefig(buf, format="png")
|
||||
buf.seek(0)
|
||||
return base64.b64encode(buf.read()).decode("utf-8")
|
||||
return getattr(obj, print_method)()
|
||||
elif print_method == "_repr_mimebundle_":
|
||||
if print_method == "_repr_mimebundle_":
|
||||
return {}, {}
|
||||
return None
|
||||
|
||||
@@ -107,7 +107,7 @@ def _format_mime(obj):
|
||||
|
||||
if output is None:
|
||||
continue
|
||||
elif mime_type not in _MIME_RENDERERS:
|
||||
if mime_type not in _MIME_RENDERERS:
|
||||
not_available.append(mime_type)
|
||||
continue
|
||||
break
|
||||
@@ -149,9 +149,11 @@ def display(*values, target=None, append=True):
|
||||
if target is None:
|
||||
target = current_target()
|
||||
elif not isinstance(target, str):
|
||||
raise TypeError(f"target must be str or None, not {target.__class__.__name__}")
|
||||
msg = f"target must be str or None, not {target.__class__.__name__}"
|
||||
raise TypeError(msg)
|
||||
elif target == "":
|
||||
raise ValueError("Cannot have an empty target")
|
||||
msg = "Cannot have an empty target"
|
||||
raise ValueError(msg)
|
||||
elif target.startswith("#"):
|
||||
# note: here target is str and not None!
|
||||
# align with @when behavior
|
||||
@@ -161,9 +163,8 @@ def display(*values, target=None, append=True):
|
||||
|
||||
# If target cannot be found on the page, a ValueError is raised
|
||||
if element is None:
|
||||
raise ValueError(
|
||||
f"Invalid selector with id={target}. Cannot be found in the page."
|
||||
)
|
||||
msg = f"Invalid selector with id={target}. Cannot be found in the page."
|
||||
raise ValueError(msg)
|
||||
|
||||
# if element is a <script type="py">, it has a 'target' attribute which
|
||||
# points to the visual element holding the displayed values. In that case,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
from functools import wraps
|
||||
from pyscript.magic_js import document
|
||||
@@ -11,37 +10,87 @@ from pyscript import config
|
||||
|
||||
class Event:
|
||||
"""
|
||||
Represents something that may happen at some point in the future.
|
||||
Events represent something that may happen at some point in time (usually
|
||||
the future). They're used to coordinate code when the timing of an event is
|
||||
not known in advance (e.g. a button click or a network response).
|
||||
|
||||
An event is triggered with an arbitrary result. If no result is given, then
|
||||
None is assumed as the result.
|
||||
|
||||
Add listener functions to the event, to be called with the result when the
|
||||
event is triggered. The listener functions can be callable or awaitable. If
|
||||
the listener is added several times, it will be called only once.
|
||||
|
||||
If the event was triggered before a listener is added, the listener will be
|
||||
called as soon as it is added, with the result of the event.
|
||||
|
||||
If the event is never triggered, then its listeners will never be called.
|
||||
It's also possible to remove listeners from the event.
|
||||
|
||||
If the result of the event is not available, a ValueError will be raised
|
||||
when trying to access the result property. A RuntimeError will be raised if
|
||||
the event is triggered more than once.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# To contain the listeners to be called when the event is triggered.
|
||||
self._listeners = []
|
||||
# The result associated with the event.
|
||||
self._result = None
|
||||
# A flag to indicate if the event has been triggered.
|
||||
self._triggered = False
|
||||
|
||||
def trigger(self, result):
|
||||
@property
|
||||
def triggered(self):
|
||||
"""
|
||||
Trigger the event with a result to pass into the handlers.
|
||||
A boolean flag to indicate if the event has been triggered.
|
||||
"""
|
||||
return self._triggered
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
"""
|
||||
The result of the event.
|
||||
"""
|
||||
if self.triggered:
|
||||
return self._result
|
||||
msg = "Event has not been triggered yet. No result available."
|
||||
raise ValueError(msg)
|
||||
|
||||
def trigger(self, result=None):
|
||||
"""
|
||||
Trigger the event with an arbitrary result to pass into the listeners.
|
||||
An event may only be triggered once (otherwise a RuntimeError is
|
||||
raised).
|
||||
"""
|
||||
if self.triggered:
|
||||
msg = "Event has already been triggered."
|
||||
raise RuntimeError(msg)
|
||||
self._triggered = True
|
||||
self._result = result
|
||||
for listener in self._listeners:
|
||||
if is_awaitable(listener):
|
||||
# Use create task to avoid making this an async function.
|
||||
asyncio.create_task(listener(result))
|
||||
else:
|
||||
listener(result)
|
||||
self._call_listener(listener)
|
||||
|
||||
def add_listener(self, listener):
|
||||
"""
|
||||
Add a callable/awaitable to listen to when this event is triggered.
|
||||
Add a callable/awaitable that listens for the result, when this event
|
||||
is triggered.
|
||||
"""
|
||||
if is_awaitable(listener) or callable(listener):
|
||||
if listener not in self._listeners:
|
||||
self._listeners.append(listener)
|
||||
if self.triggered:
|
||||
# If the event was already triggered, call the listener
|
||||
# immediately with the result.
|
||||
self._call_listener(listener)
|
||||
else:
|
||||
raise ValueError("Listener must be callable or awaitable.")
|
||||
msg = "Listener must be callable or awaitable."
|
||||
raise ValueError(msg)
|
||||
|
||||
def remove_listener(self, *args):
|
||||
"""
|
||||
Clear the specified handler functions in *args. If no handlers
|
||||
provided, clear all handlers.
|
||||
Clear the specified listener functions in *args. If no listeners are
|
||||
provided, clear all the listeners.
|
||||
"""
|
||||
if args:
|
||||
for listener in args:
|
||||
@@ -49,6 +98,15 @@ class Event:
|
||||
else:
|
||||
self._listeners = []
|
||||
|
||||
def _call_listener(self, listener):
|
||||
"""
|
||||
Call the referenced listener with the event's result.
|
||||
"""
|
||||
if is_awaitable(listener):
|
||||
asyncio.create_task(listener(self._result))
|
||||
else:
|
||||
listener(self._result)
|
||||
|
||||
|
||||
def when(target, *args, **kwargs):
|
||||
"""
|
||||
@@ -76,7 +134,8 @@ def when(target, *args, **kwargs):
|
||||
# Extract the selector from the arguments or keyword arguments.
|
||||
selector = args[0] if args else kwargs.pop("selector")
|
||||
if not selector:
|
||||
raise ValueError("No selector provided.")
|
||||
msg = "No selector provided."
|
||||
raise ValueError(msg)
|
||||
# Grab the DOM elements to which the target event will be attached.
|
||||
from pyscript.web import Element, ElementCollection
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ def _object_keys(value):
|
||||
|
||||
|
||||
def _is_array(value):
|
||||
return isinstance(value, list) or isinstance(value, tuple)
|
||||
return isinstance(value, (list, tuple))
|
||||
|
||||
|
||||
def _is_object(value):
|
||||
@@ -60,10 +60,10 @@ def _loop(keys, input, known, output):
|
||||
|
||||
|
||||
def _ref(key, value, input, known, output):
|
||||
if _is_array(value) and not value in known:
|
||||
if _is_array(value) and value not in known:
|
||||
known.append(value)
|
||||
value = _loop(_array_keys(value), input, known, value)
|
||||
elif _is_object(value) and not value in known:
|
||||
elif _is_object(value) and value not in known:
|
||||
known.append(value)
|
||||
value = _loop(_object_keys(value), input, known, value)
|
||||
|
||||
|
||||
60
core/src/stdlib/pyscript/fs.py
Normal file
60
core/src/stdlib/pyscript/fs.py
Normal file
@@ -0,0 +1,60 @@
|
||||
mounted = {}
|
||||
|
||||
|
||||
async def mount(path, mode="readwrite", root="", id="pyscript"):
|
||||
import js
|
||||
from _pyscript import fs, interpreter
|
||||
from pyscript.ffi import to_js
|
||||
from pyscript.magic_js import (
|
||||
RUNNING_IN_WORKER,
|
||||
sync,
|
||||
)
|
||||
|
||||
js.console.warn("experimental pyscript.fs ⚠️")
|
||||
|
||||
handler = None
|
||||
|
||||
uid = f"{path}@{id}"
|
||||
|
||||
options = {"id": id, "mode": mode}
|
||||
if root != "":
|
||||
options["startIn"] = root
|
||||
|
||||
if RUNNING_IN_WORKER:
|
||||
fsh = sync.storeFSHandler(uid, to_js(options))
|
||||
|
||||
# allow both async and/or SharedArrayBuffer use case
|
||||
if isinstance(fsh, bool):
|
||||
success = fsh
|
||||
else:
|
||||
success = await fsh
|
||||
|
||||
if success:
|
||||
from polyscript import IDBMap
|
||||
|
||||
idb = IDBMap.new(fs.NAMESPACE)
|
||||
handler = await idb.get(uid)
|
||||
else:
|
||||
raise RuntimeError(fs.ERROR)
|
||||
|
||||
else:
|
||||
success = await fs.idb.has(uid)
|
||||
|
||||
if success:
|
||||
handler = await fs.idb.get(uid)
|
||||
else:
|
||||
handler = await fs.getFileSystemDirectoryHandle(to_js(options))
|
||||
await fs.idb.set(uid, handler)
|
||||
|
||||
mounted[path] = await interpreter.mountNativeFS(path, handler)
|
||||
|
||||
|
||||
async def sync(path):
|
||||
await mounted[path].syncfs()
|
||||
|
||||
|
||||
async def unmount(path):
|
||||
from _pyscript import interpreter
|
||||
|
||||
await sync(path)
|
||||
interpreter._module.FS.unmount(path)
|
||||
@@ -25,6 +25,7 @@ class JSModule:
|
||||
# avoid pyodide looking for non existent fields
|
||||
if not field.startswith("_"):
|
||||
return getattr(getattr(js_modules, self.name), field)
|
||||
return None
|
||||
|
||||
|
||||
# generate N modules in the system that will proxy the real value
|
||||
|
||||
@@ -31,26 +31,22 @@ class Device:
|
||||
|
||||
@classmethod
|
||||
async def load(cls, audio=False, video=True):
|
||||
"""Load the device stream."""
|
||||
options = window.Object.new()
|
||||
options.audio = audio
|
||||
"""
|
||||
Load the device stream.
|
||||
"""
|
||||
options = {}
|
||||
options["audio"] = audio
|
||||
if isinstance(video, bool):
|
||||
options.video = video
|
||||
options["video"] = video
|
||||
else:
|
||||
# TODO: Think this can be simplified but need to check it on the pyodide side
|
||||
|
||||
# TODO: this is pyodide specific. shouldn't be!
|
||||
options.video = window.Object.new()
|
||||
options["video"] = {}
|
||||
for k in video:
|
||||
setattr(options.video, k, to_js(video[k]))
|
||||
|
||||
stream = await window.navigator.mediaDevices.getUserMedia(options)
|
||||
return stream
|
||||
options["video"][k] = video[k]
|
||||
return await window.navigator.mediaDevices.getUserMedia(to_js(options))
|
||||
|
||||
async def get_stream(self):
|
||||
key = self.kind.replace("input", "").replace("output", "")
|
||||
options = {key: {"deviceId": {"exact": self.id}}}
|
||||
|
||||
return await self.load(**options)
|
||||
|
||||
|
||||
|
||||
@@ -10,10 +10,11 @@ def _to_idb(value):
|
||||
if isinstance(value, (bool, float, int, str, list, dict, tuple)):
|
||||
return _stringify(["generic", value])
|
||||
if isinstance(value, bytearray):
|
||||
return _stringify(["bytearray", [v for v in value]])
|
||||
return _stringify(["bytearray", list(value)])
|
||||
if isinstance(value, memoryview):
|
||||
return _stringify(["memoryview", [v for v in value]])
|
||||
raise TypeError(f"Unexpected value: {value}")
|
||||
return _stringify(["memoryview", list(value)])
|
||||
msg = f"Unexpected value: {value}"
|
||||
raise TypeError(msg)
|
||||
|
||||
|
||||
# convert an IndexedDB compatible entry into a Python value
|
||||
@@ -56,5 +57,6 @@ class Storage(dict):
|
||||
|
||||
async def storage(name="", storage_class=Storage):
|
||||
if not name:
|
||||
raise ValueError("The storage name must be defined")
|
||||
msg = "The storage name must be defined"
|
||||
raise ValueError(msg)
|
||||
return storage_class(await _storage(f"@pyscript/{name}"))
|
||||
|
||||
@@ -11,7 +11,7 @@ def as_bytearray(buffer):
|
||||
ui8a = js.Uint8Array.new(buffer)
|
||||
size = ui8a.length
|
||||
ba = bytearray(size)
|
||||
for i in range(0, size):
|
||||
for i in range(size):
|
||||
ba[i] = ui8a[i]
|
||||
return ba
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
# `when` is not used in this module. It is imported here save the user an additional
|
||||
# import (i.e. they can get what they need from `pyscript.web`).
|
||||
from pyscript import document, when, Event # NOQA
|
||||
|
||||
# from __future__ import annotations # CAUTION: This is not supported in MicroPython.
|
||||
|
||||
from pyscript import document, when, Event # noqa: F401
|
||||
from pyscript.ffi import create_proxy
|
||||
|
||||
|
||||
@@ -100,7 +103,7 @@ class Element:
|
||||
If `key` is an integer or a slice we use it to index/slice the element's
|
||||
children. Otherwise, we use `key` as a query selector.
|
||||
"""
|
||||
if isinstance(key, int) or isinstance(key, slice):
|
||||
if isinstance(key, (int, slice)):
|
||||
return self.children[key]
|
||||
|
||||
return self.find(key)
|
||||
@@ -120,7 +123,7 @@ class Element:
|
||||
# attribute `for` which is a Python keyword, so you can access it on the
|
||||
# Element instance via `for_`).
|
||||
if name.endswith("_"):
|
||||
name = name[:-1]
|
||||
name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
|
||||
return getattr(self._dom_element, name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
@@ -138,7 +141,7 @@ class Element:
|
||||
# attribute `for` which is a Python keyword, so you can access it on the
|
||||
# Element instance via `for_`).
|
||||
if name.endswith("_"):
|
||||
name = name[:-1]
|
||||
name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
|
||||
|
||||
if name.startswith("on_"):
|
||||
# Ensure on-events are cached in the _on_events dict if the
|
||||
@@ -152,10 +155,12 @@ class Element:
|
||||
Get an `Event` instance for the specified event name.
|
||||
"""
|
||||
if not name.startswith("on_"):
|
||||
raise ValueError("Event names must start with 'on_'.")
|
||||
msg = "Event names must start with 'on_'."
|
||||
raise ValueError(msg)
|
||||
event_name = name[3:] # Remove the "on_" prefix.
|
||||
if not hasattr(self._dom_element, event_name):
|
||||
raise ValueError(f"Element has no '{event_name}' event.")
|
||||
msg = f"Element has no '{event_name}' event."
|
||||
raise ValueError(msg)
|
||||
if name in self._on_events:
|
||||
return self._on_events[name]
|
||||
# Such an on-event exists in the DOM element, but we haven't yet
|
||||
@@ -203,7 +208,7 @@ class Element:
|
||||
# We check for list/tuple here and NOT for any iterable as it will match
|
||||
# a JS Nodelist which is handled explicitly below.
|
||||
# NodeList.
|
||||
elif isinstance(item, list) or isinstance(item, tuple):
|
||||
elif isinstance(item, (list, tuple)):
|
||||
for child in item:
|
||||
self.append(child)
|
||||
|
||||
@@ -227,10 +232,11 @@ class Element:
|
||||
|
||||
except AttributeError:
|
||||
# Nope! This is not an element or a NodeList.
|
||||
raise TypeError(
|
||||
msg = (
|
||||
f'Element "{item}" is a proxy object, "'
|
||||
f"but not a valid element or a NodeList."
|
||||
)
|
||||
raise TypeError(msg)
|
||||
|
||||
def clone(self, clone_id=None):
|
||||
"""Make a clone of the element (clones the underlying DOM object too)."""
|
||||
@@ -401,8 +407,7 @@ class Options:
|
||||
|
||||
new_option = option(**kwargs)
|
||||
|
||||
if before:
|
||||
if isinstance(before, Element):
|
||||
if before and isinstance(before, Element):
|
||||
before = before._dom_element
|
||||
|
||||
self._element._dom_element.add(new_option._dom_element, before)
|
||||
@@ -463,7 +468,7 @@ class ContainerElement(Element):
|
||||
)
|
||||
|
||||
for child in list(args) + (children or []):
|
||||
if isinstance(child, Element) or isinstance(child, ElementCollection):
|
||||
if isinstance(child, (Element, ElementCollection)):
|
||||
self.append(child)
|
||||
|
||||
else:
|
||||
@@ -493,14 +498,13 @@ class ClassesCollection:
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
for class_name in self._all_class_names():
|
||||
yield class_name
|
||||
yield from self._all_class_names()
|
||||
|
||||
def __len__(self):
|
||||
return len(self._all_class_names())
|
||||
|
||||
def __repr__(self):
|
||||
return f"ClassesCollection({repr(self._collection)})"
|
||||
return f"ClassesCollection({self._collection!r})"
|
||||
|
||||
def __str__(self):
|
||||
return " ".join(self._all_class_names())
|
||||
@@ -553,7 +557,7 @@ class StyleCollection:
|
||||
element.style[key] = value
|
||||
|
||||
def __repr__(self):
|
||||
return f"StyleCollection({repr(self._collection)})"
|
||||
return f"StyleCollection({self._collection!r})"
|
||||
|
||||
def remove(self, key):
|
||||
"""Remove a CSS property from the elements in the collection."""
|
||||
@@ -588,7 +592,7 @@ class ElementCollection:
|
||||
if isinstance(key, int):
|
||||
return self._elements[key]
|
||||
|
||||
elif isinstance(key, slice):
|
||||
if isinstance(key, slice):
|
||||
return ElementCollection(self._elements[key])
|
||||
|
||||
return self.find(key)
|
||||
@@ -1125,7 +1129,8 @@ class video(ContainerElement):
|
||||
|
||||
elif isinstance(to, Element):
|
||||
if to.tag != "canvas":
|
||||
raise TypeError("Element to snap to must be a canvas.")
|
||||
msg = "Element to snap to must be a canvas."
|
||||
raise TypeError(msg)
|
||||
|
||||
elif getattr(to, "tagName", "") == "CANVAS":
|
||||
to = canvas(dom_element=to)
|
||||
@@ -1134,10 +1139,12 @@ class video(ContainerElement):
|
||||
elif isinstance(to, str):
|
||||
nodelist = document.querySelectorAll(to) # NOQA
|
||||
if nodelist.length == 0:
|
||||
raise TypeError("No element with selector {to} to snap to.")
|
||||
msg = "No element with selector {to} to snap to."
|
||||
raise TypeError(msg)
|
||||
|
||||
if nodelist[0].tagName != "CANVAS":
|
||||
raise TypeError("Element to snap to must be a canvas.")
|
||||
msg = "Element to snap to must be a canvas."
|
||||
raise TypeError(msg)
|
||||
|
||||
to = canvas(dom_element=nodelist[0])
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class EventMessage:
|
||||
return value
|
||||
|
||||
|
||||
class WebSocket(object):
|
||||
class WebSocket:
|
||||
CONNECTING = 0
|
||||
OPEN = 1
|
||||
CLOSING = 2
|
||||
|
||||
@@ -25,10 +25,12 @@ async def create_named_worker(src="", name="", config=None, type="py"):
|
||||
from json import dumps
|
||||
|
||||
if not src:
|
||||
raise ValueError("Named workers require src")
|
||||
msg = "Named workers require src"
|
||||
raise ValueError(msg)
|
||||
|
||||
if not name:
|
||||
raise ValueError("Named workers require a name")
|
||||
msg = "Named workers require a name"
|
||||
raise ValueError(msg)
|
||||
|
||||
s = _js.document.createElement("script")
|
||||
s.type = type
|
||||
@@ -37,7 +39,7 @@ async def create_named_worker(src="", name="", config=None, type="py"):
|
||||
_set(s, "name", name)
|
||||
|
||||
if config:
|
||||
_set(s, "config", isinstance(config, str) and config or dumps(config))
|
||||
_set(s, "config", (isinstance(config, str) and config) or dumps(config))
|
||||
|
||||
_js.document.body.append(s)
|
||||
return await workers[name]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { idb, getFileSystemDirectoryHandle } from "./fs.js";
|
||||
|
||||
export default {
|
||||
// allow pyterminal checks to bootstrap
|
||||
is_pyterminal: () => false,
|
||||
@@ -9,4 +11,21 @@ export default {
|
||||
sleep(seconds) {
|
||||
return new Promise(($) => setTimeout($, seconds * 1000));
|
||||
},
|
||||
|
||||
/**
|
||||
* Ask a user action via dialog and returns the directory handler once granted.
|
||||
* @param {string} uid
|
||||
* @param {{id?:string, mode?:"read"|"readwrite", hint?:"desktop"|"documents"|"downloads"|"music"|"pictures"|"videos"}} options
|
||||
* @returns {boolean}
|
||||
*/
|
||||
async storeFSHandler(uid, options = {}) {
|
||||
if (await idb.has(uid)) return true;
|
||||
return getFileSystemDirectoryHandle(options).then(
|
||||
async (handler) => {
|
||||
await idb.set(uid, handler);
|
||||
return true;
|
||||
},
|
||||
() => false,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
39
core/tests/javascript/media.html
Normal file
39
core/tests/javascript/media.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Pyodide Media Module Test</title>
|
||||
<link rel="stylesheet" href="../../dist/core.css">
|
||||
<script type="module" src="../../dist/core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Pyodide Media Module Test</h1>
|
||||
<div id="test-results">Running tests...</div>
|
||||
|
||||
<script type="py" terminal>
|
||||
from pyscript import window, document
|
||||
from pyscript import media
|
||||
|
||||
async def run_tests():
|
||||
# Test basic module structure
|
||||
assert hasattr(media, "Device"), "media module should have Device class"
|
||||
assert hasattr(media, "list_devices"), "media module should have list_devices function"
|
||||
|
||||
# Test device enumeration
|
||||
devices = await media.list_devices()
|
||||
assert isinstance(devices, list), "list_devices should return a list"
|
||||
|
||||
# If we have devices, test properties of one
|
||||
if devices:
|
||||
device = devices[0]
|
||||
assert hasattr(device, "id"), "Device should have id property"
|
||||
assert hasattr(device, "group"), "Device should have group property"
|
||||
assert hasattr(device, "kind"), "Device should have kind property"
|
||||
assert hasattr(device, "label"), "Device should have label property"
|
||||
|
||||
document.getElementById('test-results').innerText = "Success!"
|
||||
document.documentElement.classList.add('media-ok')
|
||||
|
||||
await run_tests()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,6 @@
|
||||
import numpy
|
||||
import matplotlib
|
||||
import numpy as np
|
||||
import matplotlib as mpl
|
||||
|
||||
# just do something with the packages
|
||||
print(len(dir(numpy)))
|
||||
print(len(dir(matplotlib)))
|
||||
print(len(dir(np)))
|
||||
print(len(dir(mpl)))
|
||||
|
||||
@@ -4,4 +4,4 @@ def runtime_version():
|
||||
return sys.version
|
||||
|
||||
|
||||
__export__ = ['runtime_version']
|
||||
__export__ = ["runtime_version"]
|
||||
|
||||
@@ -171,3 +171,24 @@ test('MicroPython buffered NO error', async ({ page }) => {
|
||||
const body = await page.evaluate(() => document.body.textContent.trim());
|
||||
await expect(body).toBe('');
|
||||
});
|
||||
|
||||
test('Pyodide media module', async ({ page }) => {
|
||||
await page.context().grantPermissions(['camera', 'microphone']);
|
||||
await page.context().addInitScript(() => {
|
||||
const originalEnumerateDevices = navigator.mediaDevices.enumerateDevices;
|
||||
navigator.mediaDevices.enumerateDevices = async function() {
|
||||
const realDevices = await originalEnumerateDevices.call(this);
|
||||
if (!realDevices || realDevices.length === 0) {
|
||||
return [
|
||||
{ deviceId: 'camera1', groupId: 'group1', kind: 'videoinput', label: 'Simulated Camera' },
|
||||
{ deviceId: 'mic1', groupId: 'group2', kind: 'audioinput', label: 'Simulated Microphone' }
|
||||
];
|
||||
}
|
||||
return realDevices;
|
||||
};
|
||||
});
|
||||
await page.goto('http://localhost:8080/tests/javascript/media.html');
|
||||
await page.waitForSelector('html.media-ok', { timeout: 10000 });
|
||||
const isSuccess = await page.evaluate(() => document.documentElement.classList.contains('media-ok'));
|
||||
expect(isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
12
core/tests/manual/fs/index.html
Normal file
12
core/tests/manual/fs/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<link rel="stylesheet" href="../../../dist/core.css">
|
||||
<script type="module" src="../../../dist/core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy" src="index.py"></script>
|
||||
</body>
|
||||
</html>
|
||||
46
core/tests/manual/fs/index.py
Normal file
46
core/tests/manual/fs/index.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import os
|
||||
from pyscript import RUNNING_IN_WORKER, fs
|
||||
|
||||
|
||||
TEST = "implicit"
|
||||
|
||||
if TEST == "implicit":
|
||||
await fs.mount("/persistent")
|
||||
|
||||
print(
|
||||
(RUNNING_IN_WORKER and "Worker") or "Main",
|
||||
os.listdir("/persistent"),
|
||||
)
|
||||
|
||||
from random import random
|
||||
|
||||
with open("/persistent/random.txt", "w") as f:
|
||||
f.write(str(random()))
|
||||
|
||||
await fs.sync("/persistent")
|
||||
|
||||
elif not RUNNING_IN_WORKER:
|
||||
from pyscript import document
|
||||
|
||||
button = document.createElement("button")
|
||||
button.textContent = "mount"
|
||||
document.body.append(button)
|
||||
|
||||
async def mount(event):
|
||||
try:
|
||||
await fs.mount("/persistent")
|
||||
print(os.listdir("/persistent"))
|
||||
button.textContent = "unmount"
|
||||
button.onclick = unmount
|
||||
|
||||
except:
|
||||
import js
|
||||
|
||||
js.alert("unable to grant access")
|
||||
|
||||
async def unmount(event):
|
||||
await fs.unmount("/persistent")
|
||||
button.textContent = "mount"
|
||||
button.onclick = mount
|
||||
|
||||
button.onclick = mount
|
||||
@@ -35,7 +35,8 @@ import pygame
|
||||
|
||||
# see if we can load more than standard BMP
|
||||
if not pygame.image.get_extended():
|
||||
raise SystemExit("Sorry, extended image module required")
|
||||
msg = "Sorry, extended image module required"
|
||||
raise SystemExit(msg)
|
||||
|
||||
|
||||
# game constants
|
||||
@@ -56,7 +57,8 @@ def load_image(file):
|
||||
try:
|
||||
surface = pygame.image.load(file)
|
||||
except pygame.error:
|
||||
raise SystemExit(f'Could not load image "{file}" {pygame.get_error()}')
|
||||
msg = f'Could not load image "{file}" {pygame.get_error()}'
|
||||
raise SystemExit(msg)
|
||||
return surface.convert()
|
||||
|
||||
|
||||
@@ -66,8 +68,7 @@ def load_sound(file):
|
||||
return None
|
||||
file = os.path.join(main_dir, "data", file)
|
||||
try:
|
||||
sound = pygame.mixer.Sound(file)
|
||||
return sound
|
||||
return pygame.mixer.Sound(file)
|
||||
except pygame.error:
|
||||
print(f"Warning, unable to load, {file}")
|
||||
return None
|
||||
@@ -227,7 +228,7 @@ class Score(pygame.sprite.Sprite):
|
||||
|
||||
def update(self):
|
||||
"""We only update the score in update() when it has changed."""
|
||||
if SCORE != self.lastscore:
|
||||
if self.lastscore != SCORE:
|
||||
self.lastscore = SCORE
|
||||
msg = "Score: %d" % SCORE
|
||||
self.image = self.font.render(msg, 0, self.color)
|
||||
@@ -296,7 +297,7 @@ async def main(winstyle=0):
|
||||
# Create Some Starting Values
|
||||
global score
|
||||
alienreload = ALIEN_RELOAD
|
||||
clock = pygame.Clock()
|
||||
_clock = pygame.Clock()
|
||||
|
||||
# initialize our starting sprites
|
||||
global SCORE
|
||||
@@ -313,8 +314,7 @@ async def main(winstyle=0):
|
||||
return
|
||||
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
|
||||
return
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_f:
|
||||
if event.type == pygame.KEYDOWN and event.key == pygame.K_f:
|
||||
if not fullscreen:
|
||||
print("Changing to FULLSCREEN")
|
||||
screen_backup = screen.copy()
|
||||
@@ -371,7 +371,7 @@ async def main(winstyle=0):
|
||||
player.kill()
|
||||
|
||||
# See if shots hit the aliens.
|
||||
for alien in pygame.sprite.groupcollide(aliens, shots, 1, 1).keys():
|
||||
for alien in pygame.sprite.groupcollide(aliens, shots, 1, 1):
|
||||
if pygame.mixer:
|
||||
boom_sound.play()
|
||||
Explosion(alien)
|
||||
|
||||
0
core/tests/manual/game/config.toml
Normal file
0
core/tests/manual/game/config.toml
Normal file
@@ -8,7 +8,7 @@
|
||||
<script type="module" src="../../../dist/core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py-game" src="aliens.py"></script>
|
||||
<script type="py-game" src="aliens.py" config="./config.toml"></script>
|
||||
<div class="demo">
|
||||
<div class="demo-header">pygame.examples.aliens</div>
|
||||
<div class="demo-content">
|
||||
|
||||
BIN
core/tests/manual/issue-2302/assets/genuary25-18.m4a
Normal file
BIN
core/tests/manual/issue-2302/assets/genuary25-18.m4a
Normal file
Binary file not shown.
20
core/tests/manual/issue-2302/glue/multipyjs.py
Normal file
20
core/tests/manual/issue-2302/glue/multipyjs.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from pyscript import config
|
||||
|
||||
MICROPYTHON = config["type"] == "mpy"
|
||||
|
||||
if MICROPYTHON:
|
||||
def new(obj, *args, **kwargs):
|
||||
return obj.new(*args, kwargs) if kwargs else obj.new(*args)
|
||||
def call(obj, *args, **kwargs):
|
||||
return obj(*args, kwargs) if kwargs else obj(*args)
|
||||
else:
|
||||
def new(obj, *args, **kwargs):
|
||||
return obj.new(*args, **kwargs)
|
||||
def call(obj, *args, **kwargs):
|
||||
return obj(*args, **kwargs)
|
||||
|
||||
if not MICROPYTHON:
|
||||
import pyodide_js
|
||||
pyodide_js.setDebug(True)
|
||||
|
||||
from pyscript.ffi import to_js, create_proxy
|
||||
Binary file not shown.
69
core/tests/manual/issue-2302/index.html
Normal file
69
core/tests/manual/issue-2302/index.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Genuary</title>
|
||||
|
||||
<!-- Recommended meta tags -->
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
|
||||
<!-- PyScript CSS -->
|
||||
<link rel="stylesheet" href="../../../dist/core.css">
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background-color: #4a315e;
|
||||
color: white;
|
||||
font-family: Inconsolata, Consolas, Monaco, Courier New;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
background-color: #eee;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50%;
|
||||
}
|
||||
|
||||
.gutter.gutter-vertical {
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=');
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
py-terminal {
|
||||
max-height: 7em;
|
||||
max-width: calc(100vw - 90px);
|
||||
}
|
||||
|
||||
#pyterm {
|
||||
background-color: #191a1a;
|
||||
}
|
||||
|
||||
#pyterm,
|
||||
#threejs {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- This script tag bootstraps PyScript -->
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://cdn.jsdelivr.net/npm/three@v0.173.0/build/three.module.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="../../../dist/core.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/split.js/1.6.5/split.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="stats"></div>
|
||||
<div id="stats-off"></div>
|
||||
<div class="split">
|
||||
<div id="pyterm"></div>
|
||||
<div id="threejs"></div>
|
||||
</div>
|
||||
<script type="py" src="./main.py" config="./pyscript.toml" async terminal></script>
|
||||
</body>
|
||||
</html>
|
||||
83
core/tests/manual/issue-2302/libfft.py
Normal file
83
core/tests/manual/issue-2302/libfft.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from dataclasses import dataclass, field
|
||||
import sys
|
||||
|
||||
@dataclass
|
||||
class BeatSync:
|
||||
fft_res: int = field()
|
||||
|
||||
on_beat: bool = False
|
||||
beat: int = -1
|
||||
since_last_beat: float = sys.maxsize
|
||||
|
||||
_prev: int = 0
|
||||
_count: int = 0
|
||||
_bins: list[int] = field(default_factory=list)
|
||||
_last_detection: float = -1.0
|
||||
_threshold: int = 50
|
||||
_diff: int = 40
|
||||
_cooldown: float = 0.2
|
||||
|
||||
_highest: int = 0
|
||||
|
||||
def __post_init__(self):
|
||||
self._bins = [int(13/16*self.fft_res/2)+17, int(13/16*self.fft_res/2)+18]
|
||||
|
||||
def reset(self):
|
||||
self.beat = -1
|
||||
self._prev = 0
|
||||
self._count = 0
|
||||
self._last_detection = -1.0
|
||||
self.since_last_beat = sys.maxsize
|
||||
# print('bs reset')
|
||||
|
||||
def update(self, data, running_time):
|
||||
self._count += 1
|
||||
self.since_last_beat = running_time - self._last_detection
|
||||
d = sum(data[bin] for bin in self._bins)
|
||||
if d < self._threshold:
|
||||
self.on_beat = False
|
||||
elif d - self._prev < self._diff:
|
||||
self.on_beat = False
|
||||
elif self.since_last_beat < self._cooldown:
|
||||
self.on_beat = False
|
||||
else:
|
||||
self._last_detection = running_time
|
||||
self.since_last_beat = 0
|
||||
self.on_beat = True
|
||||
self.beat += 1
|
||||
self._prev = d
|
||||
|
||||
@dataclass
|
||||
class FreqIntensity:
|
||||
freq: float = field()
|
||||
fft_res: int = field()
|
||||
|
||||
intensity: float = 0.0
|
||||
intensity_slew: float = 0.0
|
||||
scale_min: float = 0.0
|
||||
scale_max: float = 350
|
||||
max: float = 0.0
|
||||
_sample_rate: int = 48000
|
||||
_bin_indexes: list[int] = field(default_factory=list)
|
||||
_harmonics: int = 8
|
||||
_slew_factor: float = 0.8
|
||||
|
||||
def __post_init__(self):
|
||||
self._bin_indexes = [
|
||||
round((harmonic+1) * self.freq / self._sample_rate * self.fft_res / 2)
|
||||
for harmonic in range(self._harmonics)
|
||||
]
|
||||
print(self._bin_indexes)
|
||||
|
||||
def update(self, data):
|
||||
intensity = 0.0
|
||||
for bin in range(self._harmonics):
|
||||
intensity += data[self._bin_indexes[bin]]/(bin+1)
|
||||
self.intensity = intensity
|
||||
self.intensity_slew = self._slew_factor * self.intensity_slew + (1 - self._slew_factor) * intensity
|
||||
self.max = max(intensity, self.max)
|
||||
|
||||
@property
|
||||
def intensity_scaled(self):
|
||||
raw = max(0, min(1.0, (self.intensity_slew - self.scale_min)/(self.scale_max - self.scale_min)))
|
||||
return raw * raw
|
||||
189
core/tests/manual/issue-2302/libthree.py
Normal file
189
core/tests/manual/issue-2302/libthree.py
Normal file
@@ -0,0 +1,189 @@
|
||||
import asyncio
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Callable
|
||||
|
||||
from pyscript import document, window
|
||||
|
||||
from pyscript.js_modules import three as THREE
|
||||
from pyscript.js_modules.stats_gl import default as StatsGL
|
||||
from pyscript.js_modules import lsgeo, line2, linemat
|
||||
|
||||
from multipyjs import MICROPYTHON, new, call, to_js, create_proxy
|
||||
|
||||
@dataclass
|
||||
class SoundPlayer:
|
||||
sound: THREE.Audio = field()
|
||||
on_start: Callable[[], None] = field()
|
||||
on_stop: Callable[[], None] = field(default=lambda: None)
|
||||
|
||||
_start_time: float = -1.0
|
||||
|
||||
def play(self):
|
||||
self.sound.stop()
|
||||
self.on_start()
|
||||
self._start_time = self.sound.context.currentTime
|
||||
self.sound.play()
|
||||
|
||||
def stop(self):
|
||||
self.sound.stop()
|
||||
self.on_stop()
|
||||
self._start_time = -1.0
|
||||
|
||||
def toggle(self):
|
||||
if self.sound.isPlaying:
|
||||
self.stop()
|
||||
else:
|
||||
self.play()
|
||||
|
||||
@property
|
||||
def running_time(self):
|
||||
if self.sound.isPlaying:
|
||||
return self.sound.context.currentTime - self._start_time
|
||||
elif self._start_time != -1.0:
|
||||
self.stop()
|
||||
return 0.0
|
||||
|
||||
def get_renderer():
|
||||
renderer = new(THREE.WebGLRenderer, antialias=True)
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
renderer.setPixelRatio(window.devicePixelRatio)
|
||||
renderer.setClearColor(0xF5F0DC)
|
||||
pyterms = list(document.getElementsByTagName("py-terminal"))
|
||||
if pyterms:
|
||||
pyterm = pyterms[0]
|
||||
pyterm.parentNode.removeChild(pyterm)
|
||||
document.getElementById("pyterm").appendChild(pyterm)
|
||||
|
||||
document.getElementById("threejs").appendChild(renderer.domElement)
|
||||
|
||||
initial = {0: "115px", 1: "calc(100vh - 120px)"}
|
||||
@create_proxy
|
||||
def split_element_style(dimension, size, gutter_size, index):
|
||||
if index in initial:
|
||||
result = {dimension: initial.pop(index)}
|
||||
else:
|
||||
result = {dimension: f"calc({int(size)}vh - {gutter_size}px)"}
|
||||
return to_js(result)
|
||||
|
||||
call(
|
||||
window.Split,
|
||||
["#pyterm", "#threejs"],
|
||||
direction="vertical",
|
||||
elementStyle=split_element_style,
|
||||
minSize=0,
|
||||
maxSize=to_js([120, 10000]),
|
||||
)
|
||||
return renderer
|
||||
|
||||
def get_ortho_camera(view_size):
|
||||
aspect_ratio = window.innerWidth / window.innerHeight
|
||||
camera = new(
|
||||
THREE.OrthographicCamera,
|
||||
-view_size * aspect_ratio, # Left
|
||||
view_size * aspect_ratio, # Right
|
||||
view_size, # Top
|
||||
-view_size, # Bottom
|
||||
-view_size, # Near plane
|
||||
view_size, # Far plane
|
||||
)
|
||||
camera.updateProjectionMatrix()
|
||||
camera.position.set(0, 0, 0)
|
||||
return camera
|
||||
|
||||
def get_loading_manager():
|
||||
loading_mgr = new(THREE.LoadingManager)
|
||||
ev = asyncio.Event()
|
||||
|
||||
@create_proxy
|
||||
def on_start(url, itemsLoaded, itemsTotal):
|
||||
print(f'[{itemsLoaded}/{itemsTotal}] Started loading file: {url}')
|
||||
loading_mgr.onStart = on_start
|
||||
|
||||
@create_proxy
|
||||
def on_progress(url, itemsLoaded, itemsTotal):
|
||||
print(f'[{itemsLoaded}/{itemsTotal}] Loading file: {url}')
|
||||
loading_mgr.onProgress = on_progress
|
||||
|
||||
@create_proxy
|
||||
def on_error(url):
|
||||
print(f'There was a problem loading {url}')
|
||||
loading_mgr.onError = on_error
|
||||
|
||||
@create_proxy
|
||||
def on_load():
|
||||
print('Loading assets complete!')
|
||||
ev.set()
|
||||
loading_mgr.onLoad = on_load
|
||||
|
||||
return loading_mgr, ev
|
||||
|
||||
|
||||
def get_perspective_camera():
|
||||
aspect_ratio = window.innerWidth / window.innerHeight
|
||||
camera = new(
|
||||
THREE.PerspectiveCamera,
|
||||
45, # fov
|
||||
aspect_ratio,
|
||||
0.25, # near plane
|
||||
300, # far plane
|
||||
)
|
||||
camera.position.set(0, 0, 30)
|
||||
return camera
|
||||
|
||||
def get_stats_gl(renderer):
|
||||
stats = new(StatsGL, trackGPU=True, horizontal=False)
|
||||
stats.init(renderer)
|
||||
stats.dom.style.removeProperty("left")
|
||||
stats.dom.style.right = "90px"
|
||||
document.getElementById("stats").appendChild(stats.dom)
|
||||
return stats
|
||||
|
||||
def bg_from_v(*vertices):
|
||||
geometry = new(THREE.BufferGeometry)
|
||||
vertices_f32a = new(Float32Array, vertices)
|
||||
attr = new(THREE.Float32BufferAttribute, vertices_f32a, 3)
|
||||
return geometry.setAttribute('position', attr)
|
||||
|
||||
def bg_from_p(*points):
|
||||
buf = new(THREE.BufferGeometry)
|
||||
buf.setFromPoints(
|
||||
[new(THREE.Vector3, p[0], p[1], p[2]) for p in points]
|
||||
)
|
||||
return buf
|
||||
|
||||
def clear():
|
||||
# toggle stats and terminal?
|
||||
stats_style = document.getElementById("stats-off").style
|
||||
if stats_style.display == "none":
|
||||
# turn stuff back on
|
||||
stats_style.removeProperty("display")
|
||||
document.getElementById("pyterm").style.height = "115px"
|
||||
document.getElementById("threejs").style.height = "calc(100vh - 120px)"
|
||||
for e in document.getElementsByClassName("gutter"):
|
||||
e.style.removeProperty("display")
|
||||
for e in document.getElementsByClassName("xterm-helper-textarea"):
|
||||
e.focus()
|
||||
break
|
||||
return
|
||||
|
||||
# no longer focus on xterm
|
||||
document.activeElement.blur()
|
||||
# hide stats
|
||||
document.getElementById("stats-off").style.display = "none"
|
||||
# hide pyterm and split gutter
|
||||
document.getElementById("pyterm").style.height = "0vh"
|
||||
document.getElementById("threejs").style.height = "100vh"
|
||||
for e in document.getElementsByClassName("gutter"):
|
||||
e.style.display = "none"
|
||||
# hide ltk ad
|
||||
for e in document.getElementsByClassName("ltk-built-with"):
|
||||
e.style.display = "none"
|
||||
# hide pyscript ad
|
||||
for e in document.getElementsByTagName("div"):
|
||||
style = e.getAttribute("style")
|
||||
if style and style.startswith("z-index:999"):
|
||||
e.style.display = "none"
|
||||
for e in document.getElementsByTagName("svg"):
|
||||
style = e.getAttribute("style")
|
||||
if style and style.startswith("z-index:999"):
|
||||
e.style.display = "none"
|
||||
285
core/tests/manual/issue-2302/main.py
Normal file
285
core/tests/manual/issue-2302/main.py
Normal file
@@ -0,0 +1,285 @@
|
||||
print("Starting up...")
|
||||
|
||||
from array import array
|
||||
import asyncio
|
||||
import math
|
||||
import time
|
||||
|
||||
from pyscript import document, window, PyWorker
|
||||
|
||||
from libthree import THREE, clear, SoundPlayer
|
||||
from libthree import get_renderer, get_ortho_camera
|
||||
from libthree import get_loading_manager, get_stats_gl
|
||||
from libthree import lsgeo, line2, linemat, lsgeo
|
||||
from libfft import BeatSync
|
||||
|
||||
from multipyjs import MICROPYTHON, new, call, to_js, create_proxy
|
||||
|
||||
from js import Float32Array
|
||||
|
||||
scene = new(THREE.Scene)
|
||||
|
||||
view_size = 1
|
||||
renderer = get_renderer()
|
||||
camera = get_ortho_camera(view_size)
|
||||
loading_mgr, loaded_event = get_loading_manager()
|
||||
|
||||
t_loader = new(THREE.TextureLoader, loading_mgr)
|
||||
t_loader.setPath('assets/')
|
||||
|
||||
light = new(THREE.AmbientLight, 0xffffff, 1.0)
|
||||
scene.add(light)
|
||||
|
||||
fft_res = 2048
|
||||
audio_listener = new(THREE.AudioListener)
|
||||
camera.add(audio_listener)
|
||||
sound = new(THREE.Audio, audio_listener)
|
||||
audio_loader = new(THREE.AudioLoader, loading_mgr)
|
||||
analyser = new(THREE.AudioAnalyser, sound, fft_res)
|
||||
|
||||
@create_proxy
|
||||
def on_audio_load(buffer):
|
||||
sound.setBuffer(buffer)
|
||||
sound.setVolume(0.9)
|
||||
sound.setLoop(False)
|
||||
|
||||
audio_loader.load("assets/genuary25-18.m4a", on_audio_load)
|
||||
|
||||
spheres = new(THREE.Group)
|
||||
scene.add(spheres)
|
||||
|
||||
line_basic_mat = new(
|
||||
THREE.LineBasicMaterial,
|
||||
color=0xffffff,
|
||||
)
|
||||
|
||||
zero_mat = new(
|
||||
linemat.LineMaterial,
|
||||
color=0x662503,
|
||||
linewidth=3,
|
||||
)
|
||||
|
||||
other_mat = new(
|
||||
linemat.LineMaterial,
|
||||
color=0x662503,
|
||||
linewidth=1.5,
|
||||
)
|
||||
|
||||
grid_mat = new(
|
||||
linemat.LineMaterial,
|
||||
color=0x662503,
|
||||
linewidth=1,
|
||||
dashed=True,
|
||||
dashScale=1,
|
||||
dashSize=0.5,
|
||||
gapSize=1,
|
||||
dashOffset=0,
|
||||
)
|
||||
|
||||
lines = [new(THREE.Group), new(THREE.Group)]
|
||||
scene.add(lines[0])
|
||||
scene.add(lines[1])
|
||||
|
||||
def draw_lines(line_coords, mat_name, spy=False):
|
||||
if spy:
|
||||
line_coords_f32a = new(Float32Array, line_coords.length)
|
||||
_it = line_coords.items
|
||||
for i in range(line_coords.length):
|
||||
line_coords_f32a[i] = _it[i]
|
||||
else:
|
||||
line_coords_f32a = new(Float32Array, line_coords)
|
||||
if mat_name == 'zero':
|
||||
mat = zero_mat
|
||||
elif mat_name == 'grid':
|
||||
mat = grid_mat
|
||||
else:
|
||||
mat = other_mat
|
||||
|
||||
geo = new(THREE.BufferGeometry)
|
||||
geo.setAttribute('position', new(THREE.BufferAttribute, line_coords_f32a, 3))
|
||||
seg = new(THREE.LineSegments, geo, line_basic_mat)
|
||||
|
||||
lsg = new(lsgeo.LineSegmentsGeometry)
|
||||
lsg.fromLineSegments(seg)
|
||||
l1 = new(line2.Line2, lsg, mat)
|
||||
l1.computeLineDistances()
|
||||
l2 = new(line2.Line2, lsg, mat)
|
||||
l2.computeLineDistances()
|
||||
lines[0].add(l1)
|
||||
lines[1].add(l2)
|
||||
|
||||
seg.geometry.dispose()
|
||||
del geo
|
||||
del seg
|
||||
|
||||
def drawing_done():
|
||||
maybe_with_spy = "with SPy" if USE_SPY else "with pure Python"
|
||||
print(f"Time elapsed computing {maybe_with_spy}:", time.time() - start_ts)
|
||||
drawing_event.set()
|
||||
|
||||
grid_width = 0
|
||||
grid_height = 0
|
||||
scroll_offset = 0
|
||||
def scale_lines(grid_ws=None, grid_hs=None, offset=None):
|
||||
global grid_width, grid_height, scroll_offset
|
||||
|
||||
if grid_ws:
|
||||
grid_width = grid_ws
|
||||
else:
|
||||
grid_ws = grid_width
|
||||
|
||||
if grid_hs:
|
||||
grid_height = grid_hs
|
||||
else:
|
||||
grid_hs = grid_height
|
||||
|
||||
if offset:
|
||||
scroll_offset = offset
|
||||
else:
|
||||
offset = scroll_offset
|
||||
|
||||
scale = 2.04/grid_hs
|
||||
lines[0].scale.set(scale, scale, scale)
|
||||
lines[1].scale.set(scale, scale, scale)
|
||||
lines[0].position.set((offset - grid_ws/2) * scale, -grid_hs/2 * scale, 0)
|
||||
lines[1].position.set((offset + grid_ws/2) * scale, -grid_hs/2 * scale, 0)
|
||||
|
||||
def append_p(lines, p1, p2):
|
||||
lines.append(p1[0])
|
||||
lines.append(p1[1])
|
||||
lines.append(0)
|
||||
lines.append(p2[0])
|
||||
lines.append(p2[1])
|
||||
lines.append(0)
|
||||
|
||||
def initial_calc():
|
||||
grid_w = int(1920 * 4)
|
||||
grid_h = 1080 * 2
|
||||
grid_scale = 10
|
||||
noise_factor = 500
|
||||
grid_hs = int(grid_h/grid_scale)
|
||||
grid_ws = int(grid_w/grid_scale)
|
||||
crossfade_range = int(grid_ws/12.5)
|
||||
|
||||
def grid_lines():
|
||||
lines = array("d")
|
||||
grid_goal = 24
|
||||
grid_size_i = int(round((grid_ws - crossfade_range) / grid_goal))
|
||||
grid_actual = (grid_ws - crossfade_range) / grid_size_i
|
||||
for i in range(0, grid_size_i):
|
||||
x = i * grid_actual
|
||||
append_p(lines, (x, 0), (x, grid_hs))
|
||||
for y in range(0, grid_hs, grid_goal):
|
||||
append_p(lines, (0, y), (grid_ws-crossfade_range, y))
|
||||
return lines
|
||||
|
||||
import perlin
|
||||
spy_perlin = perlin.lib
|
||||
spy_perlin.init()
|
||||
spy_perlin.seed(44)
|
||||
scale_lines(grid_ws - crossfade_range, grid_hs)
|
||||
print("Computing the height map")
|
||||
spy_perlin.make_height_map(grid_ws, grid_hs)
|
||||
spy_perlin.update_height_map(grid_ws, grid_hs, grid_scale / noise_factor, 0)
|
||||
print("Cross-fading the height map")
|
||||
spy_perlin.crossfade_height_map(grid_ws, grid_hs, crossfade_range)
|
||||
print("Drawing grid")
|
||||
draw_lines(grid_lines(), 'grid')
|
||||
print("Marching squares")
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, 0), 'zero', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, 0.3), 'positive', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, -0.3), 'negative', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, 0.45), 'positive', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, -0.45), 'negative', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, 0.6), 'positive', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, -0.6), 'negative', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, -0.8), 'negative', spy=True)
|
||||
draw_lines(spy_perlin.marching_squares(grid_ws, grid_hs, 0.8), 'positive', spy=True)
|
||||
drawing_done()
|
||||
|
||||
drawing_event = asyncio.Event()
|
||||
start_ts = time.time()
|
||||
|
||||
USE_SPY = True
|
||||
if USE_SPY:
|
||||
initial_calc()
|
||||
else:
|
||||
worker = PyWorker("./worker.py", type="pyodide", configURL="./pyscript.toml")
|
||||
worker.sync.draw_lines = draw_lines
|
||||
worker.sync.drawing_done = drawing_done
|
||||
worker.sync.scale_lines = scale_lines
|
||||
worker.sync.print = print
|
||||
|
||||
@create_proxy
|
||||
def on_tap(event):
|
||||
clear()
|
||||
player.toggle()
|
||||
document.addEventListener("click", on_tap)
|
||||
|
||||
@create_proxy
|
||||
def on_key_down(event):
|
||||
element = document.activeElement
|
||||
_class = element.getAttribute("class")
|
||||
in_xterm = element.tagName != "BODY" and _class and "xterm" in _class
|
||||
|
||||
if event.code == "Backquote":
|
||||
# Screenshot mode.
|
||||
clear()
|
||||
elif not in_xterm:
|
||||
# Don't react to those bindings when typing code.
|
||||
if event.code == "Space":
|
||||
player.toggle()
|
||||
document.addEventListener("keydown", on_key_down)
|
||||
|
||||
@create_proxy
|
||||
def on_window_resize(event):
|
||||
aspect_ratio = window.innerWidth / window.innerHeight
|
||||
if camera.type == "OrthographicCamera":
|
||||
camera.left = -view_size * aspect_ratio
|
||||
camera.right = view_size * aspect_ratio
|
||||
camera.top = view_size
|
||||
camera.bottom = -view_size
|
||||
camera.updateProjectionMatrix()
|
||||
elif camera.type == "PerspectiveCamera":
|
||||
camera.aspect = window.innerWidth / window.innerHeight
|
||||
camera.updateProjectionMatrix()
|
||||
else:
|
||||
raise ValueError("Unknown camera type")
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
scale_lines()
|
||||
|
||||
window.addEventListener("resize", on_window_resize)
|
||||
|
||||
@create_proxy
|
||||
def animate(now=0.0):
|
||||
data = analyser.getFrequencyData()#.to_py() in Pyodide
|
||||
audio_now = player.running_time
|
||||
bs.update(data, audio_now)
|
||||
|
||||
if grid_width:
|
||||
offset = -((20 * audio_now) % grid_width)
|
||||
scale_lines(offset=offset)
|
||||
|
||||
renderer.render(scene, camera)
|
||||
stats_gl.update()
|
||||
|
||||
def reset():
|
||||
global scroll_offset
|
||||
bs.reset()
|
||||
scale_lines()
|
||||
|
||||
def on_stop():
|
||||
global scroll_offset
|
||||
bs.reset()
|
||||
scale_lines()
|
||||
|
||||
await loaded_event.wait()
|
||||
|
||||
stats_gl = get_stats_gl(renderer)
|
||||
player = SoundPlayer(sound=sound, on_start=reset, on_stop=on_stop)
|
||||
bs = BeatSync(fft_res=fft_res)
|
||||
renderer.setAnimationLoop(animate)
|
||||
print("Waiting for the contours...")
|
||||
|
||||
await drawing_event.wait()
|
||||
print("Tap the map to start...")
|
||||
110
core/tests/manual/issue-2302/perlin_py.py
Normal file
110
core/tests/manual/issue-2302/perlin_py.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# Translated from https://github.com/josephg/noisejs.
|
||||
from libthree import THREE
|
||||
from multipyjs import new
|
||||
|
||||
class V3:
|
||||
def __init__(self, x, y, z):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
|
||||
def __repr__(self):
|
||||
return f"V3({self.x}, {self.y}, {self.z})"
|
||||
|
||||
def dot2(self, x, y):
|
||||
return self.x * x + self.y * y
|
||||
|
||||
def dot3(self, x, y, z):
|
||||
return self.x * x + self.y * y + self.z * z
|
||||
|
||||
def to_js(self, scale=1.0):
|
||||
return new(THREE.Vector3, self.x * scale, self.y * scale, self.z * scale)
|
||||
|
||||
PERM = [0] * 512
|
||||
V3_P = [0] * 512 # assigned V3s in seed()
|
||||
P = [151, 160, 137, 91, 90, 15,
|
||||
131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
|
||||
190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
|
||||
88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166,
|
||||
77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244,
|
||||
102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
|
||||
135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
|
||||
5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
|
||||
223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
|
||||
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
|
||||
251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107,
|
||||
49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
|
||||
138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180]
|
||||
V3_I = [V3(1, 1, 0), V3(-1, 1, 0), V3(1, -1, 0), V3(-1, -1, 0),
|
||||
V3(1, 0, 1), V3(-1, 0, 1), V3(1, 0, -1), V3(-1, 0, -1),
|
||||
V3(0, 1, 1), V3(0, -1, 1), V3(0, 1, -1), V3(0, -1, -1)]
|
||||
|
||||
def seed(s):
|
||||
if isinstance(s, float) and 0.0 < s < 1.0:
|
||||
s *= 65536
|
||||
|
||||
s = int(s)
|
||||
if s < 256:
|
||||
s |= s << 8
|
||||
|
||||
for i in range(256):
|
||||
if i & 1:
|
||||
v = P[i] ^ (s & 255)
|
||||
else:
|
||||
v = P[i] ^ ((s >> 8) & 255)
|
||||
|
||||
PERM[i] = PERM[i + 256] = v
|
||||
V3_P[i] = V3_P[i + 256] = V3_I[v % 12]
|
||||
|
||||
seed(0)
|
||||
|
||||
def fade(t):
|
||||
return t * t * t * (t * (t * 6 - 15) + 10)
|
||||
|
||||
def lerp(a, b, t):
|
||||
return (1 - t) * a + t * b
|
||||
|
||||
def perlin3(x, y, z):
|
||||
# grid cells
|
||||
x_c = int(x)
|
||||
y_c = int(y)
|
||||
z_c = int(z)
|
||||
# relative coords within the cell
|
||||
x -= x_c
|
||||
y -= y_c
|
||||
z -= z_c
|
||||
# wrap cells
|
||||
x_c &= 255
|
||||
y_c &= 255
|
||||
z_c &= 255
|
||||
# noise contributions to corners
|
||||
n000 = V3_P[x_c + PERM[y_c + PERM[z_c]]].dot3(x, y, z)
|
||||
n001 = V3_P[x_c + PERM[y_c + PERM[z_c + 1]]].dot3(x, y, z - 1)
|
||||
n010 = V3_P[x_c + PERM[y_c + 1 + PERM[z_c]]].dot3(x, y - 1, z)
|
||||
n011 = V3_P[x_c + PERM[y_c + 1 + PERM[z_c + 1]]].dot3(x, y - 1, z - 1)
|
||||
n100 = V3_P[x_c + 1 + PERM[y_c + PERM[z_c]]].dot3(x - 1, y, z)
|
||||
n101 = V3_P[x_c + 1 + PERM[y_c + PERM[z_c + 1]]].dot3(x - 1, y, z - 1)
|
||||
n110 = V3_P[x_c + 1 + PERM[y_c + 1 + PERM[z_c]]].dot3(x - 1, y - 1, z)
|
||||
n111 = V3_P[x_c + 1 + PERM[y_c + 1 + PERM[z_c + 1]]].dot3(x - 1, y - 1, z - 1)
|
||||
# fade curve
|
||||
u = fade(x)
|
||||
v = fade(y)
|
||||
w = fade(z)
|
||||
# interpolation
|
||||
return lerp(
|
||||
lerp(lerp(n000, n100, u), lerp(n001, n101, u), w),
|
||||
lerp(lerp(n010, n110, u), lerp(n011, n111, u), w),
|
||||
v,
|
||||
)
|
||||
|
||||
def curl2(x, y, z):
|
||||
# https://www.bit-101.com/2017/2021/07/curl-noise/
|
||||
delta = 0.01
|
||||
n1 = perlin3(x + delta, y, z)
|
||||
n2 = perlin3(x - delta, y, z)
|
||||
cy = -(n1 - n2) / (delta * 2)
|
||||
n1 = perlin3(x, y + delta, z)
|
||||
n2 = perlin3(x, y - delta, z)
|
||||
cx = -(n1 - n2) / (delta * 2)
|
||||
print(n1, n2)
|
||||
return V3(cx, cy, 0)
|
||||
16
core/tests/manual/issue-2302/pyscript.toml
Normal file
16
core/tests/manual/issue-2302/pyscript.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
name = "Marching Squares with SPy Copy Copy"
|
||||
packages = [ "cffi", "./glue/perlin-0.0.0-cp312-cp312-pyodide_2024_0_wasm32.whl",]
|
||||
|
||||
[files]
|
||||
"./libthree.py" = ""
|
||||
"./libfft.py" = ""
|
||||
"./perlin_py.py" = ""
|
||||
"./worker.py" = ""
|
||||
"./glue/multipyjs.py" = "./multipyjs.py"
|
||||
|
||||
[js_modules.main]
|
||||
"https://cdn.jsdelivr.net/npm/three@v0.173.0/build/three.module.js" = "three"
|
||||
"https://cdn.jsdelivr.net/npm/three@v0.173.0/examples/jsm/lines/LineMaterial.js" = "linemat"
|
||||
"https://cdn.jsdelivr.net/npm/three@v0.173.0/examples/jsm/lines/Line2.js" = "line2"
|
||||
"https://cdn.jsdelivr.net/npm/three@v0.173.0/examples/jsm/lines/LineSegmentsGeometry.js" = "lsgeo"
|
||||
"https://cdn.jsdelivr.net/npm/stats-gl@3.6.0/dist/main.js" = "stats_gl"
|
||||
141
core/tests/manual/issue-2302/worker.py
Normal file
141
core/tests/manual/issue-2302/worker.py
Normal file
@@ -0,0 +1,141 @@
|
||||
from array import array
|
||||
|
||||
from pyscript import sync, window
|
||||
from perlin_py import perlin3, seed
|
||||
|
||||
grid_w = int(1920 * 4)
|
||||
grid_h = 1080 * 2
|
||||
grid_scale = 10
|
||||
noise_factor = 500
|
||||
grid_hs = int(grid_h/grid_scale)
|
||||
grid_ws = int(grid_w/grid_scale)
|
||||
crossfade_range = int(grid_ws/12.5)
|
||||
height_map = array("d", [0.0] * (grid_hs * grid_ws))
|
||||
edge_table = [
|
||||
(), # 0
|
||||
((3, 2),), # 1
|
||||
((2, 1),), # 2
|
||||
((3, 1),), # 3
|
||||
((0, 1),), # 4
|
||||
((0, 3), (1, 2)), # 5 (ambiguous)
|
||||
((0, 2),), # 6
|
||||
((0, 3),), # 7
|
||||
((0, 3),), # 8
|
||||
((0, 2),), # 9
|
||||
((0, 1), (2, 3)), # 10 (ambiguous)
|
||||
((0, 1),), # 11
|
||||
((3, 1),), # 12
|
||||
((2, 1),), # 13
|
||||
((3, 2),), # 14
|
||||
(), # 15
|
||||
]
|
||||
|
||||
def update_height_map(z):
|
||||
i = 0
|
||||
for y in range(0, grid_h, grid_scale):
|
||||
for x in range(0, grid_w, grid_scale):
|
||||
# 3 octaves of noise
|
||||
n = perlin3(x/noise_factor, y/noise_factor, z)
|
||||
n += 0.50 * perlin3(2*x/noise_factor, 2*y/noise_factor, z)
|
||||
n += 0.25 * perlin3(4*x/noise_factor, 4*y/noise_factor, z)
|
||||
height_map[i] = n
|
||||
i += 1
|
||||
|
||||
def crossfade_height_map():
|
||||
for y in range(grid_hs):
|
||||
for x in range(crossfade_range):
|
||||
pos_i = y*grid_ws + x
|
||||
neg_i = y*grid_ws + grid_ws - crossfade_range + x
|
||||
weight = x/crossfade_range
|
||||
old_pos = height_map[pos_i]
|
||||
old_neg = height_map[neg_i]
|
||||
height_map[neg_i] = height_map[pos_i] = weight * old_pos + (1.0 - weight) * old_neg
|
||||
|
||||
|
||||
def _crossfade_height_map():
|
||||
for y in range(grid_hs):
|
||||
for x in range(crossfade_range):
|
||||
pos_i = y*grid_ws + x
|
||||
neg_i = y*grid_ws + grid_ws - x - 1
|
||||
old_pos = height_map[pos_i]
|
||||
old_neg = height_map[neg_i]
|
||||
weight = 0.5 - x/crossfade_range/2
|
||||
height_map[pos_i] = (1.0 - weight) * old_pos + weight * old_neg
|
||||
height_map[neg_i] = (1.0 - weight) * old_neg + weight * old_pos
|
||||
|
||||
def interpolate(sq_threshold, v1, v2):
|
||||
if v1 == v2:
|
||||
return v1
|
||||
return (sq_threshold - v1) / (v2 - v1)
|
||||
|
||||
stats = {'maxx': 0, 'maxy': 0, 'minx': 0, 'miny': 0}
|
||||
def append_p(lines, p1, p2):
|
||||
lines.append(p1[0])
|
||||
lines.append(p1[1])
|
||||
lines.append(0)
|
||||
lines.append(p2[0])
|
||||
lines.append(p2[1])
|
||||
lines.append(0)
|
||||
stats['maxy'] = max(p1[1], p2[1], stats['maxy'])
|
||||
stats['miny'] = min(p1[1], p2[1], stats['miny'])
|
||||
stats['maxx'] = max(p1[0], p2[0], stats['maxx'])
|
||||
stats['minx'] = min(p1[0], p2[0], stats['minx'])
|
||||
|
||||
def marching_squares(height_map, sq_threshold):
|
||||
lines = array("d")
|
||||
|
||||
for y in range(grid_hs-1):
|
||||
for x in range(grid_ws-1): #cf
|
||||
tl = height_map[y*grid_ws + x]
|
||||
tr = height_map[y*grid_ws + x+1]
|
||||
bl = height_map[(y+1)*grid_ws + x]
|
||||
br = height_map[(y+1)*grid_ws + x+1]
|
||||
|
||||
sq_idx = 0
|
||||
if tl > sq_threshold:
|
||||
sq_idx |= 8
|
||||
if tr > sq_threshold:
|
||||
sq_idx |= 4
|
||||
if br > sq_threshold:
|
||||
sq_idx |= 2
|
||||
if bl > sq_threshold:
|
||||
sq_idx |= 1
|
||||
|
||||
edge_points = [
|
||||
(x + interpolate(sq_threshold, tl, tr), y),
|
||||
(x + 1, y + interpolate(sq_threshold, tr, br)),
|
||||
(x + interpolate(sq_threshold, bl, br), y + 1),
|
||||
(x, y + interpolate(sq_threshold, tl, bl)),
|
||||
]
|
||||
|
||||
for a, b in edge_table[sq_idx]:
|
||||
append_p(lines, edge_points[a], edge_points[b])
|
||||
|
||||
return lines
|
||||
|
||||
def grid_lines():
|
||||
lines = array("d")
|
||||
for x in range(0, grid_ws - crossfade_range, 26):
|
||||
append_p(lines, (x, 0), (x, grid_hs))
|
||||
for y in range(0, grid_hs, 24):
|
||||
append_p(lines, (0, y), (grid_ws-crossfade_range, y))
|
||||
return lines
|
||||
|
||||
seed(44)
|
||||
sync.scale_lines(grid_ws - crossfade_range, grid_hs)
|
||||
sync.print("Computing the height map")
|
||||
update_height_map(0)
|
||||
sync.print("Cross-fading the height map")
|
||||
crossfade_height_map()
|
||||
sync.draw_lines(grid_lines(), 'grid')
|
||||
sync.draw_lines(marching_squares(height_map, 0), 'zero')
|
||||
sync.draw_lines(marching_squares(height_map, 0.3), 'positive')
|
||||
sync.draw_lines(marching_squares(height_map, -0.3), 'negative')
|
||||
sync.draw_lines(marching_squares(height_map, 0.45), 'positive')
|
||||
sync.draw_lines(marching_squares(height_map, -0.45), 'negative')
|
||||
sync.draw_lines(marching_squares(height_map, 0.6), 'positive')
|
||||
sync.draw_lines(marching_squares(height_map, -0.6), 'negative')
|
||||
sync.draw_lines(marching_squares(height_map, -0.8), 'negative')
|
||||
sync.draw_lines(marching_squares(height_map, 0.8), 'positive')
|
||||
print(stats)
|
||||
sync.drawing_done()
|
||||
12
core/tests/manual/issue-2304/index.html
Normal file
12
core/tests/manual/issue-2304/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../../dist/core.css">
|
||||
<script type="module" src="../../../dist/core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="status">Status:</div>
|
||||
<canvas id="canvas" width="200" height="200"></canvas>
|
||||
<script type="py-game" src="./main.py" config="./pyscript.toml"></script>
|
||||
</body>
|
||||
</html>
|
||||
34
core/tests/manual/issue-2304/main.py
Normal file
34
core/tests/manual/issue-2304/main.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import sys
|
||||
print("Starting test...")
|
||||
|
||||
# Try NumPy
|
||||
try:
|
||||
import numpy as np
|
||||
arr = np.array([1, 2, 3])
|
||||
print(f"NumPy works: {arr.mean()}")
|
||||
except Exception as e:
|
||||
print(f"NumPy error: {e}")
|
||||
|
||||
# Try PyGame without NumPy first
|
||||
try:
|
||||
print("Testing PyGame...")
|
||||
import pygame
|
||||
screen = pygame.display.set_mode((200, 200))
|
||||
screen.fill((255, 0, 0)) # Fill with red
|
||||
pygame.display.flip()
|
||||
print("PyGame works!")
|
||||
except Exception as e:
|
||||
print(f"PyGame error: {e}")
|
||||
|
||||
# Now try PyGame with NumPy
|
||||
try:
|
||||
print("Testing PyGame+NumPy...")
|
||||
color_array = np.random.randint(0, 255, size=(50, 50, 3), dtype=np.uint8)
|
||||
surface = pygame.surfarray.make_surface(color_array)
|
||||
screen.blit(surface, (75, 75))
|
||||
pygame.display.flip()
|
||||
print("PyGame+NumPy integration works!")
|
||||
except Exception as e:
|
||||
print(f"PyGame+NumPy integration error: {e}")
|
||||
|
||||
print("Test completed")
|
||||
2
core/tests/manual/issue-2304/pyscript.toml
Normal file
2
core/tests/manual/issue-2304/pyscript.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
name = "PyGame Numpy Minimal Example Copy"
|
||||
packages = [ "numpy", ]
|
||||
21
core/tests/py_tests.main.spec.js
Normal file
21
core/tests/py_tests.main.spec.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const timeout = 60 * 1000;
|
||||
|
||||
test.setTimeout(timeout);
|
||||
|
||||
test('Python unit tests - MicroPython on MAIN thread', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/tests/python/index.html');
|
||||
const result = page.locator("#result"); // Payload for results will be here.
|
||||
await result.waitFor({ timeout }); // wait for the result.
|
||||
const data = JSON.parse(await result.textContent()); // get the result data.
|
||||
await expect(data.fails).toMatchObject([]); // ensure no test failed.
|
||||
});
|
||||
|
||||
test('Python unit tests - Pyodide on MAIN thread', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/tests/python/index.html?type=py');
|
||||
const result = page.locator("#result"); // Payload for results will be here.
|
||||
await result.waitFor({ timeout }); // wait for the result.
|
||||
const data = JSON.parse(await result.textContent()); // get the result data.
|
||||
await expect(data.fails).toMatchObject([]); // ensure no test failed.
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.setTimeout(120 * 1000);
|
||||
|
||||
test('Python unit tests - MicroPython on MAIN thread', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/tests/python/index.html');
|
||||
const result = page.locator("#result"); // Payload for results will be here.
|
||||
await result.waitFor(); // wait for the result.
|
||||
const data = JSON.parse(await result.textContent()); // get the result data.
|
||||
await expect(data.fails).toMatchObject([]); // ensure no test failed.
|
||||
});
|
||||
|
||||
test('Python unit tests - Pyodide on MAIN thread', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/tests/python/index.html?type=py');
|
||||
const result = page.locator("#result"); // Payload for results will be here.
|
||||
await result.waitFor(); // wait for the result.
|
||||
const data = JSON.parse(await result.textContent()); // get the result data.
|
||||
await expect(data.fails).toMatchObject([]); // ensure no test failed.
|
||||
});
|
||||
|
||||
test('Python unit tests - MicroPython on WORKER', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/tests/python/index.html?worker');
|
||||
const result = page.locator("#result"); // Payload for results will be here.
|
||||
await result.waitFor(); // wait for the result.
|
||||
const data = JSON.parse(await result.textContent()); // get the result data.
|
||||
await expect(data.fails).toMatchObject([]); // ensure no test failed.
|
||||
});
|
||||
|
||||
test('Python unit tests - Pyodide on WORKER', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/tests/python/index.html?type=py&worker');
|
||||
const result = page.locator("#result"); // Payload for results will be here.
|
||||
await result.waitFor(); // wait for the result.
|
||||
const data = JSON.parse(await result.textContent()); // get the result data.
|
||||
await expect(data.fails).toMatchObject([]); // ensure no test failed.
|
||||
});
|
||||
21
core/tests/py_tests.worker.spec.js
Normal file
21
core/tests/py_tests.worker.spec.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const timeout = 120 * 1000;
|
||||
|
||||
test.setTimeout(timeout);
|
||||
|
||||
test('Python unit tests - MicroPython on WORKER', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/tests/python/index.html?worker');
|
||||
const result = page.locator("#result"); // Payload for results will be here.
|
||||
await result.waitFor({ timeout }); // wait for the result.
|
||||
const data = JSON.parse(await result.textContent()); // get the result data.
|
||||
await expect(data.fails).toMatchObject([]); // ensure no test failed.
|
||||
});
|
||||
|
||||
test('Python unit tests - Pyodide on WORKER', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/tests/python/index.html?type=py&worker');
|
||||
const result = page.locator("#result"); // Payload for results will be here.
|
||||
await result.waitFor({ timeout }); // wait for the result.
|
||||
const data = JSON.parse(await result.textContent()); // get the result data.
|
||||
await expect(data.fails).toMatchObject([]); // ensure no test failed.
|
||||
});
|
||||
@@ -8,6 +8,7 @@
|
||||
"./tests/test_fetch.py": "tests/test_fetch.py",
|
||||
"./tests/test_ffi.py": "tests/test_ffi.py",
|
||||
"./tests/test_js_modules.py": "tests/test_js_modules.py",
|
||||
"./tests/test_media.py": "tests/test_media.py",
|
||||
"./tests/test_storage.py": "tests/test_storage.py",
|
||||
"./tests/test_running_in_worker.py": "tests/test_running_in_worker.py",
|
||||
"./tests/test_web.py": "tests/test_web.py",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"./tests/test_document.py": "tests/test_document.py",
|
||||
"./tests/test_fetch.py": "tests/test_fetch.py",
|
||||
"./tests/test_ffi.py": "tests/test_ffi.py",
|
||||
"./tests/test_media.py": "tests/test_media.py",
|
||||
"./tests/test_js_modules.py": "tests/test_js_modules.py",
|
||||
"./tests/test_storage.py": "tests/test_storage.py",
|
||||
"./tests/test_running_in_worker.py": "tests/test_running_in_worker.py",
|
||||
|
||||
@@ -13,10 +13,7 @@ def test_current_target():
|
||||
"""
|
||||
expected = "py-0"
|
||||
if is_micropython:
|
||||
if RUNNING_IN_WORKER:
|
||||
expected = "mpy-w0-target"
|
||||
else:
|
||||
expected = "mpy-0"
|
||||
expected = "mpy-w0-target" if RUNNING_IN_WORKER else "mpy-0"
|
||||
elif RUNNING_IN_WORKER:
|
||||
expected = "py-w0-target"
|
||||
assert current_target() == expected, f"Expected {expected} got {current_target()}"
|
||||
|
||||
@@ -256,7 +256,7 @@ async def test_image_display():
|
||||
"""
|
||||
Check an image is displayed correctly.
|
||||
"""
|
||||
mpl = await py_import("matplotlib")
|
||||
_mpl = await py_import("matplotlib")
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
xpoints = [3, 6, 9]
|
||||
|
||||
@@ -22,6 +22,17 @@ def teardown():
|
||||
container.innerHTML = ""
|
||||
|
||||
|
||||
def test_event_no_result():
|
||||
"""
|
||||
If an event has not been triggered with a result, accessing the result
|
||||
parameter raises a ValueError.
|
||||
"""
|
||||
event = Event()
|
||||
with upytest.raises(ValueError) as e:
|
||||
event.result
|
||||
assert str(e.exception) == "Event has not been triggered yet. No result available."
|
||||
|
||||
|
||||
def test_event_add_listener():
|
||||
"""
|
||||
Adding a listener to an event should add it to the list of listeners. It
|
||||
@@ -35,6 +46,52 @@ def test_event_add_listener():
|
||||
assert listener in event._listeners # The item is the expected listener.
|
||||
|
||||
|
||||
def test_event_add_invalid_listener():
|
||||
"""
|
||||
Adding an invalid listener should raise a ValueError.
|
||||
"""
|
||||
event = Event()
|
||||
with upytest.raises(ValueError) as e:
|
||||
event.add_listener("invalid")
|
||||
assert str(e.exception) == "Listener must be callable or awaitable."
|
||||
|
||||
|
||||
def test_event_add_listener_triggered():
|
||||
"""
|
||||
Adding a listener to an event that has already been triggered should call
|
||||
the listener immediately with the result.
|
||||
"""
|
||||
event = Event()
|
||||
counter = 0
|
||||
|
||||
def listener(x):
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
assert x == "ok"
|
||||
|
||||
event.trigger("ok")
|
||||
event.add_listener(listener)
|
||||
assert counter == 1 # The listener has been triggered with the expected result.
|
||||
|
||||
|
||||
def test_event_add_listener_multiple_times():
|
||||
"""
|
||||
Adding the same listener multiple times should not call it multiple times.
|
||||
"""
|
||||
event = Event()
|
||||
counter = 0
|
||||
|
||||
def listener(x):
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
assert x == "ok"
|
||||
|
||||
event.add_listener(listener)
|
||||
event.add_listener(listener)
|
||||
event.trigger("ok")
|
||||
assert counter == 1 # The listener has been triggered only once.
|
||||
|
||||
|
||||
def test_event_remove_listener():
|
||||
"""
|
||||
Removing a listener from an event should remove it from the list of
|
||||
|
||||
87
core/tests/python/tests/test_media.py
Normal file
87
core/tests/python/tests/test_media.py
Normal file
@@ -0,0 +1,87 @@
|
||||
""""
|
||||
Tests for the PyScript media module.
|
||||
"""
|
||||
|
||||
from pyscript import media
|
||||
import upytest
|
||||
|
||||
from pyscript import media
|
||||
|
||||
|
||||
@upytest.skip(
|
||||
"Uses Pyodide-specific to_js function in MicroPython",
|
||||
skip_when=upytest.is_micropython,
|
||||
)
|
||||
async def test_device_enumeration():
|
||||
"""Test enumerating media devices."""
|
||||
devices = await media.list_devices()
|
||||
assert isinstance(devices, list), "list_devices should return a list"
|
||||
|
||||
# If devices are found, verify they have the expected functionality
|
||||
if devices:
|
||||
device = devices[0]
|
||||
|
||||
# Test real device properties exist (but don't assert on their values)
|
||||
# Browser security might restrict actual values until permissions are granted
|
||||
assert hasattr(device, "id"), "Device should have id property"
|
||||
assert hasattr(device, "kind"), "Device should have kind property"
|
||||
assert device.kind in [
|
||||
"videoinput",
|
||||
"audioinput",
|
||||
"audiooutput",
|
||||
], f"Device should have a valid kind, got: {device.kind}"
|
||||
|
||||
# Verify dictionary access works with actual device
|
||||
assert (
|
||||
device["id"] == device.id
|
||||
), "Dictionary access should match property access"
|
||||
assert (
|
||||
device["kind"] == device.kind
|
||||
), "Dictionary access should match property access"
|
||||
|
||||
|
||||
@upytest.skip("Waiting on a bug-fix in MicroPython, for this test to work.", skip_when=upytest.is_micropython)
|
||||
async def test_video_stream_acquisition():
|
||||
"""Test video stream."""
|
||||
try:
|
||||
# Load a video stream
|
||||
stream = await media.Device.load(video=True)
|
||||
|
||||
# Verify we get a real stream with expected properties
|
||||
assert hasattr(stream, "active"), "Stream should have active property"
|
||||
|
||||
# Check for video tracks, but don't fail if permissions aren't granted
|
||||
if stream._dom_element and hasattr(stream._dom_element, "getVideoTracks"):
|
||||
tracks = stream._dom_element.getVideoTracks()
|
||||
if tracks.length > 0:
|
||||
assert True, "Video stream has video tracks"
|
||||
except Exception as e:
|
||||
# If the browser blocks access, the test should still pass
|
||||
# This is because we're testing the API works, not that permissions are granted
|
||||
assert (
|
||||
True
|
||||
), f"Stream acquisition attempted but may require permissions: {str(e)}"
|
||||
|
||||
|
||||
@upytest.skip("Waiting on a bug-fix in MicroPython, for this test to work.", skip_when=upytest.is_micropython)
|
||||
async def test_custom_video_constraints():
|
||||
"""Test loading video with custom constraints."""
|
||||
try:
|
||||
# Define custom constraints
|
||||
constraints = {"width": 640, "height": 480}
|
||||
|
||||
# Load stream with custom constraints
|
||||
stream = await media.Device.load(video=constraints)
|
||||
|
||||
# Basic stream property check
|
||||
assert hasattr(stream, "active"), "Stream should have active property"
|
||||
|
||||
# Check for tracks only if we have access
|
||||
if stream._dom_element and hasattr(stream._dom_element, "getVideoTracks"):
|
||||
tracks = stream._dom_element.getVideoTracks()
|
||||
if tracks.length > 0 and hasattr(tracks[0], "getSettings"):
|
||||
# Settings verification is optional - browsers may handle constraints differently
|
||||
pass
|
||||
except Exception as e:
|
||||
# If the browser blocks access, test that the API structure works
|
||||
assert True, f"Custom constraint test attempted: {str(e)}"
|
||||
@@ -65,7 +65,6 @@ async def test_storage_types():
|
||||
assert test_store["string"] == "hello"
|
||||
assert isinstance(test_store["string"], str)
|
||||
assert test_store["none"] is None
|
||||
assert isinstance(test_store["none"], type(None))
|
||||
assert test_store["list"] == [1, 2, 3]
|
||||
assert isinstance(test_store["list"], list)
|
||||
assert test_store["dict"] == {"a": 1, "b": 2}
|
||||
|
||||
@@ -248,7 +248,7 @@ class TestCollection:
|
||||
|
||||
def test_iter_eq_children(self):
|
||||
elements = web.page.find(".multi-elems")
|
||||
assert [el for el in elements] == [el for el in elements.elements]
|
||||
assert list(elements) == list(elements.elements)
|
||||
assert len(elements) == 3
|
||||
|
||||
def test_slices(self):
|
||||
@@ -427,18 +427,18 @@ class TestInput:
|
||||
class TestSelect:
|
||||
|
||||
def test_select_options_iter(self):
|
||||
select = web.page.find(f"#test_select_element_w_options")[0]
|
||||
select = web.page.find("#test_select_element_w_options")[0]
|
||||
|
||||
for i, option in enumerate(select.options, 1):
|
||||
assert option.value == f"{i}"
|
||||
assert option.innerHTML == f"Option {i}"
|
||||
|
||||
def test_select_options_len(self):
|
||||
select = web.page.find(f"#test_select_element_w_options")[0]
|
||||
select = web.page.find("#test_select_element_w_options")[0]
|
||||
assert len(select.options) == 2
|
||||
|
||||
def test_select_options_clear(self):
|
||||
select = web.page.find(f"#test_select_element_to_clear")[0]
|
||||
select = web.page.find("#test_select_element_to_clear")[0]
|
||||
assert len(select.options) == 3
|
||||
|
||||
select.options.clear()
|
||||
@@ -447,7 +447,7 @@ class TestSelect:
|
||||
|
||||
def test_select_element_add(self):
|
||||
# GIVEN the existing select element with no options
|
||||
select = web.page.find(f"#test_select_element")[0]
|
||||
select = web.page.find("#test_select_element")[0]
|
||||
|
||||
# EXPECT the select element to have no options
|
||||
assert len(select.options) == 0
|
||||
@@ -498,20 +498,14 @@ class TestSelect:
|
||||
# EXPECT the middle option to have the value and html we passed in
|
||||
assert select.options[0].value == "1"
|
||||
assert select.options[0].innerHTML == "Option 1"
|
||||
assert (
|
||||
select.options[0].selected
|
||||
== select.options[0]._dom_element.selected
|
||||
== False
|
||||
)
|
||||
assert select.options[0].selected == select.options[0]._dom_element.selected
|
||||
assert select.options[0].selected is False
|
||||
assert select.options[1].value == "2"
|
||||
assert select.options[1].innerHTML == "Option 2"
|
||||
assert select.options[2].value == "3"
|
||||
assert select.options[2].innerHTML == "Option 3"
|
||||
assert (
|
||||
select.options[2].selected
|
||||
== select.options[2]._dom_element.selected
|
||||
== True
|
||||
)
|
||||
assert select.options[2].selected == select.options[2]._dom_element.selected
|
||||
assert select.options[2].selected is True
|
||||
assert select.options[3].value == ""
|
||||
assert select.options[3].innerHTML == ""
|
||||
|
||||
@@ -538,7 +532,7 @@ class TestSelect:
|
||||
|
||||
def test_select_options_remove(self):
|
||||
# GIVEN the existing select element with 3 options
|
||||
select = web.page.find(f"#test_select_element_to_remove")[0]
|
||||
select = web.page.find("#test_select_element_to_remove")[0]
|
||||
|
||||
# EXPECT the select element to have 3 options
|
||||
assert len(select.options) == 4
|
||||
@@ -560,7 +554,7 @@ class TestSelect:
|
||||
|
||||
def test_select_get_selected_option(self):
|
||||
# GIVEN the existing select element with one selected option
|
||||
select = web.page.find(f"#test_select_element_w_options")[0]
|
||||
select = web.page.find("#test_select_element_w_options")[0]
|
||||
|
||||
# WHEN we get the selected option
|
||||
selected_option = select.options.selected
|
||||
@@ -568,7 +562,8 @@ class TestSelect:
|
||||
# EXPECT the selected option to be correct
|
||||
assert selected_option.value == "2"
|
||||
assert selected_option.innerHTML == "Option 2"
|
||||
assert selected_option.selected == selected_option._dom_element.selected == True
|
||||
assert selected_option.selected == selected_option._dom_element.selected
|
||||
assert selected_option.selected is True
|
||||
|
||||
|
||||
class TestElements:
|
||||
@@ -625,7 +620,8 @@ class TestElements:
|
||||
el = klass(*args, **kwargs)
|
||||
container.append(el)
|
||||
except Exception as e:
|
||||
assert False, f"Failed to create element {el_type}: {e}"
|
||||
msg = f"Failed to create element {el_type}: {e}"
|
||||
raise AssertionError(msg)
|
||||
|
||||
# Let's keep the tag in 2 variables, one for the selector and another to
|
||||
# check the return tag from the selector
|
||||
|
||||
@@ -3,10 +3,12 @@ Exercise the pyscript.Websocket class.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import upytest
|
||||
|
||||
from pyscript import WebSocket
|
||||
|
||||
|
||||
@upytest.skip("Websocket tests are disabled.")
|
||||
async def test_websocket_with_attributes():
|
||||
"""
|
||||
Event handlers assigned via object attributes.
|
||||
@@ -52,6 +54,7 @@ async def test_websocket_with_attributes():
|
||||
assert closed_flag is True
|
||||
|
||||
|
||||
@upytest.skip("Websocket tests are disabled.")
|
||||
async def test_websocket_with_init():
|
||||
"""
|
||||
Event handlers assigned via __init__ arguments.
|
||||
|
||||
8
core/types/fs.d.ts
vendored
Normal file
8
core/types/fs.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
export const NAMESPACE: "@pyscript.fs";
|
||||
export const ERROR: "storage permissions not granted";
|
||||
export const idb: any;
|
||||
export function getFileSystemDirectoryHandle(options: {
|
||||
id?: string;
|
||||
mode?: "read" | "readwrite";
|
||||
hint?: "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos";
|
||||
}): Promise<FileSystemDirectoryHandle>;
|
||||
1
core/types/stdlib/pyscript.d.ts
vendored
1
core/types/stdlib/pyscript.d.ts
vendored
@@ -6,6 +6,7 @@ declare namespace _default {
|
||||
"fetch.py": string;
|
||||
"ffi.py": string;
|
||||
"flatted.py": string;
|
||||
"fs.py": string;
|
||||
"magic_js.py": string;
|
||||
"media.py": string;
|
||||
"storage.py": string;
|
||||
|
||||
11
core/types/sync.d.ts
vendored
11
core/types/sync.d.ts
vendored
@@ -5,5 +5,16 @@ declare namespace _default {
|
||||
* @param {number} seconds The number of seconds to sleep.
|
||||
*/
|
||||
function sleep(seconds: number): Promise<any>;
|
||||
/**
|
||||
* Ask a user action via dialog and returns the directory handler once granted.
|
||||
* @param {string} uid
|
||||
* @param {{id?:string, mode?:"read"|"readwrite", hint?:"desktop"|"documents"|"downloads"|"music"|"pictures"|"videos"}} options
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function storeFSHandler(uid: string, options?: {
|
||||
id?: string;
|
||||
mode?: "read" | "readwrite";
|
||||
hint?: "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos";
|
||||
}): boolean;
|
||||
}
|
||||
export default _default;
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
[tool.codespell]
|
||||
ignore-words-list = "afterall"
|
||||
skip = "*.js,*.json"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 114
|
||||
lint.select = ["C4", "C90", "E", "EM", "F", "PIE", "PYI", "PLC", "Q", "RET", "W"]
|
||||
lint.ignore = ["E402", "E722", "E731", "E741", "F401", "F704", "F811", "F821"]
|
||||
lint.mccabe.max-complexity = 27
|
||||
|
||||
Reference in New Issue
Block a user