Compare commits

...

7 Commits

Author SHA1 Message Date
Andrea Giammarchi
290eb03388 Fix #2302 - Updated Polyscript to its latest (#2303)
* Fix #2302 - Updated Polyscript to its latest
2025-02-27 11:09:46 +01:00
Nicholas Tollervey
55031f2347 Update README to include acknowledgement of Anaconda's role in the project. (#2297) 2025-02-26 16:50:51 +00:00
Andrea Giammarchi
8168383653 Updated Pyodide to v0.27.3 (#2300)
This has been published on *npm* as `https://cdn.jsdelivr.net/npm/@pyscript/core@0.6.33/dist/core.js` and `https://cdn.jsdelivr.net/npm/@pyscript/core@0.6.33/dist/core.css`
2025-02-26 15:06:17 +01:00
Andrea Giammarchi
3ff2c171bc PyEditor kill switch (#2295) 2025-02-26 14:39:17 +01:00
Andrea Giammarchi
edbac13713 Splitting integration tests (#2296) 2025-02-20 15:16:36 +01:00
Christian Clauss
46239caa19 Re ruff (#2292)
* Ruff fixes

* Ruff fixes

* from __future__ import annotations breaks MicroPython

* noqa: FURB188 because there is no str.replacesuffix() in MicroPython

* Add ruff to pre-commit
2025-02-20 09:43:09 +01:00
Andrea Giammarchi
0366e48fad Introducing pyscript.fs namespace/module (#2289)
* introducing pyscript.fs namespace/module

* Added proper rejection when showDirectoryPicker is not supported

* Improved exports to make explicit import in 3rd party modules easier

* implemented `fs.unmount(path)`:

  * verified that RAM gets freed
  * allowed to mount different handlers within the same path through different `id` as that's the Web best way to do so
2025-02-17 14:45:43 +01:00
48 changed files with 1583 additions and 371 deletions

View File

@@ -28,19 +28,26 @@ repos:
rev: 25.1.0 rev: 25.1.0
hooks: hooks:
- id: black - id: black
exclude: core/tests
args: ["-l", "88", "--skip-string-normalization"] args: ["-l", "88", "--skip-string-normalization"]
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: v2.4.1 rev: v2.4.1
hooks: hooks:
- id: codespell # See 'pyproject.toml' for args - id: codespell # See 'pyproject.toml' for args
exclude: \.js\.map$ exclude: fs\.py|\.js\.map$
additional_dependencies: additional_dependencies:
- tomli - 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 - repo: https://github.com/hoodmane/pyscript-prettier-precommit
rev: "v3.0.0-alpha.6" rev: "v3.0.0-alpha.6"
hooks: hooks:
- id: prettier - 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"] args: [--tab-width, "4"]

View File

@@ -83,3 +83,12 @@ documentation for more information on how to setup your development environment.
The [PyScript organization governance](https://github.com/pyscript/governance) The [PyScript organization governance](https://github.com/pyscript/governance)
is documented in a separate repository. 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/)!

396
core/package-lock.json generated
View File

@@ -1,19 +1,19 @@
{ {
"name": "@pyscript/core", "name": "@pyscript/core",
"version": "0.6.26", "version": "0.6.35",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@pyscript/core", "name": "@pyscript/core",
"version": "0.6.26", "version": "0.6.35",
"license": "APACHE-2.0", "license": "APACHE-2.0",
"dependencies": { "dependencies": {
"@ungap/with-resolvers": "^0.1.0", "@ungap/with-resolvers": "^0.1.0",
"@webreflection/idb-map": "^0.3.2", "@webreflection/idb-map": "^0.3.2",
"add-promise-listener": "^0.1.3", "add-promise-listener": "^0.1.3",
"basic-devtools": "^0.1.6", "basic-devtools": "^0.1.6",
"polyscript": "^0.16.11", "polyscript": "^0.16.17",
"sabayon": "^0.6.6", "sabayon": "^0.6.6",
"sticky-module": "^0.1.1", "sticky-module": "^0.1.1",
"to-json-callback": "^0.1.1", "to-json-callback": "^0.1.1",
@@ -24,7 +24,7 @@
"@codemirror/lang-python": "^6.1.7", "@codemirror/lang-python": "^6.1.7",
"@codemirror/language": "^6.10.8", "@codemirror/language": "^6.10.8",
"@codemirror/state": "^6.5.2", "@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.36.2", "@codemirror/view": "^6.36.3",
"@playwright/test": "1.45.3", "@playwright/test": "1.45.3",
"@rollup/plugin-commonjs": "^28.0.2", "@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-node-resolve": "^16.0.0",
@@ -33,13 +33,13 @@
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0", "@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"bun": "^1.2.2", "bun": "^1.2.4",
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
"codedent": "^0.1.2", "codedent": "^0.1.2",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"eslint": "^9.19.0", "eslint": "^9.21.0",
"flatted": "^3.3.2", "flatted": "^3.3.3",
"rollup": "^4.34.4", "rollup": "^4.34.8",
"rollup-plugin-postcss": "^4.0.2", "rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-string": "^3.0.0", "rollup-plugin-string": "^3.0.0",
"static-handler": "^0.5.3", "static-handler": "^0.5.3",
@@ -52,9 +52,9 @@
} }
}, },
"node_modules/@codemirror/autocomplete": { "node_modules/@codemirror/autocomplete": {
"version": "6.18.4", "version": "6.18.6",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.4.tgz", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
"integrity": "sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==", "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -119,9 +119,9 @@
} }
}, },
"node_modules/@codemirror/search": { "node_modules/@codemirror/search": {
"version": "6.5.8", "version": "6.5.10",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.10.tgz",
"integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==", "integrity": "sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -141,9 +141,9 @@
} }
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.36.2", "version": "6.36.3",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.3.tgz",
"integrity": "sha512-DZ6ONbs8qdJK0fdN7AB82CgI6tYXf4HWk1wSVa0+9bhVznCuuvhQtX8bFBoy3dv8rZSQqUd8GvhVAcielcidrA==", "integrity": "sha512-N2bilM47QWC8Hnx0rMdDxO2x2ImJ1FvZWXubwKgjeoOrWwEiFrtpA7SFHcuZ+o2Ze2VzbkgbzWVj4+V18LVkeg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -210,9 +210,9 @@
} }
}, },
"node_modules/@eslint/core": { "node_modules/@eslint/core": {
"version": "0.10.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
"integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -223,9 +223,9 @@
} }
}, },
"node_modules/@eslint/eslintrc": { "node_modules/@eslint/eslintrc": {
"version": "3.2.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz",
"integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -247,9 +247,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.19.0", "version": "9.21.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz",
"integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -267,13 +267,13 @@
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.2.5", "version": "0.2.7",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz",
"integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.10.0", "@eslint/core": "^0.12.0",
"levn": "^0.4.1" "levn": "^0.4.1"
}, },
"engines": { "engines": {
@@ -333,9 +333,9 @@
} }
}, },
"node_modules/@humanwhocodes/retry": { "node_modules/@humanwhocodes/retry": {
"version": "0.4.1", "version": "0.4.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
"integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
@@ -457,9 +457,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@oven/bun-darwin-aarch64": { "node_modules/@oven/bun-darwin-aarch64": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.2.4.tgz",
"integrity": "sha512-hCDvi6GGJvsKpfGcU9xdrIhshDtzkYcGiB5wnj0jq/QM3U85qmIe8QUs7tyse9T77aZNjFIXO0GirL+oZ7C+IQ==", "integrity": "sha512-xBz/Q7X6AFwMg7MXtBemjjt5uB+tvEYBmi9Zbm1r8qnI2V8m/Smuhma0EARhiVfLuIAYj2EM5qjzxeAFV4TBJA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -471,9 +471,9 @@
] ]
}, },
"node_modules/@oven/bun-darwin-x64": { "node_modules/@oven/bun-darwin-x64": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.2.4.tgz",
"integrity": "sha512-W1MkLpfLMH4+aRoLNksLiODySVUlnKF5dTEmS2VlHxl4Mle+V/40/WSalpZVRPcztJMquwQy2VXox62WUmERXQ==", "integrity": "sha512-ufyty+2754QCFDhq447H39JiqabMlFRItLn1YFp+2hdpKak7KCYLGOUuHnlr1pmImKJzDHURjnvTTq1QRlUWAA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -485,9 +485,9 @@
] ]
}, },
"node_modules/@oven/bun-darwin-x64-baseline": { "node_modules/@oven/bun-darwin-x64-baseline": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.2.4.tgz",
"integrity": "sha512-Q4gC6fB/6BwGc6QltAAlhugCdRRIraxbNYuA0cyuwUlFmMzQIqgO+iSCIaS2PLEwVEwVx8WF++YpU+dVGyNAvg==", "integrity": "sha512-stsq8vBiYgfGunBGlf2M7ST7Ymyw3WnwrxEeJ04vkKmMEEE2LpX8Rkol6UPRvZawab9s9/scFIRgFi6hu9H4SQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -499,9 +499,9 @@
] ]
}, },
"node_modules/@oven/bun-linux-aarch64": { "node_modules/@oven/bun-linux-aarch64": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.2.4.tgz",
"integrity": "sha512-VW83fgwFAJyu76xMF2t6W1+VxcLaKJUMH+/k61PdwAulpR6M+aqf8vPQDpIx2vrs4BqR8DQ4eEspyb2/DpcuyQ==", "integrity": "sha512-OhVpzme2vvLA7w8GYeJg2SQ2h2CwJQN9oYDiGeoML4EwE+DEoYHdxgaBZsQySOwZtFIr8ufpc/8iD4hssJ50qg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -513,9 +513,9 @@
] ]
}, },
"node_modules/@oven/bun-linux-aarch64-musl": { "node_modules/@oven/bun-linux-aarch64-musl": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.2.4.tgz",
"integrity": "sha512-73srKJaPf3fKUuST4Xd+CO52qCKVMtXUICkTddZ+6itnNNrBDBw4AoayrlNImg7swL8wIZonGGYZHpsvyjmlcg==", "integrity": "sha512-+lxWF7up9MuB1ZdGxXCH3AH3XmYtdBC6soQ38+yg3+y3iOPrAlSG+wytHEkypN/UU2mGvCuaEED3cMvejrGdDw==",
"cpu": [ "cpu": [
"aarch64" "aarch64"
], ],
@@ -527,9 +527,9 @@
] ]
}, },
"node_modules/@oven/bun-linux-x64": { "node_modules/@oven/bun-linux-x64": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.2.4.tgz",
"integrity": "sha512-qoEkSdWGvTcX2/Iv3nUcwIcOk72L9Pg6X4ONUXV7luuOHsXktoQK5vuQeCC5FTxCEPjfoKy1GFAFomoU4qZDGQ==", "integrity": "sha512-oof3ii92Cz2yIOZRbVFHMHmffCutRPFITIdXLZ2/rkqVuKUe0ZdqWjHPhxJFm31AL9MlJ/dSqDbPb51SaLI7tw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -541,9 +541,9 @@
] ]
}, },
"node_modules/@oven/bun-linux-x64-baseline": { "node_modules/@oven/bun-linux-x64-baseline": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.2.4.tgz",
"integrity": "sha512-YvIilLbII9+sjwnZVumwItoUKSUHJHo9xYw5hcf6aeqDztK1ebGzNIJpE0RgjIT+GYqy504Yz5cK77CGpyVyJw==", "integrity": "sha512-3nmDDZJH73MzhBg2sRYioj4CE8wgaz0w24OieMqj4/c44BbNr3X5RewrldsMD2cU6DtVbi52FuD5WpTw3N8nmw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -555,9 +555,9 @@
] ]
}, },
"node_modules/@oven/bun-linux-x64-musl": { "node_modules/@oven/bun-linux-x64-musl": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.2.4.tgz",
"integrity": "sha512-6uyUXVNH5jUsvhyzD0uiQNe8VPujuGTsHHDTW1LvRR6Lgr6AVaYjdk45ypbDSauRQsABxDKFQdfQqemkM97sYw==", "integrity": "sha512-cLdMbK7srNoUbYSG3Tp4GYdPAO0+5mgUhdbU053GZs0DLQmQ8h1JQhALp+ZjrUWstmQe7ddcNu7l7EAu6E76XA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -569,9 +569,9 @@
] ]
}, },
"node_modules/@oven/bun-linux-x64-musl-baseline": { "node_modules/@oven/bun-linux-x64-musl-baseline": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl-baseline/-/bun-linux-x64-musl-baseline-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl-baseline/-/bun-linux-x64-musl-baseline-1.2.4.tgz",
"integrity": "sha512-K2o2vSHSnGn+ayXUuFJ+7O5L5Mc+uOIydMc5WI9bMhvmEtRqlohrwQR56HPzeFZVPWvmHdxb34VIceEKXmig6Q==", "integrity": "sha512-qDsUvKCW0WUlVOt6Yx5eEzxgCbvzuSJBsu0sXtr6uGt8TnMKghmliRO5FmiMLQ0k/PUDA8vPJCtuv5k14bDi6g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -583,9 +583,9 @@
] ]
}, },
"node_modules/@oven/bun-windows-x64": { "node_modules/@oven/bun-windows-x64": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.2.4.tgz",
"integrity": "sha512-ZjxyIcS7kQFoGxyNBMZ9mqCxCkg7ZVG9P+/GTzMpUceFF/q88lUwsFsC7YwAe0ZubqcQLRSfdyjbKbC/HuqnFw==", "integrity": "sha512-4YRJd4pdaTWEM+uawYmchOeNv874RGAFpIZQubWnN4SBf6HfcDm0OMMZcm0f0I70Wd5gbPg1+rvCRtDZWVmZog==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -597,9 +597,9 @@
] ]
}, },
"node_modules/@oven/bun-windows-x64-baseline": { "node_modules/@oven/bun-windows-x64-baseline": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.2.4.tgz",
"integrity": "sha512-bYopMWSCjjjCKjANv7xxAXQoabVUxLZxTw0iC1bGYD9VZGo48nGaJXPn7DsPfeCXGyl+CY3Cy4QIEn+3gNRS2A==", "integrity": "sha512-j/G4bnfsRAiCvpTADda40m29iSGvueIaF9Kc9hBu4jN8dTS9fEXdNNXuf8c30/z7/npxw2dhzsAn8jbc5QvD1A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -725,9 +725,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz",
"integrity": "sha512-gGi5adZWvjtJU7Axs//CWaQbQd/vGy8KGcnEaCWiyCqxWYDxwIlAHFuSe6Guoxtd0SRvSfVTDMPd5H+4KE2kKA==", "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -739,9 +739,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz",
"integrity": "sha512-1aRlh1gqtF7vNPMnlf1vJKk72Yshw5zknR/ZAVh7zycRAGF2XBMVDAHmFQz/Zws5k++nux3LOq/Ejj1WrDR6xg==", "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -753,9 +753,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz",
"integrity": "sha512-drHl+4qhFj+PV/jrQ78p9ch6A0MfNVZScl/nBps5a7u01aGf/GuBRrHnRegA9bP222CBDfjYbFdjkIJ/FurvSQ==", "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -767,9 +767,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz",
"integrity": "sha512-hQqq/8QALU6t1+fbNmm6dwYsa0PDD4L5r3TpHx9dNl+aSEMnIksHZkSO3AVH+hBMvZhpumIGrTFj8XCOGuIXjw==", "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -781,9 +781,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz",
"integrity": "sha512-/L0LixBmbefkec1JTeAQJP0ETzGjFtNml2gpQXA8rpLo7Md+iXQzo9kwEgzyat5Q+OG/C//2B9Fx52UxsOXbzw==", "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -795,9 +795,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz",
"integrity": "sha512-6Rk3PLRK+b8L/M6m/x6Mfj60LhAUcLJ34oPaxufA+CfqkUrDoUPQYFdRrhqyOvtOKXLJZJwxlOLbQjNYQcRQfw==", "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -809,9 +809,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz",
"integrity": "sha512-kmT3x0IPRuXY/tNoABp2nDvI9EvdiS2JZsd4I9yOcLCCViKsP0gB38mVHOhluzx+SSVnM1KNn9k6osyXZhLoCA==", "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -823,9 +823,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz",
"integrity": "sha512-3iSA9tx+4PZcJH/Wnwsvx/BY4qHpit/u2YoZoXugWVfc36/4mRkgGEoRbRV7nzNBSCOgbWMeuQ27IQWgJ7tRzw==", "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -837,9 +837,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz",
"integrity": "sha512-7CwSJW+sEhM9sESEk+pEREF2JL0BmyCro8UyTq0Kyh0nu1v0QPNY3yfLPFKChzVoUmaKj8zbdgBxUhBRR+xGxg==", "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -851,9 +851,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz",
"integrity": "sha512-GZdafB41/4s12j8Ss2izofjeFXRAAM7sHCb+S4JsI9vaONX/zQ8cXd87B9MRU/igGAJkKvmFmJJBeeT9jJ5Cbw==", "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -865,9 +865,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loongarch64-gnu": { "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz",
"integrity": "sha512-uuphLuw1X6ur11675c2twC6YxbzyLSpWggvdawTUamlsoUv81aAXRMPBC1uvQllnBGls0Qt5Siw8reSIBnbdqQ==", "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@@ -879,9 +879,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz",
"integrity": "sha512-KvLEw1os2gSmD6k6QPCQMm2T9P2GYvsMZMRpMz78QpSoEevHbV/KOUbI/46/JRalhtSAYZBYLAnT9YE4i/l4vg==", "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -893,9 +893,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz",
"integrity": "sha512-wcpCLHGM9yv+3Dql/CI4zrY2mpQ4WFergD3c9cpRowltEh5I84pRT/EuHZsG0In4eBPPYthXnuR++HrFkeqwkA==", "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -907,9 +907,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz",
"integrity": "sha512-nLbfQp2lbJYU8obhRQusXKbuiqm4jSJteLwfjnunDT5ugBKdxqw1X9KWwk8xp1OMC6P5d0WbzxzhWoznuVK6XA==", "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -921,9 +921,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz",
"integrity": "sha512-JGejzEfVzqc/XNiCKZj14eb6s5w8DdWlnQ5tWUbs99kkdvfq9btxxVX97AaxiUX7xJTKFA0LwoS0KU8C2faZRg==", "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -935,9 +935,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz",
"integrity": "sha512-/iFIbhzeyZZy49ozAWJ1ZR2KW6ZdYUbQXLT4O5n1cRZRoTpwExnHLjlurDXXPKEGxiAg0ujaR9JDYKljpr2fDg==", "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -949,9 +949,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz",
"integrity": "sha512-qORc3UzoD5UUTneiP2Afg5n5Ti1GAW9Gp5vHPxzvAFFA3FBaum9WqGvYXGf+c7beFdOKNos31/41PRMUwh1tpA==", "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -963,9 +963,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz",
"integrity": "sha512-5g7E2PHNK2uvoD5bASBD9aelm44nf1w4I5FEI7MPHLWcCSrR8JragXZWgKPXk5i2FU3JFfa6CGZLw2RrGBHs2Q==", "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -977,9 +977,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz",
"integrity": "sha512-p0scwGkR4kZ242xLPBuhSckrJ734frz6v9xZzD+kHVYRAkSUmdSLCIJRfql6H5//aF8Q10K+i7q8DiPfZp0b7A==", "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1238,9 +1238,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/bun": { "node_modules/bun": {
"version": "1.2.2", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/bun/-/bun-1.2.2.tgz", "resolved": "https://registry.npmjs.org/bun/-/bun-1.2.4.tgz",
"integrity": "sha512-RUc8uVVTw8WoASUzXaEQJR1s7mnwoHm3P871qBUIqSaoOpuwcU+bSVX151/xoqDwnyv38SjOX7yQ3oO0IeT73g==", "integrity": "sha512-ZY0EZ/UKqheaLeAtMsfJA6jWoWvV9HAtfFaOJSmS3LrNpFKs1Sg5fZLSsczN1h3a+Dtheo4O3p3ZYWrf40kRGw==",
"cpu": [ "cpu": [
"arm64", "arm64",
"x64", "x64",
@@ -1259,17 +1259,17 @@
"bunx": "bin/bun.exe" "bunx": "bin/bun.exe"
}, },
"optionalDependencies": { "optionalDependencies": {
"@oven/bun-darwin-aarch64": "1.2.2", "@oven/bun-darwin-aarch64": "1.2.4",
"@oven/bun-darwin-x64": "1.2.2", "@oven/bun-darwin-x64": "1.2.4",
"@oven/bun-darwin-x64-baseline": "1.2.2", "@oven/bun-darwin-x64-baseline": "1.2.4",
"@oven/bun-linux-aarch64": "1.2.2", "@oven/bun-linux-aarch64": "1.2.4",
"@oven/bun-linux-aarch64-musl": "1.2.2", "@oven/bun-linux-aarch64-musl": "1.2.4",
"@oven/bun-linux-x64": "1.2.2", "@oven/bun-linux-x64": "1.2.4",
"@oven/bun-linux-x64-baseline": "1.2.2", "@oven/bun-linux-x64-baseline": "1.2.4",
"@oven/bun-linux-x64-musl": "1.2.2", "@oven/bun-linux-x64-musl": "1.2.4",
"@oven/bun-linux-x64-musl-baseline": "1.2.2", "@oven/bun-linux-x64-musl-baseline": "1.2.4",
"@oven/bun-windows-x64": "1.2.2", "@oven/bun-windows-x64": "1.2.4",
"@oven/bun-windows-x64-baseline": "1.2.2" "@oven/bun-windows-x64-baseline": "1.2.4"
} }
}, },
"node_modules/callsites": { "node_modules/callsites": {
@@ -1296,9 +1296,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001697", "version": "1.0.30001701",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001697.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz",
"integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==", "integrity": "sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -1734,9 +1734,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.92", "version": "1.5.107",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.92.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.107.tgz",
"integrity": "sha512-BeHgmNobs05N1HMmMZ7YIuHfYBGlq/UmvlsTgg+fsbFs9xVMj+xJHFg19GN04+9Q+r8Xnh9LXqaYIyEWElnNgQ==", "integrity": "sha512-dJr1o6yCntRkXElnhsHh1bAV19bo/hKyFf7tCcWgpXbuFIF0Lakjgqv5LRfSDaNzAII8Fnxg2tqgHkgCvxdbxw==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@@ -1781,22 +1781,22 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.19.0", "version": "9.21.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz",
"integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.19.0", "@eslint/config-array": "^0.19.2",
"@eslint/core": "^0.10.0", "@eslint/core": "^0.12.0",
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.3.0",
"@eslint/js": "9.19.0", "@eslint/js": "9.21.0",
"@eslint/plugin-kit": "^0.2.5", "@eslint/plugin-kit": "^0.2.7",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.1", "@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6", "@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15", "@types/json-schema": "^7.0.15",
"ajv": "^6.12.4", "ajv": "^6.12.4",
@@ -2029,9 +2029,9 @@
} }
}, },
"node_modules/flatted": { "node_modules/flatted": {
"version": "3.3.2", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@@ -2747,9 +2747,9 @@
} }
}, },
"node_modules/polyscript": { "node_modules/polyscript": {
"version": "0.16.11", "version": "0.16.17",
"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.16.11.tgz", "resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.16.17.tgz",
"integrity": "sha512-q0tujwSPEf+08n5ckSa4Z4p9k4AaiM00TGkD0GAK1GjPC87IzFMHF6sz9M5K0EvErTuiwqW5rJms5smVLRiG9g==", "integrity": "sha512-6dWyWoZn6Z915bizfjftc82p2BhkWn6pPgd/u9GBXmdXTZBPC1ezDizqtgwpceTK6GNHPuNQ4elLWr5G0W+Mrw==",
"license": "APACHE-2.0", "license": "APACHE-2.0",
"dependencies": { "dependencies": {
"@ungap/structured-clone": "^1.3.0", "@ungap/structured-clone": "^1.3.0",
@@ -2767,9 +2767,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.1", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -3084,9 +3084,9 @@
} }
}, },
"node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": {
"version": "7.0.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
"integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3114,9 +3114,9 @@
} }
}, },
"node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": {
"version": "7.0.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
"integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3437,9 +3437,9 @@
} }
}, },
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "4.1.1", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -3482,9 +3482,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.34.4", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.4.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz",
"integrity": "sha512-spF66xoyD7rz3o08sHP7wogp1gZ6itSq22SGa/IZTcUDXDlOyrShwMwkVSB+BUxFRZZCUYqdb3KWDEOMVQZxuw==", "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3498,25 +3498,25 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.34.4", "@rollup/rollup-android-arm-eabi": "4.34.8",
"@rollup/rollup-android-arm64": "4.34.4", "@rollup/rollup-android-arm64": "4.34.8",
"@rollup/rollup-darwin-arm64": "4.34.4", "@rollup/rollup-darwin-arm64": "4.34.8",
"@rollup/rollup-darwin-x64": "4.34.4", "@rollup/rollup-darwin-x64": "4.34.8",
"@rollup/rollup-freebsd-arm64": "4.34.4", "@rollup/rollup-freebsd-arm64": "4.34.8",
"@rollup/rollup-freebsd-x64": "4.34.4", "@rollup/rollup-freebsd-x64": "4.34.8",
"@rollup/rollup-linux-arm-gnueabihf": "4.34.4", "@rollup/rollup-linux-arm-gnueabihf": "4.34.8",
"@rollup/rollup-linux-arm-musleabihf": "4.34.4", "@rollup/rollup-linux-arm-musleabihf": "4.34.8",
"@rollup/rollup-linux-arm64-gnu": "4.34.4", "@rollup/rollup-linux-arm64-gnu": "4.34.8",
"@rollup/rollup-linux-arm64-musl": "4.34.4", "@rollup/rollup-linux-arm64-musl": "4.34.8",
"@rollup/rollup-linux-loongarch64-gnu": "4.34.4", "@rollup/rollup-linux-loongarch64-gnu": "4.34.8",
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.4", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8",
"@rollup/rollup-linux-riscv64-gnu": "4.34.4", "@rollup/rollup-linux-riscv64-gnu": "4.34.8",
"@rollup/rollup-linux-s390x-gnu": "4.34.4", "@rollup/rollup-linux-s390x-gnu": "4.34.8",
"@rollup/rollup-linux-x64-gnu": "4.34.4", "@rollup/rollup-linux-x64-gnu": "4.34.8",
"@rollup/rollup-linux-x64-musl": "4.34.4", "@rollup/rollup-linux-x64-musl": "4.34.8",
"@rollup/rollup-win32-arm64-msvc": "4.34.4", "@rollup/rollup-win32-arm64-msvc": "4.34.8",
"@rollup/rollup-win32-ia32-msvc": "4.34.4", "@rollup/rollup-win32-ia32-msvc": "4.34.8",
"@rollup/rollup-win32-x64-msvc": "4.34.4", "@rollup/rollup-win32-x64-msvc": "4.34.8",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@@ -3848,9 +3848,9 @@
} }
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.37.0", "version": "5.39.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
"integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
@@ -3913,9 +3913,9 @@
} }
}, },
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.1.2", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
"integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@pyscript/core", "name": "@pyscript/core",
"version": "0.6.26", "version": "0.6.35",
"type": "module", "type": "module",
"description": "PyScript", "description": "PyScript",
"module": "./index.js", "module": "./index.js",
@@ -25,6 +25,10 @@
"types": "./types/core.d.ts", "types": "./types/core.d.ts",
"import": "./src/core.js" "import": "./src/core.js"
}, },
"./js": {
"types": "./types/core.d.ts",
"import": "./dist/core.js"
},
"./css": { "./css": {
"import": "./dist/core.css" "import": "./dist/core.css"
}, },
@@ -43,7 +47,7 @@
"build:3rd-party": "node rollup/3rd-party.cjs", "build:3rd-party": "node rollup/3rd-party.cjs",
"build:tests-index": "node rollup/build_test_index.cjs", "build:tests-index": "node rollup/build_test_index.cjs",
"clean:3rd-party": "rm src/3rd-party/*.js && rm src/3rd-party/*.css", "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", "test:ws": "bun tests/javascript/ws/index.js & playwright test tests/javascript/ws/index.spec.js",
"dev": "node dev.cjs", "dev": "node dev.cjs",
"release": "npm run build && npm run zip", "release": "npm run build && npm run zip",
@@ -62,7 +66,7 @@
"@webreflection/idb-map": "^0.3.2", "@webreflection/idb-map": "^0.3.2",
"add-promise-listener": "^0.1.3", "add-promise-listener": "^0.1.3",
"basic-devtools": "^0.1.6", "basic-devtools": "^0.1.6",
"polyscript": "^0.16.11", "polyscript": "^0.16.17",
"sabayon": "^0.6.6", "sabayon": "^0.6.6",
"sticky-module": "^0.1.1", "sticky-module": "^0.1.1",
"to-json-callback": "^0.1.1", "to-json-callback": "^0.1.1",
@@ -73,7 +77,7 @@
"@codemirror/lang-python": "^6.1.7", "@codemirror/lang-python": "^6.1.7",
"@codemirror/language": "^6.10.8", "@codemirror/language": "^6.10.8",
"@codemirror/state": "^6.5.2", "@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.36.2", "@codemirror/view": "^6.36.3",
"@playwright/test": "1.45.3", "@playwright/test": "1.45.3",
"@rollup/plugin-commonjs": "^28.0.2", "@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-node-resolve": "^16.0.0",
@@ -82,13 +86,13 @@
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0", "@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"bun": "^1.2.2", "bun": "^1.2.4",
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
"codedent": "^0.1.2", "codedent": "^0.1.2",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"eslint": "^9.19.0", "eslint": "^9.21.0",
"flatted": "^3.3.2", "flatted": "^3.3.3",
"rollup": "^4.34.4", "rollup": "^4.34.8",
"rollup-plugin-postcss": "^4.0.2", "rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-string": "^3.0.0", "rollup-plugin-string": "^3.0.0",
"static-handler": "^0.5.3", "static-handler": "^0.5.3",

View File

@@ -28,53 +28,34 @@ mpy-config {
.py-editor-run-button, .py-editor-run-button,
.mpy-editor-run-button { .mpy-editor-run-button {
position: absolute; position: absolute;
display: flex;
right: 0.5rem; right: 0.5rem;
bottom: 0.5rem; bottom: 0.5rem;
opacity: 0; opacity: 0;
transition: opacity 0.25s; transition: opacity 0.25s;
z-index: 1; z-index: 1;
padding: 0;
} }
.py-editor-box:hover .py-editor-run-button, .py-editor-box:hover .py-editor-run-button,
.mpy-editor-box:hover .mpy-editor-run-button, .mpy-editor-box:hover .mpy-editor-run-button,
.py-editor-run-button:focus, .py-editor-run-button:focus,
.py-editor-run-button:disabled, .py-editor-run-button.running,
.mpy-editor-run-button:focus, .mpy-editor-run-button:focus,
.mpy-editor-run-button:disabled { .mpy-editor-run-button.running {
opacity: 1; 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, py-terminal span,
mpy-terminal span { mpy-terminal span {
letter-spacing: 0 !important; letter-spacing: 0 !important;
} }
dialog.pyscript-fs {
border-radius: 8px;
border-width: 1px;
}
dialog.pyscript-fs > div {
display: flex;
justify-content: space-between;
}

View File

@@ -33,6 +33,7 @@ import {
createFunction, createFunction,
inputFailure, inputFailure,
} from "./hooks.js"; } from "./hooks.js";
import * as fs from "./fs.js";
import codemirror from "./plugins/codemirror.js"; import codemirror from "./plugins/codemirror.js";
export { codemirror }; export { codemirror };
@@ -167,6 +168,8 @@ for (const [TYPE, interpreter] of TYPES) {
// enrich the Python env with some JS utility for main // enrich the Python env with some JS utility for main
interpreter.registerJsModule("_pyscript", { interpreter.registerJsModule("_pyscript", {
PyWorker, PyWorker,
fs,
interpreter,
js_import: (...urls) => Promise.all(urls.map((url) => import(url))), js_import: (...urls) => Promise.all(urls.map((url) => import(url))),
get target() { get target() {
return isScript(currentElement) return isScript(currentElement)

81
core/src/fs.js Normal file
View 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;
};

View File

@@ -88,7 +88,19 @@ export const hooks = {
/** @type {Set<function>} */ /** @type {Set<function>} */
onBeforeRun: new SetFunction(), onBeforeRun: new SetFunction(),
/** @type {Set<function>} */ /** @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>} */ /** @type {Set<function>} */
onAfterRun: new SetFunction(), onAfterRun: new SetFunction(),
/** @type {Set<function>} */ /** @type {Set<function>} */

View File

@@ -4,13 +4,15 @@ import { TYPES, offline_interpreter, relative_url, stdlib } from "../core.js";
import { notify } from "./error.js"; import { notify } from "./error.js";
import codemirror from "./codemirror.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; let id = 0;
const getID = (type) => `${type}-editor-${id++}`; const getID = (type) => `${type}-editor-${id++}`;
const envs = new Map(); const envs = new Map();
const configs = new Map(); const configs = new Map();
const editors = new WeakMap();
const hooks = { const hooks = {
worker: { worker: {
@@ -30,12 +32,18 @@ const validate = (config, result) => {
return result; return result;
}; };
const getRelatedScript = (target, type) => {
const editor = target.closest(`.${type}-editor-box`);
return editor?.parentNode?.previousElementSibling;
};
async function execute({ currentTarget }) { async function execute({ currentTarget }) {
const { env, pySrc, outDiv } = this; const { env, pySrc, outDiv } = this;
const hasRunButton = !!currentTarget; const hasRunButton = !!currentTarget;
if (hasRunButton) { if (hasRunButton) {
currentTarget.disabled = true; currentTarget.classList.add("running");
currentTarget.innerHTML = STOP_BUTTON;
outDiv.innerHTML = ""; outDiv.innerHTML = "";
} }
@@ -82,8 +90,7 @@ async function execute({ currentTarget }) {
// creation and destruction of editors on the fly // creation and destruction of editors on the fly
if (hasRunButton) { if (hasRunButton) {
for (const type of TYPES.keys()) { for (const type of TYPES.keys()) {
const editor = currentTarget.closest(`.${type}-editor-box`); const script = getRelatedScript(currentTarget, type);
const script = editor?.parentNode?.previousElementSibling;
if (script) { if (script) {
defineProperties(script, { xworker: { value: xworker } }); defineProperties(script, { xworker: { value: xworker } });
break; break;
@@ -116,7 +123,10 @@ async function execute({ currentTarget }) {
}; };
const enable = () => { const enable = () => {
if (hasRunButton) currentTarget.disabled = false; if (hasRunButton) {
currentTarget.classList.remove("running");
currentTarget.innerHTML = RUN_BUTTON;
}
}; };
const { sync } = xworker; const { sync } = xworker;
sync.write = (str) => { sync.write = (str) => {
@@ -144,6 +154,24 @@ const makeRunButton = (handler, type) => {
runButton.innerHTML = RUN_BUTTON; runButton.innerHTML = RUN_BUTTON;
runButton.setAttribute("aria-label", "Python Script Run Button"); runButton.setAttribute("aria-label", "Python Script Run Button");
runButton.addEventListener("click", async (event) => { 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(); runButton.blur();
await handler.handleEvent(event); await handler.handleEvent(event);
}); });
@@ -387,6 +415,7 @@ const init = async (script, type, interpreter) => {
doc, doc,
}); });
editors.set(script, editor);
editor.focus(); editor.focus();
notifyEditor(); notifyEditor();
}; };

File diff suppressed because one or more lines are too long

View File

@@ -73,14 +73,14 @@ def _eval_formatter(obj, print_method):
""" """
if print_method == "__repr__": if print_method == "__repr__":
return repr(obj) return repr(obj)
elif hasattr(obj, print_method): if hasattr(obj, print_method):
if print_method == "savefig": if print_method == "savefig":
buf = io.BytesIO() buf = io.BytesIO()
obj.savefig(buf, format="png") obj.savefig(buf, format="png")
buf.seek(0) buf.seek(0)
return base64.b64encode(buf.read()).decode("utf-8") return base64.b64encode(buf.read()).decode("utf-8")
return getattr(obj, print_method)() return getattr(obj, print_method)()
elif print_method == "_repr_mimebundle_": if print_method == "_repr_mimebundle_":
return {}, {} return {}, {}
return None return None
@@ -107,7 +107,7 @@ def _format_mime(obj):
if output is None: if output is None:
continue continue
elif mime_type not in _MIME_RENDERERS: if mime_type not in _MIME_RENDERERS:
not_available.append(mime_type) not_available.append(mime_type)
continue continue
break break
@@ -149,9 +149,11 @@ def display(*values, target=None, append=True):
if target is None: if target is None:
target = current_target() target = current_target()
elif not isinstance(target, str): 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 == "": elif target == "":
raise ValueError("Cannot have an empty target") msg = "Cannot have an empty target"
raise ValueError(msg)
elif target.startswith("#"): elif target.startswith("#"):
# note: here target is str and not None! # note: here target is str and not None!
# align with @when behavior # 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 target cannot be found on the page, a ValueError is raised
if element is None: if element is None:
raise ValueError( msg = f"Invalid selector with id={target}. Cannot be found in the page."
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 # 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, # points to the visual element holding the displayed values. In that case,

View File

@@ -36,7 +36,8 @@ class Event:
if listener not in self._listeners: if listener not in self._listeners:
self._listeners.append(listener) self._listeners.append(listener)
else: else:
raise ValueError("Listener must be callable or awaitable.") msg = "Listener must be callable or awaitable."
raise ValueError(msg)
def remove_listener(self, *args): def remove_listener(self, *args):
""" """
@@ -76,7 +77,8 @@ def when(target, *args, **kwargs):
# Extract the selector from the arguments or keyword arguments. # Extract the selector from the arguments or keyword arguments.
selector = args[0] if args else kwargs.pop("selector") selector = args[0] if args else kwargs.pop("selector")
if not 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. # Grab the DOM elements to which the target event will be attached.
from pyscript.web import Element, ElementCollection from pyscript.web import Element, ElementCollection

View File

@@ -31,7 +31,7 @@ def _object_keys(value):
def _is_array(value): def _is_array(value):
return isinstance(value, list) or isinstance(value, tuple) return isinstance(value, (list, tuple))
def _is_object(value): def _is_object(value):
@@ -60,10 +60,10 @@ def _loop(keys, input, known, output):
def _ref(key, value, 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) known.append(value)
value = _loop(_array_keys(value), input, known, 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) known.append(value)
value = _loop(_object_keys(value), input, known, value) value = _loop(_object_keys(value), input, known, value)

View 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)

View File

@@ -25,6 +25,7 @@ class JSModule:
# avoid pyodide looking for non existent fields # avoid pyodide looking for non existent fields
if not field.startswith("_"): if not field.startswith("_"):
return getattr(getattr(js_modules, self.name), field) return getattr(getattr(js_modules, self.name), field)
return None
# generate N modules in the system that will proxy the real value # generate N modules in the system that will proxy the real value

View File

@@ -44,8 +44,7 @@ class Device:
for k in video: for k in video:
setattr(options.video, k, to_js(video[k])) setattr(options.video, k, to_js(video[k]))
stream = await window.navigator.mediaDevices.getUserMedia(options) return await window.navigator.mediaDevices.getUserMedia(options)
return stream
async def get_stream(self): async def get_stream(self):
key = self.kind.replace("input", "").replace("output", "") key = self.kind.replace("input", "").replace("output", "")

View File

@@ -10,10 +10,11 @@ def _to_idb(value):
if isinstance(value, (bool, float, int, str, list, dict, tuple)): if isinstance(value, (bool, float, int, str, list, dict, tuple)):
return _stringify(["generic", value]) return _stringify(["generic", value])
if isinstance(value, bytearray): if isinstance(value, bytearray):
return _stringify(["bytearray", [v for v in value]]) return _stringify(["bytearray", list(value)])
if isinstance(value, memoryview): if isinstance(value, memoryview):
return _stringify(["memoryview", [v for v in value]]) return _stringify(["memoryview", list(value)])
raise TypeError(f"Unexpected value: {value}") msg = f"Unexpected value: {value}"
raise TypeError(msg)
# convert an IndexedDB compatible entry into a Python value # convert an IndexedDB compatible entry into a Python value
@@ -56,5 +57,6 @@ class Storage(dict):
async def storage(name="", storage_class=Storage): async def storage(name="", storage_class=Storage):
if not name: 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}")) return storage_class(await _storage(f"@pyscript/{name}"))

View File

@@ -11,7 +11,7 @@ def as_bytearray(buffer):
ui8a = js.Uint8Array.new(buffer) ui8a = js.Uint8Array.new(buffer)
size = ui8a.length size = ui8a.length
ba = bytearray(size) ba = bytearray(size)
for i in range(0, size): for i in range(size):
ba[i] = ui8a[i] ba[i] = ui8a[i]
return ba return ba

View File

@@ -2,7 +2,10 @@
# `when` is not used in this module. It is imported here save the user an additional # `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`). # 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 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 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. 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.children[key]
return self.find(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 # attribute `for` which is a Python keyword, so you can access it on the
# Element instance via `for_`). # Element instance via `for_`).
if name.endswith("_"): if name.endswith("_"):
name = name[:-1] name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
return getattr(self._dom_element, name) return getattr(self._dom_element, name)
def __setattr__(self, name, value): 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 # attribute `for` which is a Python keyword, so you can access it on the
# Element instance via `for_`). # Element instance via `for_`).
if name.endswith("_"): if name.endswith("_"):
name = name[:-1] name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
if name.startswith("on_"): if name.startswith("on_"):
# Ensure on-events are cached in the _on_events dict if the # 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. Get an `Event` instance for the specified event name.
""" """
if not name.startswith("on_"): 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. event_name = name[3:] # Remove the "on_" prefix.
if not hasattr(self._dom_element, event_name): 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: if name in self._on_events:
return self._on_events[name] return self._on_events[name]
# Such an on-event exists in the DOM element, but we haven't yet # 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 # We check for list/tuple here and NOT for any iterable as it will match
# a JS Nodelist which is handled explicitly below. # a JS Nodelist which is handled explicitly below.
# NodeList. # NodeList.
elif isinstance(item, list) or isinstance(item, tuple): elif isinstance(item, (list, tuple)):
for child in item: for child in item:
self.append(child) self.append(child)
@@ -227,10 +232,11 @@ class Element:
except AttributeError: except AttributeError:
# Nope! This is not an element or a NodeList. # Nope! This is not an element or a NodeList.
raise TypeError( msg = (
f'Element "{item}" is a proxy object, "' f'Element "{item}" is a proxy object, "'
f"but not a valid element or a NodeList." f"but not a valid element or a NodeList."
) )
raise TypeError(msg)
def clone(self, clone_id=None): def clone(self, clone_id=None):
"""Make a clone of the element (clones the underlying DOM object too).""" """Make a clone of the element (clones the underlying DOM object too)."""
@@ -401,9 +407,8 @@ class Options:
new_option = option(**kwargs) new_option = option(**kwargs)
if before: if before and isinstance(before, Element):
if isinstance(before, Element): before = before._dom_element
before = before._dom_element
self._element._dom_element.add(new_option._dom_element, before) self._element._dom_element.add(new_option._dom_element, before)
@@ -463,7 +468,7 @@ class ContainerElement(Element):
) )
for child in list(args) + (children or []): for child in list(args) + (children or []):
if isinstance(child, Element) or isinstance(child, ElementCollection): if isinstance(child, (Element, ElementCollection)):
self.append(child) self.append(child)
else: else:
@@ -493,14 +498,13 @@ class ClassesCollection:
) )
def __iter__(self): def __iter__(self):
for class_name in self._all_class_names(): yield from self._all_class_names()
yield class_name
def __len__(self): def __len__(self):
return len(self._all_class_names()) return len(self._all_class_names())
def __repr__(self): def __repr__(self):
return f"ClassesCollection({repr(self._collection)})" return f"ClassesCollection({self._collection!r})"
def __str__(self): def __str__(self):
return " ".join(self._all_class_names()) return " ".join(self._all_class_names())
@@ -553,7 +557,7 @@ class StyleCollection:
element.style[key] = value element.style[key] = value
def __repr__(self): def __repr__(self):
return f"StyleCollection({repr(self._collection)})" return f"StyleCollection({self._collection!r})"
def remove(self, key): def remove(self, key):
"""Remove a CSS property from the elements in the collection.""" """Remove a CSS property from the elements in the collection."""
@@ -588,7 +592,7 @@ class ElementCollection:
if isinstance(key, int): if isinstance(key, int):
return self._elements[key] return self._elements[key]
elif isinstance(key, slice): if isinstance(key, slice):
return ElementCollection(self._elements[key]) return ElementCollection(self._elements[key])
return self.find(key) return self.find(key)
@@ -1125,7 +1129,8 @@ class video(ContainerElement):
elif isinstance(to, Element): elif isinstance(to, Element):
if to.tag != "canvas": 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": elif getattr(to, "tagName", "") == "CANVAS":
to = canvas(dom_element=to) to = canvas(dom_element=to)
@@ -1134,10 +1139,12 @@ class video(ContainerElement):
elif isinstance(to, str): elif isinstance(to, str):
nodelist = document.querySelectorAll(to) # NOQA nodelist = document.querySelectorAll(to) # NOQA
if nodelist.length == 0: 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": 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]) to = canvas(dom_element=nodelist[0])

View File

@@ -24,7 +24,7 @@ class EventMessage:
return value return value
class WebSocket(object): class WebSocket:
CONNECTING = 0 CONNECTING = 0
OPEN = 1 OPEN = 1
CLOSING = 2 CLOSING = 2

View File

@@ -25,10 +25,12 @@ async def create_named_worker(src="", name="", config=None, type="py"):
from json import dumps from json import dumps
if not src: if not src:
raise ValueError("Named workers require src") msg = "Named workers require src"
raise ValueError(msg)
if not name: 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 = _js.document.createElement("script")
s.type = type s.type = type
@@ -37,7 +39,7 @@ async def create_named_worker(src="", name="", config=None, type="py"):
_set(s, "name", name) _set(s, "name", name)
if config: 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) _js.document.body.append(s)
return await workers[name] return await workers[name]

View File

@@ -1,3 +1,5 @@
import { idb, getFileSystemDirectoryHandle } from "./fs.js";
export default { export default {
// allow pyterminal checks to bootstrap // allow pyterminal checks to bootstrap
is_pyterminal: () => false, is_pyterminal: () => false,
@@ -9,4 +11,21 @@ export default {
sleep(seconds) { sleep(seconds) {
return new Promise(($) => setTimeout($, seconds * 1000)); 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

View File

@@ -1,6 +1,6 @@
import numpy import numpy as np
import matplotlib import matplotlib as mpl
# just do something with the packages # just do something with the packages
print(len(dir(numpy))) print(len(dir(np)))
print(len(dir(matplotlib))) print(len(dir(mpl)))

View File

@@ -4,4 +4,4 @@ def runtime_version():
return sys.version return sys.version
__export__ = ['runtime_version'] __export__ = ["runtime_version"]

View 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>

View 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

View File

@@ -35,7 +35,8 @@ import pygame
# see if we can load more than standard BMP # see if we can load more than standard BMP
if not pygame.image.get_extended(): if not pygame.image.get_extended():
raise SystemExit("Sorry, extended image module required") msg = "Sorry, extended image module required"
raise SystemExit(msg)
# game constants # game constants
@@ -56,7 +57,8 @@ def load_image(file):
try: try:
surface = pygame.image.load(file) surface = pygame.image.load(file)
except pygame.error: 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() return surface.convert()
@@ -66,8 +68,7 @@ def load_sound(file):
return None return None
file = os.path.join(main_dir, "data", file) file = os.path.join(main_dir, "data", file)
try: try:
sound = pygame.mixer.Sound(file) return pygame.mixer.Sound(file)
return sound
except pygame.error: except pygame.error:
print(f"Warning, unable to load, {file}") print(f"Warning, unable to load, {file}")
return None return None
@@ -227,7 +228,7 @@ class Score(pygame.sprite.Sprite):
def update(self): def update(self):
"""We only update the score in update() when it has changed.""" """We only update the score in update() when it has changed."""
if SCORE != self.lastscore: if self.lastscore != SCORE:
self.lastscore = SCORE self.lastscore = SCORE
msg = "Score: %d" % SCORE msg = "Score: %d" % SCORE
self.image = self.font.render(msg, 0, self.color) self.image = self.font.render(msg, 0, self.color)
@@ -296,7 +297,7 @@ async def main(winstyle=0):
# Create Some Starting Values # Create Some Starting Values
global score global score
alienreload = ALIEN_RELOAD alienreload = ALIEN_RELOAD
clock = pygame.Clock() _clock = pygame.Clock()
# initialize our starting sprites # initialize our starting sprites
global SCORE global SCORE
@@ -313,24 +314,23 @@ async def main(winstyle=0):
return return
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
return return
elif event.type == pygame.KEYDOWN: if event.type == pygame.KEYDOWN and event.key == pygame.K_f:
if event.key == pygame.K_f: if not fullscreen:
if not fullscreen: print("Changing to FULLSCREEN")
print("Changing to FULLSCREEN") screen_backup = screen.copy()
screen_backup = screen.copy() screen = pygame.display.set_mode(
screen = pygame.display.set_mode( SCREENRECT.size, winstyle | pygame.FULLSCREEN, bestdepth
SCREENRECT.size, winstyle | pygame.FULLSCREEN, bestdepth )
) screen.blit(screen_backup, (0, 0))
screen.blit(screen_backup, (0, 0)) else:
else: print("Changing to windowed mode")
print("Changing to windowed mode") screen_backup = screen.copy()
screen_backup = screen.copy() screen = pygame.display.set_mode(
screen = pygame.display.set_mode( SCREENRECT.size, winstyle, bestdepth
SCREENRECT.size, winstyle, bestdepth )
) screen.blit(screen_backup, (0, 0))
screen.blit(screen_backup, (0, 0)) pygame.display.flip()
pygame.display.flip() fullscreen = not fullscreen
fullscreen = not fullscreen
keystate = pygame.key.get_pressed() keystate = pygame.key.get_pressed()
@@ -371,7 +371,7 @@ async def main(winstyle=0):
player.kill() player.kill()
# See if shots hit the aliens. # 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: if pygame.mixer:
boom_sound.play() boom_sound.play()
Explosion(alien) Explosion(alien)

Binary file not shown.

View 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

View 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>

View 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

View 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"

View 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...")

View 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)

View 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"

View 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()

View File

@@ -0,0 +1,19 @@
import { test, expect } from '@playwright/test';
test.setTimeout(30 * 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.
});

View File

@@ -2,22 +2,6 @@ import { test, expect } from '@playwright/test';
test.setTimeout(120 * 1000); 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 }) => { test('Python unit tests - MicroPython on WORKER', async ({ page }) => {
await page.goto('http://localhost:8080/tests/python/index.html?worker'); await page.goto('http://localhost:8080/tests/python/index.html?worker');
const result = page.locator("#result"); // Payload for results will be here. const result = page.locator("#result"); // Payload for results will be here.

View File

@@ -13,10 +13,7 @@ def test_current_target():
""" """
expected = "py-0" expected = "py-0"
if is_micropython: if is_micropython:
if RUNNING_IN_WORKER: expected = "mpy-w0-target" if RUNNING_IN_WORKER else "mpy-0"
expected = "mpy-w0-target"
else:
expected = "mpy-0"
elif RUNNING_IN_WORKER: elif RUNNING_IN_WORKER:
expected = "py-w0-target" expected = "py-w0-target"
assert current_target() == expected, f"Expected {expected} got {current_target()}" assert current_target() == expected, f"Expected {expected} got {current_target()}"

View File

@@ -256,7 +256,7 @@ async def test_image_display():
""" """
Check an image is displayed correctly. Check an image is displayed correctly.
""" """
mpl = await py_import("matplotlib") _mpl = await py_import("matplotlib")
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
xpoints = [3, 6, 9] xpoints = [3, 6, 9]

View File

@@ -65,7 +65,6 @@ async def test_storage_types():
assert test_store["string"] == "hello" assert test_store["string"] == "hello"
assert isinstance(test_store["string"], str) assert isinstance(test_store["string"], str)
assert test_store["none"] is None assert test_store["none"] is None
assert isinstance(test_store["none"], type(None))
assert test_store["list"] == [1, 2, 3] assert test_store["list"] == [1, 2, 3]
assert isinstance(test_store["list"], list) assert isinstance(test_store["list"], list)
assert test_store["dict"] == {"a": 1, "b": 2} assert test_store["dict"] == {"a": 1, "b": 2}

View File

@@ -248,7 +248,7 @@ class TestCollection:
def test_iter_eq_children(self): def test_iter_eq_children(self):
elements = web.page.find(".multi-elems") 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 assert len(elements) == 3
def test_slices(self): def test_slices(self):
@@ -427,18 +427,18 @@ class TestInput:
class TestSelect: class TestSelect:
def test_select_options_iter(self): 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): for i, option in enumerate(select.options, 1):
assert option.value == f"{i}" assert option.value == f"{i}"
assert option.innerHTML == f"Option {i}" assert option.innerHTML == f"Option {i}"
def test_select_options_len(self): 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 assert len(select.options) == 2
def test_select_options_clear(self): 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 assert len(select.options) == 3
select.options.clear() select.options.clear()
@@ -447,7 +447,7 @@ class TestSelect:
def test_select_element_add(self): def test_select_element_add(self):
# GIVEN the existing select element with no options # 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 # EXPECT the select element to have no options
assert len(select.options) == 0 assert len(select.options) == 0
@@ -498,20 +498,14 @@ class TestSelect:
# EXPECT the middle option to have the value and html we passed in # EXPECT the middle option to have the value and html we passed in
assert select.options[0].value == "1" assert select.options[0].value == "1"
assert select.options[0].innerHTML == "Option 1" assert select.options[0].innerHTML == "Option 1"
assert ( assert select.options[0].selected == select.options[0]._dom_element.selected
select.options[0].selected assert select.options[0].selected is False
== select.options[0]._dom_element.selected
== False
)
assert select.options[1].value == "2" assert select.options[1].value == "2"
assert select.options[1].innerHTML == "Option 2" assert select.options[1].innerHTML == "Option 2"
assert select.options[2].value == "3" assert select.options[2].value == "3"
assert select.options[2].innerHTML == "Option 3" assert select.options[2].innerHTML == "Option 3"
assert ( assert select.options[2].selected == select.options[2]._dom_element.selected
select.options[2].selected assert select.options[2].selected is True
== select.options[2]._dom_element.selected
== True
)
assert select.options[3].value == "" assert select.options[3].value == ""
assert select.options[3].innerHTML == "" assert select.options[3].innerHTML == ""
@@ -538,7 +532,7 @@ class TestSelect:
def test_select_options_remove(self): def test_select_options_remove(self):
# GIVEN the existing select element with 3 options # 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 # EXPECT the select element to have 3 options
assert len(select.options) == 4 assert len(select.options) == 4
@@ -560,7 +554,7 @@ class TestSelect:
def test_select_get_selected_option(self): def test_select_get_selected_option(self):
# GIVEN the existing select element with one selected option # 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 # WHEN we get the selected option
selected_option = select.options.selected selected_option = select.options.selected
@@ -568,7 +562,8 @@ class TestSelect:
# EXPECT the selected option to be correct # EXPECT the selected option to be correct
assert selected_option.value == "2" assert selected_option.value == "2"
assert selected_option.innerHTML == "Option 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: class TestElements:
@@ -625,7 +620,8 @@ class TestElements:
el = klass(*args, **kwargs) el = klass(*args, **kwargs)
container.append(el) container.append(el)
except Exception as e: 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 # Let's keep the tag in 2 variables, one for the selector and another to
# check the return tag from the selector # check the return tag from the selector

8
core/types/fs.d.ts vendored Normal file
View 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>;

View File

@@ -6,6 +6,7 @@ declare namespace _default {
"fetch.py": string; "fetch.py": string;
"ffi.py": string; "ffi.py": string;
"flatted.py": string; "flatted.py": string;
"fs.py": string;
"magic_js.py": string; "magic_js.py": string;
"media.py": string; "media.py": string;
"storage.py": string; "storage.py": string;

11
core/types/sync.d.ts vendored
View File

@@ -5,5 +5,16 @@ declare namespace _default {
* @param {number} seconds The number of seconds to sleep. * @param {number} seconds The number of seconds to sleep.
*/ */
function sleep(seconds: number): Promise<any>; 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; export default _default;

View File

@@ -1,3 +1,9 @@
[tool.codespell] [tool.codespell]
ignore-words-list = "afterall" ignore-words-list = "afterall"
skip = "*.js,*.json" 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