mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-23 12:12:59 -05:00
Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25809660ef | ||
|
|
6b9eff45bb | ||
|
|
08e83feaf5 | ||
|
|
4f05b5afc6 | ||
|
|
9a5bf9918e | ||
|
|
1c7cf0ba7d | ||
|
|
ce2d1a4513 | ||
|
|
7d50d7eea0 | ||
|
|
e9411dc796 | ||
|
|
5cf2de16d1 | ||
|
|
e53bcf15a9 | ||
|
|
bec70b60b8 | ||
|
|
8b7fb89c68 | ||
|
|
b2bbdda73d | ||
|
|
ee2f46cfb9 | ||
|
|
4337e6833a | ||
|
|
a73c73b814 | ||
|
|
e8318a98f0 | ||
|
|
94f2ac6204 | ||
|
|
cc6cb4ded0 | ||
|
|
c61d4191c3 | ||
|
|
e284da7c09 | ||
|
|
9992096654 | ||
|
|
c696d92f40 | ||
|
|
af60299324 | ||
|
|
5aa9135a34 | ||
|
|
72e23ac86f | ||
|
|
33d49ad87d | ||
|
|
aa2335ca2e | ||
|
|
b31428006c | ||
|
|
dc1d583791 | ||
|
|
4299a74e40 | ||
|
|
3e408b7baa | ||
|
|
446c131ccb | ||
|
|
b062efcf17 | ||
|
|
30e31a86ef | ||
|
|
182272e8c7 | ||
|
|
06df21e8e3 | ||
|
|
cafebd68f2 | ||
|
|
061d4b3f72 | ||
|
|
6586e79d5e | ||
|
|
cda6c6bc7e | ||
|
|
a628026838 | ||
|
|
6700856b9f | ||
|
|
411aa0bbed | ||
|
|
0d79d31b96 | ||
|
|
cb05a9b067 | ||
|
|
536f359fb9 | ||
|
|
56e888ed33 | ||
|
|
687b93d148 | ||
|
|
0e1c396d7c | ||
|
|
7e24289703 | ||
|
|
0b23310a06 | ||
|
|
41ebaaf366 | ||
|
|
b79ceea7a8 | ||
|
|
b990bcb67a | ||
|
|
3f0f2d9910 | ||
|
|
4333f5f979 | ||
|
|
07e75293b8 | ||
|
|
a9ca7106cb | ||
|
|
da2728e6df | ||
|
|
adfa9a9b05 | ||
|
|
be9b9f66d3 | ||
|
|
9521bc7175 | ||
|
|
b445f8a834 | ||
|
|
3c3dffd5ed | ||
|
|
4c8443fd00 | ||
|
|
06a5a54103 | ||
|
|
0d3c3eef4e | ||
|
|
f0a6fb913f | ||
|
|
5f0c508fed | ||
|
|
16d9657982 | ||
|
|
40d098310e | ||
|
|
1345449d57 | ||
|
|
515858f313 | ||
|
|
2f452e9dc7 | ||
|
|
66119157a7 | ||
|
|
5b671dd1d0 | ||
|
|
1017362eec | ||
|
|
f67b8e0285 | ||
|
|
4c635fe84c | ||
|
|
68e463493e | ||
|
|
f1979d60b7 | ||
|
|
9150ebafec | ||
|
|
9543019336 | ||
|
|
1c53d91c6b | ||
|
|
4850f39b5a | ||
|
|
ab085c2d92 | ||
|
|
bf4d835948 | ||
|
|
214e39537b | ||
|
|
2d33afc195 | ||
|
|
87ea24ebd4 | ||
|
|
5380f8b9b3 | ||
|
|
00121ff8ba | ||
|
|
80e5d20e37 | ||
|
|
58f7c2137d | ||
|
|
f9194cc833 | ||
|
|
d9b8b48972 | ||
|
|
aa85f5f596 | ||
|
|
5341a0be4a | ||
|
|
0cfe20ca65 | ||
|
|
c352b502c4 | ||
|
|
58b4df6b3d | ||
|
|
29ba9436c8 | ||
|
|
2a044e88ad | ||
|
|
63092f9d72 | ||
|
|
0209324d57 | ||
|
|
1587273868 | ||
|
|
beb3aa1574 | ||
|
|
fe708c9fb4 | ||
|
|
e45d8bf973 | ||
|
|
b184c92f01 | ||
|
|
6c8afb05a7 | ||
|
|
d3dd4573cf | ||
|
|
d5cf68391a | ||
|
|
4e54e93450 | ||
|
|
e4f6387f18 | ||
|
|
54cb35b60a | ||
|
|
18ede2b729 | ||
|
|
f138b5a4f4 | ||
|
|
11a517bba4 | ||
|
|
66b57bf812 | ||
|
|
a9357bd97e | ||
|
|
d7c6d42c3d | ||
|
|
4dd1dc28b1 | ||
|
|
1e05ff7c95 | ||
|
|
e8e2e65584 | ||
|
|
3727e60152 | ||
|
|
0254012db6 | ||
|
|
8b97e4757f | ||
|
|
c70e121078 | ||
|
|
a4f97e6e46 | ||
|
|
800145a83c | ||
|
|
c75f885cb4 | ||
|
|
4011a51013 | ||
|
|
f4165dabcf | ||
|
|
de6c26eb05 |
26
.github/stale.yaml
vendored
Normal file
26
.github/stale.yaml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- backlog
|
||||
- needs-triage
|
||||
- needs-work
|
||||
- epic
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed because it has not had
|
||||
recent activity. Thank you for your contributions.
|
||||
8
.github/workflows/build-unstable.yml
vendored
8
.github/workflows/build-unstable.yml
vendored
@@ -1,18 +1,20 @@
|
||||
name: '[CI] Build Unstable'
|
||||
|
||||
on:
|
||||
push: # Only run on merges into main that modify files under pyscriptjs/
|
||||
push: # Only run on merges into main that modify files under pyscriptjs/ and examples/
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- pyscriptjs/**
|
||||
- examples/**
|
||||
- .github/workflows/build-latest.yml # Test that workflow works when changed
|
||||
|
||||
pull_request: # Run on any PR that modifies files under pyscriptjs/
|
||||
pull_request: # Run on any PR that modifies files under pyscriptjs/ and examples/
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- pyscriptjs/**
|
||||
- examples/**
|
||||
- .github/workflows/build-unstable.yml # Test that workflow works when changed
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -64,7 +66,7 @@ jobs:
|
||||
run: make test-py
|
||||
|
||||
- name: Integration Tests
|
||||
run: make test-integration
|
||||
run: make test-integration-parallel
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
18
.github/workflows/dashboard.yaml
vendored
18
.github/workflows/dashboard.yaml
vendored
@@ -1,18 +0,0 @@
|
||||
name: Push issue to Github Project dashboard
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add_to_project:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@v0.0.3
|
||||
with:
|
||||
project-url: https://github.com/orgs/pyscript/projects/4/
|
||||
github-token: ${{ secrets.PROJECT_TOKEN }}
|
||||
9
.github/workflows/docs-release.yml
vendored
9
.github/workflows/docs-release.yml
vendored
@@ -55,3 +55,12 @@ jobs:
|
||||
|
||||
- name: Sync to S3
|
||||
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/${{ github.ref_name }}/
|
||||
|
||||
# Make sure to remove the latest folder so we sync the full docs upon release
|
||||
- name: Delete latest directory
|
||||
run: aws s3 rm --recursive s3://docs.pyscript.net/latest/
|
||||
|
||||
# Note that the files are the same as above, but we want to have folders with
|
||||
# /<tag name>/ AND /latest/ which latest will always point to the latest release
|
||||
- name: Sync to /latest
|
||||
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/latest/
|
||||
|
||||
3
.github/workflows/docs-review.yml
vendored
3
.github/workflows/docs-review.yml
vendored
@@ -63,9 +63,6 @@ jobs:
|
||||
- name: Copy redirect file
|
||||
run: aws s3 cp --quiet ./docs/_build/html/_static/redirect.html s3://docs.pyscript.net/index.html
|
||||
|
||||
# - name: Delete review directory
|
||||
# run: aws s3 rm --recursive s3://docs.pyscript.net/review/${{ github.event.number }}/
|
||||
|
||||
- name: Sync to S3
|
||||
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/review/${{ github.event.number }}/
|
||||
|
||||
|
||||
@@ -50,11 +50,6 @@ jobs:
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
|
||||
|
||||
- name: Copy redirect file
|
||||
run: aws s3 cp --quiet ./docs/_build/html/_static/redirect.html s3://docs.pyscript.net/index.html
|
||||
|
||||
# - name: Delete latest directory
|
||||
# run: aws s3 rm --recursive s3://docs.pyscript.net/latest/
|
||||
|
||||
# Sync will only copy changed files
|
||||
- name: Sync to S3
|
||||
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/latest/
|
||||
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/unstable/
|
||||
4
.github/workflows/prepare-release.yml
vendored
4
.github/workflows/prepare-release.yml
vendored
@@ -48,9 +48,13 @@ jobs:
|
||||
- name: Build and Test
|
||||
run: make test
|
||||
|
||||
- name: Zip build folder
|
||||
run: zip -r -q ./build.zip ./build
|
||||
|
||||
- name: Prepare Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: true
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
files: ./build.zip
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
default_stages: [commit]
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-builtin-literals
|
||||
- id: check-case-conflict
|
||||
@@ -28,55 +28,54 @@ repos:
|
||||
- --skip=B101,B201
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.8.0
|
||||
rev: 22.12.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.1
|
||||
rev: v2.2.2
|
||||
hooks:
|
||||
- id: codespell # See 'setup.cfg' for args
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 5.0.4
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8 # See 'setup.cfg' for args
|
||||
additional_dependencies: [flake8-bugbear, flake8-comprehensions]
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
rev: 5.11.1
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
args: [--profile, black]
|
||||
|
||||
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
|
||||
rev: v2.4.0
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: pretty-format-yaml
|
||||
args: [--autofix, --indent, '4']
|
||||
exclude: .github/ISSUE_TEMPLATE/.*\.yml$
|
||||
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.38.2
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args:
|
||||
- --py310-plus
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: v8.24.0
|
||||
rev: v8.29.0
|
||||
hooks:
|
||||
- id: eslint
|
||||
files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
|
||||
types: [file]
|
||||
additional_dependencies:
|
||||
- eslint
|
||||
- eslint-plugin-svelte3
|
||||
- typescript
|
||||
- '@typescript-eslint/eslint-plugin'
|
||||
- '@typescript-eslint/parser'
|
||||
- eslint@8.25.0
|
||||
- typescript@4.8.4
|
||||
- '@typescript-eslint/eslint-plugin@5.39.0'
|
||||
- '@typescript-eslint/parser@5.39.0'
|
||||
|
||||
# Commented out until mdformat-myst supports custom extensions
|
||||
# See https://github.com/executablebooks/mdformat-myst/pull/9
|
||||
|
||||
156
CONTRIBUTING.md
156
CONTRIBUTING.md
@@ -9,7 +9,7 @@ Thank you for wanting to contribute to the PyScript project!
|
||||
* [Reporting bugs](#reporting-bugs)
|
||||
* [Reporting security issues](#reporting-security-issues)
|
||||
* [Asking questions](#asking-questions)
|
||||
* [Setting up your local environment](#setting-up-your-local-environment)
|
||||
* [Setting up your local environment and developing](#setting-up-your-local-environment-and-developing)
|
||||
* [Places to start](#places-to-start)
|
||||
* [Submitting a change](#submitting-a-change)
|
||||
* [License terms for contributions](#license-terms-for-contributions)
|
||||
@@ -41,149 +41,6 @@ If you aren't confident that it is appropriate to submit a security issue using
|
||||
|
||||
If you have questions about the project, using PyScript, or anything else, please ask in the [PyScript forum](https://community.anaconda.cloud/c/tech-topics/pyscript).
|
||||
|
||||
## Setting up your local environment
|
||||
|
||||
* Fork the repository - [quicklink](https://github.com/pyscript/pyscript/fork)
|
||||
|
||||
* Clone your fork of the project
|
||||
|
||||
```
|
||||
git clone https://github.com/<your username>/pyscript
|
||||
```
|
||||
|
||||
* Add the original project as your upstream (this will allow you to pull the latest changes)
|
||||
|
||||
```
|
||||
git remote add upstream git@github.com:pyscript/pyscript.git
|
||||
```
|
||||
|
||||
* cd into the `pyscriptjs` folder using the line below in your terminal (if your terminal is already in pyscript then use **cd pyscriptjs** instead)
|
||||
```
|
||||
cd pyscript/pyscriptjs
|
||||
```
|
||||
* Install the dependencies with the command below
|
||||
|
||||
```
|
||||
make setup
|
||||
```
|
||||
**NOTE**: If `make setup` gives a node/npm version required error then go to [troubleshooting](https://github.com/pyscript/pyscript/blob/main/TROUBLESHOOTING.md)
|
||||
|
||||
* You can also run the examples locally by running the command below in your terminal
|
||||
```
|
||||
make examples
|
||||
```
|
||||
* Run ***npm run dev*** to build and run the dev server. This will also watch for changes and rebuild when a file is saved.
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
**NOTE**: To access your local build paste `http://localhost:8080` into your browser
|
||||
|
||||
|
||||
Now that node and npm have both been updated `make setup` should work, and you can continue [setting up your local environment](#setting-up-your-local-environment) without problems (hopefully).
|
||||
|
||||
### Developing
|
||||
|
||||
* First, make sure you are using the latest version of the pyscript main branch
|
||||
|
||||
```
|
||||
git pull upstream main
|
||||
```
|
||||
|
||||
* Update your fork with the latest changes
|
||||
|
||||
```
|
||||
git push origin main
|
||||
```
|
||||
|
||||
* Activate the conda environment (this environment will contain all the necessary dependencies)
|
||||
|
||||
```
|
||||
conda activate pyscriptjs/env/
|
||||
```
|
||||
**NOTE**: We are assuming you are in the root folder. If you are in the pyscriptjs you can run `conda activate env/` instead.
|
||||
|
||||
* Install pre-commit (you only need to do this once)
|
||||
|
||||
```
|
||||
pre-commit install
|
||||
```
|
||||
**NOTE**: On first run, pre-commit installs a bunch of hooks that will be run when you commit changes to your branch - this will make sure that your code is following our style (it will also lint your code automatically).
|
||||
|
||||
* Create a branch for the issue that you want to work on
|
||||
|
||||
```
|
||||
git checkout -b <your branch name>
|
||||
```
|
||||
|
||||
* Work on your change
|
||||
|
||||
**NOTE**: If you are working on a python file, you may encounter linting issues when pre-commit runs. Pyscript uses [black](https://black.readthedocs.io/en/stable/) to fix any linting problems automatically. All you need to do is add the changes again and commit using your previous commit message (the previous one that failed didn't complete due to black formatting files).
|
||||
|
||||
* Run tests before pushing the changes
|
||||
|
||||
```
|
||||
make tests
|
||||
```
|
||||
|
||||
* When you make changes locally, double check that your contribution follows the PyScript formatting rules by running `npm run lint`. Note that in this case you're looking for the errors, <u>**NOT**</u> the warnings (Unless the warning is created by a local change). If an error is found by lint you should fix it <u>**before**</u> creating a pull request
|
||||
|
||||
#### Rebasing changes
|
||||
|
||||
Sometimes you might be asked to rebase main into your branch. You can do such by:
|
||||
|
||||
* Checkout into your main branch and pull the upstream changes
|
||||
|
||||
```
|
||||
git checkout main
|
||||
git pull upstream main
|
||||
```
|
||||
|
||||
* Checkout your branch and rebase on main
|
||||
|
||||
```
|
||||
git rebase -i main
|
||||
```
|
||||
|
||||
If you have conflicts, you must fix them by comparing yours and incoming changes. Your editor can probably help you with this, but do ask for help if you need it!
|
||||
|
||||
* Once all conflicts have been fixed
|
||||
|
||||
```
|
||||
git rebase --continue
|
||||
```
|
||||
**NOTE**: You may see more conflicts that you need to address until all are resolved.
|
||||
|
||||
* Force push the fixed conflicts
|
||||
|
||||
```
|
||||
git push -f origin <your branch name>
|
||||
```
|
||||
|
||||
|
||||
### Building the docs
|
||||
|
||||
To build the documentation locally first make sure you are in the `docs` directory.
|
||||
|
||||
You'll need `make` and `conda` installed in your machine. The rest of the environment should be automatically download and created for you once you use the command:
|
||||
|
||||
```
|
||||
make setup
|
||||
```
|
||||
|
||||
Use `conda activate $environment_name` to activate your environment.
|
||||
|
||||
To add new information to the documentation make sure you conform with PyScript's code of conduct and with the general principles of Diataxis. Don't worry about reading too much on it, just do your best to keep your contributions on the correct axis.
|
||||
|
||||
Write your documentation files using [Markedly Structured Text](https://myst-parser.readthedocs.io/en/latest/syntax/optional.html), which is very similar to vanilla Markdown but with some addons to create the documentation infrastructure.
|
||||
|
||||
Once done, initialize a server to check your work:
|
||||
|
||||
```
|
||||
make livehtml
|
||||
```
|
||||
|
||||
Visible here: http:///127.0.0.1:8000
|
||||
|
||||
## Places to start
|
||||
|
||||
If you would like to contribute to PyScript, but you aren't sure where to begin, here are some suggestions.
|
||||
@@ -193,21 +50,16 @@ If you would like to contribute to PyScript, but you aren't sure where to begin,
|
||||
* **Look over the open pull requests.** Do you have comments or suggestions for the proposed changes? Add them.
|
||||
* **Check out the examples.** Is there a use case that would be good to have sample code for? Create an example for it.
|
||||
|
||||
# Submitting a change
|
||||
## Setting up your local environment and developing
|
||||
|
||||
All contributions must be licensed Apache 2.0, and all files must have a copy of the boilerplate license comment (can be copied from an existing file).
|
||||
If you would like to contribute to PyScript, you will need to set up a local development environment. The [following instructions](https://docs.pyscript.net/latest/development/setting-up-environment.html) will help you get started.
|
||||
|
||||
To create a change for PyScript, you can follow the process described [here](https://docs.github.com/en/get-started/quickstart/contributing-to-projects).
|
||||
|
||||
* Follow the steps in [setting your local environment](#setting-up-your-local-environment) and [developing](#developing)
|
||||
* Make the changes you would like (don't forget to test them with `make test`)
|
||||
* Open a pull request back to the PyScript project and address any comments/questions from the maintainers and other contributors.
|
||||
You can also read the [developing process](https://docs.pyscript.net/latest/development/developing.html) and how to rebase your branch with the latest changes.
|
||||
|
||||
## License terms for contributions
|
||||
|
||||
This Project welcomes contributions, suggestions, and feedback. All contributions, suggestions, and feedback you submitted are accepted under the [Apache 2.0](./LICENSE) license. You represent that if you do not own copyright in the code that you have the authority to submit it under the [Apache 2.0](./LICENSE) license. All feedback, suggestions, or contributions are not confidential.
|
||||
|
||||
|
||||
## Becoming a maintainer
|
||||
|
||||
Contributors are invited to be maintainers of the project by demonstrating good decision making in their contributions, a commitment to the goals of the project, and consistent adherence to the [code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md). New maintainers are invited by a 3/4 vote of the existing maintainers.
|
||||
|
||||
@@ -14,7 +14,8 @@ This document lists the Maintainers of the Project. Maintainers may be added onc
|
||||
| Madhur Tandon | Anaconda, Inc |
|
||||
| Ted Patrick | Anaconda, Inc |
|
||||
| Jeff Glass | --- |
|
||||
| --- | --- |
|
||||
| Paul Everitt | --- |
|
||||
| Fabio Rosado | --- |
|
||||
|
||||
______________________________________________________________________
|
||||
|
||||
|
||||
@@ -35,9 +35,11 @@ Read the [contributing guide](CONTRIBUTING.md) to learn about our development pr
|
||||
|
||||
## Resources
|
||||
|
||||
* [Official docs](https://docs.pyscript.net)
|
||||
* [Discussion board](https://community.anaconda.cloud/c/tech-topics/pyscript)
|
||||
* [Home Page](https://pyscript.net/)
|
||||
* [Blog Post](https://engineering.anaconda.com/2022/04/welcome-pyscript.html)
|
||||
* [Discord Channel](https://discord.gg/BYB2kvyFwm)
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
12
docs/_static/examples/what-is-pyscript.html
vendored
12
docs/_static/examples/what-is-pyscript.html
vendored
@@ -16,10 +16,6 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<py-env>
|
||||
- numpy
|
||||
- matplotlib
|
||||
</py-env>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -29,6 +25,14 @@
|
||||
<p style='font-family: monospace sans-serif;'><big><big><big><big>❰py❱</big></big></big></big></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"numpy",
|
||||
"matplotlib"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
BIN
docs/_static/images/avatar.jpg
vendored
BIN
docs/_static/images/avatar.jpg
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
16
docs/_static/images/pyscript.svg
vendored
Normal file
16
docs/_static/images/pyscript.svg
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg width="100%" viewBox="0 0 2057 974" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#fda703" stroke="none" transform="translate(0 100)">
|
||||
<path
|
||||
d="M 1092.534 158.364 C 1095.764 169.589 1102.374 179.795 1107.224 190.364 C 1119.104 216.243 1131.874 241.728 1144.274 267.364 C 1179.204 339.56 1214.064 411.844 1248.314 484.364 C 1260.474 510.112 1273.154 535.617 1285.314 561.364 C 1290.014 571.319 1299.154 583.378 1300.684 594.364 C 1301.444 599.785 1296.944 606.478 1294.984 611.364 C 1289.004 626.289 1282.004 640.557 1273.734 654.364 C 1265.284 668.483 1256.704 683.257 1245.444 695.364 C 1237.304 704.123 1228.664 712.851 1218.534 719.31 C 1176.654 746.023 1130.104 739.811 1084.534 729.364 L 1084.534 796.364 C 1137.744 803.235 1191.744 806.988 1241.534 782.094 C 1291.224 757.25 1321.144 708.125 1345.794 660.364 C 1391.424 571.949 1425.474 477.074 1463.954 385.364 C 1484.774 335.759 1505.144 285.968 1525.954 236.364 C 1532.804 220.048 1539.454 203.643 1546.384 187.364 C 1550.314 178.14 1555.824 168.274 1557.534 158.364 L 1503.534 158.364 C 1498.104 158.364 1487.624 156.363 1482.924 159.392 C 1477.284 163.031 1474.824 176.375 1472.254 182.364 C 1463.294 203.198 1455.174 224.401 1446.524 245.364 C 1422.624 303.289 1398.764 361.248 1375.334 419.364 C 1365.024 444.923 1349.894 471.569 1343.534 498.364 L 1341.534 498.364 L 1326.784 467.364 L 1300.794 414.364 L 1219.784 248.364 L 1188.284 184.364 L 1174.894 159.392 L 1152.534 158.364 L 1092.534 158.364 Z">
|
||||
</path>
|
||||
<path
|
||||
d="M 100.534 391.364 C 109.625 398.897 122.97 403.329 133.534 408.611 L 197.534 440.611 L 405.534 544.611 C 436.606 560.147 467.458 576.073 498.534 591.611 C 511.98 598.334 527.713 609.722 542.534 612.364 L 542.534 563.364 L 541.506 543.754 L 518.534 531.117 L 460.534 502.117 L 307.534 425.117 L 240.534 391.364 L 307.534 358.117 L 459.534 282.611 L 518.534 253.117 L 541.506 240.727 L 542.534 221.364 L 542.534 171.364 C 527.073 174.12 510.565 186.102 496.534 193.117 L 398.534 242.117 L 200.534 341.117 C 167.367 357.701 132.553 372.676 100.534 391.364 Z">
|
||||
</path>
|
||||
<path
|
||||
d="M 1600.534 171.364 L 1600.534 220.364 C 1600.534 225.605 1598.654 235.422 1601.564 239.974 C 1605.194 245.662 1617.614 249.159 1623.534 252.117 L 1680.534 280.611 C 1730.924 305.806 1781.134 331.41 1831.534 356.611 C 1853.974 367.829 1877.404 384.412 1901.534 391.364 L 1901.534 393.364 C 1875.624 400.829 1849.674 418.049 1825.534 430.117 L 1679.534 503.117 C 1661.964 511.903 1644.564 521.567 1626.534 529.364 C 1619.964 532.203 1605.494 536.596 1601.564 542.754 C 1598.654 547.306 1600.534 557.122 1600.534 562.364 L 1600.534 612.364 L 1655.534 585.611 L 1763.534 531.611 L 1947.534 439.611 L 2041.534 392.364 C 2031.474 382.202 2012.324 376.511 1999.534 370.117 L 1907.534 324.117 L 1701.534 221.117 L 1635.534 188.117 C 1624.294 182.495 1612.624 174.847 1600.534 171.364 Z">
|
||||
</path>
|
||||
<path
|
||||
d="M 704.534 384.364 C 704.534 374.13 702.051 360.064 705.503 350.364 C 710.589 336.071 722.183 321.459 731.164 309.364 C 737.516 300.809 743.992 292.429 750.959 284.364 C 786.81 242.863 854.576 189.488 905.519 239.403 C 931.848 265.201 939.204 301.065 941.623 336.364 C 946.631 409.413 926.04 491.22 860.534 532.928 C 811.862 563.917 757.912 556.382 704.534 545.364 Z M 705.534 259.364 L 704.534 259.364 L 704.534 158.364 L 628.534 158.364 L 628.534 789.364 L 704.534 789.364 L 704.534 613.364 C 728.157 613.38 751.915 618.29 775.534 619.325 C 816.206 621.106 857.009 614.508 893.534 596.116 C 989.069 548.011 1025.008 434.77 1024.535 335.364 C 1024.298 285.5 1013.766 232.452 979.364 194.364 C 968.209 182.013 954.851 171.287 940.534 162.816 C 875.388 124.27 794.704 158.21 745.534 207.364 C 730.887 222.007 713.84 240.114 705.534 259.364 Z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -54,7 +54,7 @@ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "_env", "README.md"]
|
||||
#
|
||||
html_theme = "pydata_sphinx_theme"
|
||||
|
||||
html_logo = "_static/images/avatar.jpg"
|
||||
html_logo = "_static/images/pyscript.svg"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
|
||||
30
docs/development/deprecation-cycle.md
Normal file
30
docs/development/deprecation-cycle.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Deprecation Cycle
|
||||
|
||||
Pyscript is under heavy development, which means that some things may change, and some features might need to be deprecated so you can use different alternative implementations.
|
||||
|
||||
This page describes the deprecation cycle for pyscript.
|
||||
|
||||
## Deprecation Steps
|
||||
|
||||
1. Remove usage of deprecated features from all examples.
|
||||
2. Add warnings to all elements/features marked for deprecation.
|
||||
3. Release a new version of pyscript with the deprecation warnings.
|
||||
4. Next release, remove the deprecated features.
|
||||
|
||||
## Deprecation Warnings
|
||||
|
||||
Deprecation warnings are added to the codebase using the `showWarning` function from the `pyscriptjs.utils` module.
|
||||
|
||||
This function creates a warning banner on the page if any of the deprecated features was used. You can use HTML to write the message; ideally, you should provide an alternative to the deprecated feature.
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
import {showWarning} from './utils'
|
||||
|
||||
showWarning(`
|
||||
<p>
|
||||
The <code>py-deprecated</code> tag is deprecated. Please use the <code>py-actual</code> tag instead. Please refer to <a href="#">this documentation page</a> for more information.
|
||||
</p>
|
||||
`, "html")
|
||||
```
|
||||
203
docs/development/developing.md
Normal file
203
docs/development/developing.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Developing Process
|
||||
|
||||
This document is intended to help you get started developing for pyscript, it assumes that you have [setup your development environment](setting-up-environment.md).
|
||||
|
||||
* First, make sure you are using the latest version of the pyscript main branch
|
||||
|
||||
```
|
||||
git pull upstream main
|
||||
```
|
||||
|
||||
* Update your fork with the latest changes
|
||||
|
||||
```
|
||||
git push origin main
|
||||
```
|
||||
|
||||
* Activate the conda environment (this environment will contain all the necessary dependencies)
|
||||
|
||||
```
|
||||
conda activate pyscriptjs/env/
|
||||
```
|
||||
**NOTE**: We are assuming you are in the root folder. If you are in the pyscriptjs you can run `conda activate env/` instead.
|
||||
|
||||
* Install pre-commit (you only need to do this once)
|
||||
|
||||
```
|
||||
pre-commit install
|
||||
```
|
||||
**NOTE**: On first run, pre-commit installs a bunch of hooks that will be run when you commit changes to your branch - this will make sure that your code is following our style (it will also lint your code automatically).
|
||||
|
||||
* Create a branch for the issue that you want to work on
|
||||
|
||||
```
|
||||
git checkout -b <your branch name>
|
||||
```
|
||||
|
||||
* Work on your change
|
||||
|
||||
**NOTE**: If you are working on a python file, you may encounter linting issues when pre-commit runs. Pyscript uses [black](https://black.readthedocs.io/en/stable/) to fix any linting problems automatically. All you need to do is add the changes again and commit using your previous commit message (the previous one that failed didn't complete due to black formatting files).
|
||||
|
||||
* Run tests before pushing the changes
|
||||
|
||||
```
|
||||
make tests
|
||||
```
|
||||
|
||||
* When you make changes locally, double check that your contribution follows the PyScript formatting rules by running `npm run lint`. Note that in this case you're looking for the errors, <u>**NOT**</u> the warnings (Unless the warning is created by a local change). If an error is found by lint you should fix it <u>**before**</u> creating a pull request
|
||||
|
||||
|
||||
## Rebasing changes
|
||||
|
||||
Sometimes you might be asked to rebase main into your branch. Please refer to this [section on git rebase from GitHub docs](https://docs.github.com/en/get-started/using-git/about-git-rebase).
|
||||
|
||||
If you need help with anything, feel free to reach out and ask for help!
|
||||
|
||||
|
||||
## pytest quick guide
|
||||
|
||||
We make a heavy usage of `pytest`. Here is a quick guide and collection of
|
||||
useful options:
|
||||
|
||||
- To run all tests in the current directory and subdirectories: `pytest`
|
||||
|
||||
- To run tests in a specific directory or file: `pytest path/to/dir/test_foo.py`
|
||||
|
||||
- `-s`: disables output capturing
|
||||
|
||||
- `--pdb`: in case of exception, enter a `(Pdb)` prompt so that you can
|
||||
inspect what went wrong.
|
||||
|
||||
- `-v`: verbose mode
|
||||
|
||||
- `-x`: stop the execution as soon as one test fails
|
||||
|
||||
- `-k foo`: run only the tests whose full name contains `foo`
|
||||
|
||||
- `-k 'foo and bar'`
|
||||
|
||||
- `-k 'foo and not bar'`
|
||||
|
||||
|
||||
## Running integration tests under pytest
|
||||
|
||||
`make test` is useful to run all the tests, but during the development is
|
||||
useful to have more control on how tests are run. The following guide assumes
|
||||
that you are in the directory `pyscriptjs/tests/integration/`.
|
||||
|
||||
#### To run all the integration tests, single or multi core
|
||||
|
||||
```
|
||||
$ pytest -xv
|
||||
...
|
||||
|
||||
test_00_support.py::TestSupport::test_basic[chromium] PASSED [ 0%]
|
||||
test_00_support.py::TestSupport::test_console[chromium] PASSED [ 1%]
|
||||
test_00_support.py::TestSupport::test_check_js_errors_simple[chromium] PASSED [ 2%]
|
||||
test_00_support.py::TestSupport::test_check_js_errors_expected[chromium] PASSED [ 3%]
|
||||
test_00_support.py::TestSupport::test_check_js_errors_expected_but_didnt_raise[chromium] PASSED [ 4%]
|
||||
test_00_support.py::TestSupport::test_check_js_errors_multiple[chromium] PASSED [ 5%]
|
||||
...
|
||||
```
|
||||
|
||||
`-x` means "stop at the first failure". `-v` means "verbose", so that you can
|
||||
see all the test names one by one. We try to keep tests in a reasonable order,
|
||||
from most basic to most complex. This way, if you introduced some bug in very
|
||||
basic things, you will notice immediately.
|
||||
|
||||
If you have the `pytest-xdist` plugin installed, you can run all the
|
||||
integration tests on 4 cores in parallel:
|
||||
```
|
||||
$ pytest -n 4
|
||||
```
|
||||
|
||||
#### To run a single test, headless
|
||||
```
|
||||
$ pytest test_01_basic.py -k test_pyscript_hello -s
|
||||
...
|
||||
[ 0.00 page.goto ] pyscript_hello.html
|
||||
[ 0.01 request ] 200 - fake_server - http://fake_server/pyscript_hello.html
|
||||
...
|
||||
[ 0.17 console.info ] [py-loader] Downloading pyodide-0.21.3...
|
||||
[ 0.18 request ] 200 - CACHED - https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js
|
||||
...
|
||||
[ 3.59 console.info ] [pyscript/main] PyScript page fully initialized
|
||||
[ 3.60 console.log ] hello pyscript
|
||||
```
|
||||
|
||||
`-k` selects tests by pattern matching as described above. `-s` instructs
|
||||
`pytest` to show the output to the terminal instead of capturing it. In the
|
||||
output you can see various useful things, including network requests and JS
|
||||
console messages.
|
||||
|
||||
#### To run a single test, headed
|
||||
```
|
||||
$ pytest test_01_basic.py -k test_pyscript_hello -s --headed
|
||||
...
|
||||
```
|
||||
|
||||
Same as above, but with `--headed` the browser is shown in a window, and you
|
||||
can interact with it. The browser uses a fake server, which means that HTTP
|
||||
requests are cached.
|
||||
|
||||
Unfortunately, in this mode source maps does not seem to work, and you cannot
|
||||
debug the original typescript source code. This seems to be a bug in
|
||||
playwright, for which we have a workaround:
|
||||
|
||||
```
|
||||
$ pytest test_01_basic.py -k test_pyscript_hello -s --headed --no-fake-server
|
||||
...
|
||||
```
|
||||
|
||||
As the name implies, `-no-fake-server` disables the fake server: HTTP requests
|
||||
are not cached, but source-level debugging works.
|
||||
|
||||
Finally:
|
||||
|
||||
```
|
||||
$ pytest test_01_basic.py -k test_pyscript_hello -s --dev
|
||||
...
|
||||
```
|
||||
|
||||
`--dev` implies `--headed --no-fake-server`. In addition, it also
|
||||
automatically open chrome dev tools.
|
||||
|
||||
|
||||
#### Fake server, HTTP cache
|
||||
|
||||
By default, our test machinery uses a playwright router which intercepts and
|
||||
cache HTTP requests, so that for example you don't have to download pyodide
|
||||
again and again. This also enables the possibility of running tests in
|
||||
parallel on multiple cores.
|
||||
|
||||
The cache is stored using the `pytest-cache` plugin, which means that it
|
||||
survives across sessions.
|
||||
|
||||
If you want to temporarily disable the cache, the easiest thing is to use
|
||||
`--no-fake-server`, which bypasses it completely.
|
||||
|
||||
If you want to clear the cache, you can use the special option
|
||||
`--clear-http-cache`:
|
||||
|
||||
```
|
||||
$ pytest --clear-http-cache
|
||||
...
|
||||
-------------------- SmartRouter HTTP cache --------------------
|
||||
Requests found in the cache:
|
||||
https://raw.githubusercontent.com/pyscript/pyscript/main/README.md
|
||||
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/repodata.json
|
||||
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.js
|
||||
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/micropip-0.1-py3-none-any.whl
|
||||
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.data
|
||||
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js
|
||||
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.wasm
|
||||
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide_py.tar
|
||||
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyparsing-3.0.9-py3-none-any.whl
|
||||
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/distutils.tar
|
||||
https://cdn.jsdelivr.net/pyodide/v0.21.3/full/packaging-21.3-py3-none-any.whl
|
||||
Cache cleared
|
||||
```
|
||||
|
||||
**NOTE**: this works only if you are inside `tests/integration`, or if you
|
||||
explicitly specify `tests/integration` from the command line. This is due to
|
||||
how `pytest` decides to search for and load the various `conftest.py`.
|
||||
12
docs/development/index.md
Normal file
12
docs/development/index.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Development
|
||||
|
||||
This section contains various topics related to pyscript development.
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
---
|
||||
setting-up-environment
|
||||
deprecation-cycle
|
||||
developing
|
||||
```
|
||||
69
docs/development/setting-up-environment.md
Normal file
69
docs/development/setting-up-environment.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Setting up your development environment
|
||||
|
||||
* Fork the repository - [quicklink](https://github.com/pyscript/pyscript/fork)
|
||||
|
||||
* Clone your fork of the project
|
||||
|
||||
```
|
||||
git clone https://github.com/<your username>/pyscript
|
||||
```
|
||||
|
||||
* Add the original project as your upstream (this will allow you to pull the latest changes)
|
||||
|
||||
```sh
|
||||
git remote add upstream git@github.com:pyscript/pyscript.git
|
||||
```
|
||||
|
||||
* cd into the `pyscriptjs` folder using the line below in your terminal (if your terminal is already in pyscript then use **cd pyscriptjs** instead)
|
||||
|
||||
```
|
||||
cd pyscript/pyscriptjs
|
||||
```
|
||||
|
||||
* Install the dependencies with the command below (you must have node >=16)
|
||||
|
||||
```
|
||||
make setup
|
||||
```
|
||||
**NOTE**: If `make setup` gives a node/npm version required error then go to [troubleshooting](https://github.com/pyscript/pyscript/blob/main/TROUBLESHOOTING.md)
|
||||
|
||||
* You can also run the examples locally by running the command below in your terminal
|
||||
|
||||
```
|
||||
make examples
|
||||
```
|
||||
|
||||
* Run ***npm run dev*** to build and run the dev server. This will also watch for changes and rebuild when a file is saved.
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
**NOTE**: To access your local build paste `http://localhost:8080` into your browser
|
||||
|
||||
|
||||
Now that node and npm have both been updated `make setup` should work, and you can continue [setting up your local environment](setting-up-environment.md) without problems (hopefully).
|
||||
|
||||
|
||||
## Setting up and building the docs
|
||||
|
||||
To build the documentation locally first make sure you are in the `docs` directory.
|
||||
|
||||
You'll need `make` and `conda` installed in your machine. The rest of the environment should be automatically download and created for you once you use the command:
|
||||
|
||||
```
|
||||
make setup
|
||||
```
|
||||
|
||||
Use `conda activate $environment_name` to activate your environment.
|
||||
|
||||
To add new information to the documentation make sure you conform with PyScript's code of conduct and with the general principles of Diataxis. Don't worry about reading too much on it, just do your best to keep your contributions on the correct axis.
|
||||
|
||||
Write your documentation files using [Markedly Structured Text](https://myst-parser.readthedocs.io/en/latest/syntax/optional.html), which is very similar to vanilla Markdown but with some addons to create the documentation infrastructure.
|
||||
|
||||
Once done, initialize a server to check your work:
|
||||
|
||||
```
|
||||
make livehtml
|
||||
```
|
||||
|
||||
Visible here: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||
3
docs/error.md
Normal file
3
docs/error.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Not found!
|
||||
|
||||
The page that you looked for could not be found. If you think this is a mistake, please [open an issue](https://github.com/pyscript/pyscript/issues/new/)
|
||||
35
docs/guides/asyncio.md
Normal file
35
docs/guides/asyncio.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Using Async/Await and Asyncio
|
||||
|
||||
## {bdg-warning-line}`Deprecated` Implicit Coroutine Scheduling / Top-Level Await
|
||||
|
||||
In PyScript versions 2022.09.1 and earlier, \<py-script\> tags could be written in a way that enabled "Implicit Coroutine Scheduling." The keywords `await`, `async for` and `await with` were permitted to be used outside of `async` functions. Any \<py-script\> tags with these keywords at the top level were compiled into coroutines and automatically scheuled to run in the browser's event loop. This functionality was deprecated, and these keywords are no longer allowed outside of `async` functions.
|
||||
|
||||
To transition code from using top-level await statements to the currently-acceptable syntax, wrap the code into a coroutine using `async def()` and schedule it to run in the browser's event looping using `asyncio.ensure_future()` or `asyncio.create_task()`.
|
||||
|
||||
The following two pieces of code are functionally equivalent - the first only works in versions 2022.09.1, the latter is the currently acceptable equivalent.
|
||||
|
||||
```python
|
||||
# This version is deprecated, since
|
||||
# it uses 'await' outside an async function
|
||||
<py-script>
|
||||
import asyncio
|
||||
|
||||
for i in range(3):
|
||||
print(i)
|
||||
await asyncio.sleep(1)
|
||||
</py-script>
|
||||
```
|
||||
|
||||
```python
|
||||
# This version is acceptable
|
||||
<py-script>
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
for i in range(3):
|
||||
print(i)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
asyncio.ensure_future(main())
|
||||
</py-script>
|
||||
```
|
||||
@@ -14,14 +14,18 @@ from `PyScript` using Python, since currently, the common tools such as `request
|
||||
|
||||
The `fetch` API is a modern way to make HTTP requests. It is available in all modern browsers, and in Pyodide.
|
||||
|
||||
Although there are two ways to use `fetch`, 1) using `JavaScript` from `PyScript`, and 2) using Pyodide's Python wrapper,
|
||||
`Pyodide.http.pyfetch`, this example will only show how to use the Python wrapper. Still, the
|
||||
Although there are two ways to use `fetch`:
|
||||
1) using `JavaScript` from `PyScript`
|
||||
2) using Pyodide's Python wrapper,
|
||||
`pyodide.http.pyfetch`
|
||||
|
||||
This example will only show how to use the Python wrapper. Still, the
|
||||
[fetch documentation](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters) is a useful reference, as its
|
||||
parameters can be called from Python using the `pyfetch` wrapper.
|
||||
|
||||
## Pyodide.http, pyfetch, and FetchResponse
|
||||
|
||||
[Pyodide.http module](https://pyodide.org/en/stable/usage/api/python-api/http.html#module-pyodide.http) is a Python API
|
||||
The [pyodide.http module](https://pyodide.org/en/stable/usage/api/python-api/http.html#module-pyodide.http) is a Python API
|
||||
for dealing with HTTP requests. It provides the `pyfetch` function as a wrapper for the `fetch` API,
|
||||
which returns a `FetchResponse` object whenever a request is made. Extra keyword arguments can be passed to `pyfetch`
|
||||
which will be passed to the `fetch` API.
|
||||
@@ -92,13 +96,13 @@ concluding html code.
|
||||
<title>GET, POST, PUT, DELETE example</title>
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link rel="stylesheet" href="../build/pyscript.css" />
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
<script defer src="../build/pyscript.js"></script>
|
||||
<py-env>
|
||||
- paths:
|
||||
- /request.py
|
||||
</py-env>
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["/request.py"]
|
||||
</py-config>
|
||||
</head>
|
||||
|
||||
<body><p>
|
||||
@@ -106,34 +110,33 @@ concluding html code.
|
||||
Here is the output of your request:
|
||||
</p>
|
||||
<py-script>
|
||||
```
|
||||
```python
|
||||
import asyncio # important!!
|
||||
import json
|
||||
from request import request # import our request function.
|
||||
import asyncio
|
||||
import json
|
||||
from request import request # import our request function.
|
||||
|
||||
baseurl = "https://jsonplaceholder.typicode.com/"
|
||||
async def main():
|
||||
baseurl = "https://jsonplaceholder.typicode.com"
|
||||
|
||||
# GET
|
||||
headers = {"Content-type": "application/json"}
|
||||
response = await request(baseurl+"posts/2", method="GET", headers=headers)
|
||||
print(f"GET request=> status:{response.status}, json:{await response.json()}")
|
||||
# GET
|
||||
headers = {"Content-type": "application/json"}
|
||||
response = await request(f"{baseurl}/posts/2", method="GET", headers=headers)
|
||||
print(f"GET request=> status:{response.status}, json:{await response.json()}")
|
||||
|
||||
# POST
|
||||
body = json.dumps({"title": "test_title", "body": "test body", "userId": 1})
|
||||
new_post = await request(baseurl+"posts", body=body, method="POST", headers=headers)
|
||||
print(f"POST request=> status:{new_post.status}, json:{await new_post.json()}")
|
||||
# POST
|
||||
body = json.dumps({"title": "test_title", "body": "test body", "userId": 1})
|
||||
new_post = await request(f"{baseurl}/posts", body=body, method="POST", headers=headers)
|
||||
print(f"POST request=> status:{new_post.status}, json:{await new_post.json()}")
|
||||
|
||||
# PUT
|
||||
body = json.dumps({"id": 1, "title": "test_title", "body": "test body", "userId": 2})
|
||||
new_post = await request(baseurl+"posts/1", body=body, method="PUT", headers=headers)
|
||||
print(f"PUT request=> status:{new_post.status}, json:{await new_post.json()}")
|
||||
# PUT
|
||||
body = json.dumps({"id": 1, "title": "test_title", "body": "test body", "userId": 2})
|
||||
new_post = await request(f"{baseurl}/posts/1", body=body, method="PUT", headers=headers)
|
||||
print(f"PUT request=> status:{new_post.status}, json:{await new_post.json()}")
|
||||
|
||||
# DELETE
|
||||
new_post = await request(baseurl+"posts/1", method="DELETE", headers=headers)
|
||||
print(f"DELETE request=> status:{new_post.status}, json:{await new_post.json()}")
|
||||
```
|
||||
```html
|
||||
# DELETE
|
||||
new_post = await request(f"{baseurl}/posts/1", method="DELETE", headers=headers)
|
||||
print(f"DELETE request=> status:{new_post.status}, json:{await new_post.json()}")
|
||||
|
||||
asyncio.ensure_future(main())
|
||||
</py-script>
|
||||
|
||||
<div>
|
||||
@@ -153,8 +156,8 @@ print(f"DELETE request=> status:{new_post.status}, json:{await new_post.json()}"
|
||||
```
|
||||
|
||||
## Explanation
|
||||
### `py-env` tag for importing our Python code
|
||||
The very first thing to notice is the `py-env` tag. This tag is used to import Python files into the `PyScript`.
|
||||
### `py-config` tag for importing our Python code
|
||||
The very first thing to notice is the `py-config` tag. This tag is used to import Python files into the `PyScript`.
|
||||
In this case, we are importing the `request.py` file, which contains the `request` function we wrote above.
|
||||
|
||||
### `py-script` tag for making async HTTP requests.
|
||||
@@ -181,7 +184,7 @@ HTTP requests are defined by standards-setting bodies in [RFC 1945](https://www.
|
||||
|
||||
# Conclusion
|
||||
This tutorial demonstrates how to make HTTP requests using `pyfetch` and the `FetchResponse` objects. Importing Python
|
||||
code/files into the `PyScript` using the `py-env` tag is also covered.
|
||||
code/files into the `PyScript` using the `py-config` tag is also covered.
|
||||
|
||||
Although a simple example, the principals here can be used to create complex web applications inside of `PyScript`,
|
||||
or load data into `PyScript` for use by an application, all served as a static HTML page, which is pretty amazing!
|
||||
@@ -195,7 +198,7 @@ await pyodide.http.pyfetch(url: str, **kwargs: Any) -> FetchResponse
|
||||
```
|
||||
Use `pyfetch` to make HTTP requests in `PyScript`. This is a wrapper around the `fetch` API. Returns a `FetchResponse`.
|
||||
|
||||
### [`pyfetch` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.pyfetch)
|
||||
- [`pyfetch` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.pyfetch)
|
||||
|
||||
## pyodide.http.FetchResponse
|
||||
### Usage
|
||||
@@ -207,4 +210,4 @@ json = await response.json()
|
||||
Class for handling HTTP responses. This is a wrapper around the `JavaScript` fetch `Response`. Contains common (async)
|
||||
methods and properties for handling HTTP responses, such as `json()`, `url`, `status`, `headers`, etc.
|
||||
|
||||
### [`FetchResponse` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.FetchResponse)
|
||||
- [`FetchResponse` Docs.](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.FetchResponse)
|
||||
@@ -1,4 +1,4 @@
|
||||
# How-to guides
|
||||
# Guides
|
||||
|
||||
Welcome to the how-to documentation section for PyScript. If you've already
|
||||
gained some experience with PyScript before and just need practical guides
|
||||
@@ -16,4 +16,5 @@ caption: 'Contents:'
|
||||
---
|
||||
passing-objects
|
||||
http-requests
|
||||
asyncio
|
||||
```
|
||||
295
docs/guides/passing-objects.md
Normal file
295
docs/guides/passing-objects.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# How to Pass Objects from PyScript to Javascript (and Vice Versa)
|
||||
|
||||
[Pyodide](https://pyodide.org), the runtime that underlies PyScript, does a lot of work under the hood to translate objects between Python and JavaScript. This allows code in one language to access objects defined in the other.
|
||||
|
||||
This guide discusses how to pass objects between JavaScript and Python within PyScript. For more details on how Pyodide handles translating and proxying objects between the two languages, see the [Pyodide Type Translations Page](https://pyodide.org/en/stable/usage/type-conversions.html).
|
||||
|
||||
For our purposes, an 'object' is anything that can be bound to a variable (a number, string, object, [function](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function), etc).
|
||||
|
||||
## JavaScript to PyScript
|
||||
|
||||
We can use the syntax `from js import ...` to import JavaScript objects directly into PyScript. Simple JavaScript objects are converted to equivalent Python types; these are called [implicit conversions](https://pyodide.org/en/stable/usage/type-conversions.html#implicit-conversions). More complicated objects are wrapped in [JSProxy](https://pyodide.org/en/stable/usage/type-conversions.html) objects to make them behave like Python objects.
|
||||
|
||||
`import js` and `from js import ...` [in Pyodide](https://pyodide.org/en/stable/usage/type-conversions.html#type-translations-using-js-obj-from-py) get objects from the [JavaScript globalThis scope](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis), so keep the[ rules of JavaScript variable scoping](https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/) in mind.
|
||||
|
||||
```html
|
||||
<script>
|
||||
name = "Guido" //A JS variable
|
||||
|
||||
// Define a JS Function
|
||||
function addTwoNumbers(x, y){
|
||||
return x + y;
|
||||
}
|
||||
</script>
|
||||
```
|
||||
```python
|
||||
<py-script>
|
||||
# Import and use JS function and variable into Python
|
||||
from js import name, addTwoNumbers
|
||||
|
||||
print(f"Hello {name}")
|
||||
print("Adding 1 and 2 in Javascript: " + str(addTwoNumbers(1, 2)))
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## PyScript to JavaScript
|
||||
|
||||
### Using Pyodide's globals access
|
||||
|
||||
The [PyScript JavaScript module](../reference/modules/pyscript.md) exposes its underlying Pyodide runtime as `PyScript.runtime`, and maintains a reference to the [globals()](https://docs.python.org/3/library/functions.html#globals) dictionary of the Python namespace. Thus, any global variables in python are accessible in JavaScript at `PyScript.runtime.globals.get('my_variable_name')`
|
||||
|
||||
```html
|
||||
<body>
|
||||
<py-script>x = 42</py-script>
|
||||
|
||||
<button onclick="showX()">Click Me to Get 'x' from Python</button>
|
||||
<script>
|
||||
function showX(){
|
||||
console.log(`In Python right now, x = ${pyscript.runtime.globals.get('x')}`)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
Since [everything is an object](https://docs.python.org/3/reference/datamodel.html) in Python, this applies not only to user created variables, but also to classes, functions, built-ins, etc. If we want, we can even apply Python functions to JavaScript data and variables:
|
||||
|
||||
```html
|
||||
<body>
|
||||
<!-- Click this button to log 'Apple', 'Banana', 'Candy', 'Donut' by sorting in Python-->
|
||||
<button onclick="sortInPython(['Candy', 'Donut', 'Apple', 'Banana'])">Sort In Python And Log</button>
|
||||
<script>
|
||||
function sortInPython(data){
|
||||
js_sorted = pyscript.runtime.globals.get('sorted') //grab python's 'sorted' function
|
||||
const sorted_data = js_sorted(data) //apply the function to the 'data' argument
|
||||
for (const item of sorted_data){
|
||||
console.log(item)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
### Using JavaScript's eval()
|
||||
|
||||
There may be some situations where it isn't possible or ideal to use `PyScript.runtime.globals.get()` to retrieve a variable from the Pyodide global dictionary. For example, some JavaScript frameworks may take a function/Callable as an html attribute in a context where code execution isn't allowed (i.e. `get()` fails). In these cases, you can create JavaScript proxies of Python objects more or less "manually" using [JavaScript's eval() function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval), which executes a string as code much like [Python's eval()](https://docs.python.org/3/library/functions.html#eval).
|
||||
|
||||
First, we create a JS function `createObject` which takes an object and a string, then uses `eval()` to create a variable named after the string and bind it to that object. By calling this function from PyScript (where we have access to the Pyodide global namespace), we can bind JavaScript variables to Python objects without having direct access to that global namespace.
|
||||
|
||||
Include the following script tag anywhere in your html document:
|
||||
|
||||
```html
|
||||
<script>
|
||||
function createObject(object, variableName){
|
||||
//Bind a variable whose name is the string variableName
|
||||
// to the object called 'object'
|
||||
let execString = variableName + " = object"
|
||||
console.log("Running '" + execString + "'");
|
||||
eval(execString)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
This function takes a Python Object and creates a variable pointing to it in the JavaScript global scope.
|
||||
|
||||
### Exporting all Global Python Objects
|
||||
|
||||
We can use our new `createObject` function to "export" the entire Python global object dictionary as a JavaScript object:
|
||||
|
||||
```python
|
||||
<py-script>
|
||||
from js import createObject
|
||||
from pyodide.ffi import create_proxy
|
||||
createObject(create_proxy(globals()), "pyodideGlobals")
|
||||
</py-script>
|
||||
```
|
||||
This will make all Python global variables available in JavaScript with `pyodideGlobals.get('my_variable_name')`.
|
||||
|
||||
(Since PyScript tags evaluate _after_ all JavaScript on the page, we can't just dump a `console.log(...)` into a `<script>` tag, since that tag will evaluate before any PyScript has a chance to. We need to delay accessing the Python variable in JavaScript until after the Python code has a chance to run. The following example uses a button with `id="do-math"` to achieve this, but any method would be valid.)
|
||||
|
||||
```python
|
||||
<py-script>
|
||||
# create some Python objects:
|
||||
symbols = {'pi': 3.1415926, 'e': 2.7182818}
|
||||
|
||||
def rough_exponential(x):
|
||||
return symbols['e']**x
|
||||
|
||||
class Circle():
|
||||
def __init__(self, radius):
|
||||
self.radius = radius
|
||||
|
||||
@property
|
||||
def area(self):
|
||||
return symbols['pi'] * self.radius**2
|
||||
</py-script>
|
||||
```
|
||||
|
||||
```html
|
||||
<input type="button" value="Log Python Variables" id="do-math">
|
||||
<script>
|
||||
document.getElementById("do-math").addEventListener("click", () => {
|
||||
const exp = pyodideGlobals.get('rough_exponential');
|
||||
console.log(`e squared is about ${exp(2)}`);
|
||||
const c = pyodideGlobals.get('Circle')(4);
|
||||
console.log(`The area of c is ${c.area}`);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
#### Full example
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Exporting all Global Python Objects</title>
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<input type="button" value="Log Python Variables" id="do-math">
|
||||
<py-script>
|
||||
from js import createObject
|
||||
from pyodide.ffi import create_proxy
|
||||
|
||||
createObject(create_proxy(globals()), "pyodideGlobals")
|
||||
|
||||
# create some Python objects:
|
||||
symbols = {'pi': 3.1415926, 'e': 2.7182818}
|
||||
|
||||
def rough_exponential(x):
|
||||
return symbols['e']**x
|
||||
|
||||
class Circle():
|
||||
def __init__(self, radius):
|
||||
self.radius = radius
|
||||
|
||||
@property
|
||||
def area(self):
|
||||
return symbols['pi'] * self.radius**2
|
||||
</py-script>
|
||||
<script>
|
||||
function createObject(object, variableName){
|
||||
//Bind a variable whose name is the string variableName
|
||||
// to the object called 'object'
|
||||
let execString = variableName + " = object"
|
||||
console.log("Running '" + execString + "'");
|
||||
eval(execString)
|
||||
}
|
||||
|
||||
document.getElementById("do-math").addEventListener("click", () => {
|
||||
const exp = pyodideGlobals.get('rough_exponential');
|
||||
console.log(`e squared is about ${exp(2)}`);
|
||||
const c = pyodideGlobals.get('Circle')(4);
|
||||
console.log(`The area of c is ${c.area}`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
### Exporting Individual Python Objects
|
||||
|
||||
We can also export individual Python objects to the JavaScript global scope if we wish.
|
||||
|
||||
(As above, the following example uses a button to delay the execution of the `<script>` until after the PyScript has run.)
|
||||
|
||||
```python
|
||||
<py-script>
|
||||
import js
|
||||
from pyodide.ffi import create_proxy
|
||||
|
||||
# Create 3 python objects
|
||||
language = "Python 3"
|
||||
animals = ['dog', 'cat', 'bird']
|
||||
multiply3 = lambda a, b, c: a * b * c
|
||||
|
||||
# js object can be named the same as Python objects...
|
||||
js.createObject(language, "language")
|
||||
|
||||
# ...but don't have to be
|
||||
js.createObject(create_proxy(animals), "animals_from_py")
|
||||
|
||||
# functions are objects too, in both Python and Javascript
|
||||
js.createObject(create_proxy(multiply3), "multiply")
|
||||
</py-script>
|
||||
```
|
||||
```html
|
||||
<input type="button" value="Log Python Variables" id="log-python-variables">
|
||||
<script>
|
||||
document.getElementById("log-python-variables").addEventListener("click", () => {
|
||||
console.log(`Nice job using ${language}`);
|
||||
for (const animal of animals_from_py){
|
||||
console.log(`Do you like ${animal}s? `);
|
||||
}
|
||||
console.log(`2 times 3 times 4 is ${multiply(2,3,4)}`);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
#### Full example
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Exporting Individual Python Objects</title>
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-script>
|
||||
import js
|
||||
from pyodide.ffi import create_proxy
|
||||
|
||||
# Create 3 python objects
|
||||
language = "Python 3"
|
||||
animals = ['dog', 'cat', 'bird']
|
||||
multiply3 = lambda a, b, c: a * b * c
|
||||
|
||||
# js object can be named the same as Python objects...
|
||||
js.createObject(language, "language")
|
||||
|
||||
# ...but don't have to be
|
||||
js.createObject(create_proxy(animals), "animals_from_py")
|
||||
|
||||
# functions are objects too, in both Python and Javascript
|
||||
js.createObject(create_proxy(multiply3), "multiply")
|
||||
</py-script>
|
||||
|
||||
<input type="button" value="Log Python Variables" id="log-python-variables">
|
||||
<script>
|
||||
function createObject(object, variableName){
|
||||
//Bind a variable whose name is the string variableName
|
||||
// to the object called 'object'
|
||||
let execString = variableName + " = object"
|
||||
console.log("Running '" + execString + "'");
|
||||
eval(execString)
|
||||
}
|
||||
|
||||
document.getElementById("log-python-variables").addEventListener("click", () => {
|
||||
console.log(`Nice job using ${language}`);
|
||||
for (const animal of animals_from_py){
|
||||
console.log(`Do you like ${animal}s? `);
|
||||
}
|
||||
console.log(`2 times 3 times 4 is ${multiply(2,3,4)}`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -1,140 +0,0 @@
|
||||
# How to Pass Objects from PyScript to Javascript (and Vice Versa)
|
||||
|
||||
[Pyodide](https://pyodide.org), the runtime that underlies PyScript, does a lot of work under the hood to translate objects between Python and JavaScript. This allows code in one language to access objects defined in the other.
|
||||
|
||||
This guide discusses how to pass objects between JavaScript and Python within PyScript. For more details on how Pyodide handles translating and proxying objects between the two languages, see the [Pyodide Type Translations Page](https://pyodide.org/en/stable/usage/type-conversions.html).
|
||||
|
||||
For our purposes, an 'object' is anything that can be bound to a variable (a number, string, object, [function](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function), etc).
|
||||
|
||||
## JavaScript to PyScript
|
||||
|
||||
We can use the syntax `from js import ...` to import JavaScript objects directly into PyScript. Simple JavaScript objects are converted to equivalent Python types; these are called [implicit conversions](https://pyodide.org/en/stable/usage/type-conversions.html#implicit-conversions). More complicated objects are wrapped in [JSProxy](https://pyodide.org/en/stable/usage/type-conversions.html) objects to make them behave like Python objects.
|
||||
|
||||
`import js` and `from js import ...` [in Pyodide](https://pyodide.org/en/stable/usage/type-conversions.html#type-translations-using-js-obj-from-py) get objects from the [JavaScript globalThis scope](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis), so keep the[ rules of JavaScript variable scoping](https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/) in mind.
|
||||
|
||||
```html
|
||||
<script>
|
||||
name = "Guido" //A JS variable
|
||||
|
||||
// Define a JS Function
|
||||
function addTwoNumbers(x, y){
|
||||
return x + y;
|
||||
}
|
||||
</script>
|
||||
```
|
||||
```python
|
||||
<py-script>
|
||||
# Import and use JS function and variable into Python
|
||||
from js import name, addTwoNumbers
|
||||
|
||||
print(f"Hello {name}")
|
||||
print("Adding 1 and 2 in Javascript: " + str(addTwoNumbers(1, 2)))
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## PyScript to JavaScript
|
||||
|
||||
Since [PyScript doesn't export its instance of Pyodide](https://github.com/pyscript/pyscript/issues/494) and only one instance of Pyodide can be running in a browser window at a time, there isn't currently a way for Javascript to access Objects defined inside PyScript tags "directly".
|
||||
|
||||
We can work around this limitation using [JavaScript's eval() function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval), which executes a string as code much like [Python's eval()](https://docs.python.org/3/library/functions.html#eval). First, we create a JS function `createObject` which takes an object and a string, then uses `eval()` to create a variable named after the string and bind it to that object. By calling this function from PyScript (where we have access to the Pyodide global namespace), we can bind JavaScript variables to Python objects without having direct access to that global namespace.
|
||||
|
||||
Include the following script tag anywhere in your html document:
|
||||
|
||||
```html
|
||||
<script>
|
||||
function createObject(object, variableName){
|
||||
//Bind a variable whose name is the string variableName
|
||||
// to the object called 'object'
|
||||
let execString = variableName + " = object"
|
||||
console.log("Running '" + execString + "'");
|
||||
eval(execString)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
This function takes a Python Object and creates a variable pointing to it in the JavaScript global scope.
|
||||
|
||||
### Exporting all Global Python Objects
|
||||
|
||||
We can use our new `createObject` function to "export" the entire Python global object dictionary as a JavaScript object:
|
||||
|
||||
```python
|
||||
<py-script>
|
||||
from js import createObject
|
||||
from pyodide.ffi import create_proxy
|
||||
createObject(create_proxy(globals()), "pyodideGlobals")
|
||||
</py-script>
|
||||
```
|
||||
This will make all Python global variables available in JavaScript with `pyodideGlobals.get('my_variable_name')`.
|
||||
|
||||
(Since PyScript tags evaluate _after_ all JavaScript on the page, we can't just dump a `console.log(...)` into a `<script>` tag, since that tag will evaluate before any PyScript has a chance to. We need to delay accessing the Python variable in JavaScript until after the Python code has a chance to run. The following example uses a button with `id="do-math"` to achieve this, but any method would be valid.)
|
||||
|
||||
```python
|
||||
<py-script>
|
||||
# create some Python objects:
|
||||
symbols = {'pi': 3.1415926, 'e': 2.7182818}
|
||||
|
||||
def rough_exponential(x):
|
||||
return symbols['e']**x
|
||||
|
||||
class Circle():
|
||||
def __init__(self, radius):
|
||||
self.radius = radius
|
||||
|
||||
@property
|
||||
def area:
|
||||
return symbols['pi'] * self.radius**2
|
||||
</py-script>
|
||||
```
|
||||
|
||||
```html
|
||||
<input type="button" value="Log Python Variables" id="do-mmath">
|
||||
<script>
|
||||
document.getElementById("do-math").addEventListener("click", () => {
|
||||
const exp = pyodideGlobals.get('rough_exponential');
|
||||
console.log("e squared is about ${exp(2)}");
|
||||
const c = pyodideGlobals.get('Circle')(4);
|
||||
console.log("The area of c is ${c.area}");
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
### Exporting Individual Python Objects
|
||||
|
||||
We can also export individual Python objects to the JavaScript global scope if we wish.
|
||||
|
||||
(As above, the following example uses a button to delay the execution of the `<script>` until after the PyScript has run.)
|
||||
|
||||
```python
|
||||
<py-script>
|
||||
import js
|
||||
from pyodide.ffi import create_proxy
|
||||
|
||||
# Create 3 python objects
|
||||
language = "Python 3"
|
||||
animals = ['dog', 'cat', 'bird']
|
||||
multiply3 = lambda a, b, c: a * b * c
|
||||
|
||||
# js object can be named the same as Python objects...
|
||||
js.createObject(language, "language")
|
||||
|
||||
# ...but don't have to be
|
||||
js.createObject(create_proxy(animals), "animals_from_py")
|
||||
|
||||
# functions are objects too, in both Python and Javascript
|
||||
js.createObject(create_proxy(multiply3), "multiply")
|
||||
</py-script>
|
||||
```
|
||||
```html
|
||||
<input type="button" value="Log Python Variables" id="log-python-variables">
|
||||
<script>
|
||||
document.getElementById("log-python-variables").addEventListener("click", () => {
|
||||
console.log(`Nice job using ${language}`);
|
||||
for (const animal of animals_from_py){
|
||||
console.log(`Do you like ${animal}s? `);
|
||||
}
|
||||
console.log(`2 times 3 times 4 is ${multiply(2,3,4)}`);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
@@ -1,10 +1,5 @@
|
||||
# PyScript
|
||||
|
||||
```{warning}
|
||||
Please note, this documentation is just a placeholder and **should not be used
|
||||
in reference material**. Thank you!
|
||||
```
|
||||
|
||||
Welcome to the PyScript documentation!
|
||||
|
||||
PyScript provides a way for you to run Python code directly in your browser, giving
|
||||
@@ -22,12 +17,15 @@ Just getting started with PyScript?
|
||||
|
||||
Check out our [getting started guide](tutorials/getting-started.md)!
|
||||
:::
|
||||
:::{grid-item-card} [How-to guides](howtos/index.md)
|
||||
:::{grid-item-card} [Guides](guides/index.md)
|
||||
|
||||
You already know the basics and want to learn specifics!
|
||||
|
||||
[Passing Objects between JavaScript and Python](howtos/passing-objects.md)
|
||||
[Making async HTTP requests in pure Python](howtos/http-requests.md)
|
||||
[Passing Objects between JavaScript and Python](guides/passing-objects.md)
|
||||
|
||||
[Making async HTTP requests in pure Python](guides/http-requests.md)
|
||||
|
||||
[Async/Await and Asyncio](guides/asyncio.md)
|
||||
|
||||
:::
|
||||
:::{grid-item-card} [Concepts](concepts/index.md)
|
||||
@@ -39,6 +37,8 @@ You already know the basics and want to learn specifics!
|
||||
|
||||
[Frequently asked questions](reference/faq.md)
|
||||
|
||||
[The PyScript JS Module](reference/modules/pyscript.md)
|
||||
|
||||
:::{toctree}
|
||||
:maxdepth: 1
|
||||
|
||||
@@ -51,7 +51,7 @@ maxdepth: 1
|
||||
hidden:
|
||||
---
|
||||
tutorials/index
|
||||
howtos/index
|
||||
guides/index
|
||||
concepts/index
|
||||
reference/index
|
||||
```
|
||||
|
||||
8
docs/reference/API/__version__.md
Normal file
8
docs/reference/API/__version__.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# `__version__`
|
||||
|
||||
`PyScript.__version__` is a `str` representing the current version of PyScript in a human-readable form. For a structured version more suitable to comparisons, and for details of what each part of the version number represents, see [`version_info`](version_info.md)
|
||||
|
||||
```shell
|
||||
>>> pyscript.__version__
|
||||
'2023.02.1.dev'
|
||||
```
|
||||
87
docs/reference/API/display.md
Normal file
87
docs/reference/API/display.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# `display(*values, target=None, append=True)`
|
||||
|
||||
## Parameters
|
||||
|
||||
`*values` - the objects to be displayed. String objects are output as-written. For non-string objects, the default content to display is the the object's `repr()`. Objects may implement the following methods to indicate that they should be displayed as a different MIME type. MIME types with a * indicate that the content will be wrapped in the appropriate html tags and attributes before output:
|
||||
|
||||
| Method | Inferred MIME type |
|
||||
|---------------------|------------------------|
|
||||
| `__repr__` | text/plain |
|
||||
| `_repr_html_` | text/html |
|
||||
| `_repr_svg_` | image/svg+xml |
|
||||
| `_repr_png_` | image/png* |
|
||||
| `_repr_pdf_` | application/pdf |
|
||||
| `_repr_jpeg_` | image/jpeg* |
|
||||
| `_repr_json_` | application/json |
|
||||
| `_repr_javascript_` | application/javascript*|
|
||||
| `savefig` | image/png |
|
||||
| | |
|
||||
|
||||
`target` - Element's ID. The default value for `target` is the current `py-script` tag ID, it's possible to specify different IDs for this parameter
|
||||
|
||||
`append` - `boolean` if the output is going to be appended or not to the `target`ed element. It creates a `<div>` tag if `True` and a `<py-script>` tag with a random ID if `False`. The default value for `append` is `True`.
|
||||
|
||||
### Description
|
||||
|
||||
Display is the default function to display objects on the screen. Functions like the Python `print()` or JavaScript `console.log()` are now defaulted to only appear on the terminal.
|
||||
|
||||
Display will throw an exception if the target is not clear. E.g. the following code is invalid:
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
def display_hello():
|
||||
# this fails because we don't have any implicit target
|
||||
# from event handlers
|
||||
display('hello')
|
||||
</py-script>
|
||||
<button id="my-button" py-onClick="display_hello()">Click me</button>
|
||||
```
|
||||
|
||||
Because it's considered unclear if the `hello` string should be displayed underneath the `<py-script>` tag or the `<button>` tag.
|
||||
|
||||
To write compliant code, make sure to specify the target using the `target` parameter, for example:
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
def display_hello():
|
||||
# this fails because we don't have any implicit target
|
||||
# from event handlers
|
||||
display('hello', target="helloDiv")
|
||||
</py-script>
|
||||
<div id="helloDiv"></div>
|
||||
<button id="my-button" py-onClick="display_hello()">Click me</button>
|
||||
```
|
||||
|
||||
#### Using matplotlib with display
|
||||
|
||||
`matplotlib` has two ways of plotting things as mentioned [here](https://matplotlib.org/matplotblog/posts/pyplot-vs-object-oriented-interface/)
|
||||
|
||||
- In case of using the `pyplot` interface, the graph can be shown using `display(plt)`.
|
||||
|
||||
```python
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
# Data for plotting
|
||||
t = np.arange(0.0, 2.0, 0.01)
|
||||
s = 1 + np.sin(2 * np.pi * t)
|
||||
plt.plot(t,s)
|
||||
|
||||
display(plt)
|
||||
```
|
||||
|
||||
- In case of using the `object oriented` interface, the graph can be shown using `display(fig)` or `display(plt)` both.
|
||||
|
||||
```python
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
# Data for plotting
|
||||
t = np.arange(0.0, 2.0, 0.01)
|
||||
s = 1 + np.sin(2 * np.pi * t)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(t, s)
|
||||
|
||||
display(fig) # but even display(plt) would have worked!
|
||||
```
|
||||
309
docs/reference/API/element.md
Normal file
309
docs/reference/API/element.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# `Element`
|
||||
|
||||
The `Element` API is a helpful way to create and manipulate elements in the DOM. It is a wrapper around the native DOM API, and is designed to be as intuitive as possible.
|
||||
|
||||
## Methods and Properties
|
||||
|
||||
| Property | Description |
|
||||
|----------|-----------------------------------------|
|
||||
| `element` | Returns the element with the given ID. |
|
||||
| `id` | Returns the element's ID. |
|
||||
| `value` | Returns the element's value. |
|
||||
| `innerHtml` | Returns the element's inner HTML. |
|
||||
|
||||
|
||||
|
||||
| Method | Description |
|
||||
|----------------------|--------------------------------------------------------------|
|
||||
| `write` | Writes `value` to element and handles various mime types. `append` defaults to `False`, if set to true, it will create a child element. |
|
||||
| `clear` | Clears the element's value or content. |
|
||||
| `select` | Select element from `query` which uses [Document.querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector). |
|
||||
| `clone` | Clones the with `new_id` if provided and `to` element if provided. |
|
||||
| `remove_class` | Removes one or more class name from the element. |
|
||||
| `add_class` | Adds one or more class name to the element. |
|
||||
|
||||
## Element.element
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-----------|---------|------|
|
||||
| | | |
|
||||
|
||||
The `element` property returns the DOM element with the given ID.
|
||||
|
||||
```html
|
||||
from pyscript import Element
|
||||
|
||||
my_div = Element('my-div')
|
||||
print(my_div.element)
|
||||
```
|
||||
|
||||
## Element.id
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-----------|---------|------|
|
||||
| | | |
|
||||
|
||||
Return the element's ID.
|
||||
|
||||
```html
|
||||
|
||||
<div id="my-div"></div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
my_div = Element('my-div')
|
||||
print(my_div.id) # prints 'my-div'
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.value
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-----------|---------|------|
|
||||
| | | |
|
||||
|
||||
Return the element's value.
|
||||
|
||||
```html
|
||||
<input id="my-input" value="hello world"></input>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
my_input = Element('my-input')
|
||||
print(my_input.value) # prints 'hello world'
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.innerHtml
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-----------|---------|------|
|
||||
| | | |
|
||||
|
||||
Return the element's inner HTML.
|
||||
|
||||
```html
|
||||
<div id="my-innerHtml">
|
||||
<b>hello world</b>
|
||||
</div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
my_innerHtml = Element('my-innerHtml')
|
||||
print(my_innerHtml.innerHtml) # prints <b> hello world </b>
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.write
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-------------|---------|-----------------------------|
|
||||
| `value` | | `str` or `__mime_type__` |
|
||||
| `append` | False | `bool` |
|
||||
|
||||
Writes `value` to element and handles various mime types. This method also contains a `append` parameter, which defaults to `False`.
|
||||
|
||||
Currently, these are the MIME types that are supported when rendering content using this method
|
||||
|
||||
| Method | Inferred MIME type |
|
||||
|---------------------|------------------------|
|
||||
| `__repr__` | text/plain |
|
||||
| `_repr_html_` | text/html |
|
||||
| `_repr_svg_` | image/svg+xml |
|
||||
| `_repr_png_` | image/png* |
|
||||
| `_repr_pdf_` | application/pdf |
|
||||
| `_repr_jpeg_` | image/jpeg* |
|
||||
| `_repr_json_` | application/json |
|
||||
| `_repr_javascript_` | application/javascript*|
|
||||
| `savefig` | image/png |
|
||||
|
||||
```html
|
||||
<div id="foo"></div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.write("Hello!")
|
||||
el.write("World!") # will replace the previous content
|
||||
</py-script>
|
||||
```
|
||||
|
||||
If we set `append` to `True`, it will create a child element using a `div`.
|
||||
|
||||
```html
|
||||
<div id="foo"></div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.write("Hello!", append=True)
|
||||
|
||||
# This will create a child div with the id "foo-1"
|
||||
el.write("World!", append=True)
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.clear
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-----------|---------|------|
|
||||
| | | |
|
||||
|
||||
Clears the element's value or content. For example, we can clear the value of an input element.
|
||||
|
||||
```html
|
||||
<input id="foo" value="Hello!"></input>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.clear() # Removes value from input
|
||||
</py-script>
|
||||
```
|
||||
|
||||
Or we can clear the content of a div element.
|
||||
|
||||
```html
|
||||
<div id="foo">Hello!</div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.clear() # Removes Hello from div content
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.select
|
||||
|
||||
Select element from `query`, it will look into the main Element if `from_content` is `True`. This method is a wrapper of [Document.querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
|
||||
|
||||
```html
|
||||
<div id="foo">
|
||||
<div id="bar"></div>
|
||||
</div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
bar = el.select("#bar")
|
||||
print(bar.id) # prints 'bar'
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.clone
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-------------|---------|-----------|
|
||||
| `new_id` | None | `str` |
|
||||
| `to` | None | `Element` |
|
||||
|
||||
Clones the element to a new element. You can provide `new_id` to set a different id to the cloned element. You can also use a `to` element to append the cloned element to.
|
||||
|
||||
```html
|
||||
<div id="foo">
|
||||
HI!
|
||||
</div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
el = Element("foo")
|
||||
# Creates two divs with the id "foo" and content "HI!"
|
||||
el.clone()
|
||||
</py-script>
|
||||
```
|
||||
|
||||
It's always a good idea to pass a new id to the element you are cloning to avoid confusion if you need to reference the element by id again.
|
||||
|
||||
```html
|
||||
<div id="foo">Hello!</div>
|
||||
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
# Clones foo and its contents, but uses the id 'bar'
|
||||
el.clone(new_id="bar")
|
||||
</py-script>
|
||||
```
|
||||
|
||||
You can also clone an element into another element.
|
||||
|
||||
```html
|
||||
<div id="bond">
|
||||
Bond
|
||||
</div>
|
||||
<div id="james">
|
||||
James
|
||||
</div>
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
bond_div = Element("bond")
|
||||
james_div = Element("james")
|
||||
|
||||
bond_div.clone(new_id="bond-2", to=james_div)
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.remove_class
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-------------|---------|-----------------------|
|
||||
| `classname` | None | `str` or `List[str]` |
|
||||
|
||||
Removes one or more class names from the element.
|
||||
|
||||
```html
|
||||
<div id="foo" class="bar baz"></div>
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
el = Element("foo")
|
||||
el.remove_class("bar")
|
||||
</py-script>
|
||||
```
|
||||
|
||||
You can also remove multiple classes by passing a list of strings.
|
||||
|
||||
```html
|
||||
<div id="foo" class="bar baz"></div>
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
|
||||
el = Element("foo")
|
||||
el.remove_class(["bar", "baz"]) # Remove all classes from element
|
||||
</py-script>
|
||||
```
|
||||
|
||||
## Element.add_class
|
||||
|
||||
| Parameter | Default | Type |
|
||||
|-------------|---------|-----------------------|
|
||||
| `classname` | None | `str` or `List[str]` |
|
||||
|
||||
Adds one or more class names to the element.
|
||||
|
||||
```html
|
||||
<style> .red { color: red; } </style>
|
||||
<div id="foo">Hi!</div>
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.add_class("red")
|
||||
</py-script>
|
||||
```
|
||||
|
||||
You can also add multiple classes at once by passing a list of strings.
|
||||
|
||||
```html
|
||||
<style> .red { color: red; } .bold { font-weight: bold; } </style>
|
||||
<div id="foo">Hi!</div>
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
el = Element("foo")
|
||||
el.add_class(["red", "bold"])
|
||||
</py-script>
|
||||
```
|
||||
16
docs/reference/API/version_info.md
Normal file
16
docs/reference/API/version_info.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# `version_info`
|
||||
|
||||
`PyScript.version_info` is a `namedtuple` representing the current version of PyScript. It can be used to compare whether current version precedes or follows a desired version. For a human-readable version of the same info, see [`__version__`](__version__.md)
|
||||
|
||||
```sh
|
||||
>>> pyscript.version_info
|
||||
version_info(year=2023, month=2, minor=1, releaselevel='dev')
|
||||
```
|
||||
|
||||
## Version Fields
|
||||
| **parameter** | **CalVer equivalent field** | **example value** | **description** |
|
||||
|-----------------|-----------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `year` | Full year (YYYY) | 2023 | The year of the release; when printed or represented as a string, always written with 4 digits |
|
||||
| `month` | Short Month (MM) | 2 | The month of the release; when printed or represented as a string, written with 1 or 2 digits as necessary |
|
||||
| `minor` | | 1 | The incremental number of the release for this month; when printed or represented as a string, written with 1 or two digits as necessary |
|
||||
| `releaselevel` | | 'dev' | A string representing the qualifications of this build |
|
||||
487
docs/reference/elements/py-config.md
Normal file
487
docs/reference/elements/py-config.md
Normal file
@@ -0,0 +1,487 @@
|
||||
# <py-config>
|
||||
|
||||
Use the `<py-config>` tag to set and configure general metadata along with declaring dependencies for your PyScript application. The configuration has to be set in either [TOML](https://toml.io/)(default) or [JSON](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON) format.
|
||||
|
||||
If you are unfamiliar with TOML, consider [reading about it](https://learnxinyminutes.com/docs/toml/) or if you are unfamiliar with JSON, consider reading [freecodecamp's JSON for beginners](https://www.freecodecamp.org/news/what-is-json-a-json-file-example/) guide for more information.
|
||||
|
||||
The `<py-config>` element should be placed within the `<body>` element.
|
||||
|
||||
## Attributes
|
||||
|
||||
| attribute | type | default | description |
|
||||
|-----------|--------|---------|---------------------------------------------------------------------------------------------------------|
|
||||
| **type** | string | "toml" | Syntax type of the `<py-config>`. Value can be `json` or `toml`. Default: "toml" if type is unspecifed. |
|
||||
| **src** | url | | Source url to an external configuration file. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Defining an inline config
|
||||
|
||||
- `<py-config>` using TOML (default)
|
||||
|
||||
```{note}
|
||||
Reminder: when using TOML, any Arrays of Tables defined with double-brackets (like `[[runtimes]]` and `[[fetch]]` must come after individual keys (like `plugins = ...` and `packages=...`)
|
||||
```
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[splashscreen]
|
||||
autoclose = true
|
||||
|
||||
[[runtimes]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
|
||||
name = "pyodide-0.21.2"
|
||||
lang = "python"
|
||||
</py-config>
|
||||
```
|
||||
|
||||
- `<py-config>` using JSON via `type` attribute
|
||||
|
||||
```html
|
||||
<py-config type="json">
|
||||
{
|
||||
"splashscreen": {
|
||||
"autoclose": true
|
||||
},
|
||||
"runtimes": [{
|
||||
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
|
||||
"name": "pyodide-0.21.2",
|
||||
"lang": "python"
|
||||
}]
|
||||
}
|
||||
</py-config>
|
||||
```
|
||||
|
||||
### Defining a file based config
|
||||
|
||||
- Use of the `src` attribute
|
||||
|
||||
```html
|
||||
<py-config src="./custom.toml"></py-config>
|
||||
```
|
||||
where `custom.toml` contains
|
||||
|
||||
```toml
|
||||
[splashscreen]
|
||||
autoclose = true
|
||||
|
||||
[[runtimes]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
|
||||
name = "pyodide-0.21.2"
|
||||
lang = "python"
|
||||
```
|
||||
|
||||
- JSON using the `type` and `src` attribute
|
||||
|
||||
```html
|
||||
<py-config type="json" src="./custom.json"></py-config>
|
||||
```
|
||||
where `custom.json` contains
|
||||
|
||||
```json
|
||||
{
|
||||
"splashscreen": {
|
||||
"autoclose": true,
|
||||
},
|
||||
"runtimes": [{
|
||||
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
|
||||
"name": "pyodide-0.21.2",
|
||||
"lang": "python"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Mixing inline and file based configs
|
||||
|
||||
One can also use both i.e pass the config from `src` attribute as well as specify it as `inline`. So the following snippet is also valid:
|
||||
|
||||
```html
|
||||
<py-config src="./custom.toml">
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
</py-config>
|
||||
```
|
||||
|
||||
This can also be done via JSON using the `type` attribute.
|
||||
|
||||
```html
|
||||
<py-config type="json" src="./custom.json">
|
||||
{
|
||||
"fetch": [{
|
||||
"files": ["./utils.py"]
|
||||
}]
|
||||
}
|
||||
</py-config>
|
||||
```
|
||||
|
||||
Note: While the `<py-config>` tag supports both TOML and JSON, one cannot mix the type of config passed from 2 different sources i.e. the case when inline config is in TOML format while config from src is in JSON format is NOT allowed. Similarly for the opposite case.
|
||||
|
||||
---
|
||||
|
||||
This is helpful in cases where a number of applications share a common configuration (which can be supplied via `src`), but their specific keys need to be customised and overridden.
|
||||
|
||||
The keys supplied through `inline` override the values present in config supplied via `src`.
|
||||
|
||||
## Dependencies and Packages
|
||||
|
||||
One can also declare dependencies so as to get access to many 3rd party OSS packages that are supported by PyScript.
|
||||
You can also link to `.whl` files directly on disk like in our [toga example](https://github.com/pyscript/pyscript/blob/main/examples/toga/freedom.html).
|
||||
|
||||
Package dependencies in the `<py-config>` can be declared by using the direct link to the package URL (whl or any other format supported by the chosen runtime) or by just providing the package name [and version]. If only the name [and version] are provided, packages will be installed directly from what's provided by your runtime or from PyPI.
|
||||
|
||||
NOTICE that only pure python packages from PyPI will work and packages with C dependencies will not. These need to be built specifically for WASM (please, consult the Pyodide project for more information about what's supported and on how to build packages with C dependencies)
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
packages = ["./static/wheels/travertino-0.1.3-py3-none-any.whl"]
|
||||
</py-config>
|
||||
```
|
||||
|
||||
OR in JSON like
|
||||
|
||||
```html
|
||||
<py-config type="json">
|
||||
{
|
||||
"packages": ["./static/wheels/travertino-0.1.3-py3-none-any.whl"]
|
||||
}
|
||||
</py-config>
|
||||
```
|
||||
|
||||
If your `.whl` is not a pure Python wheel, then open a PR or issue with [pyodide](https://github.com/pyodide/pyodide) to get it added [here](https://github.com/pyodide/pyodide/tree/main/packages).
|
||||
|
||||
If there's enough popular demand, the pyodide team will likely work on supporting your package. Regardless, things will likely move faster if you make the PR and consult with the team to get unblocked.
|
||||
|
||||
For example, NumPy and Matplotlib are available. Notice here we're using `display(fig, target="plot")`, which takes the graph and displays it in the element with the id `plot`.
|
||||
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Let's plot random numbers</h1>
|
||||
<div id="plot"></div>
|
||||
<py-config type="json">
|
||||
{
|
||||
"packages": ["numpy", "matplotlib"]
|
||||
}
|
||||
</py-config>
|
||||
<py-script>
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
x = np.random.randn(1000)
|
||||
y = np.random.randn(1000)
|
||||
fig, ax = plt.subplots()
|
||||
ax.scatter(x, y)
|
||||
display(fig, target="plot")
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Local modules
|
||||
|
||||
In addition to packages, you can declare local Python modules that will
|
||||
be imported in the `<py-script>` tag. For example, we can place the random
|
||||
number generation steps in a function in the file `data.py`.
|
||||
|
||||
```python
|
||||
# data.py
|
||||
import numpy as np
|
||||
def make_x_and_y(n):
|
||||
x = np.random.randn(n)
|
||||
y = np.random.randn(n)
|
||||
return x, y
|
||||
```
|
||||
|
||||
In the HTML tag `<py-config>`, paths to local modules are provided in the
|
||||
`files` key within the `fetch` section. Refer to the [fetch](#fetch) section for
|
||||
more details.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Let's plot random numbers</h1>
|
||||
<div id="plot"></div>
|
||||
<py-config type="toml">
|
||||
packages = ["numpy", "matplotlib"]
|
||||
|
||||
[[fetch]]
|
||||
files = ["./data.py"]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import matplotlib.pyplot as plt
|
||||
from data import make_x_and_y
|
||||
x, y = make_x_and_y(n=1000)
|
||||
fig, ax = plt.subplots()
|
||||
ax.scatter(x, y)
|
||||
display(fig, target="plot")
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Supported configuration values
|
||||
|
||||
The following optional values are supported by `<py-config>`:
|
||||
| Value | Type | Description |
|
||||
| ------ | ---- | ----------- |
|
||||
| `name` | string | Name of the user application. This field can be any string and is to be used by the application author for their own customization purposes. |
|
||||
| `description` | string | Description of the user application. This field can be any string and is to be used by the application author for their own customization purposes. |
|
||||
| `version` | string | Version of the user application. This field can be any string and is to be used by the application author for their own customization purposes. It is not related to the PyScript version. |
|
||||
| `schema_version` | number | The version of the config schema which determines what all keys are supported. This can be supplied by the user so PyScript knows what to expect in the config. If not supplied, the latest version for the schema is automatically used. |
|
||||
| `type` | string | Type of the project. The default is an "app" i.e. a user application |
|
||||
| `author_name` | string | Name of the author. |
|
||||
| `author_email` | string | Email of the author. |
|
||||
| `license` | string | License to be used for the user application. |
|
||||
| `autoclose_loader` | boolean | If false, PyScript will not close the loading splash screen when the startup operations finish. |
|
||||
| `packages` | List of Packages | Dependencies on 3rd party OSS packages are specified here. The default value is an empty list. |
|
||||
| `fetch` | List of Stuff to fetch | Local Python modules OR resources from the internet are to be specified here using a Fetch Configuration, described below. The default value is an empty list. |
|
||||
| `plugins` | List of Plugins | List of Plugins are to be specified here. The default value is an empty list. |
|
||||
| `runtimes` | List of Runtimes | List of runtime configurations, described below. The default value contains a single Pyodide based runtime. |
|
||||
|
||||
### <a name="fetch">Fetch</a>
|
||||
|
||||
A fetch configuration consists of the following:
|
||||
|
||||
| Value | Type | Description |
|
||||
|--------------|-----------------|-------------------------------------------------|
|
||||
| `from` | string | Base URL for the resource to be fetched. |
|
||||
| `to_folder` | string | Name of the folder to create in the filesystem. |
|
||||
| `to_file` | string | Name of the target to create in the filesystem. |
|
||||
| `files` | List of strings | List of files to be downloaded. |
|
||||
|
||||
The parameters `to_file` and `files` shouldn't be supplied together.
|
||||
|
||||
#### Mechanism
|
||||
|
||||
The `fetch` mechanism works in the following manner:
|
||||
|
||||
- If both `files` and `to_file` parameters are supplied: Error!
|
||||
- `from` defaults to an empty string i.e. `""` to denote relative URLs of the serving directory
|
||||
- `to_folder` defaults to `.` i.e. the current working directory of the filesystem
|
||||
- If `files` is specified
|
||||
- for each `file` present in the `files` array
|
||||
- the `sourcePath` is calculated as `from + file`
|
||||
- the `destination` is calculated as `to_folder + file`
|
||||
- thus, the object is downloaded from `sourcePath` to `destination`
|
||||
- Else i.e. `files` is NOT specified
|
||||
- If `to_file` is specified
|
||||
- the object is downloaded from `from` to `to_folder + to_file`
|
||||
- Otherwise, calculate the `filename` at the end of `from` i.e. the part after last `/`
|
||||
- the object is downloaded from `from` to `to_folder + filename at the end of 'from'`
|
||||
|
||||
Learn more about `fetch` on PyScript [here](https://jeff.glass/post/whats-new-pyscript-2022-12-1)
|
||||
|
||||
#### Use-Cases
|
||||
|
||||
Assumptions:
|
||||
|
||||
The directory being served has the following tree structure:
|
||||
|
||||
```
|
||||
content/
|
||||
├─ index.html <<< File with <py-config>
|
||||
├─ info.txt
|
||||
├─ data/
|
||||
│ ├─ sensordata.csv
|
||||
├─ packages/
|
||||
│ ├─ my_package/
|
||||
│ │ ├─ __init__.py
|
||||
│ │ ├─ helloworld/
|
||||
│ │ │ ├─ __init__.py
|
||||
│ │ │ ├─ greetings.py
|
||||
```
|
||||
|
||||
1. Fetching a single file
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ['info.txt']
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('info.txt', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
2. Single File with Renaming
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'info.txt'
|
||||
to_file = 'info_loaded_from_web.txt'
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('info_loaded_from_web.txt', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
3. Single File to another Directory
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ['info.txt']
|
||||
to_folder = 'infofiles/loaded_info'
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('infofiles/loaded_info/info.txt', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
4. Single File to another Directory with Renaming
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'info.txt'
|
||||
to_folder = 'infofiles/loaded_info'
|
||||
to_file = 'info_loaded_from_web.txt'
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('infofiles/loaded_info/info_loaded_from_web.txt', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
5. Single file from a folder to the current working directory
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'data/'
|
||||
files = ['sensordata.csv']
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('./sensordata.csv', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
6. Single file from a folder to another folder (i.e. not the current working directory)
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'data/'
|
||||
to_folder = './local_data'
|
||||
files = ['sensordata.csv']
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
with open('./local_data/sensordata.csv', 'r') as fp:
|
||||
print(fp.read())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
7. Multiple files preserving directory structure
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'packages/my_package/'
|
||||
files = ['__init__.py', 'helloworld/greetings.py', 'helloworld/__init__.py']
|
||||
to_folder = 'custom_pkg'
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
from custom_pkg.helloworld.greetings import say_hi
|
||||
print(say_hi())
|
||||
</py-script>
|
||||
```
|
||||
|
||||
8. From an API endpoint which doesn't end in a filename
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = 'https://catfact.ninja/fact'
|
||||
to_file = './cat_fact.json'
|
||||
</py-config>
|
||||
```
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
import json
|
||||
with open("cat_fact.json", "r") as fp:
|
||||
data = json.load(fp)
|
||||
</py-script>
|
||||
```
|
||||
|
||||
### Runtime
|
||||
|
||||
A runtime configuration consists of the following:
|
||||
| Value | Type | Description |
|
||||
|--------|-------------------|-------------|
|
||||
| `src` | string (Required) | URL to the runtime source. |
|
||||
| `name` | string | Name of the runtime. This field can be any string and is to be used by the application author for their own customization purposes |
|
||||
| `lang` | string | Programming language supported by the runtime. This field can be used by the application author to provide clarification. It currently has no implications on how PyScript behaves. |
|
||||
|
||||
#### Example
|
||||
|
||||
- The default runtime is `pyodide`, another version of which can be specified as following
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
[[runtimes]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"
|
||||
name = "pyodide-0.20.0"
|
||||
lang = "python"
|
||||
</py-config>
|
||||
```
|
||||
|
||||
## Supplying extra information (or metadata)
|
||||
|
||||
Besides the above schema, a user can also supply any extra keys and values that are relevant as metadata information or perhaps are being used within the application.
|
||||
|
||||
For example, a valid config could also be with the snippet below:
|
||||
|
||||
```html
|
||||
<py-config type="toml">
|
||||
magic = "unicorn"
|
||||
</py-config>
|
||||
```
|
||||
|
||||
OR in JSON like
|
||||
|
||||
```html
|
||||
<py-config type="json">
|
||||
{
|
||||
"magic": "unicorn"
|
||||
}
|
||||
</py-config>
|
||||
```
|
||||
|
||||
If this `"magic"` key is present in config supplied via `src` and also present in config supplied via `inline`, then the value in the inline config is given priority i.e. the overriding process also works for custom keys.
|
||||
30
docs/reference/elements/py-repl.md
Normal file
30
docs/reference/elements/py-repl.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# <py-repl>
|
||||
|
||||
The `<py-repl>` element provides a REPL(Read Eval Print Loop) to evaluate multi-line Python and display output.
|
||||
|
||||
## Attributes
|
||||
|
||||
| attribute | type | default | description |
|
||||
|-------------------|---------|---------|---------------------------------------|
|
||||
| **auto-generate** | boolean | | Auto-generates REPL after evaluation |
|
||||
| **output** | string | | The element to write output into |
|
||||
|
||||
### Examples
|
||||
|
||||
#### `<py-repl>` element set to auto-generate
|
||||
|
||||
```html
|
||||
<py-repl auto-generate="true"> </py-repl>
|
||||
```
|
||||
|
||||
#### `<py-repl>` element with output
|
||||
|
||||
```html
|
||||
<div id="replOutput"></div>
|
||||
<py-repl output="replOutput">
|
||||
hello = "Hello world!"
|
||||
hello
|
||||
</py-repl>
|
||||
```
|
||||
|
||||
Note that if we `print` any element in the repl, the output will be printed in the [`py-terminal`](../plugins/py-terminal.md) if is enabled.
|
||||
111
docs/reference/elements/py-script.md
Normal file
111
docs/reference/elements/py-script.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# <py-script>
|
||||
|
||||
The `<py-script>` element lets you execute multi-line Python scripts both inline and via a src attribute.
|
||||
|
||||
## Attributes
|
||||
|
||||
| attribute | type | default | description |
|
||||
|-----------|------|---------|------------------------------|
|
||||
| **src** | url | | Url of a python source file. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Inline `<py-script>` element
|
||||
|
||||
Let's execute this multi-line Python script to compute π and print it back onto the page
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-script>
|
||||
print("Let's compute π:")
|
||||
def compute_pi(n):
|
||||
pi = 2
|
||||
for i in range(1,n):
|
||||
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
|
||||
return pi
|
||||
|
||||
pi = compute_pi(100000)
|
||||
s = f"π is approximately {pi:.3f}"
|
||||
print(s)
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Using `<py-script>` element with `src` attribute
|
||||
|
||||
we can also move our python code to its own file and reference it via the `src` attribute.
|
||||
|
||||
|
||||
```python
|
||||
# compute_pi.py
|
||||
print("Let's compute π:")
|
||||
def compute_pi(n):
|
||||
pi = 2
|
||||
for i in range(1,n):
|
||||
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
|
||||
return pi
|
||||
|
||||
pi = compute_pi(100000)
|
||||
s = f"π is approximately {pi:.3f}"
|
||||
print(s)
|
||||
```
|
||||
|
||||
Since both compute_pi.py and index.html are in the same directory, we can reference the python file with a relative path.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-script src="compute_pi.py"></py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Writing into labeled elements
|
||||
|
||||
In the example above, we had a single `<py-script>` tag printing
|
||||
one or more lines onto the page in order. Within the `<py-script>`, you can
|
||||
use the `Element` class to create a python object for interacting with
|
||||
page elements. Objects created from the `Element` class provide the `.write()` method
|
||||
which enables you to send strings into the page elements referenced by those objects.
|
||||
|
||||
For example, we'll add some style elements and provide placeholders for
|
||||
the `<py-script>` tag to write to.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<b><p>Today is <u><label id='today'></label></u></p></b>
|
||||
<br>
|
||||
<div id="pi" class="alert alert-primary"></div>
|
||||
<py-script>
|
||||
import datetime as dt
|
||||
Element('today').write(dt.date.today().strftime('%A %B %d, %Y'))
|
||||
|
||||
def compute_pi(n):
|
||||
pi = 2
|
||||
for i in range(1,n):
|
||||
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
|
||||
return pi
|
||||
|
||||
pi = compute_pi(100000)
|
||||
Element('pi').write(f'π is approximately {pi:.3f}')
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
39
docs/reference/exceptions.md
Normal file
39
docs/reference/exceptions.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Exceptions and error codes
|
||||
|
||||
When creating pages with PyScript, you may encounter exceptions. Each handled exception will contain a specific code which will give you more information about it.
|
||||
This reference guide contains the error codes you might find and a description of each of them.
|
||||
|
||||
## User Errors
|
||||
|
||||
| Error code | Description | Recommendation |
|
||||
|------------|--------------------------------|--------------------|
|
||||
| PY1000 | Invalid configuration supplied | Confirm that your `py-config` tag is using a valid `TOML` or `JSON` syntax and is using the correct configuration type. |
|
||||
| PY1001 | Unable to install package(s) | Confirm that the package contains a pure Python 3 wheel or the name of the package is correct. |
|
||||
| PY9000 | Top level await is deprecated | Create a coroutine with your code and schedule it with `asyncio.ensure_future` or similar |
|
||||
|
||||
|
||||
|
||||
## Fetch Errors
|
||||
|
||||
These error codes are related to any exception raised when trying to fetch a resource. If, while trying to fetch a resource, we encounter a status code that is not 200, the error code will contain the HTTP status code and the `PY0` prefix. For example, if we encounter a 404 error, the error code will be `P02404`.
|
||||
|
||||
|
||||
| Error Code | Description |
|
||||
|------------|--------------------------------------------------------------|
|
||||
| PY0001 | Generic fetch error, failed to fetch page from the server |
|
||||
| PY0002 | Name supplied when trying to fetch resource is invalid |
|
||||
| PY0401 | You are not authorized to access this resource. |
|
||||
| PY0403 | You are not allowed to access this resource. |
|
||||
| PY0404 | The page you are trying to fetch does not exist. |
|
||||
| PY0500 | The server encountered an internal error. |
|
||||
| PY0503 | The server is currently unavailable. |
|
||||
|
||||
## PY1001
|
||||
|
||||
Pyscript cannot install the package(s) you specified in your `py-config` tag. This can happen for a few reasons:
|
||||
|
||||
- The package does not exist
|
||||
- The package does not contain a pure Python 3 wheel
|
||||
- An error occurred while trying to install the package
|
||||
|
||||
An error banner should appear on your page with the error code and a description of the error or a traceback. You can also check the developer console for more information.
|
||||
@@ -1,11 +1,54 @@
|
||||
# Reference
|
||||
|
||||
This reference section will have manually documented or fully
|
||||
automated code documentation. **Coming soon!**
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
glob:
|
||||
caption: Elements
|
||||
---
|
||||
elements/*
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
glob:
|
||||
caption: Modules
|
||||
---
|
||||
modules/*
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
glob:
|
||||
caption: Plugins
|
||||
---
|
||||
plugins/*
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
glob:
|
||||
caption: API
|
||||
---
|
||||
API/*
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 2
|
||||
glob:
|
||||
caption: Exceptions
|
||||
---
|
||||
exceptions
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 1
|
||||
caption: Miscellaneous
|
||||
---
|
||||
faq
|
||||
```
|
||||
|
||||
77
docs/reference/modules/pyscript.md
Normal file
77
docs/reference/modules/pyscript.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# pyscript
|
||||
|
||||
The code underlying PyScript is a TypeScript/JavaScript module, which is loaded and executed by the browser. This is what loads when you include, for example, `<script defer src="https://pyscript.net/latest/pyscript.js">` in your HTML.
|
||||
|
||||
The module is exported to the browser as `pyscript`. The exports from this module are:
|
||||
|
||||
## pyscript.version
|
||||
|
||||
Once `pyscript.js` has loaded, the version of PyScript that is currently running can be accessed via `pyscript.version`.
|
||||
```html
|
||||
<script defer onload="console.log(`${pyscript.version}`)" src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
```
|
||||
```js
|
||||
//example result
|
||||
Object { year: 2022, month: 11, patch: 1, releaselevel: "dev" }
|
||||
```
|
||||
|
||||
## pyscript.runtime
|
||||
|
||||
The RunTime object which is responsible for executing Python code in the Browser. Currently, all runtimes are assumed to be Pyodide runtimes, but there is flexibility to expand this to other web-based Python runtimes in future versions.
|
||||
|
||||
The RunTime object has the following attributes
|
||||
|
||||
| attribute | type | description |
|
||||
|---------------------|---------------------|-----------------------------------------------------------------------------|
|
||||
| **src** | string | The URL from which the current runtime was fetched |
|
||||
| **interpreter** | RuntimeInterpreter | A reference to the runtime object itself |
|
||||
| **globals** | any | The globals dictionary of the runtime, if applicable/accessible |
|
||||
| **name (optional)** | string | A user-designated name for the runtime |
|
||||
| **lang (optional)** | string | A user-designation for the language the runtime runs ('Python', 'C++', etc) |
|
||||
|
||||
### pyscript.runtime.src
|
||||
|
||||
The URL from which the current runtime was fetched.
|
||||
|
||||
### pyscript.runtime.interpreter
|
||||
|
||||
A reference to the Runtime wrapper that PyScript uses to execute code. object itself. This allows other frameworks, modules etc to interact with the same [(Pyodide) runtime instance](https://pyodide.org/en/stable/usage/api/js-api.html) that PyScript uses.
|
||||
|
||||
For example, assuming we've loaded Pyodide, we can access the methods of the Pyodide runtime as follows:
|
||||
|
||||
```html
|
||||
<button onclick="logFromPython()">Click Me to Run Some Python</button>
|
||||
<script>
|
||||
function logFromPython(){
|
||||
pyscript.runtime.interpreter.runPython(`
|
||||
animal = "Python"
|
||||
sound = "sss"
|
||||
console.warn(f"{animal}s go " + sound * 5)
|
||||
`)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### pyscript.runtime.globals
|
||||
|
||||
A proxy for the runtime's `globals()` dictionary. For example:
|
||||
|
||||
```html
|
||||
<body>
|
||||
<py-script>x = 42</py-script>
|
||||
|
||||
<button onclick="showX()">Click Me to Get 'x' from Python</button>
|
||||
<script>
|
||||
function showX(){
|
||||
console.log(`In Python right now, x = ${pyscript.runtime.globals.get('x')}`)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
### pyscript.runtime.name
|
||||
|
||||
A user-supplied string for the runtime given at its creation. For user reference only - does not affect the operation of the runtime or PyScript.
|
||||
|
||||
### PyScript.runtime.lang
|
||||
|
||||
A user-supplied string for the language the runtime uses given at its creation. For user reference only - does not affect the operation of the runtime or PyScript.
|
||||
41
docs/reference/plugins/py-terminal.md
Normal file
41
docs/reference/plugins/py-terminal.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# <py-terminal>
|
||||
|
||||
This is one of the core plugins in PyScript, which is active by default. With it, you can print to `stdout` and `stderr` from your python code, and the output will be displayed on the page in `<py-terminal>`.
|
||||
|
||||
## Configuration
|
||||
|
||||
You can control how `<py-terminal>` behaves by setting the value of the `terminal` configuration in your `<py-config>`.
|
||||
|
||||
| value | description |
|
||||
|-------|-------------|
|
||||
| `false` | Don't add `<py-terminal>` to the page |
|
||||
| `true` | Automatically add a `<py-terminal>` to the page |
|
||||
| `"auto"` | This is the default. Automatically add a `<py-terminal auto>`, to the page. The terminal is initially hidden and automatically shown as soon as something writes to `stdout` and/or `stderr` |
|
||||
|
||||
### Examples
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
terminal = true
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
print("Hello, world!")
|
||||
</py-script>
|
||||
```
|
||||
|
||||
This example will create a new `<py-terminal>`, the value "Hello, world!" that was printed will show in it.
|
||||
|
||||
You can also add one (or more) `<py-terminal>` to the page manually.
|
||||
|
||||
```html
|
||||
<py-script>
|
||||
print("Hello, world!")
|
||||
</py-script>
|
||||
|
||||
<py-terminal></py-terminal>
|
||||
```
|
||||
|
||||
```{note}
|
||||
If you include a `<py-terminal>` in the page, you can skip `terminal` from your `<py-config>`.
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
# Deployment
|
||||
|
||||
**Coming soon!**
|
||||
@@ -5,46 +5,12 @@ This page will guide you through getting started with PyScript.
|
||||
## Development setup
|
||||
|
||||
PyScript does not require any development environment other
|
||||
than a web browser (we recommend using [Chrome](https://www.google.com/chrome/)) and a text editor, even though using your [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) of choice might be convenient.
|
||||
then a web browser (we recommend using [Chrome](https://www.google.com/chrome/)) and a text editor, even though using your [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) of choice might be convenient.
|
||||
|
||||
If you're using [VSCode](https://code.visualstudio.com/), the
|
||||
[Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
|
||||
can be used to reload the page as you edit the HTML file.
|
||||
|
||||
## Trying before installing
|
||||
|
||||
If you're new to programming and know nothing about HTML or just want to try some of PyScript features, we recommend using the [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) element in the [PyScript REPL example](https://pyscript.net/examples/repl.html) instead so you can have a programming experience in a REPL that doesn't require any setup. This REPL can be used to have an interactive experience using Python directly.
|
||||
|
||||
Alternatively, you can also use an online editor like W3School's [TryIt Editor](https://www.w3schools.com/html/tryit.asp?filename=tryhtml_default_default) and just plug the code below into it, as shown in the [example](https://docs.pyscript.net/latest/concepts/what-is-pyscript.html#example) page and click the run button.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>REPL</title>
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Hello world! <br>
|
||||
This is the current date and time, as computed by Python:
|
||||
<py-script>
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
now.strftime("%m/%d/%Y, %H:%M:%S")
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
You could try changing the code above to explore and play with pyscript yourself.
|
||||
|
||||
## Installation
|
||||
|
||||
There is no installation required. In this document, we'll use
|
||||
@@ -62,25 +28,6 @@ the same directory as your PyScript, JavaScript, and CSS files with the
|
||||
following content, and open the file in your web browser. You can typically
|
||||
open an HTML by double-clicking it in your file explorer.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body> <py-script> print('Hello, World!') </py-script> </body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Notice the use of the `<py-script>` tag in the HTML body. This
|
||||
is where you'll write your Python code. In the following sections, we'll
|
||||
introduce the eight tags provided by PyScript.
|
||||
|
||||
## The py-script tag
|
||||
|
||||
The `<py-script>` tag lets you execute multi-line Python scripts and
|
||||
print back onto the page. For example, we can compute π.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
@@ -88,417 +35,234 @@ print back onto the page. For example, we can compute π.
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-script>
|
||||
print("Let's compute π:")
|
||||
def compute_pi(n):
|
||||
pi = 2
|
||||
for i in range(1,n):
|
||||
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
|
||||
return pi
|
||||
|
||||
pi = compute_pi(100000)
|
||||
s = f"π is approximately {pi:.3f}"
|
||||
print(s)
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Writing into labeled elements
|
||||
|
||||
In the example above, we had a single `<py-script>` tag printing
|
||||
one or more lines onto the page in order. Within the `<py-script>`, you
|
||||
have access to the `pyscript` module, which provides a `.write()` method
|
||||
to send strings into labeled elements on the page.
|
||||
|
||||
For example, we'll add some style elements and provide placeholders for
|
||||
the `<py-script>` tag to write to.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<b><p>Today is <u><label id='today'></label></u></p></b>
|
||||
<br>
|
||||
<div id="pi" class="alert alert-primary"></div>
|
||||
<py-script>
|
||||
import datetime as dt
|
||||
pyscript.write('today', dt.date.today().strftime('%A %B %d, %Y'))
|
||||
|
||||
def compute_pi(n):
|
||||
pi = 2
|
||||
for i in range(1,n):
|
||||
pi *= 4 * i ** 2 / (4 * i ** 2 - 1)
|
||||
return pi
|
||||
|
||||
pi = compute_pi(100000)
|
||||
pyscript.write('pi', f'π is approximately {pi:.3f}')
|
||||
print('Hello, World!')
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## The py-config tag
|
||||
## A more complex example
|
||||
|
||||
Use the `<py-config>` tag to set and configure general metadata along with declaring dependencies for your PyScript application. The configuration has to be set in either TOML or JSON format. If you are unfamiliar with JSON, consider reading [freecodecamp's JSON for beginners](https://www.freecodecamp.org/news/what-is-json-a-json-file-example/) guide for more information. And for TOML, consider reading about it [here](https://learnxinyminutes.com/docs/toml/).
|
||||
Now that we know how you can create a simple 'Hello, World!' example, let's see a more complex example. This example will use the Demo created by [Cheuk Ting Ho](https://github.com/Cheukting). In this example, we will use more features from PyScript.
|
||||
|
||||
The ideal place to use `<py-config>` in between the `<head>...</head>` tags.
|
||||
### Setting up the base index file
|
||||
|
||||
The `<py-config>` tag can be used as follows:
|
||||
Let's create a new file called `index.html` and add the following content:
|
||||
|
||||
```html
|
||||
<py-config>
|
||||
autoclose_loader = true
|
||||
|
||||
[[runtimes]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
|
||||
name = "pyodide-0.21.2"
|
||||
lang = "python"
|
||||
</py-config>
|
||||
```
|
||||
|
||||
Alternatively, a JSON config can be passed using the `type` attribute.
|
||||
|
||||
```html
|
||||
<py-config type="json">
|
||||
{
|
||||
"autoclose_loader": true,
|
||||
"runtimes": [{
|
||||
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
|
||||
"name": "pyodide-0.21.2",
|
||||
"lang": "python"
|
||||
}]
|
||||
}
|
||||
</py-config>
|
||||
```
|
||||
|
||||
Besides passing the config as inline (as shown above), one can also pass it with the `src` attribute. This is demonstrated below:
|
||||
|
||||
```
|
||||
<py-config src="./custom.toml"></py-config>
|
||||
```
|
||||
|
||||
where `custom.toml` contains
|
||||
|
||||
```
|
||||
autoclose_loader = true
|
||||
[[runtimes]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
|
||||
name = "pyodide-0.21.2"
|
||||
lang = "python"
|
||||
```
|
||||
|
||||
This can also be done via JSON using the `type` attribute. By default, `type` is set to `"toml"` if not supplied.
|
||||
|
||||
```
|
||||
<py-config type="json" src="./custom.json"></py-config>
|
||||
```
|
||||
|
||||
where `custom.json` contains
|
||||
|
||||
```
|
||||
{
|
||||
"autoclose_loader": true,
|
||||
"runtimes": [{
|
||||
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
|
||||
"name": "pyodide-0.21.2",
|
||||
"lang": "python"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
One can also use both i.e pass the config from `src` attribute as well as specify it as `inline`. So the following snippet is also valid:
|
||||
|
||||
```
|
||||
<py-config src="./custom.toml">
|
||||
paths = ["./utils.py"]
|
||||
</py-config>
|
||||
```
|
||||
|
||||
This can also be done via JSON using the `type` attribute.
|
||||
|
||||
```
|
||||
<py-config type="json" src="./custom.json">
|
||||
{
|
||||
"paths": ["./utils.py"]
|
||||
}
|
||||
</py-config>
|
||||
```
|
||||
|
||||
Note: While the `<py-config>` tag supports both TOML and JSON, one cannot mix the type of config passed from 2 different sources i.e. the case when inline config is in TOML format while config from src is in JSON format is NOT allowed. Similarly for the opposite case.
|
||||
|
||||
---
|
||||
|
||||
This is helpful in cases where a number of applications share a common configuration (which can be supplied via `src`), but their specific keys need to be customised and overridden.
|
||||
|
||||
The keys supplied through `inline` override the values present in config supplied via `src`.
|
||||
|
||||
One can also declare dependencies so as to get access to many 3rd party OSS packages that are supported by PyScript.
|
||||
You can also link to `.whl` files directly on disk like in our [toga example](https://github.com/pyscript/pyscript/blob/main/examples/toga/freedom.html).
|
||||
|
||||
```
|
||||
<py-config>
|
||||
packages = ["./static/wheels/travertino-0.1.3-py3-none-any.whl"]
|
||||
</py-config>
|
||||
```
|
||||
|
||||
OR in JSON like
|
||||
|
||||
```
|
||||
<py-config type="json">
|
||||
{
|
||||
"packages": ["./static/wheels/travertino-0.1.3-py3-none-any.whl"]
|
||||
}
|
||||
</py-config>
|
||||
```
|
||||
|
||||
If your `.whl` is not a pure Python wheel, then open a PR or issue with [pyodide](https://github.com/pyodide/pyodide) to get it added [here](https://github.com/pyodide/pyodide/tree/main/packages).
|
||||
If there's enough popular demand, the pyodide team will likely work on supporting your package. Regardless, things will likely move faster if you make the PR and consult with the team to get unblocked.
|
||||
For example, NumPy and Matplotlib are available. Notice here we're using `<py-script output="plot">`
|
||||
as a shortcut, which takes the expression on the last line of the script and runs `pyscript.write('plot', fig)`.
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-config type="json">
|
||||
{
|
||||
"packages": ["numpy", "matplotlib"]
|
||||
}
|
||||
</py-config>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Let's plot random numbers</h1>
|
||||
<div id="plot"></div>
|
||||
<py-script output="plot">
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
x = np.random.randn(1000)
|
||||
y = np.random.randn(1000)
|
||||
fig, ax = plt.subplots()
|
||||
ax.scatter(x, y)
|
||||
fig
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Local modules
|
||||
In addition to packages, you can declare local Python modules that will
|
||||
be imported in the `<py-script>` tag. For example, we can place the random
|
||||
number generation steps in a function in the file `data.py`.
|
||||
```python
|
||||
# data.py
|
||||
import numpy as np
|
||||
def make_x_and_y(n):
|
||||
x = np.random.randn(n)
|
||||
y = np.random.randn(n)
|
||||
return x, y
|
||||
```
|
||||
|
||||
In the HTML tag `<py-config>`, paths to local modules are provided in the
|
||||
`paths:` key.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-config type="toml">
|
||||
packages = ["numpy", "matplotlib"]
|
||||
paths = ["./data.py"]
|
||||
</py-config>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Let's plot random numbers</h1>
|
||||
<div id="plot"></div>
|
||||
<py-script output="plot">
|
||||
import matplotlib.pyplot as plt
|
||||
from data import make_x_and_y
|
||||
x, y = make_x_and_y(n=1000)
|
||||
fig, ax = plt.subplots()
|
||||
ax.scatter(x, y)
|
||||
fig
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
The following optional values are supported by `<py-config>`:
|
||||
| Value | Type | Description |
|
||||
| ------ | ---- | ----------- |
|
||||
| `name` | string | Name of the user application. This field can be any string and is to be used by the application author for their own customization purposes. |
|
||||
| `description` | string | Description of the user application. This field can be any string and is to be used by the application author for their own customization purposes. |
|
||||
| `version` | string | Version of the user application. This field can be any string and is to be used by the application author for their own customization purposes. It is not related to the PyScript version. |
|
||||
| `schema_version` | number | The version of the config schema which determines what all keys are supported. This can be supplied by the user so PyScript knows what to expect in the config. If not supplied, the latest version for the schema is automatically used. |
|
||||
| `type` | string | Type of the project. The default is an "app" i.e. a user application |
|
||||
| `author_name` | string | Name of the author. |
|
||||
| `author_email` | string | Email of the author. |
|
||||
| `license` | string | License to be used for the user application. |
|
||||
| `autoclose_loader` | boolean | If false, PyScript will not close the loading splash screen when the startup operations finish. |
|
||||
| `packages` | List of Packages | Dependencies on 3rd party OSS packages are specified here. The default value is an empty list. |
|
||||
| `paths` | List of Paths | Local Python modules are to be specified here. The default value is an empty list. |
|
||||
| `plugins` | List of Plugins | List of Plugins are to be specified here. The default value is an empty list. |
|
||||
| `runtimes` | List of Runtimes | List of runtime configurations, described below. The default value contains a single Pyodide based runtime. |
|
||||
|
||||
A runtime configuration consists of the following:
|
||||
| Value | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `src` | string (Required) | URL to the runtime source. |
|
||||
| `name` | string | Name of the runtime. This field can be any string and is to be used by the application author for their own customization purposes |
|
||||
| `lang` | string | Programming language supported by the runtime. This field can be used by the application author to provide clarification. It currently has no implications on how PyScript behaves. |
|
||||
|
||||
Besides the above format, a user can also supply any extra keys and values that are relevant as metadata information or perhaps are being used within the application.
|
||||
|
||||
For example, a valid config could also be with the snippet below:
|
||||
|
||||
```
|
||||
<py-config type="toml">
|
||||
magic = "unicorn"
|
||||
</py-config>
|
||||
```
|
||||
|
||||
OR in JSON like
|
||||
|
||||
```
|
||||
<py-config type="json">
|
||||
{
|
||||
"magic": "unicorn"
|
||||
}
|
||||
</py-config>
|
||||
```
|
||||
|
||||
If this `"magic"` key is present in config supplied via `src` and also present in config supplied via `inline`, then the value in the inline config is given priority i.e. the overriding process also works for custom keys.
|
||||
|
||||
## The py-env tag (Deprecated)
|
||||
|
||||
**The <py-env> tag is deprecated as of `2022.09.1` release but you can still use the functionality explained below. It will be removed in the next release. To specify packages in the recommended way, please see the <py-config> section.**
|
||||
|
||||
In addition to the [Python Standard Library](https://docs.python.org/3/library/) and
|
||||
the `pyscript` module, many 3rd-party OSS packages will work out-of-the-box with PyScript.
|
||||
|
||||
In order to use them, you will need to declare the dependencies using the `<py-env>` tag in the
|
||||
HTML head. You can also link to `.whl` files directly on disk like in our [toga example](https://github.com/pyscript/pyscript/blob/main/examples/toga/freedom.html).
|
||||
|
||||
```
|
||||
<py-env>
|
||||
- './static/wheels/travertino-0.1.3-py3-none-any.whl'
|
||||
</py-env>
|
||||
```
|
||||
|
||||
If your `.whl` is not a pure Python wheel, then open a PR or issue with [pyodide](https://github.com/pyodide/pyodide) to get it added [here](https://github.com/pyodide/pyodide/tree/main/packages).
|
||||
If there's enough popular demand, the pyodide team will likely work on supporting your package. Regardless, things will likely move faster if you make the PR and consult with the team to get unblocked.
|
||||
|
||||
For example, NumPy and Matplotlib are available. Notice here we're using `<py-script output="plot">`
|
||||
as a shortcut, which takes the expression on the last line of the script and runs `pyscript.write('plot', fig)`.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-env>
|
||||
- numpy
|
||||
- matplotlib
|
||||
</py-env>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Let's plot random numbers</h1>
|
||||
<div id="plot"></div>
|
||||
<py-script output="plot">
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
x = np.random.randn(1000)
|
||||
y = np.random.randn(1000)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.scatter(x, y)
|
||||
fig
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Local modules with py-env (Deprecated)
|
||||
|
||||
**The <py-env> tag is deprecated as of `2022.09.1` release but you can still use the functionality explained below. It will be removed in the next release. To specify local modules in the recommended way, please see the <py-config> section.**
|
||||
|
||||
In addition to packages, you can declare local Python modules that will
|
||||
be imported in the `<py-script>` tag. For example, we can place the random
|
||||
number generation steps in a function in the file `data.py`.
|
||||
```python
|
||||
# data.py
|
||||
import numpy as np
|
||||
def make_x_and_y(n):
|
||||
x = np.random.randn(n)
|
||||
y = np.random.randn(n)
|
||||
return x, y
|
||||
```
|
||||
|
||||
In the HTML tag `<py-env>`, paths to local modules are provided in the
|
||||
`paths:` key.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-env>
|
||||
- numpy
|
||||
- matplotlib
|
||||
- paths:
|
||||
- ./data.py
|
||||
</py-env>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Let's plot random numbers</h1>
|
||||
<div id="plot"></div>
|
||||
<py-script output="plot">
|
||||
import matplotlib.pyplot as plt
|
||||
from data import make_x_and_y
|
||||
x, y = make_x_and_y(n=1000)
|
||||
fig, ax = plt.subplots()
|
||||
ax.scatter(x, y)
|
||||
fig
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## The py-repl tag
|
||||
|
||||
The `<py-repl>` tag creates a REPL component that is rendered to the page as a code editor, allowing you to write executable code inline.
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Ice Cream Picker</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<py-repl></py-repl>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Visual component tags
|
||||
In this first step, we have created the index file, imported `pyscript.css` and `pyscript.js`. We are ready to start adding the elements we need for our application.
|
||||
|
||||
The following tags can be used to add visual attributes to your HTML page.
|
||||
### Importing the needed libraries
|
||||
|
||||
| Tag | Description |
|
||||
| --- | ----------- |
|
||||
| `<py-inputbox>` | Adds an input box that can be used to prompt users to enter input values. |
|
||||
| `<py-box>` | Creates a container object that can be used to host one or more visual components that define how elements of `<py-box>` should align and show on the page. |
|
||||
| `<py-button>` | Adds a button to which authors can add labels and event handlers for actions on the button, such as `on_focus` or `on_click`. |
|
||||
| `<py-title>` | Adds a static text title component that styles the text inside the tag as a page title. |
|
||||
For this example, we will need to install `pandas` and `matplotlib`. We can install libraries using the `<py-config>` tag so we can import them later. Please refer to the [`<py-config>`](../reference/elements/py-config.md) documentation for more information.
|
||||
|
||||
```{note}
|
||||
All the elements above are experimental and not implemented at their full functionality. Use them with the understanding that the APIs or full support might change or be removed until the visual components are more mature.
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Ice Cream Picker</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<py-config>
|
||||
packages = ["matplotlib", "pandas"]
|
||||
</py-config>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Importing the data and exploring
|
||||
|
||||
Now that we have installed the needed libraries, we can import and explore the data. In this step, we need to create a `<py-script>` tag to import our dependencies, read the data with pandas and then use `py-repl` to explore the data.
|
||||
|
||||
You may want to read the [`<py-script>`](../reference/elements/py-script.md) and [`<py-repl>`](../reference/elements/py-repl.md) documentation for more information about these elements.
|
||||
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Ice Cream Picker</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<py-config>
|
||||
packages = ["matplotlib", "pandas"]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import pandas as pd
|
||||
|
||||
from pyodide.http import open_url
|
||||
|
||||
url = (
|
||||
"https://raw.githubusercontent.com/Cheukting/pyscript-ice-cream/main/bj-products.csv"
|
||||
)
|
||||
ice_data = pd.read_csv(open_url(url))
|
||||
</py-script>
|
||||
|
||||
<py-repl>
|
||||
ice_data
|
||||
</py-repl>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Note that we are adding `ice_data` to `py-repl` to pre-populate the REPL with this variable, so you don't have to type it yourself.
|
||||
|
||||
### Creating the plot
|
||||
|
||||
Now that we have the data, we can create the plot. We will use the `matplotlib` library to make the plot. We will use the `display` API to display the plot on the page. You may want to read the [`display`](../reference/API/display.md) documentation for more information.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Ice Cream Picker</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<py-config>
|
||||
packages = ["matplotlib", "pandas"]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from pyodide.http import open_url
|
||||
|
||||
url = (
|
||||
"https://raw.githubusercontent.com/Cheukting/pyscript-ice-cream/main/bj-products.csv"
|
||||
)
|
||||
ice_data = pd.read_csv(open_url(url))
|
||||
|
||||
def plot(data):
|
||||
plt.rcParams["figure.figsize"] = (22,20)
|
||||
fig, ax = plt.subplots()
|
||||
bars = ax.barh(data["name"], data["rating"], height=0.7)
|
||||
ax.bar_label(bars)
|
||||
plt.title("Rating of ice cream flavours of your choice")
|
||||
display(fig, target="graph-area", append=False)
|
||||
|
||||
plot(ice_data)
|
||||
</py-script>
|
||||
|
||||
<py-repl>
|
||||
ice_data
|
||||
</py-repl>
|
||||
|
||||
<div id="graph-area"></div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Select specific flavours
|
||||
|
||||
Now that we have a way to explore the data using `py-repl` and a way to create the plot using all of the data, it's time for us to add a way to select specific flavours.
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Ice Cream Picker</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<py-config>
|
||||
packages = ["matplotlib", "pandas"]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from pyodide.http import open_url
|
||||
from pyodide.ffi import create_proxy
|
||||
|
||||
url = (
|
||||
"https://raw.githubusercontent.com/Cheukting/pyscript-ice-cream/main/bj-products.csv"
|
||||
)
|
||||
ice_data = pd.read_csv(open_url(url))
|
||||
|
||||
current_selected = []
|
||||
flavour_elements = document.getElementsByName("flavour")
|
||||
|
||||
def plot(data):
|
||||
plt.rcParams["figure.figsize"] = (22,20)
|
||||
fig, ax = plt.subplots()
|
||||
bars = ax.barh(data["name"], data["rating"], height=0.7)
|
||||
ax.bar_label(bars)
|
||||
plt.title("Rating of ice cream flavours of your choice")
|
||||
display(fig, target="graph-area", append=False)
|
||||
|
||||
def select_flavour(event):
|
||||
for ele in flavour_elements:
|
||||
if ele.checked:
|
||||
current_selected = ele.value
|
||||
break
|
||||
if current_selected == "ALL":
|
||||
plot(ice_data)
|
||||
else:
|
||||
filter = ice_data.apply(lambda x: ele.value in x["ingredients"], axis=1)
|
||||
plot(ice_data[filter])
|
||||
|
||||
ele_proxy = create_proxy(select_flavour)
|
||||
|
||||
for ele in flavour_elements:
|
||||
if ele.value == "ALL":
|
||||
ele.checked = True
|
||||
current_selected = ele.value
|
||||
ele.addEventListener("change", ele_proxy)
|
||||
|
||||
plot(ice_data)
|
||||
|
||||
</py-script>
|
||||
|
||||
<div id="input" style="margin: 20px;">
|
||||
Select your 🍨 flavour: <br/>
|
||||
<input type="radio" id="all" name="flavour" value="ALL">
|
||||
<label for="all"> All 🍧</label>
|
||||
<input type="radio" id="chocolate" name="flavour" value="COCOA">
|
||||
<label for="chocolate"> Chocolate 🍫</label>
|
||||
<input type="radio" id="cherrie" name="flavour" value="CHERRIES">
|
||||
<label for="cherrie"> Cherries 🍒</label>
|
||||
<input type="radio" id="berries" name="flavour" value="BERRY">
|
||||
<label for="berries"> Berries 🍓</label>
|
||||
<input type="radio" id="cheese" name="flavour" value="CHEESE">
|
||||
<label for="cheese"> Cheese 🧀</label>
|
||||
<input type="radio" id="peanut" name="flavour" value="PEANUT">
|
||||
<label for="peanut"> Peanut 🥜</label>
|
||||
</div>
|
||||
|
||||
<py-repl>
|
||||
ice_data
|
||||
</py-repl>
|
||||
|
||||
<div id="graph-area"></div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
@@ -7,6 +7,7 @@ This is the tutorials section for beginners.
|
||||
maxdepth: 2
|
||||
---
|
||||
getting-started
|
||||
deployment
|
||||
setup
|
||||
py-config-fetch
|
||||
py-config-runtime
|
||||
writing-to-page
|
||||
```
|
||||
|
||||
188
docs/tutorials/py-config-fetch.md
Normal file
188
docs/tutorials/py-config-fetch.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# Using the fetch from py-config
|
||||
|
||||
This tutorial shows how to use the fetch configuration from `py-config` to fetch two files from a remote server, store them in a local directory, and verify their contents.
|
||||
|
||||
## Development setup
|
||||
|
||||
We will create a todo list application similar to the one in the [examples](https://pyscript.net/examples/todo.html). To do this, we need three things:
|
||||
|
||||
* An `index.html` file containing the HTML for the application.
|
||||
* A `todo.py` file containing the Python code for the application.
|
||||
* A `utils.py` file containing some utility functions for the application.
|
||||
|
||||
|
||||
We will use the `fetch` configuration from `py-config` to fetch these files from a remote server and store them in a local directory.
|
||||
|
||||
### Creating the html file
|
||||
|
||||
In this first step, we will create the `index.html` file and import both `pyscript.css` and `pyscript.js`. These are needed to run our Python code in the browser and style the application.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>My Todo</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Fetching the files
|
||||
|
||||
### Using `fetch` to get the python files
|
||||
|
||||
Now we will use the `fetch` configuration from `py-config` to fetch the `todo.py` and `utils.py` files from a remote server and store them in a local directory called `todo`. Here we will fetch files from different URLs, using a `fetch` per item.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>My Todo</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = "https://pyscript.net/examples/"
|
||||
files = ["utils.py"]
|
||||
[[fetch]]
|
||||
from = "https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/"
|
||||
files = ["todo.py"]
|
||||
</py-config>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Creating a todo application
|
||||
|
||||
### Creating the todo elements
|
||||
|
||||
Now we will create the todo elements in the `body` of the `index.html` file.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>My Todo</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = "https://pyscript.net/examples/"
|
||||
files = ["utils.py"]
|
||||
[[fetch]]
|
||||
from = "https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/"
|
||||
files = ["todo.py"]
|
||||
</py-config>
|
||||
<section>
|
||||
<div class="text-center w-full mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List</h1>
|
||||
</div>
|
||||
<div>
|
||||
<input id="new-task-content" class="py-input" type="text">
|
||||
<button id="new-task-btn" class="py-button" type="submit" py-click="add_task()">
|
||||
Add task
|
||||
</button>
|
||||
</div>
|
||||
<div id="list-tasks-container" class="flex flex-col-reverse mt-4"></div>
|
||||
<template id="task-template">
|
||||
<section class="task py-li-element">
|
||||
<label for="flex items-center p-2 ">
|
||||
<input class="mr-2" type="checkbox">
|
||||
<p class="m-0 inline"></p>
|
||||
</label>
|
||||
</section>
|
||||
</template>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Our todo application is starting to shape up, although if you try to add any tasks, you will notice that nothing happens. This is because we have not yet imported the `todo.py` file.
|
||||
|
||||
### Importing the needed functions from `todo.py`
|
||||
|
||||
This is where the magic happens. We can import the `todo.py` file by adding it as a source to the `py-script` tag. By specifying the file, pyscript will automatically import the file and run the code in it.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>My Todo</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = "https://pyscript.net/examples/"
|
||||
files = ["utils.py"]
|
||||
[[fetch]]
|
||||
from = "https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/"
|
||||
files = ["todo.py"]
|
||||
</py-config>
|
||||
<py-script>
|
||||
from todo import add_task, add_task_event
|
||||
</py-script>
|
||||
<section>
|
||||
<div class="text-center w-full mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List</h1>
|
||||
</div>
|
||||
<div>
|
||||
<input id="new-task-content" class="py-input" type="text">
|
||||
<button id="new-task-btn" class="py-button" type="submit" py-click="add_task()">
|
||||
Add task
|
||||
</button>
|
||||
</div>
|
||||
<div id="list-tasks-container" class="flex flex-col-reverse mt-4"></div>
|
||||
<template id="task-template">
|
||||
<section class="task py-li-element">
|
||||
<label for="flex items-center p-2 ">
|
||||
<input class="mr-2" type="checkbox">
|
||||
<p class="m-0 inline"></p>
|
||||
</label>
|
||||
</section>
|
||||
</template>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
You can now save the file and refresh the page. You should now be able to add tasks to your todo list.
|
||||
|
||||
## That's it!
|
||||
|
||||
You have now created a todo application using pyscript. You can add tasks and mark them as done. Let's take a recap of what we have achieved:
|
||||
|
||||
* We have imported three separate files into our `index.html` file using the `py-config` tag.
|
||||
* We have created the necessary HTML code to create our todo's
|
||||
* We have imported functions from the `todo.py` file, using the `py-script` tag.
|
||||
|
||||
For reference, the code from [the gist](https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/todo.py) is the same code that our [todo example](https://pyscript.net/examples/todo.html) uses with a slight change of importing `Element` from `pyscript`.
|
||||
88
docs/tutorials/py-config-runtime.md
Normal file
88
docs/tutorials/py-config-runtime.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Setting a pyodide runtime
|
||||
|
||||
Pyscript will automatically set the runtime for you, but you can also set it manually. This is useful if you want to use a different version than the one set by default.
|
||||
|
||||
## Development setup
|
||||
|
||||
To get started, let's create a new `index.html` file and import `pyscript.js`.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Runtime</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
We are using the pyodide CDN to setup our runtime, but you can also download the files from [the pyodide GitHub release](https://github.com/pyodide/pyodide/releases/tag/0.22.0a3), unzip them and use the `pyodide.js` file as your runtime.
|
||||
|
||||
## Setting the runtime
|
||||
|
||||
To set the runtime, you can use the `runtime` configuration in the `py-config` element. In this tutorial, we will use the default `TOML` format, but know that you can also use `json` if you prefer by changing the `type` attribute of the `py-config` element.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Runtime</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
[[runtimes]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.22.0a3/full/pyodide.js"
|
||||
name = "pyodide-0.22.0a3"
|
||||
lang = "python"
|
||||
</py-config>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Confirming the runtime version
|
||||
|
||||
To confirm that the runtime is set correctly, you can open the DevTools and check the version from the console. But for the sake of this tutorial, let's create a `py-script` tag and print pyodide's version.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Runtime</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-config>
|
||||
[[runtimes]]
|
||||
src = "https://cdn.jsdelivr.net/pyodide/v0.22.0a3/full/pyodide.js"
|
||||
name = "pyodide-0.22.0a3"
|
||||
lang = "python"
|
||||
</py-config>
|
||||
<py-script>
|
||||
import pyodide
|
||||
print(pyodide.__version__)
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
# Setup
|
||||
|
||||
**Coming soon!**
|
||||
212
docs/tutorials/writing-to-page.md
Normal file
212
docs/tutorials/writing-to-page.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# How to write content to the page
|
||||
|
||||
When creating your PyScript application, you will want to write content to the page. This tutorial will explore the different methods you can use to write content to the page and their differences.
|
||||
|
||||
## Development setup
|
||||
|
||||
To get started, we will create an `index.html` file, import PyScript and start building on top of it.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Writing content to an element
|
||||
|
||||
Let's first see how we can write content to an element on the page. We will start by creating a `div` element with an `id` of `manual-write`, then create a `py-script` tag that, upon a click of a button, will write 'Hello World' to the `div` element.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="manual-write"></div>
|
||||
<button py-click="write_to_page()" id="manual">Say Hello</button>
|
||||
|
||||
<py-script>
|
||||
def write_to_page():
|
||||
manual_div = Element("manual-write")
|
||||
manual_div.element.innerText = "Hello World"
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```{note}
|
||||
When using `py-click` you must supply an `id` to the element you want to use as the trigger.
|
||||
```
|
||||
|
||||
We can now open our `index.html` file and click the button. You will see that "Hello World" will appear in the `div` element. You could also write HTML using `manual_div.element.innerHTML` instead of `innerText`. For example:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="manual-write"></div>
|
||||
<button py-click="write_to_page()" id="manual">Say Hello</button>
|
||||
|
||||
<py-script>
|
||||
def write_to_page():
|
||||
manual_div = Element("manual-write")
|
||||
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Writing content with the `display` API
|
||||
|
||||
The `display` API is a simple way to write content to the page. Not only does it allow you to write content to the page, but it also allows you to display a range of different content types such as images, markdown, svgs, json, etc.
|
||||
|
||||
Using the' display' API, let's reuse our previous example and write "Hello World" to the page.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="manual-write"></div>
|
||||
<button py-click="write_to_page()" id="manual">Say Hello</button>
|
||||
<div id="display-write"></div>
|
||||
<button py-click="display_to_div()" id="display">Say Things!</button>
|
||||
|
||||
<py-script>
|
||||
def write_to_page():
|
||||
manual_div = Element("manual-write")
|
||||
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
|
||||
|
||||
def display_to_div():
|
||||
display("I display things!", target="display-write")
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```{note}
|
||||
When using the `display` API, you must specify the `target` parameter to tell PyScript where to write the content. If you do not use this parameter, an error will be thrown.
|
||||
```
|
||||
|
||||
You may be interested in reading more about the `display` API in the [Display API](../reference/api/display) section of the documentation.
|
||||
|
||||
## Printing to the page
|
||||
|
||||
We couldn't have a tutorial on writing to the page without mentioning the `print` function. The `print` function is a simple way to write content to the page, that any Python developer will be familiar with. When you use the `print` function, the content will be written to the page in a `py-terminal` element.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="manual-write"></div>
|
||||
<button py-click="write_to_page()" id="manual">Say Hello</button>
|
||||
<div id="display-write"></div>
|
||||
<button py-click="display_to_div()" id="display">Say Things!</button>
|
||||
<button py-click="print_to_page()" id="print">Print Things!</button>
|
||||
|
||||
<py-script>
|
||||
def write_to_page():
|
||||
manual_div = Element("manual-write")
|
||||
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
|
||||
|
||||
def display_to_div():
|
||||
display("I display things!", target="display-write")
|
||||
|
||||
def print_to_page():
|
||||
print("I print things!")
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
You may be surprised to see that when you click the "Print Things!" button, the content is written below the rest of the elements on the page in a black canvas. This is because the `print` function writes content to the page in a `py-terminal` element. You can read more about the `py-terminal` element in the [Terminal Element](../reference/plugins/py-terminal) section of the documentation.
|
||||
|
||||
PyScript comes with the `py-terminal` plugin by default and any `stdout` or `stderr` content will be shown in this element. We can be explicit about where we want the terminal to be shown by adding the `<py-terminal>` tag to our HTML.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Writing to the page</title>
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="manual-write"></div>
|
||||
<button py-click="write_to_page()" id="manual">Say Hello</button>
|
||||
<div id="display-write"></div>
|
||||
<button py-click="display_to_div()" id="display">Say Things!</button>
|
||||
<div>
|
||||
<py-terminal>
|
||||
</div>
|
||||
<button py-click="print_to_page()" id="print">Print Things!</button>
|
||||
|
||||
<py-script>
|
||||
def write_to_page():
|
||||
manual_div = Element("manual-write")
|
||||
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
|
||||
|
||||
def display_to_div():
|
||||
display("I display things!", target="display-write")
|
||||
|
||||
def print_to_page():
|
||||
print("I print things!")
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -4,59 +4,151 @@
|
||||
<meta charset="utf-8">
|
||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-config>
|
||||
packages = [
|
||||
"altair",
|
||||
"pandas",
|
||||
"vega_datasets"
|
||||
]
|
||||
</py-config>
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="altair" style="width: 100%; height: 100%"></div>
|
||||
<py-script output="altair">
|
||||
import altair as alt
|
||||
from vega_datasets import data
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Altair</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div id="altair"></div>
|
||||
<py-config>
|
||||
packages = [
|
||||
"altair",
|
||||
"pandas",
|
||||
"vega_datasets"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import altair as alt
|
||||
from vega_datasets import data
|
||||
|
||||
source = data.movies.url
|
||||
source = data.movies.url
|
||||
|
||||
pts = alt.selection(type="single", encodings=['x'])
|
||||
pts = alt.selection(type="single", encodings=['x'])
|
||||
|
||||
rect = alt.Chart(data.movies.url).mark_rect().encode(
|
||||
alt.X('IMDB_Rating:Q', bin=True),
|
||||
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
|
||||
alt.Color('count()',
|
||||
scale=alt.Scale(scheme='greenblue'),
|
||||
legend=alt.Legend(title='Total Records')
|
||||
)
|
||||
)
|
||||
rect = alt.Chart(data.movies.url).mark_rect().encode(
|
||||
alt.X('IMDB_Rating:Q', bin=True),
|
||||
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
|
||||
alt.Color('count()',
|
||||
scale=alt.Scale(scheme='greenblue'),
|
||||
legend=alt.Legend(title='Total Records')
|
||||
)
|
||||
)
|
||||
|
||||
circ = rect.mark_point().encode(
|
||||
alt.ColorValue('grey'),
|
||||
alt.Size('count()',
|
||||
legend=alt.Legend(title='Records in Selection')
|
||||
)
|
||||
).transform_filter(
|
||||
pts
|
||||
)
|
||||
circ = rect.mark_point().encode(
|
||||
alt.ColorValue('grey'),
|
||||
alt.Size('count()',
|
||||
legend=alt.Legend(title='Records in Selection')
|
||||
)
|
||||
).transform_filter(
|
||||
pts
|
||||
)
|
||||
|
||||
bar = alt.Chart(source).mark_bar().encode(
|
||||
x='Major_Genre:N',
|
||||
y='count()',
|
||||
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
|
||||
).properties(
|
||||
width=550,
|
||||
height=200
|
||||
).add_selection(pts)
|
||||
bar = alt.Chart(source).mark_bar().encode(
|
||||
x='Major_Genre:N',
|
||||
y='count()',
|
||||
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
|
||||
).properties(
|
||||
width=550,
|
||||
height=200
|
||||
).add_selection(pts)
|
||||
|
||||
alt.vconcat(
|
||||
rect + circ,
|
||||
bar
|
||||
).resolve_legend(
|
||||
color="independent",
|
||||
size="independent"
|
||||
)
|
||||
</py-script>
|
||||
</body>
|
||||
display(alt.vconcat(
|
||||
rect + circ,
|
||||
bar
|
||||
).resolve_legend(
|
||||
color="independent",
|
||||
size="independent"
|
||||
), target="altair")
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-config>
|
||||
packages = [
|
||||
"altair",
|
||||
"pandas",
|
||||
"vega_datasets"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import altair as alt
|
||||
from vega_datasets import data
|
||||
|
||||
source = data.movies.url
|
||||
|
||||
pts = alt.selection(type="single", encodings=['x'])
|
||||
|
||||
rect = alt.Chart(data.movies.url).mark_rect().encode(
|
||||
alt.X('IMDB_Rating:Q', bin=True),
|
||||
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
|
||||
alt.Color('count()',
|
||||
scale=alt.Scale(scheme='greenblue'),
|
||||
legend=alt.Legend(title='Total Records')
|
||||
)
|
||||
)
|
||||
|
||||
circ = rect.mark_point().encode(
|
||||
alt.ColorValue('grey'),
|
||||
alt.Size('count()',
|
||||
legend=alt.Legend(title='Records in Selection')
|
||||
)
|
||||
).transform_filter(
|
||||
pts
|
||||
)
|
||||
|
||||
bar = alt.Chart(source).mark_bar().encode(
|
||||
x='Major_Genre:N',
|
||||
y='count()',
|
||||
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
|
||||
).properties(
|
||||
width=550,
|
||||
height=200
|
||||
).add_selection(pts)
|
||||
|
||||
display(alt.vconcat(
|
||||
rect + circ,
|
||||
bar
|
||||
).resolve_legend(
|
||||
color="independent",
|
||||
size="independent"
|
||||
), target="altair")
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -4,18 +4,115 @@
|
||||
<meta charset="utf-8">
|
||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
<py-config>
|
||||
paths = [
|
||||
"./antigravity.py"
|
||||
]
|
||||
</py-config>
|
||||
<body>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Antigravity</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./antigravity.py"]
|
||||
</py-config>
|
||||
<b>Based on xkcd: antigravity https://xkcd.com/353/.</b>
|
||||
<py-script>
|
||||
import antigravity
|
||||
antigravity.fly()
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./antigravity.py"]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import antigravity
|
||||
antigravity.fly()
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
<p>antigravity.py</p>
|
||||
<pre class="prism-code language-python">
|
||||
<code class="language-python">
|
||||
import random
|
||||
import sys
|
||||
|
||||
from js import DOMParser, document, setInterval
|
||||
from pyodide.ffi import create_proxy
|
||||
from pyodide.http import open_url
|
||||
|
||||
|
||||
class Antigravity:
|
||||
|
||||
url = "./antigravity.svg"
|
||||
|
||||
def __init__(self, target=None, interval=10, append=True, fly=False):
|
||||
target = target or sys.stdout._out
|
||||
self.target = (
|
||||
document.getElementById(target) if isinstance(target, str) else target
|
||||
)
|
||||
doc = DOMParser.new().parseFromString(
|
||||
open_url(self.url).read(), "image/svg+xml"
|
||||
)
|
||||
self.node = doc.documentElement
|
||||
if append:
|
||||
self.target.append(self.node)
|
||||
else:
|
||||
self.target.replaceChildren(self.node)
|
||||
self.xoffset, self.yoffset = 0, 0
|
||||
self.interval = interval
|
||||
if fly:
|
||||
self.fly()
|
||||
|
||||
def fly(self):
|
||||
setInterval(create_proxy(self.move), self.interval)
|
||||
|
||||
def move(self):
|
||||
char = self.node.getElementsByTagName("g")[1]
|
||||
char.setAttribute("transform", f"translate({self.xoffset}, {-self.yoffset})")
|
||||
self.xoffset += random.normalvariate(0, 1) / 20
|
||||
if self.yoffset < 50:
|
||||
self.yoffset += 0.1
|
||||
else:
|
||||
self.yoffset += random.normalvariate(0, 1) / 20
|
||||
|
||||
_auto = Antigravity(append=True)
|
||||
fly = _auto.fly
|
||||
</code>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import random
|
||||
import sys
|
||||
|
||||
from js import DOMParser, document, setInterval
|
||||
from pyodide.ffi import create_proxy
|
||||
@@ -11,9 +10,10 @@ class Antigravity:
|
||||
url = "./antigravity.svg"
|
||||
|
||||
def __init__(self, target=None, interval=10, append=True, fly=False):
|
||||
target = target or sys.stdout._out
|
||||
self.target = (
|
||||
document.getElementById(target) if isinstance(target, str) else target
|
||||
document.getElementById(target)
|
||||
if isinstance(target, str)
|
||||
else document.body
|
||||
)
|
||||
doc = DOMParser.new().parseFromString(
|
||||
open_url(self.url).read(), "image/svg+xml"
|
||||
|
||||
90
examples/assets/css/examples.css
Normal file
90
examples/assets/css/examples.css
Normal file
@@ -0,0 +1,90 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pyscript {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.code {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
z-index: 9998;
|
||||
top: 7rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1300px) {
|
||||
.code:has(> .code-section-visible) {
|
||||
width: 90%;
|
||||
/* Absolute position is messing up the layout on small screens */
|
||||
right: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
.code-section-hidden {
|
||||
width: 0px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.code-section-visible {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
background-color: rgb(45 46 53 / 90%);
|
||||
padding: 1rem;
|
||||
border-radius: 10px 0px 0px 10px;
|
||||
color: #c6c6c8;
|
||||
}
|
||||
.code-section-visible p{
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.language-html, .language-python {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#view-code-button {
|
||||
writing-mode: tb-rl;
|
||||
text-orientation: sideways-right;
|
||||
background-color: #1D1D22;
|
||||
color: white;
|
||||
padding: 0.5rem;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
height: 81px;
|
||||
}
|
||||
|
||||
nav {
|
||||
position: sticky;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.logo {
|
||||
padding-right: 10px;
|
||||
font-size: 28px;
|
||||
height: 30px;
|
||||
max-width: inherit;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-decoration: none;
|
||||
text-decoration-line: none;
|
||||
text-decoration-style: initial;
|
||||
text-decoration-color: initial;
|
||||
font-weight: 400;
|
||||
font-size: 1.5em;
|
||||
line-height: 2em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.example h2{
|
||||
color: #000000;
|
||||
/* color: #000000; */
|
||||
font-family: "Inconsolata", monospace;
|
||||
font-size: 2.25rem;
|
||||
margin-bottom: 1rem;
|
||||
@@ -11,10 +11,14 @@
|
||||
|
||||
.card {
|
||||
height: 15rem;
|
||||
background-color: #FFFFFF;
|
||||
background-color: var(--color-secondary);
|
||||
padding: 1rem;
|
||||
-webkit-box-shadow: var(--card-shadow);
|
||||
box-shadow: var(--card-shadow);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.card:hover, .card:hover a, .card:hover a:visited, .card:hover h2 {
|
||||
background-color: var(--color-primary);
|
||||
color: #1D1D22
|
||||
}
|
||||
|
||||
.card a h2 {
|
||||
@@ -24,6 +28,17 @@
|
||||
font-size: 2.25rem;
|
||||
}
|
||||
|
||||
.card a p {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
a .card {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@@ -31,6 +46,10 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-content a, .card-content a:visited {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.container-card {
|
||||
max-width: 1500px;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
@import "./variables.css";
|
||||
@import "./reset.css";
|
||||
|
||||
|
||||
body {
|
||||
background: #2D2E35 url('https://assets.anaconda.com/production/Content/1650828148240.png?w=3240&auto=compress%2Cformat&fit=crop&dm=1650828161&s=c558dc55e0ed1f8419a892e842a5728f') repeat-x center bottom / 250px;
|
||||
background-attachment: fixed;
|
||||
overflow-x: hidden;
|
||||
color: var(--text-color)
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1510px;
|
||||
margin: auto;
|
||||
@@ -9,7 +17,6 @@
|
||||
}
|
||||
|
||||
.title-main {
|
||||
color: #000000;
|
||||
font-size: 4.25rem;
|
||||
font-family: "Inconsolata", monospace;
|
||||
text-align: center;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
:root {
|
||||
--color-primary: #2563eb;
|
||||
--color-primary: #FDA703;
|
||||
--color-secondary: #1D1D22;
|
||||
--text-color: white;
|
||||
--card-shadow: 0px 5px 11px 0px rgb(0 0 0 / 15%);
|
||||
}
|
||||
|
||||
3
examples/assets/prism/prism.css
Normal file
3
examples/assets/prism/prism.css
Normal file
@@ -0,0 +1,3 @@
|
||||
/* PrismJS 1.29.0
|
||||
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+clike+javascript+python */
|
||||
code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
|
||||
7
examples/assets/prism/prism.js
Normal file
7
examples/assets/prism/prism.js
Normal file
File diff suppressed because one or more lines are too long
@@ -14,18 +14,29 @@
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
</py-config>
|
||||
<h1>Bokeh Example</h1>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div id="myplot"></div>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script id="main">
|
||||
import json
|
||||
import pyodide
|
||||
@@ -45,6 +56,60 @@ p_json = json.dumps(json_item(p, "myplot"))
|
||||
|
||||
Bokeh.embed.embed_item(JSON.parse(p_json))
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
</py-config>
|
||||
<py-script id="main">
|
||||
import json
|
||||
import pyodide
|
||||
|
||||
</body>
|
||||
from js import Bokeh, console, JSON
|
||||
|
||||
from bokeh.embed import json_item
|
||||
from bokeh.plotting import figure
|
||||
from bokeh.resources import CDN
|
||||
|
||||
# create a new plot with default tools, using figure
|
||||
p = figure(plot_width=400, plot_height=400)
|
||||
|
||||
# add a circle renderer with x and y coordinates, size, color, and alpha
|
||||
p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=15, line_color="navy", fill_color="orange", fill_alpha=0.5)
|
||||
p_json = json.dumps(json_item(p, "myplot"))
|
||||
|
||||
Bokeh.embed.embed_item(JSON.parse(p_json))
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.2.min.js"></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
Bokeh.set_log_level("info");
|
||||
@@ -14,18 +17,27 @@
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
</py-config>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<h1>Bokeh Example</h1>
|
||||
<div id="myplot"></div>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script id="main">
|
||||
import asyncio
|
||||
import json
|
||||
@@ -93,8 +105,112 @@ async def show(plot, target):
|
||||
jsdoc = views[0].model.document
|
||||
_link_docs(pydoc, jsdoc)
|
||||
|
||||
await show(row, 'myplot')
|
||||
asyncio.ensure_future(show(row, 'myplot'))
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
</py-config>
|
||||
<py-script id="main">
|
||||
import asyncio
|
||||
import json
|
||||
import pyodide
|
||||
|
||||
</body>
|
||||
from js import Bokeh, console, JSON
|
||||
|
||||
from bokeh import __version__
|
||||
from bokeh.document import Document
|
||||
from bokeh.embed.util import OutputDocumentFor, standalone_docs_json_and_render_items
|
||||
from bokeh.models import Slider, Div
|
||||
from bokeh.layouts import Row
|
||||
from bokeh.protocol.messages.patch_doc import process_document_events
|
||||
|
||||
# create a new plot with default tools, using figure
|
||||
p = Slider(start=0.1, end=10, value=1, step=.1, title="Amplitude")
|
||||
div = Div(text=f'Amplitude is: {p.value}')
|
||||
|
||||
def callback(attr, old, new):
|
||||
div.text = f'Amplitude is: {new}'
|
||||
|
||||
p.on_change('value', callback)
|
||||
|
||||
row = Row(children=[p, div])
|
||||
|
||||
def doc_json(model, target):
|
||||
with OutputDocumentFor([model]) as doc:
|
||||
doc.title = ""
|
||||
docs_json, _ = standalone_docs_json_and_render_items(
|
||||
[model], suppress_callback_warning=True
|
||||
)
|
||||
|
||||
doc_json = list(docs_json.values())[0]
|
||||
root_id = doc_json['roots']['root_ids'][0]
|
||||
|
||||
return doc, json.dumps(dict(
|
||||
target_id = target,
|
||||
root_id = root_id,
|
||||
doc = doc_json,
|
||||
version = __version__,
|
||||
))
|
||||
|
||||
def _link_docs(pydoc, jsdoc):
|
||||
def jssync(event):
|
||||
if getattr(event, 'setter_id', None) is not None:
|
||||
return
|
||||
events = [event]
|
||||
json_patch = jsdoc.create_json_patch_string(pyodide.ffi.to_js(events))
|
||||
pydoc.apply_json_patch(json.loads(json_patch))
|
||||
|
||||
jsdoc.on_change(pyodide.ffi.create_proxy(jssync), pyodide.ffi.to_js(False))
|
||||
|
||||
def pysync(event):
|
||||
json_patch, buffers = process_document_events([event], use_buffers=True)
|
||||
buffer_map = {}
|
||||
for (ref, buffer) in buffers:
|
||||
buffer_map[ref['id']] = buffer
|
||||
jsdoc.apply_json_patch(JSON.parse(json_patch), pyodide.ffi.to_js(buffer_map), setter_id='js')
|
||||
|
||||
pydoc.on_change(pysync)
|
||||
|
||||
async def show(plot, target):
|
||||
pydoc, model_json = doc_json(plot, target)
|
||||
views = await Bokeh.embed.embed_item(JSON.parse(model_json))
|
||||
jsdoc = views[0].model.document
|
||||
_link_docs(pydoc, jsdoc)
|
||||
|
||||
asyncio.ensure_future(show(row, 'myplot'))
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
168
examples/d3.html
168
examples/d3.html
@@ -6,7 +6,10 @@
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<style>
|
||||
.loading {
|
||||
display: inline-block;
|
||||
@@ -27,6 +30,15 @@
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Simple d3 visualization</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<b>
|
||||
Based on <i><a href="https://observablehq.com/@d3/learn-d3-shapes?collection=@d3/learn-d3>">Learn D3: Shapes</a></i> tutorial.
|
||||
</b>
|
||||
@@ -45,16 +57,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"d3": "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import * as d3 from "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js";
|
||||
|
||||
const fruits = [
|
||||
{name: "🍊", count: 21},
|
||||
@@ -109,8 +112,9 @@ for (const d of data) {
|
||||
</script>
|
||||
|
||||
<py-script>
|
||||
import js
|
||||
from pyodide.ffi import create_proxy, to_js
|
||||
import d3
|
||||
d3 = js.d3
|
||||
|
||||
fruits = [
|
||||
dict(name="🍊", count=21),
|
||||
@@ -165,6 +169,146 @@ for d in data:
|
||||
.attr("dy", "1.3em")
|
||||
.text(d_py["value"]))
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<script>
|
||||
import * as d3 from "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js";
|
||||
|
||||
</body>
|
||||
const fruits = [
|
||||
{name: "🍊", count: 21},
|
||||
{name: "🍇", count: 13},
|
||||
{name: "🍏", count: 8},
|
||||
{name: "🍌", count: 5},
|
||||
{name: "🍐", count: 3},
|
||||
{name: "🍋", count: 2},
|
||||
{name: "🍎", count: 1},
|
||||
{name: "🍉", count: 1},
|
||||
]
|
||||
|
||||
const fn = (d) => d.count
|
||||
const data = d3.pie().value(fn)(fruits)
|
||||
|
||||
const arc = d3.arc()
|
||||
.innerRadius(210)
|
||||
.outerRadius(310)
|
||||
.padRadius(300)
|
||||
.padAngle(2 / 300)
|
||||
.cornerRadius(8)
|
||||
|
||||
const js = d3.select("#js")
|
||||
js.select(".loading").remove()
|
||||
|
||||
const svg = js
|
||||
.append("svg")
|
||||
.attr("viewBox", "-320 -320 640 640")
|
||||
.attr("width", "400")
|
||||
.attr("height", "400")
|
||||
|
||||
for (const d of data) {
|
||||
svg.append("path")
|
||||
.style("fill", "steelblue")
|
||||
.attr("d", arc(d))
|
||||
|
||||
const text = svg.append("text")
|
||||
.style("fill", "white")
|
||||
.attr("transform", `translate(${arc.centroid(d).join(",")})`)
|
||||
.attr("text-anchor", "middle")
|
||||
|
||||
text.append("tspan")
|
||||
.style("font-size", "24")
|
||||
.attr("x", "0").text(d.data.name)
|
||||
|
||||
text.append("tspan")
|
||||
.style("font-size", "18")
|
||||
.attr("x", "0")
|
||||
.attr("dy", "1.3em")
|
||||
.text(d.value)
|
||||
}
|
||||
</script>
|
||||
<py-script>
|
||||
from pyodide.ffi import create_proxy, to_js
|
||||
import d3
|
||||
|
||||
fruits = [
|
||||
dict(name="🍊", count=21),
|
||||
dict(name="🍇", count=13),
|
||||
dict(name="🍏", count=8),
|
||||
dict(name="🍌", count=5),
|
||||
dict(name="🍐", count=3),
|
||||
dict(name="🍋", count=2),
|
||||
dict(name="🍎", count=1),
|
||||
dict(name="🍉", count=1),
|
||||
]
|
||||
|
||||
fn = create_proxy(lambda d, *_: d["count"])
|
||||
data = d3.pie().value(fn)(to_js(fruits))
|
||||
|
||||
arc = (d3.arc()
|
||||
.innerRadius(210)
|
||||
.outerRadius(310)
|
||||
.padRadius(300)
|
||||
.padAngle(2 / 300)
|
||||
.cornerRadius(8))
|
||||
|
||||
py = d3.select("#py")
|
||||
py.select(".loading").remove()
|
||||
|
||||
svg = (py
|
||||
.append("svg")
|
||||
.attr("viewBox", "-320 -320 640 640")
|
||||
.attr("width", "400")
|
||||
.attr("height", "400"))
|
||||
|
||||
for d in data:
|
||||
d_py = d.to_py()
|
||||
|
||||
(svg.append("path")
|
||||
.style("fill", "steelblue")
|
||||
.attr("d", arc(d)))
|
||||
|
||||
text = (svg.append("text")
|
||||
.style("fill", "white")
|
||||
.attr("transform", f"translate({arc.centroid(d).join(',')})")
|
||||
.attr("text-anchor", "middle"))
|
||||
|
||||
(text.append("tspan")
|
||||
.style("font-size", "24")
|
||||
.attr("x", "0")
|
||||
.text(d_py["data"]["name"]))
|
||||
|
||||
(text.append("tspan")
|
||||
.style("font-size", "18")
|
||||
.attr("x", "0")
|
||||
.attr("dy", "1.3em")
|
||||
.text(d_py["value"]))
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -5,16 +5,30 @@
|
||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-config>
|
||||
packages = [
|
||||
"folium",
|
||||
"pandas"
|
||||
]
|
||||
</py-config>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="folium" style="width: 100%; height: 100%"></div>
|
||||
<py-script output="folium">
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Folium</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div id="folium"></div>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"folium",
|
||||
"pandas"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import folium
|
||||
import json
|
||||
import pandas as pd
|
||||
@@ -45,7 +59,78 @@ folium.Choropleth(
|
||||
|
||||
folium.LayerControl().add_to(m)
|
||||
|
||||
m
|
||||
display(m, target="folium")
|
||||
</py-script>
|
||||
</body>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
|
||||
<div id="folium"></div>
|
||||
<py-config>
|
||||
packages = [
|
||||
"folium",
|
||||
"pandas"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import folium
|
||||
import json
|
||||
import pandas as pd
|
||||
|
||||
from pyodide.http import open_url
|
||||
|
||||
url = (
|
||||
"https://raw.githubusercontent.com/python-visualization/folium/master/examples/data"
|
||||
)
|
||||
state_geo = f"{url}/us-states.json"
|
||||
state_unemployment = f"{url}/US_Unemployment_Oct2012.csv"
|
||||
state_data = pd.read_csv(open_url(state_unemployment))
|
||||
geo_json = json.loads(open_url(state_geo).read())
|
||||
|
||||
m = folium.Map(location=[48, -102], zoom_start=3)
|
||||
|
||||
folium.Choropleth(
|
||||
geo_data=geo_json,
|
||||
name="choropleth",
|
||||
data=state_data,
|
||||
columns=["State", "Unemployment"],
|
||||
key_on="feature.id",
|
||||
fill_color="YlGn",
|
||||
fill_opacity=0.7,
|
||||
line_opacity=0.2,
|
||||
legend_name="Unemployment Rate (%)",
|
||||
).add_to(m)
|
||||
|
||||
folium.LayerControl().add_to(m)
|
||||
|
||||
display(m, target="folium")
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Svelte app</title>
|
||||
<title>Say Hello</title>
|
||||
|
||||
<link rel="icon" type="image/png" href="../favicon.png" />
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
@@ -8,17 +8,64 @@
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
Hello world! <br>
|
||||
This is the current date and time, as computed by Python:
|
||||
<py-script>
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
now.strftime("%m/%d/%Y, %H:%M:%S")
|
||||
</py-script>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Hello World</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
Hello world! <br>
|
||||
This is the current date and time, as computed by Python:
|
||||
<py-script>
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-script>
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -254,20 +254,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<a href="./toga/freedom.html" target="_blank">
|
||||
<h2 class="text-2xl font-bold text-blue-600">
|
||||
Freedom Units!
|
||||
</h2>
|
||||
</a>
|
||||
<p>
|
||||
A <a href="https://beeware.org/project/projects/libraries/toga/" target="_blank">Toga</a>
|
||||
application (a Fahrenheit to Celsius converter), rendered as a Single Page App
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<a href="./numpy_canvas_fractals.html" target="_blank">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Svelte app</title>
|
||||
<title>Mario</title>
|
||||
|
||||
<link rel="icon" type="image/png" href="../favicon.png" />
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
26
examples/markdown-plugin.html
Normal file
26
examples/markdown-plugin.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>PyMarkdown</title>
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link rel="stylesheet" href="../build/pyscript.css" />
|
||||
|
||||
<script defer src="../build/pyscript.js"></script>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"markdown"
|
||||
]
|
||||
plugins = [
|
||||
"../build/plugins/python/py_markdown.py"
|
||||
]
|
||||
</py-config>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<py-md>#Hello world!</py-md>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,15 +5,29 @@
|
||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-config>
|
||||
packages = [
|
||||
"matplotlib"
|
||||
]
|
||||
</py-config>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Matplotlib</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div id="mpl"></div>
|
||||
<py-script output="mpl">
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"matplotlib"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.tri as tri
|
||||
import numpy as np
|
||||
@@ -46,7 +60,79 @@ tpc = ax1.tripcolor(triang, z, shading='flat')
|
||||
fig1.colorbar(tpc)
|
||||
ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
|
||||
|
||||
fig1
|
||||
display(fig1, target="mpl")
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<div id="mpl"></div>
|
||||
<py-config>
|
||||
packages = [
|
||||
"matplotlib",
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.tri as tri
|
||||
import numpy as np
|
||||
|
||||
# First create the x and y coordinates of the points.
|
||||
n_angles = 36
|
||||
n_radii = 8
|
||||
min_radius = 0.25
|
||||
radii = np.linspace(min_radius, 0.95, n_radii)
|
||||
|
||||
angles = np.linspace(0, 2 * np.pi, n_angles, endpoint=False)
|
||||
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
|
||||
angles[:, 1::2] += np.pi / n_angles
|
||||
|
||||
x = (radii * np.cos(angles)).flatten()
|
||||
y = (radii * np.sin(angles)).flatten()
|
||||
z = (np.cos(radii) * np.cos(3 * angles)).flatten()
|
||||
|
||||
# Create the Triangulation; no triangles so Delaunay triangulation created.
|
||||
triang = tri.Triangulation(x, y)
|
||||
|
||||
# Mask off unwanted triangles.
|
||||
triang.set_mask(np.hypot(x[triang.triangles].mean(axis=1),
|
||||
y[triang.triangles].mean(axis=1))
|
||||
< min_radius)
|
||||
|
||||
fig1, ax1 = plt.subplots()
|
||||
ax1.set_aspect('equal')
|
||||
tpc = ax1.tripcolor(triang, z, shading='flat')
|
||||
fig1.colorbar(tpc)
|
||||
ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
|
||||
|
||||
display(fig1, target="mpl")
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,13 +7,15 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"numpy",
|
||||
"networkx",
|
||||
"matplotlib"
|
||||
]
|
||||
packages = [
|
||||
"numpy",
|
||||
"networkx",
|
||||
"matplotlib"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import numpy as np
|
||||
import networkx as nx
|
||||
|
||||
@@ -9,19 +9,20 @@
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"micrograd",
|
||||
"numpy",
|
||||
"matplotlib"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
</head>
|
||||
|
||||
<body style="padding-top: 20px; padding-right: 20px; padding-bottom: 20px; padding-left: 20px">
|
||||
<h1>Micrograd - A tiny Autograd engine (with a bite! :))</h1><br>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"micrograd",
|
||||
"numpy",
|
||||
"matplotlib"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
<a href="https://github.com/karpathy/micrograd">Micrograd</a> is a tiny Autograd engine created
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
<style>
|
||||
.loading {
|
||||
display: inline-block;
|
||||
@@ -30,8 +32,15 @@
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<b>
|
||||
</b>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Fractals with NumPy and canvas</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div style="display: flex; flex-direction: column; gap: 1em; width: 600px">
|
||||
<div id="mandelbrot">
|
||||
<div style="text-align: center">Mandelbrot set</div>
|
||||
@@ -78,18 +87,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<py-config type="json">
|
||||
{
|
||||
"packages": [
|
||||
"numpy",
|
||||
"sympy"
|
||||
],
|
||||
"paths": [
|
||||
"./palettes.py",
|
||||
"./fractals.py"
|
||||
]
|
||||
}
|
||||
</py-config>
|
||||
<py-config type="json">
|
||||
{
|
||||
"packages": [
|
||||
"numpy",
|
||||
"sympy"
|
||||
],
|
||||
"fetch": [
|
||||
{
|
||||
"files": [
|
||||
"./palettes.py",
|
||||
"./fractals.py"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
from pyodide.ffi import to_js, create_proxy
|
||||
@@ -312,12 +325,290 @@ canvas.addEventListener("mousemove", create_proxy(mousemove))
|
||||
|
||||
import asyncio
|
||||
|
||||
_ = await asyncio.gather(
|
||||
draw_mandelbrot(),
|
||||
draw_julia(),
|
||||
draw_newton(),
|
||||
)
|
||||
</py-script>
|
||||
async def main():
|
||||
_ = await asyncio.gather(
|
||||
draw_mandelbrot(),
|
||||
draw_julia(),
|
||||
draw_newton(),
|
||||
)
|
||||
|
||||
</body>
|
||||
asyncio.ensure_future(main())
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-config type="json">
|
||||
{
|
||||
"packages": [
|
||||
"numpy",
|
||||
"sympy"
|
||||
],
|
||||
"fetch": [
|
||||
{
|
||||
"files": [
|
||||
"./palettes.py",
|
||||
"./fractals.py"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
</py-config>
|
||||
<py-script>
|
||||
from pyodide.ffi import to_js, create_proxy
|
||||
|
||||
import numpy as np
|
||||
import sympy
|
||||
|
||||
from palettes import Magma256
|
||||
from fractals import mandelbrot, julia, newton
|
||||
|
||||
from js import (
|
||||
console,
|
||||
document,
|
||||
devicePixelRatio,
|
||||
ImageData,
|
||||
Uint8ClampedArray,
|
||||
CanvasRenderingContext2D as Context2d,
|
||||
requestAnimationFrame,
|
||||
)
|
||||
|
||||
def prepare_canvas(width: int, height: int, canvas: Element) -> Context2d:
|
||||
ctx = canvas.getContext("2d")
|
||||
|
||||
canvas.style.width = f"{width}px"
|
||||
canvas.style.height = f"{height}px"
|
||||
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
|
||||
return ctx
|
||||
|
||||
def color_map(array: np.array, palette: np.array) -> np.array:
|
||||
size, _ = palette.shape
|
||||
index = (array/array.max()*(size - 1)).round().astype("uint8")
|
||||
|
||||
width, height = array.shape
|
||||
image = np.full((width, height, 4), 0xff, dtype=np.uint8)
|
||||
image[:, :, :3] = palette[index]
|
||||
|
||||
return image
|
||||
|
||||
def draw_image(ctx: Context2d, image: np.array) -> None:
|
||||
data = Uint8ClampedArray.new(to_js(image.tobytes()))
|
||||
width, height, _ = image.shape
|
||||
image_data = ImageData.new(data, width, height)
|
||||
ctx.putImageData(image_data, 0, 0)
|
||||
|
||||
width, height = 600, 600
|
||||
|
||||
async def draw_mandelbrot() -> None:
|
||||
spinner = document.querySelector("#mandelbrot .loading")
|
||||
canvas = document.querySelector("#mandelbrot canvas")
|
||||
|
||||
spinner.style.display = ""
|
||||
canvas.style.display = "none"
|
||||
|
||||
ctx = prepare_canvas(width, height, canvas)
|
||||
|
||||
console.log("Computing Mandelbrot set ...")
|
||||
console.time("mandelbrot")
|
||||
iters = mandelbrot(width, height)
|
||||
console.timeEnd("mandelbrot")
|
||||
|
||||
image = color_map(iters, Magma256)
|
||||
draw_image(ctx, image)
|
||||
|
||||
spinner.style.display = "none"
|
||||
canvas.style.display = "block"
|
||||
|
||||
async def draw_julia() -> None:
|
||||
spinner = document.querySelector("#julia .loading")
|
||||
canvas = document.querySelector("#julia canvas")
|
||||
|
||||
spinner.style.display = ""
|
||||
canvas.style.display = "none"
|
||||
|
||||
ctx = prepare_canvas(width, height, canvas)
|
||||
|
||||
console.log("Computing Julia set ...")
|
||||
console.time("julia")
|
||||
iters = julia(width, height)
|
||||
console.timeEnd("julia")
|
||||
|
||||
image = color_map(iters, Magma256)
|
||||
draw_image(ctx, image)
|
||||
|
||||
spinner.style.display = "none"
|
||||
canvas.style.display = "block"
|
||||
|
||||
def ranges():
|
||||
x0_in = document.querySelector("#x0")
|
||||
x1_in = document.querySelector("#x1")
|
||||
y0_in = document.querySelector("#y0")
|
||||
y1_in = document.querySelector("#y1")
|
||||
|
||||
xr = (float(x0_in.value), float(x1_in.value))
|
||||
yr = (float(y0_in.value), float(y1_in.value))
|
||||
|
||||
return xr, yr
|
||||
|
||||
current_image = None
|
||||
async def draw_newton() -> None:
|
||||
spinner = document.querySelector("#newton .loading")
|
||||
canvas = document.querySelector("#newton canvas")
|
||||
|
||||
spinner.style.display = ""
|
||||
canvas.style.display = "none"
|
||||
|
||||
ctx = prepare_canvas(width, height, canvas)
|
||||
|
||||
console.log("Computing Newton set ...")
|
||||
|
||||
poly_in = document.querySelector("#poly")
|
||||
coef_in = document.querySelector("#coef")
|
||||
conv_in = document.querySelector("#conv")
|
||||
iter_in = document.querySelector("#iter")
|
||||
|
||||
xr, yr = ranges()
|
||||
|
||||
# z**3 - 1
|
||||
# z**8 + 15*z**4 - 16
|
||||
# z**3 - 2*z + 2
|
||||
|
||||
expr = sympy.parse_expr(poly_in.value)
|
||||
coeffs = [ complex(c) for c in reversed(sympy.Poly(expr, sympy.Symbol("z")).all_coeffs()) ]
|
||||
poly = np.polynomial.Polynomial(coeffs)
|
||||
|
||||
coef = complex(sympy.parse_expr(coef_in.value))
|
||||
|
||||
console.time("newton")
|
||||
iters, roots = newton(width, height, p=poly, a=coef, xr=xr, yr=yr)
|
||||
console.timeEnd("newton")
|
||||
|
||||
if conv_in.checked:
|
||||
n = poly.degree() + 1
|
||||
k = int(len(Magma256)/n)
|
||||
|
||||
colors = Magma256[::k, :][:n]
|
||||
colors[0, :] = [255, 0, 0] # red: no convergence
|
||||
|
||||
image = color_map(roots, colors)
|
||||
else:
|
||||
image = color_map(iters, Magma256)
|
||||
|
||||
global current_image
|
||||
current_image = image
|
||||
draw_image(ctx, image)
|
||||
|
||||
spinner.style.display = "none"
|
||||
canvas.style.display = "block"
|
||||
|
||||
handler = create_proxy(lambda _event: draw_newton())
|
||||
document.querySelector("#newton fieldset").addEventListener("change", handler)
|
||||
|
||||
canvas = document.querySelector("#newton canvas")
|
||||
|
||||
is_selecting = False
|
||||
init_sx, init_sy = None, None
|
||||
sx, sy = None, None
|
||||
async def mousemove(event):
|
||||
global is_selecting
|
||||
global init_sx
|
||||
global init_sy
|
||||
global sx
|
||||
global sy
|
||||
|
||||
def invert(sx, source_range, target_range):
|
||||
source_start, source_end = source_range
|
||||
target_start, target_end = target_range
|
||||
factor = (target_end - target_start)/(source_end - source_start)
|
||||
offset = -(factor * source_start) + target_start
|
||||
return (sx - offset) / factor
|
||||
|
||||
bds = canvas.getBoundingClientRect()
|
||||
event_sx, event_sy = event.clientX - bds.x, event.clientY - bds.y
|
||||
|
||||
ctx = canvas.getContext("2d")
|
||||
|
||||
pressed = event.buttons == 1
|
||||
if is_selecting:
|
||||
if not pressed:
|
||||
xr, yr = ranges()
|
||||
|
||||
x0 = invert(init_sx, xr, (0, width))
|
||||
x1 = invert(sx, xr, (0, width))
|
||||
y0 = invert(init_sy, yr, (0, height))
|
||||
y1 = invert(sy, yr, (0, height))
|
||||
|
||||
document.querySelector("#x0").value = x0
|
||||
document.querySelector("#x1").value = x1
|
||||
document.querySelector("#y0").value = y0
|
||||
document.querySelector("#y1").value = y1
|
||||
|
||||
is_selecting = False
|
||||
init_sx, init_sy = None, None
|
||||
sx, sy = init_sx, init_sy
|
||||
|
||||
await draw_newton()
|
||||
else:
|
||||
ctx.save()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
draw_image(ctx, current_image)
|
||||
sx, sy = event_sx, event_sy
|
||||
ctx.beginPath()
|
||||
ctx.rect(init_sx, init_sy, sx - init_sx, sy - init_sy)
|
||||
ctx.fillStyle = "rgba(255, 255, 255, 0.4)"
|
||||
ctx.strokeStyle = "rgba(255, 255, 255, 1.0)"
|
||||
ctx.fill()
|
||||
ctx.stroke()
|
||||
ctx.restore()
|
||||
else:
|
||||
if pressed:
|
||||
is_selecting = True
|
||||
init_sx, init_sy = event_sx, event_sy
|
||||
sx, sy = init_sx, init_sy
|
||||
|
||||
canvas.addEventListener("mousemove", create_proxy(mousemove))
|
||||
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
_ = await asyncio.gather(
|
||||
draw_mandelbrot(),
|
||||
draw_julia(),
|
||||
draw_newton(),
|
||||
)
|
||||
|
||||
asyncio.ensure_future(main())
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -3,23 +3,36 @@
|
||||
<title>Panel Example</title>
|
||||
<meta charset="iso-8859-1">
|
||||
<link rel="icon" type="image/x-icon" href="./favicon.png">
|
||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@holoviz/panel@0.13.1/dist/panel.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@holoviz/panel@0.14.1/dist/panel.min.js"></script>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
<body>
|
||||
<h1>Panel Example</h1>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Panel Example</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div id="simple_app"></div>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"panel==0.14.1"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import panel as pn
|
||||
|
||||
@@ -30,5 +43,52 @@ packages = [
|
||||
|
||||
pn.Row(slider, pn.bind(callback, slider)).servable(target='simple_app');
|
||||
</py-script>
|
||||
</body>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import panel as pn
|
||||
|
||||
slider = pn.widgets.FloatSlider(start=0, end=10, name='Amplitude')
|
||||
|
||||
def callback(new):
|
||||
return f'Amplitude is: {new}'
|
||||
|
||||
pn.Row(slider, pn.bind(callback, slider)).servable(target='simple_app');
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -40,44 +40,43 @@
|
||||
</style>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
||||
<nav class="navbar navbar-expand-md navbar-dark sticky-top shadow" id="header" style="background-color: #000000;">
|
||||
<button type="button" class="navbar-toggle collapsed" id="sidebarCollapse">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="app-header">
|
||||
<a class="navbar-brand app-logo" href="/">
|
||||
<img src="./logo.png" class="app-logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Panel DeckGL NYC Taxi Demo</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Panel DeckGL NYC Taxi Demo</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div class="d-flex flex-nowrap" id="content">
|
||||
<div class="sidenav" id="sidebar">
|
||||
<ul class="nav flex-column" >
|
||||
<div class="bk-root" id="widgets"></div>
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col mh-100" style="padding: 0">
|
||||
<div class="bk-root" id="plot"></div>
|
||||
</div>
|
||||
<div class="sidenav" id="sidebar">
|
||||
<ul class="nav flex-column" >
|
||||
<div class="bk-root" id="widgets"></div>
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col mh-100" style="padding: 0">
|
||||
<div class="bk-root" id="plot"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import panel as pn
|
||||
import pandas as pd
|
||||
@@ -219,5 +218,185 @@ packages = [
|
||||
app.deck_gl.servable(target='plot')
|
||||
controls.servable(target='widgets');
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0" style="height: 95px;">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import panel as pn
|
||||
import pandas as pd
|
||||
import param
|
||||
|
||||
from pyodide.http import open_url
|
||||
|
||||
MAPBOX_KEY = "pk.eyJ1IjoicGFuZWxvcmciLCJhIjoiY2s1enA3ejhyMWhmZjNobjM1NXhtbWRrMyJ9.B_frQsAVepGIe-HiOJeqvQ"
|
||||
|
||||
class App(param.Parameterized):
|
||||
|
||||
data = param.DataFrame(precedence=-1)
|
||||
|
||||
view = param.DataFrame(precedence=-1)
|
||||
|
||||
arc_view = param.DataFrame(precedence=-1)
|
||||
|
||||
radius = param.Integer(default=50, bounds=(20, 1000))
|
||||
|
||||
elevation = param.Integer(default=10, bounds=(0, 50))
|
||||
|
||||
hour = param.Integer(default=0, bounds=(0, 23))
|
||||
|
||||
speed = param.Integer(default=1, bounds=(0, 10), precedence=-1)
|
||||
|
||||
play = param.Event(label='▷')
|
||||
|
||||
def __init__(self, **params):
|
||||
self.deck_gl = None
|
||||
super().__init__(**params)
|
||||
self.deck_gl = pn.pane.DeckGL(
|
||||
dict(self.spec),
|
||||
mapbox_api_key=MAPBOX_KEY,
|
||||
throttle={'click': 10},
|
||||
sizing_mode='stretch_both',
|
||||
margin=0
|
||||
)
|
||||
self.deck_gl.param.watch(self._update_arc_view, 'click_state')
|
||||
self._playing = False
|
||||
self._cb = pn.state.add_periodic_callback(
|
||||
self._update_hour, 1000//self.speed, start=False
|
||||
)
|
||||
|
||||
@property
|
||||
def spec(self):
|
||||
return {
|
||||
"initialViewState": {
|
||||
"bearing": 0,
|
||||
"latitude": 40.7,
|
||||
"longitude": -73.9,
|
||||
"maxZoom": 15,
|
||||
"minZoom": 5,
|
||||
"pitch": 40.5,
|
||||
"zoom": 11
|
||||
},
|
||||
"layers": [self.hex_layer, self.arc_layer],
|
||||
"mapStyle": "mapbox://styles/mapbox/dark-v9",
|
||||
"views": [
|
||||
{"@@type": "MapView", "controller": True}
|
||||
]
|
||||
}
|
||||
|
||||
@property
|
||||
def hex_layer(self):
|
||||
return {
|
||||
"@@type": "HexagonLayer",
|
||||
"autoHighlight": True,
|
||||
"coverage": 1,
|
||||
"data": self.data if self.view is None else self.view,
|
||||
"elevationRange": [0, 100],
|
||||
"elevationScale": self.elevation,
|
||||
"radius": self.radius,
|
||||
"extruded": True,
|
||||
"getPosition": "@@=[pickup_x, pickup_y]",
|
||||
"id": "8a553b25-ef3a-489c-bbe2-e102d18a3211"
|
||||
}
|
||||
|
||||
@property
|
||||
def arc_layer(self):
|
||||
return {
|
||||
"@@type": "ArcLayer",
|
||||
"id": 'arc-layer',
|
||||
"data": self.arc_view,
|
||||
"pickable": True,
|
||||
"getWidth": 1,
|
||||
"getSourcePosition": "@@=[pickup_x, pickup_y]",
|
||||
"getTargetPosition": "@@=[dropoff_x, dropoff_y]",
|
||||
"getSourceColor": [0, 255, 0, 180],
|
||||
"getTargetColor": [240, 100, 0, 180]
|
||||
}
|
||||
|
||||
def _update_hour(self):
|
||||
self.hour = (self.hour+1) % 24
|
||||
|
||||
@param.depends('view', watch=True)
|
||||
def _update_arc_view(self, event=None):
|
||||
data = self.data if self.view is None else self.view
|
||||
if not self.deck_gl or not self.deck_gl.click_state:
|
||||
self.arc_view = data.iloc[:0]
|
||||
return
|
||||
lon, lat = self.deck_gl.click_state['coordinate']
|
||||
tol = 0.001
|
||||
self.arc_view = data[
|
||||
(df.pickup_x>=float(lon-tol)) &
|
||||
(df.pickup_x<=float(lon+tol)) &
|
||||
(df.pickup_y>=float(lat-tol)) &
|
||||
(df.pickup_y<=float(lat+tol))
|
||||
]
|
||||
|
||||
@param.depends('hour', watch=True, on_init=True)
|
||||
def _update_hourly_view(self):
|
||||
self.view = self.data[self.data.hour==self.hour]
|
||||
|
||||
@param.depends('speed', watch=True)
|
||||
def _update_speed(self):
|
||||
self._cb.period = 1000//self.speed
|
||||
|
||||
@param.depends('play', watch=True)
|
||||
def _play_pause(self):
|
||||
if self._playing:
|
||||
self._cb.stop()
|
||||
self.param.play.label = '▷'
|
||||
self.param.speed.precedence = -1
|
||||
else:
|
||||
self._cb.start()
|
||||
self.param.play.label = '❚❚'
|
||||
self.param.speed.precedence = 1
|
||||
self._playing = not self._playing
|
||||
|
||||
@param.depends('view', 'radius', 'elevation', 'arc_view', watch=True)
|
||||
def update_spec(self):
|
||||
self.deck_gl.object = dict(self.spec)
|
||||
|
||||
url = 'https://s3.eu-west-1.amazonaws.com/assets.holoviews.org/data/nyc_taxi_wide.csv'
|
||||
df = pd.read_csv(open_url(url))
|
||||
app = App(data=df)
|
||||
controls = pn.Param(app.param, sizing_mode='stretch_width', show_name=False)
|
||||
|
||||
app.deck_gl.servable(target='plot')
|
||||
controls.servable(target='widgets')
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -41,48 +41,47 @@
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"altair",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"scikit-learn",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
||||
<nav class="navbar navbar-expand-md navbar-dark sticky-top shadow" id="header" style="background-color: #000000;">
|
||||
<button type="button" class="navbar-toggle collapsed" id="sidebarCollapse">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="app-header">
|
||||
<a class="navbar-brand app-logo" href="/">
|
||||
<img src="./logo.png" class="app-logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Panel KMeans Clustering Demo</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Panel KMeans Clustering Demo</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div class="row overflow-hidden" id="content">
|
||||
<div class="sidenav" id="sidebar">
|
||||
<ul class="nav flex-column">
|
||||
<div class="bk-root" id="x-widget"></div>
|
||||
<div class="bk-root" id="y-widget"></div>
|
||||
<div class="bk-root" id="n-widget"></div>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col mh-100 float-left" id="main">
|
||||
<div class="bk-root" id="intro"></div>
|
||||
<div class="bk-root" id="cluster-plot"></div>
|
||||
<div class="bk-root" id="table"></div>
|
||||
</div>
|
||||
<div class="sidenav" id="sidebar">
|
||||
<ul class="nav flex-column">
|
||||
<div class="bk-root" id="x-widget"></div>
|
||||
<div class="bk-root" id="y-widget"></div>
|
||||
<div class="bk-root" id="n-widget"></div>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col mh-100 float-left" id="main">
|
||||
<div class="bk-root" id="intro"></div>
|
||||
<div class="bk-root" id="cluster-plot"></div>
|
||||
<div class="bk-root" id="table"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"altair",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"scikit-learn",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import altair as alt
|
||||
import panel as pn
|
||||
@@ -159,6 +158,122 @@ packages = [
|
||||
update_table()
|
||||
update_chart()
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0" style="height: 95px;">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-config>
|
||||
packages = [
|
||||
"altair",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"scikit-learn",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import altair as alt
|
||||
import panel as pn
|
||||
import pandas as pd
|
||||
|
||||
from sklearn.cluster import KMeans
|
||||
from pyodide.http import open_url
|
||||
|
||||
pn.config.sizing_mode = 'stretch_width'
|
||||
|
||||
url = 'https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2020/2020-07-28/penguins.csv'
|
||||
penguins = pd.read_csv(open_url(url)).dropna()
|
||||
cols = list(penguins.columns)[2:6]
|
||||
|
||||
x = pn.widgets.Select(name='x', options=cols, value='bill_depth_mm').servable(target='x-widget')
|
||||
y = pn.widgets.Select(name='y', options=cols, value='bill_length_mm').servable(target='y-widget')
|
||||
n_clusters = pn.widgets.IntSlider(name='n_clusters', start=1, end=5, value=3).servable(target='n-widget')
|
||||
|
||||
brush = alt.selection_interval(name='brush') # selection of type "interval"
|
||||
|
||||
def get_clusters(n_clusters):
|
||||
kmeans = KMeans(n_clusters=n_clusters)
|
||||
est = kmeans.fit(penguins[cols].values)
|
||||
df = penguins.copy()
|
||||
df['labels'] = est.labels_.astype('str')
|
||||
return df
|
||||
|
||||
def get_chart(x, y, df):
|
||||
centers = df.groupby('labels').mean()
|
||||
return (
|
||||
alt.Chart(df)
|
||||
.mark_point(size=100)
|
||||
.encode(
|
||||
x=alt.X(x, scale=alt.Scale(zero=False)),
|
||||
y=alt.Y(y, scale=alt.Scale(zero=False)),
|
||||
shape='labels',
|
||||
color='species'
|
||||
).add_selection(brush).properties(width=800) +
|
||||
alt.Chart(centers)
|
||||
.mark_point(size=250, shape='cross', color='black')
|
||||
.encode(x=x+':Q', y=y+':Q')
|
||||
)
|
||||
|
||||
intro = pn.pane.Markdown("""
|
||||
This app provides an example of **building a simple dashboard using
|
||||
Panel**.\n\nIt demonstrates how to take the output of **k-means
|
||||
clustering on the Penguins dataset** using scikit-learn,
|
||||
parameterizing the number of clusters and the variables to
|
||||
plot.\n\nThe plot and the table are linked, i.e. selecting on the plot
|
||||
will filter the data in the table.\n\n The **`x` marks the center** of
|
||||
the cluster.
|
||||
""").servable(target='intro')
|
||||
|
||||
chart = pn.pane.Vega().servable(target='cluster-plot')
|
||||
table = pn.widgets.Tabulator(pagination='remote', page_size=10).servable(target='table')
|
||||
|
||||
def update_table(event=None):
|
||||
table.value = get_clusters(n_clusters.value)
|
||||
|
||||
n_clusters.param.watch(update_table, 'value')
|
||||
|
||||
@pn.depends(x, y, n_clusters, watch=True)
|
||||
def update_chart(*events):
|
||||
chart.object = get_chart(x.value, y.value, table.value)
|
||||
chart.selection.param.watch(update_filters, 'brush')
|
||||
|
||||
def update_filters(event=None):
|
||||
filters = []
|
||||
for k, v in (getattr(event, 'new') or {}).items():
|
||||
filters.append(dict(field=k, type='>=', value=v[0]))
|
||||
filters.append(dict(field=k, type='<=', value=v[1]))
|
||||
table.filters = filters
|
||||
|
||||
update_table()
|
||||
update_chart()
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
@@ -32,31 +32,21 @@
|
||||
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
|
||||
<nav class="navbar navbar-expand-md navbar-dark sticky-top shadow" id="header" style="background-color: #000000;">
|
||||
<button type="button" class="navbar-toggle collapsed" id="sidebarCollapse">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="app-header">
|
||||
<a class="navbar-brand app-logo" href="/">
|
||||
<img src="./logo.png" class="app-logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Panel Streaming Demo</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Panel Streaming Demo</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div class="row overflow-hidden" id="content">
|
||||
<div class="col mh-100 float-left" id="main">
|
||||
<div class="bk-root" id="controls"></div>
|
||||
@@ -68,6 +58,15 @@ packages = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
import panel as pn
|
||||
import numpy as np
|
||||
@@ -115,5 +114,90 @@ packages = [
|
||||
pn.pane.Bokeh(p).servable(target='plot')
|
||||
pn.Row(cb.param.period, rollover, follow, width=400).servable(target='controls')
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0" style="height: 95px;">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy",
|
||||
"pandas",
|
||||
"panel==0.13.1"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import panel as pn
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from bokeh.models import ColumnDataSource
|
||||
from bokeh.plotting import figure
|
||||
|
||||
df = pd.DataFrame(np.random.randn(10, 4), columns=list('ABCD')).cumsum()
|
||||
|
||||
rollover = pn.widgets.IntInput(name='Rollover', value=15)
|
||||
follow = pn.widgets.Checkbox(name='Follow', value=True, align='end')
|
||||
|
||||
tabulator = pn.widgets.Tabulator(df, height=450, width=400).servable(target='table')
|
||||
|
||||
def color_negative_red(val):
|
||||
"""
|
||||
Takes a scalar and returns a string with
|
||||
the css property `'color: red'` for negative
|
||||
strings, black otherwise.
|
||||
"""
|
||||
color = 'red' if val < 0 else 'green'
|
||||
return 'color: %s' % color
|
||||
|
||||
tabulator.style.applymap(color_negative_red)
|
||||
|
||||
p = figure(height=450, width=600)
|
||||
|
||||
cds = ColumnDataSource(data=ColumnDataSource.from_df(df))
|
||||
|
||||
p.line('index', 'A', source=cds, line_color='red')
|
||||
p.line('index', 'B', source=cds, line_color='green')
|
||||
p.line('index', 'C', source=cds, line_color='blue')
|
||||
p.line('index', 'D', source=cds, line_color='purple')
|
||||
|
||||
def stream():
|
||||
data = df.iloc[-1] + np.random.randn(4)
|
||||
tabulator.stream(data, rollover=rollover.value, follow=follow.value)
|
||||
value = {k: [v] for k, v in tabulator.value.iloc[-1].to_dict().items()}
|
||||
value['index'] = [tabulator.value.index[-1]]
|
||||
cds.stream(value)
|
||||
|
||||
cb = pn.state.add_periodic_callback(stream, 200)
|
||||
|
||||
pn.pane.Bokeh(p).servable(target='plot')
|
||||
pn.Row(cb.param.period, rollover, follow, width=400).servable(target='controls')
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from datetime import datetime as dt
|
||||
|
||||
import pyscript
|
||||
|
||||
class PyItem(PyItemTemplate):
|
||||
|
||||
class PyItem(pyscript.PyItemTemplate):
|
||||
def on_click(self, evt=None):
|
||||
self.data["done"] = not self.data["done"]
|
||||
self.strike(self.data["done"])
|
||||
@@ -9,7 +11,7 @@ class PyItem(PyItemTemplate):
|
||||
self.select("input").element.checked = self.data["done"]
|
||||
|
||||
|
||||
class PyList(PyListTemplate):
|
||||
class PyList(pyscript.PyListTemplate):
|
||||
item_class = PyItem
|
||||
|
||||
def add(self, item):
|
||||
|
||||
@@ -9,20 +9,115 @@
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
|
||||
<py-config>
|
||||
paths = [
|
||||
"./antigravity.py"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<body>
|
||||
<h1><b>pyscript REPL</b></h1>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">PyScript REPL</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<h1><b>PyScript REPL</b></h1>
|
||||
Tip: press Shift-ENTER to evaluate a cell
|
||||
<br>
|
||||
<div>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./antigravity.py"]
|
||||
</py-config>
|
||||
<div style="margin-right: 3rem;">
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
</div>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./antigravity.py"]
|
||||
</py-config>
|
||||
<div>
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
</div>
|
||||
</code>
|
||||
</pre>
|
||||
<p>antigravity.py</p>
|
||||
<pre class="prism-code language-python">
|
||||
<code class="language-python">
|
||||
import random
|
||||
import sys
|
||||
|
||||
from js import DOMParser, document, setInterval
|
||||
from pyodide.ffi import create_proxy
|
||||
from pyodide.http import open_url
|
||||
|
||||
|
||||
class Antigravity:
|
||||
|
||||
url = "./antigravity.svg"
|
||||
|
||||
def __init__(self, target=None, interval=10, append=True, fly=False):
|
||||
target = target or sys.stdout._out
|
||||
self.target = (
|
||||
document.getElementById(target) if isinstance(target, str) else target
|
||||
)
|
||||
doc = DOMParser.new().parseFromString(
|
||||
open_url(self.url).read(), "image/svg+xml"
|
||||
)
|
||||
self.node = doc.documentElement
|
||||
if append:
|
||||
self.target.append(self.node)
|
||||
else:
|
||||
self.target.replaceChildren(self.node)
|
||||
self.xoffset, self.yoffset = 0, 0
|
||||
self.interval = interval
|
||||
if fly:
|
||||
self.fly()
|
||||
|
||||
def fly(self):
|
||||
setInterval(create_proxy(self.move), self.interval)
|
||||
|
||||
def move(self):
|
||||
char = self.node.getElementsByTagName("g")[1]
|
||||
char.setAttribute("transform", f"translate({self.xoffset}, {-self.yoffset})")
|
||||
self.xoffset += random.normalvariate(0, 1) / 20
|
||||
if self.yoffset < 50:
|
||||
self.yoffset += 0.1
|
||||
else:
|
||||
self.yoffset += random.normalvariate(0, 1) / 20
|
||||
|
||||
_auto = Antigravity(append=True)
|
||||
fly = _auto.fly
|
||||
</code>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -9,28 +9,140 @@
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<link rel="stylesheet" href="repl.css" />
|
||||
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
paths = [
|
||||
"./utils.py",
|
||||
"./antigravity.py"
|
||||
]
|
||||
</py-config>
|
||||
|
||||
<body>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Custom REPL</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<h1 class="font-semibold text-2xl ml-5">Custom REPL</h1>
|
||||
<py-box widths="2/3;1/3">
|
||||
<py-repl id="my-repl" auto-generate="true" std-out="output" std-err="err-div"> </py-repl>
|
||||
<py-config>
|
||||
packages = [
|
||||
"bokeh",
|
||||
"numpy"
|
||||
]
|
||||
|
||||
[[fetch]]
|
||||
files = ["./utils.py", "./antigravity.py"]
|
||||
</py-config>
|
||||
<div style="margin-right: 3rem;">
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
<div id="output" class="p-4"></div>
|
||||
</py-box>
|
||||
<footer id="err-div" class="bg-red-700 text-white text-center border-t-4 border-green-500 fixed inset-x-0 bottom-0 p-4 hidden">
|
||||
</footer>
|
||||
</div>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<div>
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
<div id="output" class="p-4"></div>
|
||||
</div>
|
||||
</code>
|
||||
</pre>
|
||||
<p>utils.py</p>
|
||||
<pre class="prism-code language-python">
|
||||
<code class="language-python">
|
||||
from datetime import datetime as dt
|
||||
|
||||
|
||||
def format_date(dt_, fmt="%m/%d/%Y, %H:%M:%S"):
|
||||
return f"{dt_:{fmt}}"
|
||||
|
||||
|
||||
def now(fmt="%m/%d/%Y, %H:%M:%S"):
|
||||
return format_date(dt.now(), fmt)
|
||||
|
||||
|
||||
def remove_class(element, class_name):
|
||||
element.element.classList.remove(class_name)
|
||||
|
||||
|
||||
def add_class(element, class_name):
|
||||
element.element.classList.add(class_name)
|
||||
</code>
|
||||
</pre>
|
||||
<p>antigravity.py</p>
|
||||
<pre class="prism-code language-python">
|
||||
<code class="language-python">
|
||||
import random
|
||||
import sys
|
||||
|
||||
from js import DOMParser, document, setInterval
|
||||
from pyodide.ffi import create_proxy
|
||||
from pyodide.http import open_url
|
||||
|
||||
|
||||
class Antigravity:
|
||||
|
||||
url = "./antigravity.svg"
|
||||
|
||||
def __init__(self, target=None, interval=10, append=True, fly=False):
|
||||
target = target or sys.stdout._out
|
||||
self.target = (
|
||||
document.getElementById(target) if isinstance(target, str) else target
|
||||
)
|
||||
doc = DOMParser.new().parseFromString(
|
||||
open_url(self.url).read(), "image/svg+xml"
|
||||
)
|
||||
self.node = doc.documentElement
|
||||
if append:
|
||||
self.target.append(self.node)
|
||||
else:
|
||||
self.target.replaceChildren(self.node)
|
||||
self.xoffset, self.yoffset = 0, 0
|
||||
self.interval = interval
|
||||
if fly:
|
||||
self.fly()
|
||||
|
||||
def fly(self):
|
||||
setInterval(create_proxy(self.move), self.interval)
|
||||
|
||||
def move(self):
|
||||
char = self.node.getElementsByTagName("g")[1]
|
||||
char.setAttribute("transform", f"translate({self.xoffset}, {-self.yoffset})")
|
||||
self.xoffset += random.normalvariate(0, 1) / 20
|
||||
if self.yoffset < 50:
|
||||
self.yoffset += 0.1
|
||||
else:
|
||||
self.yoffset += random.normalvariate(0, 1) / 20
|
||||
|
||||
_auto = Antigravity(append=True)
|
||||
fly = _auto.fly
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -10,22 +10,31 @@
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-config>
|
||||
paths = [
|
||||
"./utils.py"
|
||||
]
|
||||
</py-config>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Simple Clock</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<div class="font-mono">start time: <label id="outputDiv"></label></div>
|
||||
<div id="outputDiv2" class="font-mono"></div>
|
||||
<div id="outputDiv3" class="font-mono"></div>
|
||||
<py-script output="outputDiv">
|
||||
# demonstrates how use the global PyScript pyscript_loader
|
||||
# to send operation log messages to it
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import utils
|
||||
utils.now()
|
||||
display(utils.now())
|
||||
</py-script>
|
||||
|
||||
<py-script>
|
||||
@@ -44,9 +53,80 @@ async def foo():
|
||||
else:
|
||||
out3.clear()
|
||||
|
||||
# close the global PyScript pyscript_loader
|
||||
pyscript_loader.close()
|
||||
pyscript.run_until_complete(foo())
|
||||
</py-script>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<div class="font-mono">start time: <label id="outputDiv"></label></div>
|
||||
<div id="outputDiv2" class="font-mono"></div>
|
||||
<div id="outputDiv3" class="font-mono"></div>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
</py-config>
|
||||
<py-script>
|
||||
from utils import now
|
||||
import asyncio
|
||||
|
||||
async def foo():
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
output = now()
|
||||
Element("outputDiv2").write(output)
|
||||
|
||||
out3 = Element("outputDiv3")
|
||||
if output[-1] in ["0", "4", "8"]:
|
||||
out3.write("It's espresso time!")
|
||||
else:
|
||||
out3.clear()
|
||||
|
||||
pyscript.run_until_complete(foo())
|
||||
</py-script>
|
||||
</code>
|
||||
</pre>
|
||||
<p>utils.py</p>
|
||||
<pre class="prism-code language-python">
|
||||
<code class="language-python">
|
||||
from datetime import datetime as dt
|
||||
|
||||
def format_date(dt_, fmt="%m/%d/%Y, %H:%M:%S"):
|
||||
return f"{dt_:{fmt}}"
|
||||
|
||||
def now(fmt="%m/%d/%Y, %H:%M:%S"):
|
||||
return format_date(dt.now(), fmt)
|
||||
|
||||
def remove_class(element, class_name):
|
||||
element.element.classList.remove(class_name)
|
||||
|
||||
def add_class(element, class_name):
|
||||
element.element.classList.add(class_name)
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -9,16 +9,36 @@
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-config>
|
||||
paths = [
|
||||
"./utils.py"
|
||||
]
|
||||
</py-config>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Pyscript Native TODO App</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<h1>To Do List</h1>
|
||||
<py-register-widget src="./pylist.py" name="py-list" klass="PyList"></py-register-widget>
|
||||
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
from js import document
|
||||
from pyodide.ffi.wrappers import add_event_listener
|
||||
|
||||
def add_task(*ags, **kws):
|
||||
# create a new dictionary representing the new task
|
||||
new_task_content = Element("new-task-content")
|
||||
task = { "content": new_task_content.value, "done": False, "created_at": dt.now() }
|
||||
|
||||
# add a new task to the list and tell it to use the `content` key to show in the UI
|
||||
@@ -28,24 +48,123 @@ paths = [
|
||||
# clear the inputbox element used to create the new task
|
||||
new_task_content.clear()
|
||||
|
||||
</py-script>
|
||||
</head>
|
||||
def on_click(evt):
|
||||
add_task()
|
||||
|
||||
<body>
|
||||
<py-title>To Do List</py-title>
|
||||
<py-box widths="4/5;1/5">
|
||||
<py-inputbox id="new-task-content">
|
||||
def on_keypress(e):
|
||||
if (e.code == "Enter"):
|
||||
add_task()
|
||||
</py-inputbox>
|
||||
<py-button id="new-task-btn" label="Add Task!">
|
||||
def on_click(evt):
|
||||
add_task()
|
||||
</py-button>
|
||||
</py-box>
|
||||
def handle_keypress(evt):
|
||||
if evt.key == "Enter":
|
||||
add_task()
|
||||
|
||||
add_event_listener(
|
||||
document.getElementById("new-task-content"),
|
||||
"keypress",
|
||||
handle_keypress
|
||||
)
|
||||
|
||||
</py-script>
|
||||
<div class="py-box">
|
||||
<input id="new-task-content" />
|
||||
<button py-click="add_task()" id="new-task-btn" class="py-button">Add Task!</button>
|
||||
</div>
|
||||
|
||||
<py-list id="myList"></py-list>
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-register-widget src="./pylist.py" name="py-list" klass="PyList"></py-register-widget>
|
||||
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
</py-config>
|
||||
|
||||
<py-script>
|
||||
from js import document
|
||||
from pyodide.ffi.wrappers import add_event_listener
|
||||
|
||||
def add_task(*ags, **kws):
|
||||
# create a new dictionary representing the new task
|
||||
new_task_content = Element("new-task-content")
|
||||
task = { "content": new_task_content.value, "done": False, "created_at": dt.now() }
|
||||
|
||||
# add a new task to the list and tell it to use the `content` key to show in the UI
|
||||
# and to use the key `done` to sync the task status with a checkbox element in the UI
|
||||
myList.add(task)
|
||||
|
||||
# clear the inputbox element used to create the new task
|
||||
new_task_content.clear()
|
||||
|
||||
def on_click(evt):
|
||||
add_task()
|
||||
|
||||
def handle_keypress(evt):
|
||||
if evt.key == "Enter":
|
||||
add_task()
|
||||
|
||||
add_event_listener(
|
||||
document.getElementById("new-task-content"),
|
||||
"keypress",
|
||||
handle_keypress
|
||||
)
|
||||
</py-script>
|
||||
<div class="py-box">
|
||||
<input id="new-task-content" />
|
||||
<button py-click="add_task()" id="new-task-btn" class="py-button">Add Task!</button>
|
||||
</div>
|
||||
|
||||
<py-list id="myList"></py-list>
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
</code>
|
||||
</pre>
|
||||
<p>pylist.py</p>
|
||||
<pre class="prism-code language-python">
|
||||
<code class="language-python">
|
||||
import pyscript
|
||||
from datetime import datetime as dt
|
||||
|
||||
class PyItem(pyscript.PyItemTemplate):
|
||||
def on_click(self, evt=None):
|
||||
self.data["done"] = not self.data["done"]
|
||||
self.strike(self.data["done"])
|
||||
|
||||
self.select("input").element.checked = self.data["done"]
|
||||
|
||||
class PyList(pyscript.PyListTemplate):
|
||||
item_class = PyItem
|
||||
|
||||
def add(self, item):
|
||||
if isinstance(item, str):
|
||||
item = {"content": item, "done": False, "created_at": dt.now()}
|
||||
|
||||
super().add(item, labels=["content"], state_key="done")
|
||||
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -10,15 +10,26 @@
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<py-config>
|
||||
paths = [
|
||||
"./utils.py"
|
||||
]
|
||||
</py-config>
|
||||
<link rel="stylesheet" href="./assets/css/examples.css" />
|
||||
<link rel="stylesheet" href="./assets/prism/prism.css" />
|
||||
<script defer src="./assets/prism/prism.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="container">
|
||||
<!-- <py-repl id="my-repl" auto-generate="true"> </py-repl> -->
|
||||
<body>
|
||||
<nav class="navbar" style="background-color: #000000;">
|
||||
<div class="app-header">
|
||||
<a href="/">
|
||||
<img src="./logo.png" class="logo">
|
||||
</a>
|
||||
<a class="title" href="" style="color: #f0ab3c;">Todo App</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="pyscript">
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
</py-config>
|
||||
|
||||
<py-script src="./todo.py"> </py-script>
|
||||
|
||||
<main>
|
||||
@@ -49,5 +60,149 @@ paths = [
|
||||
|
||||
</section>
|
||||
</main>
|
||||
</section>
|
||||
<section class="code">
|
||||
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div>
|
||||
<div id="code-section" class="code-section-hidden">
|
||||
<p>index.html</p>
|
||||
<pre class="prism-code language-html">
|
||||
<code class="language-html">
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
files = ["./utils.py"]
|
||||
</py-config>
|
||||
<py-script src="./todo.py">
|
||||
</py-script>
|
||||
<main>
|
||||
<section>
|
||||
|
||||
<div class="text-center w-full mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">To Do List</h1>
|
||||
</div>
|
||||
<div>
|
||||
<input id="new-task-content" class="py-input" type="text">
|
||||
<button id="new-task-btn" class="py-button" type="submit" py-click="add_task()">
|
||||
Add task
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<py-list id="myList"></py-list>
|
||||
<div id="list-tasks-container" class="flex flex-col-reverse mt-4">
|
||||
</div>
|
||||
|
||||
<template id="task-template">
|
||||
<section class="task py-li-element">
|
||||
<label for="flex items-center p-2 ">
|
||||
<input class="mr-2" type="checkbox">
|
||||
<p class="m-0 inline"></p>
|
||||
</label>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
</section>
|
||||
</main>
|
||||
</code>
|
||||
</pre>
|
||||
<p>utils.py</p>
|
||||
<pre class="prism-code language-python">
|
||||
<code class="language-python">
|
||||
from datetime import datetime as dt
|
||||
|
||||
|
||||
def format_date(dt_, fmt="%m/%d/%Y, %H:%M:%S"):
|
||||
return f"{dt_:{fmt}}"
|
||||
|
||||
|
||||
def now(fmt="%m/%d/%Y, %H:%M:%S"):
|
||||
return format_date(dt.now(), fmt)
|
||||
|
||||
|
||||
def remove_class(element, class_name):
|
||||
element.element.classList.remove(class_name)
|
||||
|
||||
|
||||
def add_class(element, class_name):
|
||||
element.element.classList.add(class_name)
|
||||
</code>
|
||||
</pre>
|
||||
<p>todo.py</p>
|
||||
<pre class="prism-code language-python">
|
||||
<code class="language-python">
|
||||
from datetime import datetime as dt
|
||||
|
||||
from utils import add_class, remove_class
|
||||
|
||||
tasks = []
|
||||
|
||||
# define the task template that will be use to render new templates to the page
|
||||
task_template = Element("task-template").select(".task", from_content=True)
|
||||
task_list = Element("list-tasks-container")
|
||||
new_task_content = Element("new-task-content")
|
||||
|
||||
|
||||
def add_task(*ags, **kws):
|
||||
# ignore empty task
|
||||
if not new_task_content.element.value:
|
||||
return None
|
||||
|
||||
# create task
|
||||
task_id = f"task-{len(tasks)}"
|
||||
task = {
|
||||
"id": task_id,
|
||||
"content": new_task_content.element.value,
|
||||
"done": False,
|
||||
"created_at": dt.now(),
|
||||
}
|
||||
|
||||
tasks.append(task)
|
||||
|
||||
# add the task element to the page as new node in the list by cloning from a
|
||||
# template
|
||||
task_html = task_template.clone(task_id)
|
||||
task_html_content = task_html.select("p")
|
||||
task_html_content.element.innerText = task["content"]
|
||||
task_html_check = task_html.select("input")
|
||||
task_list.element.appendChild(task_html.element)
|
||||
|
||||
def check_task(evt=None):
|
||||
task["done"] = not task["done"]
|
||||
if task["done"]:
|
||||
add_class(task_html_content, "line-through")
|
||||
else:
|
||||
remove_class(task_html_content, "line-through")
|
||||
|
||||
new_task_content.clear()
|
||||
task_html_check.element.onclick = check_task
|
||||
|
||||
|
||||
def add_task_event(e):
|
||||
if e.key == "Enter":
|
||||
add_task()
|
||||
|
||||
|
||||
new_task_content.element.onkeypress = add_task_event
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
const viewCodeButton = document.getElementById("view-code-button");
|
||||
const codeSection = document.getElementById("code-section");
|
||||
const handleClick = () => {
|
||||
if (codeSection.classList.contains("code-section-hidden")) {
|
||||
codeSection.classList.remove("code-section-hidden");
|
||||
codeSection.classList.add("code-section-visible");
|
||||
} else {
|
||||
codeSection.classList.remove("code-section-visible");
|
||||
codeSection.classList.add("code-section-hidden");
|
||||
}
|
||||
}
|
||||
viewCodeButton.addEventListener("click", handleClick)
|
||||
viewCodeButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||
handleClick();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -16,10 +16,6 @@
|
||||
|
||||
<title>Loading...</title>
|
||||
<link rel="icon" type="image/png" href="../favicon.png" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="toga-placeholder">Loading...</div>
|
||||
|
||||
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
|
||||
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
|
||||
@@ -33,20 +29,27 @@
|
||||
integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<py-config>
|
||||
packages = [
|
||||
"./static/wheels/travertino-0.1.3-py3-none-any.whl",
|
||||
"./static/wheels/toga_core-0.3.0.dev39-py3-none-any.whl",
|
||||
"./static/wheels/toga_web-0.3.0.dev39-py3-none-any.whl",
|
||||
"./static/wheels/freedom-0.0.1-py3-none-any.whl"
|
||||
]
|
||||
terminal = false
|
||||
</py-config>
|
||||
|
||||
<div id="app-placeholder">Loading...</div>
|
||||
<py-script>
|
||||
from pyscript import Element
|
||||
from freedom.__main__ import main
|
||||
|
||||
|
||||
# Remove text from placeholder
|
||||
Element('app-placeholder').write('', append=False)
|
||||
app = main().main_loop()
|
||||
</py-script>
|
||||
</body>
|
||||
<py-config>
|
||||
packages = [
|
||||
"./static/wheels/travertino-0.1.3-py3-none-any.whl",
|
||||
"./static/wheels/toga_core-0.3.0.dev33-py3-none-any.whl",
|
||||
"./static/wheels/toga_web-0.3.0.dev33-py3-none-any.whl",
|
||||
"./static/wheels/freedom-0.0.1-py3-none-any.whl"
|
||||
]
|
||||
</py-config>
|
||||
<py-script>
|
||||
from toga_web.dom import handle as dom_handle
|
||||
|
||||
from freedom.__main__ import main
|
||||
|
||||
app = main().main_loop(spa=True)
|
||||
</py-script>
|
||||
</html>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -20,7 +20,7 @@
|
||||
<div class="col"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/89/three.min.js'></script>
|
||||
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/0.147.0/three.min.js'></script>
|
||||
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
@@ -31,10 +31,9 @@ from js import Math
|
||||
from js import THREE
|
||||
from js import performance
|
||||
from js import Object
|
||||
from js import document
|
||||
import asyncio
|
||||
|
||||
|
||||
|
||||
mouse = THREE.Vector2.new();
|
||||
|
||||
renderer = THREE.WebGLRenderer.new({"antialias":True})
|
||||
@@ -145,49 +144,50 @@ scene.add(light);
|
||||
scene.add(lightBack);
|
||||
|
||||
rectSize = 2
|
||||
intensity = 100
|
||||
intensity = 14
|
||||
rectLight = THREE.RectAreaLight.new( 0x0FFFFF, intensity, rectSize, rectSize )
|
||||
rectLight.position.set( 0, 0, 1 )
|
||||
rectLight.lookAt( 0, 0, 0 )
|
||||
scene.add( rectLight )
|
||||
|
||||
rectLightHelper = THREE.RectAreaLightHelper.new( rectLight );
|
||||
raycaster = THREE.Raycaster.new();
|
||||
uSpeed = 0.1
|
||||
|
||||
time = 0.0003;
|
||||
camera.lookAt(scene.position)
|
||||
|
||||
while True:
|
||||
time = performance.now() * 0.0003;
|
||||
i = 0
|
||||
while i < particularGroup.children.length:
|
||||
newObject = particularGroup.children[i];
|
||||
newObject.rotation.x += newObject.speedValue/10;
|
||||
newObject.rotation.y += newObject.speedValue/10;
|
||||
newObject.rotation.z += newObject.speedValue/10;
|
||||
i += 1
|
||||
async def main():
|
||||
while True:
|
||||
time = performance.now() * 0.0003;
|
||||
i = 0
|
||||
while i < particularGroup.children.length:
|
||||
newObject = particularGroup.children[i];
|
||||
newObject.rotation.x += newObject.speedValue/10;
|
||||
newObject.rotation.y += newObject.speedValue/10;
|
||||
newObject.rotation.z += newObject.speedValue/10;
|
||||
i += 1
|
||||
|
||||
i = 0
|
||||
while i < modularGroup.children.length:
|
||||
newCubes = modularGroup.children[i];
|
||||
newCubes.rotation.x += 0.008;
|
||||
newCubes.rotation.y += 0.005;
|
||||
newCubes.rotation.z += 0.003;
|
||||
i = 0
|
||||
while i < modularGroup.children.length:
|
||||
newCubes = modularGroup.children[i];
|
||||
newCubes.rotation.x += 0.008;
|
||||
newCubes.rotation.y += 0.005;
|
||||
newCubes.rotation.z += 0.003;
|
||||
|
||||
newCubes.position.x = Math.sin(time * newCubes.positionZ) * newCubes.positionY;
|
||||
newCubes.position.y = Math.cos(time * newCubes.positionX) * newCubes.positionZ;
|
||||
newCubes.position.z = Math.sin(time * newCubes.positionY) * newCubes.positionX;
|
||||
i += 1
|
||||
newCubes.position.x = Math.sin(time * newCubes.positionZ) * newCubes.positionY;
|
||||
newCubes.position.y = Math.cos(time * newCubes.positionX) * newCubes.positionZ;
|
||||
newCubes.position.z = Math.sin(time * newCubes.positionY) * newCubes.positionX;
|
||||
i += 1
|
||||
|
||||
particularGroup.rotation.y += 0.005;
|
||||
particularGroup.rotation.y += 0.005;
|
||||
|
||||
modularGroup.rotation.y -= ((mouse.x * 4) + modularGroup.rotation.y) * uSpeed;
|
||||
modularGroup.rotation.x -= ((-mouse.y * 4) + modularGroup.rotation.x) * uSpeed;
|
||||
modularGroup.rotation.y -= ((mouse.x * 4) + modularGroup.rotation.y) * uSpeed;
|
||||
modularGroup.rotation.x -= ((-mouse.y * 4) + modularGroup.rotation.x) * uSpeed;
|
||||
|
||||
renderer.render( scene, camera )
|
||||
await asyncio.sleep(0.02)
|
||||
renderer.render( scene, camera )
|
||||
await asyncio.sleep(0.02)
|
||||
|
||||
asyncio.ensure_future(main())
|
||||
|
||||
</py-script>
|
||||
</body>
|
||||
|
||||
@@ -86,12 +86,15 @@ test-integration:
|
||||
make examples
|
||||
$(PYTEST_EXE) -vv $(ARGS) tests/integration/ --log-cli-level=warning
|
||||
|
||||
test-integration-parallel:
|
||||
make examples
|
||||
$(PYTEST_EXE) --numprocesses auto -vv $(ARGS) tests/integration/ --log-cli-level=warning
|
||||
|
||||
test-py:
|
||||
@echo "Tests from $(src_dir)"
|
||||
$(PYTEST_EXE) -vv $(ARGS) tests/py-unit/ --log-cli-level=warning
|
||||
|
||||
test-ts:
|
||||
@echo "Tests are coming :( this is a placeholder and it's meant to fail!"
|
||||
npm run test
|
||||
|
||||
fmt: fmt-py fmt-ts
|
||||
|
||||
14
pyscriptjs/__mocks__/_pyscript.js
Normal file
14
pyscriptjs/__mocks__/_pyscript.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* this file mocks the `src/python/pyscript.py` file
|
||||
* since importing of `.py` files isn't usually supported
|
||||
* inside JS/TS files.
|
||||
*
|
||||
* It sets the value of whatever is imported from
|
||||
* `src/python/pyscript.py` the contents of that file
|
||||
*
|
||||
* This is needed since the imported object is further
|
||||
* passed to a function which only accepts a string.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
module.exports = fs.readFileSync('./src/python/pyscript.py', 'utf8');
|
||||
1
pyscriptjs/__mocks__/cssMock.js
Normal file
1
pyscriptjs/__mocks__/cssMock.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = "";
|
||||
@@ -1,12 +1,16 @@
|
||||
/**
|
||||
* this file mocks the `src/python/pyscript.py` file
|
||||
* since importing of `.py` files isn't usually supported
|
||||
* inside JS/TS files.
|
||||
*
|
||||
* It sets the value of whatever is imported from
|
||||
* `src/python/pyscript.py` to be an empty string i.e. ""
|
||||
* this file mocks python files that are not explicitly
|
||||
* matched by a regex in jest.config.js, since importing of
|
||||
* `.py` files isn't usually supported inside JS/TS files.
|
||||
*
|
||||
* This is needed since the imported object is further
|
||||
* passed to a function which only accepts a string.
|
||||
*
|
||||
* The mocked contents of the `.py` file will be "", e.g.
|
||||
* nothing.
|
||||
*/
|
||||
module.exports = "";
|
||||
|
||||
console.warn(`.py files that are not explicitly mocked in \
|
||||
jest.config.js and /__mocks__/ are mocked as empty strings`);
|
||||
|
||||
module.exports = "";
|
||||
|
||||
@@ -13,7 +13,8 @@ dependencies:
|
||||
- pre-commit
|
||||
- pillow
|
||||
- numpy
|
||||
|
||||
- markdown
|
||||
- pip:
|
||||
- playwright
|
||||
- pytest-playwright
|
||||
- pytest-xdist
|
||||
|
||||
@@ -3,17 +3,22 @@ module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: 'tsconfig.json',
|
||||
useESM: true
|
||||
}
|
||||
transform: {
|
||||
'^.+\\.tsx?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
tsconfig: 'tsconfig.json',
|
||||
useESM: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
verbose: true,
|
||||
testEnvironmentOptions: {
|
||||
url: "http://localhost"
|
||||
url: 'http://localhost',
|
||||
},
|
||||
moduleNameMapper: {
|
||||
"^[./a-zA-Z0-9$_-]+\\.py$": "<rootDir>/__mocks__/fileMock.js",
|
||||
}
|
||||
};
|
||||
'^.*?pyscript\.py$': '<rootDir>/__mocks__/_pyscript.js',
|
||||
'^[./a-zA-Z0-9$_-]+\\.py$': '<rootDir>/__mocks__/fileMock.js',
|
||||
'\\.(css)$': '<rootDir>/__mocks__/cssMock.js',
|
||||
},
|
||||
};
|
||||
|
||||
3989
pyscriptjs/package-lock.json
generated
3989
pyscriptjs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,7 @@
|
||||
"build-min": "NODE_ENV=production rollup -c",
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"start": "sirv public --no-clear --port 8080",
|
||||
"validate": "svelte-check",
|
||||
"tsc": "tsc --noEmit",
|
||||
"format:check": "prettier --check './src/**/*.{js,html,ts}'",
|
||||
"format": "prettier --write './src/**/*.{js,html,ts}'",
|
||||
"lint": "eslint './src/**/*.{js,html,ts}'",
|
||||
@@ -16,45 +15,40 @@
|
||||
"test:watch": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||
"@rollup/plugin-typescript": "^8.4.0",
|
||||
"@tsconfig/svelte": "^1.0.0",
|
||||
"@types/jest": "^28.1.6",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/node": "^18.7.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.36.0",
|
||||
"@typescript-eslint/parser": "^5.36.0",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.14.0",
|
||||
"jest": "^28.1.3",
|
||||
"jest-environment-jsdom": "^28.1.3",
|
||||
"prettier": "^2.6.2",
|
||||
"pyodide": "0.21.2",
|
||||
"rollup": "^2.71.1",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-css-only": "^3.1.0",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
"rollup-plugin-serve": "^1.1.0",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
"rollup-plugin-terser": "^7.0.0",
|
||||
"svelte": "^3.48.0",
|
||||
"svelte-check": "^1.0.0",
|
||||
"ts-jest": "^28.0.7",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.8.2"
|
||||
"@jest/globals": "29.1.2",
|
||||
"@rollup/plugin-commonjs": "22.0.2",
|
||||
"@rollup/plugin-legacy": "2.2.0",
|
||||
"@rollup/plugin-node-resolve": "14.1.0",
|
||||
"@rollup/plugin-typescript": "8.5.0",
|
||||
"@types/codemirror": "^5.60.5",
|
||||
"@types/jest": "29.1.2",
|
||||
"@types/node": "18.8.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.39.0",
|
||||
"@typescript-eslint/parser": "5.39.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.25.0",
|
||||
"jest": "29.1.2",
|
||||
"jest-environment-jsdom": "29.1.2",
|
||||
"prettier": "2.7.1",
|
||||
"pyodide": "0.21.3",
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-copy": "3.4.0",
|
||||
"rollup-plugin-css-only": "3.1.0",
|
||||
"rollup-plugin-livereload": "2.0.5",
|
||||
"rollup-plugin-serve": "2.0.1",
|
||||
"rollup-plugin-string": "3.0.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"ts-jest": "29.0.3",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/basic-setup": "^0.19.1",
|
||||
"@codemirror/lang-python": "^0.19.5",
|
||||
"@codemirror/state": "^0.19.9",
|
||||
"@codemirror/theme-one-dark": "^0.19.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.0.0",
|
||||
"codemirror": "^5.65.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"sirv-cli": "^1.0.0",
|
||||
"svelte-fa": "^2.4.0",
|
||||
"svelte-promisable-stores": "^0.1.3"
|
||||
"@codemirror/commands": "6.1.1",
|
||||
"@codemirror/lang-python": "6.0.2",
|
||||
"@codemirror/language": "6.2.1",
|
||||
"@codemirror/state": "6.1.2",
|
||||
"@codemirror/theme-one-dark": "6.1.0",
|
||||
"@codemirror/view": "6.3.0",
|
||||
"codemirror": "6.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
54
pyscriptjs/public/index.html
Normal file
54
pyscriptjs/public/index.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css">
|
||||
<link rel="stylesheet" href="pyscript.css">
|
||||
<script defer src="pyscript.min.js"></script>
|
||||
<title>PyScript</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1><py-script></h1>
|
||||
<ul>
|
||||
<li><a href="pyscript.js">pyscript.js</a></li>
|
||||
<li><a href="pyscript.min.js">pyscript.min.js</a></li>
|
||||
<li><a href="pyscript.css">pyscript.css</a></li>
|
||||
<li><a href="pyscript.min.js.map">pyscript.min.js.map</a></li>
|
||||
<li><a href="pyscript.js.map">pyscript.js.map</a></li>
|
||||
</ul>
|
||||
<div id="out"></div>
|
||||
<py-script std-out="out">
|
||||
import sys
|
||||
print(sys.version)
|
||||
</py-script>
|
||||
|
||||
<h2>Example</h2>
|
||||
<pre style="padding:1em;border:1px solid #000000;"><!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>PyScript Hello World</title>
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Hello world! <br>
|
||||
This is the current date and time, as computed by Python:
|
||||
<py-script>
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
now.strftime("%m/%d/%Y, %H:%M:%S")
|
||||
</py-script>
|
||||
</body>
|
||||
</html></pre>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,7 +1,7 @@
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import livereload from "rollup-plugin-livereload";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import legacy from '@rollup/plugin-legacy';
|
||||
import typescript from "@rollup/plugin-typescript";
|
||||
import css from "rollup-plugin-css-only";
|
||||
import serve from "rollup-plugin-serve";
|
||||
@@ -10,6 +10,17 @@ import copy from 'rollup-plugin-copy'
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH || (process.env.NODE_ENV === "production");
|
||||
|
||||
const copy_targets = {
|
||||
targets: [
|
||||
{ src: 'public/index.html', dest: 'build' },
|
||||
{ src: 'src/plugins/*', dest: 'build/plugins' }
|
||||
]
|
||||
}
|
||||
|
||||
if( !production ){
|
||||
copy_targets.targets.push({ src: 'build/*', dest: 'examples/build' })
|
||||
}
|
||||
|
||||
export default {
|
||||
input: "src/main.ts",
|
||||
output:[
|
||||
@@ -17,7 +28,7 @@ export default {
|
||||
sourcemap: true,
|
||||
format: "iife",
|
||||
inlineDynamicImports: true,
|
||||
name: "app",
|
||||
name: "pyscript",
|
||||
file: "build/pyscript.js",
|
||||
},
|
||||
{
|
||||
@@ -34,9 +45,9 @@ export default {
|
||||
string({
|
||||
include: "./src/**/*.py",
|
||||
}),
|
||||
legacy({ 'src/toml.js': 'toml' }),
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: ["svelte"],
|
||||
}),
|
||||
commonjs(),
|
||||
typescript({
|
||||
@@ -44,13 +55,7 @@ export default {
|
||||
inlineSources: !production,
|
||||
}),
|
||||
// This will make sure that examples will always get the latest build folder
|
||||
!production && copy({
|
||||
targets: [
|
||||
{ src: 'build/*', dest: 'examples/build' }
|
||||
]
|
||||
}),
|
||||
!production && serve(),
|
||||
!production && livereload("public"),
|
||||
copy(copy_targets),
|
||||
// production && terser(),
|
||||
!production && serve({
|
||||
port: 8080,
|
||||
|
||||
@@ -1,370 +0,0 @@
|
||||
import { runtimeLoaded } from '../stores';
|
||||
import { guidGenerator, addClasses, removeClasses } from '../utils';
|
||||
|
||||
import type { Runtime } from '../runtime';
|
||||
import { getLogger } from '../logger';
|
||||
|
||||
const logger = getLogger('pyscript/base');
|
||||
|
||||
// Global `Runtime` that implements the generic runtimes API
|
||||
let runtime: Runtime;
|
||||
let Element;
|
||||
|
||||
runtimeLoaded.subscribe(value => {
|
||||
runtime = value;
|
||||
});
|
||||
|
||||
export class BaseEvalElement extends HTMLElement {
|
||||
shadow: ShadowRoot;
|
||||
wrapper: HTMLElement;
|
||||
code: string;
|
||||
source: string;
|
||||
btnConfig: HTMLElement;
|
||||
btnRun: HTMLElement;
|
||||
outputElement: HTMLElement;
|
||||
errorElement: HTMLElement;
|
||||
theme: string;
|
||||
appendOutput: boolean;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// attach shadow so we can preserve the element original innerHtml content
|
||||
this.shadow = this.attachShadow({ mode: 'open' });
|
||||
this.wrapper = document.createElement('slot');
|
||||
this.shadow.appendChild(this.wrapper);
|
||||
this.setOutputMode("append");
|
||||
}
|
||||
|
||||
addToOutput(s: string) {
|
||||
this.outputElement.innerHTML += '<div>' + s + '</div>';
|
||||
this.outputElement.hidden = false;
|
||||
}
|
||||
|
||||
setOutputMode(defaultMode = "append") {
|
||||
const mode = this.hasAttribute('output-mode') ? this.getAttribute('output-mode') : defaultMode;
|
||||
|
||||
switch (mode) {
|
||||
case "append":
|
||||
this.appendOutput = true;
|
||||
break;
|
||||
case "replace":
|
||||
this.appendOutput = false;
|
||||
break;
|
||||
default:
|
||||
logger.warn(`${this.id}: custom output-modes are currently not implemented`);
|
||||
}
|
||||
}
|
||||
|
||||
// subclasses should overwrite this method to define custom logic
|
||||
// before code gets evaluated
|
||||
preEvaluate(): void {
|
||||
return null;
|
||||
}
|
||||
|
||||
// subclasses should overwrite this method to define custom logic
|
||||
// after code has been evaluated
|
||||
postEvaluate(): void {
|
||||
return null;
|
||||
}
|
||||
|
||||
checkId() {
|
||||
if (!this.id) this.id = 'py-' + guidGenerator();
|
||||
}
|
||||
|
||||
getSourceFromElement(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
async getSourceFromFile(s: string): Promise<string> {
|
||||
const response = await fetch(s);
|
||||
this.code = await response.text();
|
||||
return this.code;
|
||||
}
|
||||
|
||||
protected async _register_esm(runtime: Runtime): Promise<void> {
|
||||
const imports: { [key: string]: unknown } = {};
|
||||
const nodes = document.querySelectorAll("script[type='importmap']");
|
||||
const importmaps: Array<any> = [];
|
||||
nodes.forEach( node =>
|
||||
{
|
||||
let importmap;
|
||||
try {
|
||||
importmap = JSON.parse(node.textContent);
|
||||
if (importmap?.imports == null) return;
|
||||
importmaps.push(importmap);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
)
|
||||
for (const importmap of importmaps){
|
||||
for (const [name, url] of Object.entries(importmap.imports)) {
|
||||
if (typeof name != 'string' || typeof url != 'string') continue;
|
||||
|
||||
try {
|
||||
// XXX: pyodide doesn't like Module(), failing with
|
||||
// "can't read 'name' of undefined" at import time
|
||||
imports[name] = { ...(await import(url)) };
|
||||
} catch {
|
||||
logger.error(`failed to fetch '${url}' for '${name}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runtime.registerJsModule('esm', imports);
|
||||
}
|
||||
|
||||
async evaluate(): Promise<void> {
|
||||
this.preEvaluate();
|
||||
|
||||
let source: string;
|
||||
let output: string;
|
||||
try {
|
||||
source = this.source ? await this.getSourceFromFile(this.source)
|
||||
: this.getSourceFromElement();
|
||||
|
||||
this._register_esm(runtime);
|
||||
<string>await runtime.run(
|
||||
`output_manager.change(out="${this.outputElement.id}", err="${this.errorElement.id}", append=${this.appendOutput ? 'True' : 'False'})`,
|
||||
);
|
||||
output = <string>await runtime.run(source);
|
||||
|
||||
if (output !== undefined) {
|
||||
if (Element === undefined) {
|
||||
Element = <Element>runtime.globals.get('Element');
|
||||
}
|
||||
const out = Element(this.outputElement.id);
|
||||
out.write.callKwargs(output, { append: this.appendOutput });
|
||||
|
||||
this.outputElement.hidden = false;
|
||||
this.outputElement.style.display = 'block';
|
||||
}
|
||||
|
||||
await runtime.run(`output_manager.revert()`);
|
||||
|
||||
// check if this REPL contains errors, delete them and remove error classes
|
||||
const errorElements = document.querySelectorAll(`div[id^='${this.errorElement.id}'][error]`);
|
||||
if (errorElements.length > 0) {
|
||||
errorElements.forEach( errorElement =>
|
||||
{
|
||||
errorElement.classList.add('hidden');
|
||||
if (this.hasAttribute('std-err')) {
|
||||
this.errorElement.hidden = true;
|
||||
this.errorElement.style.removeProperty('display');
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
removeClasses(this.errorElement, ['bg-red-200', 'p-2']);
|
||||
|
||||
this.postEvaluate();
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
try{
|
||||
if (Element === undefined) {
|
||||
Element = <Element>runtime.globals.get('Element');
|
||||
}
|
||||
const out = Element(this.errorElement.id);
|
||||
|
||||
addClasses(this.errorElement, ['bg-red-200', 'p-2']);
|
||||
out.write.callKwargs(err.toString(), { append: this.appendOutput });
|
||||
if (this.errorElement.children.length === 0){
|
||||
this.errorElement.setAttribute('error', '');
|
||||
}else{
|
||||
this.errorElement.children[this.errorElement.children.length - 1].setAttribute('error', '');
|
||||
}
|
||||
|
||||
this.errorElement.hidden = false;
|
||||
this.errorElement.style.display = 'block';
|
||||
this.errorElement.style.visibility = 'visible';
|
||||
} catch (internalErr){
|
||||
logger.error("Unnable to write error to error element in page.")
|
||||
}
|
||||
|
||||
}
|
||||
} // end evaluate
|
||||
|
||||
async eval(source: string): Promise<void> {
|
||||
try {
|
||||
const output = await runtime.run(source);
|
||||
if (output !== undefined) {
|
||||
logger.info(output);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
}
|
||||
} // end eval
|
||||
|
||||
runAfterRuntimeInitialized(callback: () => Promise<void>) {
|
||||
runtimeLoaded.subscribe(value => {
|
||||
if ('run' in value) {
|
||||
setTimeout(() => {
|
||||
void callback();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createWidget(name: string, code: string, klass: string) {
|
||||
class CustomWidget extends HTMLElement {
|
||||
shadow: ShadowRoot;
|
||||
wrapper: HTMLElement;
|
||||
|
||||
name: string = name;
|
||||
klass: string = klass;
|
||||
code: string = code;
|
||||
proxy: any;
|
||||
proxyClass: any;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// attach shadow so we can preserve the element original innerHtml content
|
||||
this.shadow = this.attachShadow({ mode: 'open' });
|
||||
|
||||
this.wrapper = document.createElement('slot');
|
||||
this.shadow.appendChild(this.wrapper);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// TODO: we are calling with a 2secs delay to allow pyodide to load
|
||||
// ideally we can just wait for it to load and then run. To do
|
||||
// so we need to replace using the promise and actually using
|
||||
// the interpreter after it loads completely
|
||||
// setTimeout(() => {
|
||||
// void (async () => {
|
||||
// await this.eval(this.code);
|
||||
// this.proxy = this.proxyClass(this);
|
||||
// console.log('proxy', this.proxy);
|
||||
// this.proxy.connect();
|
||||
// this.registerWidget();
|
||||
// })();
|
||||
// }, 2000);
|
||||
runtimeLoaded.subscribe(value => {
|
||||
if ('run' in value) {
|
||||
runtime = value;
|
||||
setTimeout(() => {
|
||||
void (async () => {
|
||||
await this.eval(this.code);
|
||||
this.proxy = this.proxyClass(this);
|
||||
this.proxy.connect();
|
||||
this.registerWidget();
|
||||
})();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
registerWidget() {
|
||||
logger.info('new widget registered:', this.name);
|
||||
runtime.globals.set(this.id, this.proxy);
|
||||
}
|
||||
|
||||
async eval(source: string): Promise<void> {
|
||||
try {
|
||||
const output = await runtime.run(source);
|
||||
this.proxyClass = runtime.globals.get(this.klass);
|
||||
if (output !== undefined) {
|
||||
logger.info('CustomWidget.eval: ', output);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('CustomWidget.eval: ', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
const xPyWidget = customElements.define(name, CustomWidget);
|
||||
}
|
||||
|
||||
export class PyWidget extends HTMLElement {
|
||||
shadow: ShadowRoot;
|
||||
name: string;
|
||||
klass: string;
|
||||
outputElement: HTMLElement;
|
||||
errorElement: HTMLElement;
|
||||
wrapper: HTMLElement;
|
||||
theme: string;
|
||||
source: string;
|
||||
code: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// attach shadow so we can preserve the element original innerHtml content
|
||||
this.shadow = this.attachShadow({ mode: 'open' });
|
||||
|
||||
this.wrapper = document.createElement('slot');
|
||||
this.shadow.appendChild(this.wrapper);
|
||||
|
||||
this.addAttributes('src','name','klass');
|
||||
}
|
||||
|
||||
addAttributes(...attrs:string[]){
|
||||
for (const each of attrs){
|
||||
const property = each === "src" ? "source" : each;
|
||||
if (this.hasAttribute(each)) {
|
||||
this[property]=this.getAttribute(each);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
if (this.id === undefined) {
|
||||
throw new ReferenceError(
|
||||
`No id specified for component. Components must have an explicit id. Please use id="" to specify your component id.`,
|
||||
);
|
||||
}
|
||||
|
||||
const mainDiv = document.createElement('div');
|
||||
mainDiv.id = this.id + '-main';
|
||||
this.appendChild(mainDiv);
|
||||
logger.debug('PyWidget: reading source', this.source);
|
||||
this.code = await this.getSourceFromFile(this.source);
|
||||
createWidget(this.name, this.code, this.klass);
|
||||
}
|
||||
|
||||
initOutErr(): void {
|
||||
if (this.hasAttribute('output')) {
|
||||
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
|
||||
|
||||
// in this case, the default output-mode is append, if hasn't been specified
|
||||
if (!this.hasAttribute('output-mode')) {
|
||||
this.setAttribute('output-mode', 'append');
|
||||
}
|
||||
} else {
|
||||
if (this.hasAttribute('std-out')) {
|
||||
this.outputElement = document.getElementById(this.getAttribute('std-out'));
|
||||
} else {
|
||||
// In this case neither output or std-out have been provided so we need
|
||||
// to create a new output div to output to
|
||||
this.outputElement = document.createElement('div');
|
||||
this.outputElement.classList.add('output');
|
||||
this.outputElement.hidden = true;
|
||||
this.outputElement.id = this.id + '-' + this.getAttribute('exec-id');
|
||||
}
|
||||
|
||||
if (this.hasAttribute('std-err')) {
|
||||
this.errorElement = document.getElementById(this.getAttribute('std-err'));
|
||||
} else {
|
||||
this.errorElement = this.outputElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getSourceFromFile(s: string): Promise<string> {
|
||||
const response = await fetch(s);
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
async eval(source: string): Promise<void> {
|
||||
try {
|
||||
const output = await runtime.run(source);
|
||||
if (output !== undefined) {
|
||||
logger.info('PyWidget.eval: ', output);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('PyWidget.eval: ', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,17 @@
|
||||
import { PyRepl } from './pyrepl';
|
||||
import type { Runtime } from '../runtime';
|
||||
import { make_PyRepl } from './pyrepl';
|
||||
import { PyBox } from './pybox';
|
||||
import { PyButton } from './pybutton';
|
||||
import { make_PyButton } from './pybutton';
|
||||
import { PyTitle } from './pytitle';
|
||||
import { PyInputBox } from './pyinputbox';
|
||||
import { PyWidget } from './base';
|
||||
import { make_PyInputBox } from './pyinputbox';
|
||||
import { make_PyWidget } from './pywidget';
|
||||
|
||||
/*
|
||||
These were taken from main.js because some of our components call
|
||||
runAfterRuntimeInitialized immediately when we are creating the custom
|
||||
element, this was causing tests to fail since runAfterRuntimeInitialized
|
||||
expects the runtime to have been loaded before being called.
|
||||
function createCustomElements(runtime: Runtime) {
|
||||
const PyInputBox = make_PyInputBox(runtime);
|
||||
const PyButton = make_PyButton(runtime);
|
||||
const PyWidget = make_PyWidget(runtime);
|
||||
const PyRepl = make_PyRepl(runtime);
|
||||
|
||||
This function is now called from within the `runtime.initialize`. Once
|
||||
the runtime finished initializing, then we will create the custom elements
|
||||
so they are rendered in the page and we will always have a runtime available.
|
||||
|
||||
Ideally, this would live under utils.js, but importing all the components in
|
||||
the utils.js file was causing jest to fail with weird errors such as:
|
||||
"ReferenceError: Cannot access 'BaseEvalElement' before initialization" coming
|
||||
from the PyScript class.
|
||||
|
||||
*/
|
||||
function createCustomElements() {
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
const xPyRepl = customElements.define('py-repl', PyRepl);
|
||||
const xPyBox = customElements.define('py-box', PyBox);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { addClasses } from '../utils';
|
||||
import { getAttribute, addClasses, createDeprecationWarning } from '../utils';
|
||||
import { getLogger } from '../logger';
|
||||
|
||||
const logger = getLogger('py-box');
|
||||
@@ -7,7 +7,7 @@ export class PyBox extends HTMLElement {
|
||||
shadow: ShadowRoot;
|
||||
wrapper: HTMLElement;
|
||||
theme: string;
|
||||
widths: Array<string>;
|
||||
widths: string[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -20,6 +20,10 @@ export class PyBox extends HTMLElement {
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const deprecationMessage =
|
||||
'The element <py-box> is deprecated, you should create a ' +
|
||||
'div with "py-box" class name instead. For example: <div class="py-box">';
|
||||
createDeprecationWarning(deprecationMessage, 'py-box');
|
||||
const mainDiv = document.createElement('div');
|
||||
addClasses(mainDiv, ['py-box']);
|
||||
|
||||
@@ -47,10 +51,14 @@ export class PyBox extends HTMLElement {
|
||||
// now we need to set widths
|
||||
this.widths = [];
|
||||
|
||||
if (this.hasAttribute('widths')) {
|
||||
for (const w of this.getAttribute('widths').split(';')) {
|
||||
if (w.includes('/')) this.widths.push(w.split('/')[0])
|
||||
else this.widths.push(w)
|
||||
const widthsAttr = getAttribute(this, 'widths');
|
||||
if (widthsAttr) {
|
||||
for (const w of widthsAttr.split(';')) {
|
||||
if (w.includes('/')) {
|
||||
this.widths.push(w.split('/')[0]);
|
||||
} else {
|
||||
this.widths.push(w);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.widths = Array<string>(mainDiv.children.length).fill('1 1 0');
|
||||
@@ -59,7 +67,7 @@ export class PyBox extends HTMLElement {
|
||||
this.widths.forEach((width, index) => {
|
||||
const node: ChildNode = mainDiv.childNodes[index];
|
||||
(<HTMLElement>node).style.flex = width;
|
||||
addClasses((<HTMLElement>node), ['py-box-child']);
|
||||
addClasses(<HTMLElement>node, ['py-box-child']);
|
||||
});
|
||||
|
||||
this.appendChild(mainDiv);
|
||||
|
||||
@@ -1,77 +1,85 @@
|
||||
import { BaseEvalElement } from './base';
|
||||
import { addClasses, htmlDecode } from '../utils';
|
||||
import { getLogger } from '../logger'
|
||||
import { getAttribute, addClasses, htmlDecode, ensureUniqueId, createDeprecationWarning } from '../utils';
|
||||
import { getLogger } from '../logger';
|
||||
import type { Runtime } from '../runtime';
|
||||
|
||||
const logger = getLogger('py-button');
|
||||
|
||||
export function make_PyButton(runtime: Runtime) {
|
||||
class PyButton extends HTMLElement {
|
||||
widths: string[] = [];
|
||||
label: string | undefined = undefined;
|
||||
class: string[];
|
||||
defaultClass: string[];
|
||||
mount_name: string | undefined = undefined;
|
||||
code: string;
|
||||
|
||||
export class PyButton extends BaseEvalElement {
|
||||
widths: Array<string>;
|
||||
label: string;
|
||||
class: Array<string>;
|
||||
defaultClass: Array<string>;
|
||||
mount_name: string;
|
||||
constructor() {
|
||||
super();
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.defaultClass = ['py-button'];
|
||||
this.defaultClass = ['py-button'];
|
||||
|
||||
if (this.hasAttribute('label')) {
|
||||
this.label = this.getAttribute('label');
|
||||
}
|
||||
|
||||
// Styling does the same thing as class in normal HTML. Using the name "class" makes the style to malfunction
|
||||
if (this.hasAttribute('styling')) {
|
||||
const klass = this.getAttribute('styling').trim();
|
||||
if (klass === '') {
|
||||
this.class = this.defaultClass;
|
||||
} else {
|
||||
// trim each element to remove unnecessary spaces which makes the button style to malfunction
|
||||
this.class = klass
|
||||
.split(' ')
|
||||
.map(x => x.trim())
|
||||
.filter(x => x !== '');
|
||||
const label = getAttribute(this, 'label');
|
||||
if (label) {
|
||||
this.label = label;
|
||||
}
|
||||
} else {
|
||||
this.class = this.defaultClass;
|
||||
|
||||
// Styling does the same thing as class in normal HTML. Using the name "class" makes the style to malfunction
|
||||
const styling = getAttribute(this, 'styling');
|
||||
if (styling) {
|
||||
const klass = styling.trim();
|
||||
if (klass === '') {
|
||||
this.class = this.defaultClass;
|
||||
} else {
|
||||
// trim each element to remove unnecessary spaces which makes the button style to malfunction
|
||||
this.class = klass
|
||||
.split(' ')
|
||||
.map(x => x.trim())
|
||||
.filter(x => x !== '');
|
||||
}
|
||||
} else {
|
||||
this.class = this.defaultClass;
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const deprecationMessage =
|
||||
'The element <py-button> is deprecated, create a function with your ' +
|
||||
'inline code and use <button py-click="function()" class="py-button"> instead.';
|
||||
createDeprecationWarning(deprecationMessage, 'py-button');
|
||||
|
||||
ensureUniqueId(this);
|
||||
this.code = htmlDecode(this.innerHTML) || '';
|
||||
this.mount_name = this.id.split('-').join('_');
|
||||
this.innerHTML = '';
|
||||
|
||||
const mainDiv = document.createElement('button');
|
||||
mainDiv.innerHTML = this.label;
|
||||
addClasses(mainDiv, this.class);
|
||||
|
||||
mainDiv.id = this.id;
|
||||
this.id = `${this.id}-container`;
|
||||
|
||||
this.appendChild(mainDiv);
|
||||
this.code = this.code.split('self').join(this.mount_name);
|
||||
let registrationCode = `from pyodide.ffi import create_proxy`;
|
||||
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
|
||||
if (this.code.includes('def on_focus')) {
|
||||
this.code = this.code.replace('def on_focus', `def on_focus_${this.mount_name}`);
|
||||
registrationCode += `\n${this.mount_name}.element.addEventListener('focus', create_proxy(on_focus_${this.mount_name}))`;
|
||||
}
|
||||
|
||||
if (this.code.includes('def on_click')) {
|
||||
this.code = this.code.replace('def on_click', `def on_click_${this.mount_name}`);
|
||||
registrationCode += `\n${this.mount_name}.element.addEventListener('click', create_proxy(on_click_${this.mount_name}))`;
|
||||
}
|
||||
|
||||
// now that we appended and the element is attached, lets connect with the event handlers
|
||||
// defined for this widget
|
||||
runtime.runButDontRaise(this.code);
|
||||
runtime.runButDontRaise(registrationCode);
|
||||
logger.debug('py-button connected');
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.checkId();
|
||||
this.code = htmlDecode(this.innerHTML);
|
||||
this.mount_name = this.id.split('-').join('_');
|
||||
this.innerHTML = '';
|
||||
|
||||
const mainDiv = document.createElement('button');
|
||||
mainDiv.innerHTML = this.label;
|
||||
addClasses(mainDiv, this.class);
|
||||
|
||||
mainDiv.id = this.id;
|
||||
this.id = `${this.id}-container`;
|
||||
|
||||
this.appendChild(mainDiv);
|
||||
this.code = this.code.split('self').join(this.mount_name);
|
||||
let registrationCode = `from pyodide.ffi import create_proxy`;
|
||||
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
|
||||
if (this.code.includes('def on_focus')) {
|
||||
this.code = this.code.replace('def on_focus', `def on_focus_${this.mount_name}`);
|
||||
registrationCode += `\n${this.mount_name}.element.addEventListener('focus', create_proxy(on_focus_${this.mount_name}))`;
|
||||
}
|
||||
|
||||
if (this.code.includes('def on_click')) {
|
||||
this.code = this.code.replace('def on_click', `def on_click_${this.mount_name}`);
|
||||
registrationCode += `\n${this.mount_name}.element.addEventListener('click', create_proxy(on_click_${this.mount_name}))`;
|
||||
}
|
||||
|
||||
// now that we appended and the element is attached, lets connect with the event handlers
|
||||
// defined for this widget
|
||||
this.runAfterRuntimeInitialized(async () => {
|
||||
await this.eval(this.code);
|
||||
await this.eval(registrationCode);
|
||||
logger.debug('registered handlers');
|
||||
});
|
||||
|
||||
logger.debug('py-button connected');
|
||||
}
|
||||
return PyButton;
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import { BaseEvalElement } from './base';
|
||||
import { appConfig, addInitializer, runtimeLoaded } from '../stores';
|
||||
import type { AppConfig, Runtime } from '../runtime';
|
||||
import { version } from '../runtime';
|
||||
import { PyodideRuntime } from '../pyodide';
|
||||
import { getLogger } from '../logger';
|
||||
import { readTextFromPath, handleFetchError, mergeConfig, validateConfig, defaultConfig } from '../utils'
|
||||
|
||||
// Subscriber used to connect to the first available runtime (can be pyodide or others)
|
||||
let runtimeSpec: Runtime;
|
||||
runtimeLoaded.subscribe(value => {
|
||||
runtimeSpec = value;
|
||||
});
|
||||
|
||||
let appConfig_: AppConfig;
|
||||
appConfig.subscribe(value => {
|
||||
appConfig_ = value;
|
||||
});
|
||||
|
||||
const logger = getLogger('py-config');
|
||||
|
||||
/**
|
||||
* Configures general metadata about the PyScript application such
|
||||
* as a list of runtimes, name, version, closing the loader
|
||||
* automatically, etc.
|
||||
*
|
||||
* Also initializes the different runtimes passed. If no runtime is passed,
|
||||
* the default runtime based on Pyodide is used.
|
||||
*/
|
||||
|
||||
export class PyConfig extends BaseEvalElement {
|
||||
widths: Array<string>;
|
||||
label: string;
|
||||
mount_name: string;
|
||||
details: HTMLElement;
|
||||
operation: HTMLElement;
|
||||
values: AppConfig;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
extractFromSrc(configType: string) {
|
||||
if (this.hasAttribute('src'))
|
||||
{
|
||||
logger.info('config set from src attribute');
|
||||
return validateConfig(readTextFromPath(this.getAttribute('src')), configType);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
extractFromInline(configType: string) {
|
||||
if (this.innerHTML!=='')
|
||||
{
|
||||
this.code = this.innerHTML;
|
||||
this.innerHTML = '';
|
||||
logger.info('config set from inline');
|
||||
return validateConfig(this.code, configType);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
injectMetadata() {
|
||||
this.values.pyscript = {
|
||||
"version": version,
|
||||
"time": new Date().toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const configType: string = this.hasAttribute("type") ? this.getAttribute("type") : "toml";
|
||||
let srcConfig = this.extractFromSrc(configType);
|
||||
const inlineConfig = this.extractFromInline(configType);
|
||||
// first make config from src whole if it is partial
|
||||
srcConfig = mergeConfig(srcConfig, defaultConfig);
|
||||
// then merge inline config and config from src
|
||||
this.values = mergeConfig(inlineConfig, srcConfig);
|
||||
this.injectMetadata();
|
||||
|
||||
appConfig.set(this.values);
|
||||
logger.info('config set:', this.values);
|
||||
|
||||
addInitializer(this.loadPackages);
|
||||
addInitializer(this.loadPaths);
|
||||
this.loadRuntimes();
|
||||
}
|
||||
|
||||
log(msg: string) {
|
||||
const newLog = document.createElement('p');
|
||||
newLog.innerText = msg;
|
||||
this.details.appendChild(newLog);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.remove();
|
||||
}
|
||||
|
||||
loadPackages = async () => {
|
||||
const env = appConfig_.packages;
|
||||
logger.info("Loading env: ", env);
|
||||
await runtimeSpec.installPackage(env);
|
||||
}
|
||||
|
||||
loadPaths = async () => {
|
||||
const paths = appConfig_.paths;
|
||||
logger.info("Paths to load: ", paths)
|
||||
for (const singleFile of paths) {
|
||||
logger.info(` loading path: ${singleFile}`);
|
||||
try {
|
||||
await runtimeSpec.loadFromFile(singleFile);
|
||||
} catch (e) {
|
||||
//Should we still export full error contents to console?
|
||||
handleFetchError(<Error>e, singleFile);
|
||||
}
|
||||
}
|
||||
logger.info("All paths loaded");
|
||||
}
|
||||
|
||||
loadRuntimes() {
|
||||
logger.info('Initializing runtimes');
|
||||
for (const runtime of this.values.runtimes) {
|
||||
const runtimeObj: Runtime = new PyodideRuntime(runtime.src, runtime.name, runtime.lang);
|
||||
const script = document.createElement('script'); // create a script DOM node
|
||||
script.src = runtimeObj.src; // set its src to the provided URL
|
||||
script.addEventListener('load', () => {
|
||||
void runtimeObj.initialize();
|
||||
});
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import * as jsyaml from 'js-yaml';
|
||||
|
||||
import { runtimeLoaded, addInitializer } from '../stores';
|
||||
import { handleFetchError } from '../utils';
|
||||
import type { Runtime } from '../runtime';
|
||||
import { getLogger } from '../logger';
|
||||
|
||||
const logger = getLogger('py-env');
|
||||
|
||||
// Premise used to connect to the first available runtime (can be pyodide or others)
|
||||
let runtime: Runtime;
|
||||
|
||||
runtimeLoaded.subscribe(value => {
|
||||
runtime = value;
|
||||
});
|
||||
|
||||
export class PyEnv extends HTMLElement {
|
||||
shadow: ShadowRoot;
|
||||
wrapper: HTMLElement;
|
||||
code: string;
|
||||
environment: unknown;
|
||||
runtime: Runtime;
|
||||
env: string[];
|
||||
paths: string[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.shadow = this.attachShadow({ mode: 'open' });
|
||||
this.wrapper = document.createElement('slot');
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
logger.info("The <py-env> tag is deprecated, please use <py-config> instead.")
|
||||
this.code = this.innerHTML;
|
||||
this.innerHTML = '';
|
||||
|
||||
const env: string[] = [];
|
||||
const paths: string[] = [];
|
||||
|
||||
this.environment = jsyaml.load(this.code);
|
||||
if (this.environment === undefined) return;
|
||||
|
||||
for (const entry of Array.isArray(this.environment) ? this.environment : []) {
|
||||
if (typeof entry == 'string') {
|
||||
env.push(entry);
|
||||
} else if (entry && typeof entry === 'object') {
|
||||
const obj = <Record<string, unknown>>entry;
|
||||
for (const path of Array.isArray(obj.paths) ? obj.paths : []) {
|
||||
if (typeof path === 'string') {
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.env = env;
|
||||
this.paths = paths;
|
||||
|
||||
async function loadEnv() {
|
||||
logger.info("Loading env: ", env);
|
||||
await runtime.installPackage(env);
|
||||
}
|
||||
|
||||
async function loadPaths() {
|
||||
logger.info("Paths to load: ", paths)
|
||||
for (const singleFile of paths) {
|
||||
logger.info(` loading path: ${singleFile}`);
|
||||
try {
|
||||
await runtime.loadFromFile(singleFile);
|
||||
} catch (e) {
|
||||
//Should we still export full error contents to console?
|
||||
handleFetchError(<Error>e, singleFile);
|
||||
}
|
||||
}
|
||||
logger.info("All paths loaded");
|
||||
}
|
||||
|
||||
addInitializer(loadEnv);
|
||||
addInitializer(loadPaths);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,58 @@
|
||||
import { BaseEvalElement } from './base';
|
||||
import { addClasses, htmlDecode } from '../utils';
|
||||
import { getLogger } from '../logger'
|
||||
import { getAttribute, addClasses, htmlDecode, ensureUniqueId, createDeprecationWarning } from '../utils';
|
||||
import { getLogger } from '../logger';
|
||||
import type { Runtime } from '../runtime';
|
||||
|
||||
const logger = getLogger('py-inputbox');
|
||||
|
||||
export class PyInputBox extends BaseEvalElement {
|
||||
widths: Array<string>;
|
||||
label: string;
|
||||
mount_name: string;
|
||||
constructor() {
|
||||
super();
|
||||
export function make_PyInputBox(runtime: Runtime) {
|
||||
class PyInputBox extends HTMLElement {
|
||||
widths: string[] = [];
|
||||
label: string | undefined = undefined;
|
||||
mount_name: string | undefined = undefined;
|
||||
code: string;
|
||||
|
||||
if (this.hasAttribute('label')) {
|
||||
this.label = this.getAttribute('label');
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const label = getAttribute(this, 'label');
|
||||
if (label) {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const deprecationMessage =
|
||||
'The element <py-input> is deprecated, ' + 'use <input class="py-input"> instead.';
|
||||
createDeprecationWarning(deprecationMessage, 'py-input');
|
||||
ensureUniqueId(this);
|
||||
this.code = htmlDecode(this.innerHTML);
|
||||
this.mount_name = this.id.split('-').join('_');
|
||||
this.innerHTML = '';
|
||||
|
||||
const mainDiv = document.createElement('input');
|
||||
mainDiv.type = 'text';
|
||||
addClasses(mainDiv, ['py-input']);
|
||||
|
||||
mainDiv.id = this.id;
|
||||
this.id = `${this.id}-container`;
|
||||
this.appendChild(mainDiv);
|
||||
|
||||
// now that we appended and the element is attached, lets connect with the event handlers
|
||||
// defined for this widget
|
||||
this.appendChild(mainDiv);
|
||||
this.code = this.code.split('self').join(this.mount_name);
|
||||
let registrationCode = `from pyodide.ffi import create_proxy`;
|
||||
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
|
||||
if (this.code.includes('def on_keypress')) {
|
||||
this.code = this.code.replace('def on_keypress', `def on_keypress_${this.mount_name}`);
|
||||
registrationCode += `\n${this.mount_name}.element.addEventListener('keypress', create_proxy(on_keypress_${this.mount_name}))`;
|
||||
}
|
||||
|
||||
runtime.runButDontRaise(this.code);
|
||||
runtime.runButDontRaise(registrationCode);
|
||||
logger.debug('py-inputbox connected');
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.checkId();
|
||||
this.code = htmlDecode(this.innerHTML);
|
||||
this.mount_name = this.id.split('-').join('_');
|
||||
this.innerHTML = '';
|
||||
|
||||
const mainDiv = document.createElement('input');
|
||||
mainDiv.type = 'text';
|
||||
addClasses(mainDiv, ['py-input']);
|
||||
|
||||
mainDiv.id = this.id;
|
||||
this.id = `${this.id}-container`;
|
||||
this.appendChild(mainDiv);
|
||||
|
||||
// now that we appended and the element is attached, lets connect with the event handlers
|
||||
// defined for this widget
|
||||
this.appendChild(mainDiv);
|
||||
this.code = this.code.split('self').join(this.mount_name);
|
||||
let registrationCode = `from pyodide.ffi import create_proxy`;
|
||||
registrationCode += `\n${this.mount_name} = Element("${mainDiv.id}")`;
|
||||
if (this.code.includes('def on_keypress')) {
|
||||
this.code = this.code.replace('def on_keypress', `def on_keypress_${this.mount_name}`);
|
||||
registrationCode += `\n${this.mount_name}.element.addEventListener('keypress', create_proxy(on_keypress_${this.mount_name}))`;
|
||||
}
|
||||
|
||||
// TODO: For now we delay execution to allow pyodide to load but in the future this
|
||||
// should really wait for it to load..
|
||||
this.runAfterRuntimeInitialized(async () => {
|
||||
await this.eval(this.code);
|
||||
await this.eval(registrationCode);
|
||||
logger.debug('registered handlers');
|
||||
});
|
||||
}
|
||||
return PyInputBox;
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { BaseEvalElement } from './base';
|
||||
import { getLogger } from '../logger';
|
||||
|
||||
const logger = getLogger('py-loader');
|
||||
|
||||
export class PyLoader extends BaseEvalElement {
|
||||
widths: Array<string>;
|
||||
label: string;
|
||||
mount_name: string;
|
||||
details: HTMLElement;
|
||||
operation: HTMLElement;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.innerHTML = `<div id="pyscript_loading_splash" class="py-overlay">
|
||||
<div class="py-pop-up">
|
||||
<div class="smooth spinner"></div>
|
||||
<div id="pyscript-loading-label" class="label">
|
||||
<div id="pyscript-operation-details">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
this.mount_name = this.id.split('-').join('_');
|
||||
this.operation = document.getElementById('pyscript-operation');
|
||||
this.details = document.getElementById('pyscript-operation-details');
|
||||
}
|
||||
|
||||
log(msg: string) {
|
||||
// loader messages are showed both in the HTML and in the console
|
||||
logger.info(msg);
|
||||
const newLog = document.createElement('p');
|
||||
newLog.innerText = msg;
|
||||
this.details.appendChild(newLog);
|
||||
}
|
||||
|
||||
close() {
|
||||
logger.info('Closing');
|
||||
this.remove();
|
||||
}
|
||||
}
|
||||
@@ -1,201 +1,239 @@
|
||||
import { basicSetup, EditorState, EditorView } from '@codemirror/basic-setup';
|
||||
import { basicSetup, EditorView } from 'codemirror';
|
||||
import { python } from '@codemirror/lang-python';
|
||||
import { Compartment, StateCommand } from '@codemirror/state';
|
||||
import { indentUnit } from '@codemirror/language';
|
||||
import { Compartment } from '@codemirror/state';
|
||||
import { keymap } from '@codemirror/view';
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import { oneDarkTheme } from '@codemirror/theme-one-dark';
|
||||
import { addClasses, htmlDecode } from '../utils';
|
||||
import { BaseEvalElement } from './base';
|
||||
|
||||
import { getAttribute, ensureUniqueId, htmlDecode } from '../utils';
|
||||
import type { Runtime } from '../runtime';
|
||||
import { pyExec, pyDisplay } from '../pyexec';
|
||||
import { getLogger } from '../logger';
|
||||
|
||||
const logger = getLogger('py-repl');
|
||||
const RUNBUTTON = `<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>`;
|
||||
|
||||
export function make_PyRepl(runtime: Runtime) {
|
||||
/* High level structure of py-repl DOM, and the corresponding JS names.
|
||||
|
||||
function createCmdHandler(el: PyRepl): StateCommand {
|
||||
// Creates a codemirror cmd handler that calls the el.evaluate when an event
|
||||
// triggers that specific cmd
|
||||
return () => {
|
||||
void el.evaluate();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
this <py-repl>
|
||||
shadow #shadow-root
|
||||
<slot></slot>
|
||||
boxDiv <div class='py-repl-box'>
|
||||
editorLabel <label>...</label>
|
||||
editorDiv <div class="py-repl-editor"></div>
|
||||
outDiv <div class="py-repl-output"></div>
|
||||
</div>
|
||||
</py-repl>
|
||||
*/
|
||||
class PyRepl extends HTMLElement {
|
||||
shadow: ShadowRoot;
|
||||
outDiv: HTMLElement;
|
||||
editor: EditorView;
|
||||
|
||||
let initialTheme: string;
|
||||
function getEditorTheme(el: BaseEvalElement): string {
|
||||
return initialTheme || (initialTheme = el.getAttribute('theme'));
|
||||
}
|
||||
|
||||
export class PyRepl extends BaseEvalElement {
|
||||
editor: EditorView;
|
||||
editorNode: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// add an extra div where we can attach the codemirror editor
|
||||
this.editorNode = document.createElement('div');
|
||||
addClasses(this.editorNode, ['editor-box']);
|
||||
this.shadow.appendChild(this.wrapper);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.checkId();
|
||||
this.code = htmlDecode(this.innerHTML);
|
||||
this.innerHTML = '';
|
||||
const languageConf = new Compartment();
|
||||
|
||||
const extensions = [
|
||||
basicSetup,
|
||||
languageConf.of(python()),
|
||||
keymap.of([
|
||||
...defaultKeymap,
|
||||
{ key: 'Ctrl-Enter', run: createCmdHandler(this) },
|
||||
{ key: 'Shift-Enter', run: createCmdHandler(this) },
|
||||
]),
|
||||
];
|
||||
|
||||
if (getEditorTheme(this) === 'dark') {
|
||||
extensions.push(oneDarkTheme);
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
this.editor = new EditorView({
|
||||
state: EditorState.create({
|
||||
doc: this.code.trim(),
|
||||
connectedCallback() {
|
||||
ensureUniqueId(this);
|
||||
this.shadow = this.attachShadow({ mode: 'open' });
|
||||
const slot = document.createElement('slot');
|
||||
this.shadow.appendChild(slot);
|
||||
|
||||
if (!this.hasAttribute('exec-id')) {
|
||||
this.setAttribute('exec-id', '1');
|
||||
}
|
||||
if (!this.hasAttribute('root')) {
|
||||
this.setAttribute('root', this.id);
|
||||
}
|
||||
|
||||
const pySrc = htmlDecode(this.innerHTML).trim();
|
||||
this.innerHTML = '';
|
||||
this.editor = this.makeEditor(pySrc);
|
||||
const boxDiv = this.makeBoxDiv();
|
||||
this.appendChild(boxDiv);
|
||||
this.editor.focus();
|
||||
logger.debug(`element ${this.id} successfully connected`);
|
||||
}
|
||||
|
||||
/** Create and configure the codemirror editor
|
||||
*/
|
||||
makeEditor(pySrc: string): EditorView {
|
||||
const languageConf = new Compartment();
|
||||
const extensions = [
|
||||
indentUnit.of(' '),
|
||||
basicSetup,
|
||||
languageConf.of(python()),
|
||||
keymap.of([
|
||||
...defaultKeymap,
|
||||
{ key: 'Ctrl-Enter', run: this.execute.bind(this) },
|
||||
{ key: 'Shift-Enter', run: this.execute.bind(this) },
|
||||
]),
|
||||
];
|
||||
|
||||
if (getAttribute(this, 'theme') === 'dark') {
|
||||
extensions.push(oneDarkTheme);
|
||||
}
|
||||
|
||||
return new EditorView({
|
||||
doc: pySrc,
|
||||
extensions,
|
||||
}),
|
||||
parent: this.editorNode,
|
||||
});
|
||||
|
||||
const mainDiv = document.createElement('div');
|
||||
addClasses(mainDiv, ['py-repl-box']);
|
||||
|
||||
// Styles that we use to hide the labels whilst also keeping it accessible for screen readers
|
||||
const labelStyle = 'overflow:hidden; display:block; width:1px; height:1px';
|
||||
|
||||
// Code editor Label
|
||||
this.editorNode.id = 'code-editor';
|
||||
const editorLabel = document.createElement('label');
|
||||
editorLabel.innerHTML = 'Python Script Area';
|
||||
editorLabel.setAttribute('style', labelStyle);
|
||||
editorLabel.htmlFor = 'code-editor';
|
||||
|
||||
mainDiv.append(editorLabel);
|
||||
|
||||
// add Editor to main PyScript div
|
||||
mainDiv.appendChild(this.editorNode);
|
||||
|
||||
// Play Button
|
||||
this.btnRun = document.createElement('button');
|
||||
this.btnRun.id = 'btnRun';
|
||||
this.btnRun.innerHTML =
|
||||
'<svg id="" class="svelte-fa svelte-ps5qeg" 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>';
|
||||
addClasses(this.btnRun, ['absolute', 'repl-play-button']);
|
||||
|
||||
// Play Button Label
|
||||
const btnLabel = document.createElement('label');
|
||||
btnLabel.innerHTML = 'Python Script Run Button';
|
||||
btnLabel.setAttribute('style', labelStyle);
|
||||
btnLabel.htmlFor = 'btnRun';
|
||||
|
||||
this.editorNode.appendChild(btnLabel);
|
||||
this.editorNode.appendChild(this.btnRun);
|
||||
|
||||
this.btnRun.addEventListener('click', () => {
|
||||
void this.evaluate();
|
||||
});
|
||||
|
||||
if (!this.id) {
|
||||
logger.warn(
|
||||
"WARNING: <py-repl> defined without an id. <py-repl> should always have an id, otherwise multiple <py-repl> in the same page will not work!"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.hasAttribute('exec-id')) {
|
||||
this.setAttribute('exec-id', '1');
|
||||
// ******** main entry point for py-repl DOM building **********
|
||||
//
|
||||
// The following functions are written in a top-down, depth-first
|
||||
// order (so that the order of code roughly matches the order of
|
||||
// execution)
|
||||
makeBoxDiv(): HTMLElement {
|
||||
const boxDiv = document.createElement('div');
|
||||
boxDiv.className = 'py-repl-box';
|
||||
|
||||
const editorDiv = this.makeEditorDiv();
|
||||
const editorLabel = this.makeLabel('Python Script Area', editorDiv);
|
||||
this.outDiv = this.makeOutDiv();
|
||||
|
||||
boxDiv.append(editorLabel);
|
||||
boxDiv.appendChild(editorDiv);
|
||||
boxDiv.appendChild(this.outDiv);
|
||||
|
||||
return boxDiv;
|
||||
}
|
||||
|
||||
if (!this.hasAttribute('root')) {
|
||||
this.setAttribute('root', this.id);
|
||||
makeEditorDiv(): HTMLElement {
|
||||
const editorDiv = document.createElement('div');
|
||||
editorDiv.id = 'code-editor';
|
||||
editorDiv.className = 'py-repl-editor';
|
||||
editorDiv.appendChild(this.editor.dom);
|
||||
|
||||
const runButton = this.makeRunButton();
|
||||
const runLabel = this.makeLabel('Python Script Run Button', runButton);
|
||||
editorDiv.appendChild(runLabel);
|
||||
editorDiv.appendChild(runButton);
|
||||
|
||||
return editorDiv;
|
||||
}
|
||||
|
||||
if (this.hasAttribute('output')) {
|
||||
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
|
||||
} else {
|
||||
if (this.hasAttribute('std-out')) {
|
||||
this.outputElement = document.getElementById(this.getAttribute('std-out'));
|
||||
} else {
|
||||
// In this case neither output or std-out have been provided so we need
|
||||
// to create a new output div to output to
|
||||
this.outputElement = document.createElement('div');
|
||||
this.outputElement.classList.add('output');
|
||||
this.outputElement.hidden = true;
|
||||
this.outputElement.id = this.id + '-' + this.getAttribute('exec-id');
|
||||
makeLabel(text: string, elementFor: HTMLElement): HTMLElement {
|
||||
ensureUniqueId(elementFor);
|
||||
const lbl = document.createElement('label');
|
||||
lbl.innerHTML = text;
|
||||
lbl.htmlFor = elementFor.id;
|
||||
// XXX this should be a CSS class
|
||||
// Styles that we use to hide the labels whilst also keeping it accessible for screen readers
|
||||
const labelStyle = 'overflow:hidden; display:block; width:1px; height:1px';
|
||||
lbl.setAttribute('style', labelStyle);
|
||||
return lbl;
|
||||
}
|
||||
|
||||
// add the output div id if there's not output pre-defined
|
||||
mainDiv.appendChild(this.outputElement);
|
||||
makeRunButton(): HTMLElement {
|
||||
const runButton = document.createElement('button');
|
||||
runButton.id = 'runButton';
|
||||
runButton.className = 'absolute py-repl-run-button';
|
||||
runButton.innerHTML = RUNBUTTON;
|
||||
runButton.addEventListener('click', this.execute.bind(this));
|
||||
return runButton;
|
||||
}
|
||||
|
||||
makeOutDiv(): HTMLElement {
|
||||
const outDiv = document.createElement('div');
|
||||
outDiv.className = 'py-repl-output';
|
||||
outDiv.id = this.id + '-' + this.getAttribute('exec-id');
|
||||
return outDiv;
|
||||
}
|
||||
|
||||
// ********************* execution logic *********************
|
||||
|
||||
/** Execute the python code written in the editor, and automatically
|
||||
* display() the last evaluated expression
|
||||
*/
|
||||
execute(): void {
|
||||
const pySrc = this.getPySrc();
|
||||
|
||||
// determine the output element
|
||||
const outEl = this.getOutputElement();
|
||||
if (outEl === undefined) {
|
||||
// this happens if we specified output="..." but we couldn't
|
||||
// find the ID. We already displayed an error message inside
|
||||
// getOutputElement, stop the execution.
|
||||
return;
|
||||
}
|
||||
|
||||
this.errorElement = this.hasAttribute('std-err')
|
||||
? document.getElementById(this.getAttribute('std-err'))
|
||||
: this.outputElement;
|
||||
}
|
||||
// clear the old output before executing the new code
|
||||
outEl.innerHTML = '';
|
||||
|
||||
this.appendChild(mainDiv);
|
||||
this.editor.focus();
|
||||
logger.debug(`element ${this.id} successfully connected`);
|
||||
}
|
||||
// execute the python code
|
||||
const pyResult = pyExec(runtime, pySrc, outEl);
|
||||
|
||||
addToOutput(s: string): void {
|
||||
this.outputElement.innerHTML += '<div>' + s + '</div>';
|
||||
this.outputElement.hidden = false;
|
||||
}
|
||||
|
||||
preEvaluate(): void {
|
||||
this.setOutputMode("replace");
|
||||
if(!this.appendOutput) {
|
||||
this.outputElement.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
postEvaluate(): void {
|
||||
this.outputElement.hidden = false;
|
||||
this.outputElement.style.display = 'block';
|
||||
|
||||
if (this.hasAttribute('auto-generate')) {
|
||||
const allPyRepls = document.querySelectorAll(`py-repl[root='${this.getAttribute('root')}'][exec-id]`);
|
||||
const lastRepl = allPyRepls[allPyRepls.length - 1];
|
||||
const lastExecId = lastRepl.getAttribute('exec-id');
|
||||
const nextExecId = parseInt(lastExecId) + 1;
|
||||
|
||||
const newPyRepl = document.createElement('py-repl');
|
||||
newPyRepl.setAttribute('root', this.getAttribute('root'));
|
||||
newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString();
|
||||
|
||||
if(this.hasAttribute('auto-generate')) {
|
||||
newPyRepl.setAttribute('auto-generate', '');
|
||||
this.removeAttribute('auto-generate');
|
||||
// display the value of the last evaluated expression (REPL-style)
|
||||
if (pyResult !== undefined) {
|
||||
pyDisplay(runtime, pyResult, { target: outEl.id });
|
||||
}
|
||||
|
||||
if(this.hasAttribute('output-mode')) {
|
||||
newPyRepl.setAttribute('output-mode', this.getAttribute('output-mode'));
|
||||
}
|
||||
this.autogenerateMaybe();
|
||||
}
|
||||
|
||||
const addReplAttribute = (attribute: string) => {
|
||||
if (this.hasAttribute(attribute)) {
|
||||
newPyRepl.setAttribute(attribute, this.getAttribute(attribute));
|
||||
getPySrc(): string {
|
||||
return this.editor.state.doc.toString();
|
||||
}
|
||||
|
||||
getOutputElement(): HTMLElement {
|
||||
const outputID = getAttribute(this, 'output');
|
||||
if (outputID !== null) {
|
||||
const el = document.getElementById(outputID);
|
||||
if (el === null) {
|
||||
const err = `py-repl ERROR: cannot find the output element #${outputID} in the DOM`;
|
||||
this.outDiv.innerText = err;
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
return el;
|
||||
} else {
|
||||
return this.outDiv;
|
||||
}
|
||||
}
|
||||
|
||||
addReplAttribute('output');
|
||||
addReplAttribute('std-out');
|
||||
addReplAttribute('std-err');
|
||||
// XXX the autogenerate logic is very messy. We should redo it, and it
|
||||
// should be the default.
|
||||
autogenerateMaybe(): void {
|
||||
if (this.hasAttribute('auto-generate')) {
|
||||
const allPyRepls = document.querySelectorAll(`py-repl[root='${this.getAttribute('root')}'][exec-id]`);
|
||||
const lastRepl = allPyRepls[allPyRepls.length - 1];
|
||||
const lastExecId = lastRepl.getAttribute('exec-id');
|
||||
const nextExecId = parseInt(lastExecId) + 1;
|
||||
|
||||
newPyRepl.setAttribute('exec-id', nextExecId.toString());
|
||||
this.parentElement.appendChild(newPyRepl);
|
||||
const newPyRepl = document.createElement('py-repl');
|
||||
newPyRepl.setAttribute('root', this.getAttribute('root'));
|
||||
newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString();
|
||||
|
||||
if (this.hasAttribute('auto-generate')) {
|
||||
newPyRepl.setAttribute('auto-generate', '');
|
||||
this.removeAttribute('auto-generate');
|
||||
}
|
||||
|
||||
const outputMode = getAttribute(this, 'output-mode');
|
||||
if (outputMode) {
|
||||
newPyRepl.setAttribute('output-mode', outputMode);
|
||||
}
|
||||
|
||||
const addReplAttribute = (attribute: string) => {
|
||||
const attr = getAttribute(this, attribute);
|
||||
if (attr) {
|
||||
newPyRepl.setAttribute(attribute, attr);
|
||||
}
|
||||
};
|
||||
|
||||
addReplAttribute('output');
|
||||
|
||||
newPyRepl.setAttribute('exec-id', nextExecId.toString());
|
||||
if (this.parentElement) {
|
||||
this.parentElement.appendChild(newPyRepl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSourceFromElement(): string {
|
||||
return this.editor.state.doc.toString();
|
||||
}
|
||||
return PyRepl;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user