Compare commits

...

182 Commits

Author SHA1 Message Date
Ted Patrick
848d77b1c2 Same steps as build-unstable (#1550)
* Same steps as build-unstable

* make setup
2023-06-20 13:01:56 -05:00
Ted Patrick
e4e8f2edae Fix build unstable to run on ubuntu-latest-8core (#1549)
* Fix build unstable to run on ubuntu-latest-8core

* ubuntu-latest to ubuntu-latest-8core
2023-06-20 12:17:08 -05:00
Jeff Glass
ea9bdcc961 Fix previous changlog date (#1546)
* Previous version updated from 2023.01.1 to 2023.03.1
2023-06-20 08:08:24 -05:00
Hood Chatham
79ad39260e Fix lifetime for pyExec results (#1540)
Currently if the result from pyExec is a PyProxy, it gets destroyed.
This switches to using `to_js` to handle this (it is better to use
than an explicit `create_proxy` since it automatically decides whether
to create a proxy or not).

I also added `destroyIfProxy` which checks if something is a `PyProxy`
and then destroys it. Each use of `pyExec` needs to call `destroyIfProxy`
on the result after it is done with it.
2023-06-15 11:51:36 -07:00
Jeff Glass
f4936316ab Make getPySrc() a Documented Feature (#1516)
* Add documentation and tests (no real code changes)

* Add Changelog
2023-06-14 12:56:57 -05:00
Madhur Tandon
8879187e6a fix access to interpreter globals without awaits (#1529) 2023-06-14 19:42:38 +05:30
Fábio Rosado
258b80a6a5 Add step to install next deps and run test (#1528) 2023-06-14 14:12:42 +01:00
Fábio Rosado
a108e6e97e Add workflow to run next CI (#1527)
* Add workflow to run next CI

* Don't actually replace the whole unstable

* Unstable name

* Add empty line

* Make CI run on changes to workflow build-unstable-next

* Rename workflow for test-next instead of build

* Update path to use the next test-next

* Add pyscript.core to path
2023-06-13 15:06:19 +01:00
Peter W
dfef7eda3b Use 8-core, 32GB runners for testing and faster builds (#1524)
* swap 3 jobs to 8 core runners

* 16 core test

* test 4 core

* back to 8 core
2023-06-12 15:25:58 -05:00
Neon22
4467898473 Update py-click.md (#1499)
Add links to other events so its clear py-click is not a limitation or umbrella term for the only kind of interaction.
2023-06-05 20:42:42 +05:30
Madhur Tandon
17d16b987f kill unwrapped_remote (#1490)
* kill unwrapped_remote

* linting

* don't use callKwargs for python plugins

* fix tests and improve types
2023-06-01 22:52:23 +05:30
Ted Patrick
8e86daac71 Year 2023 (#1496) 2023-05-31 17:13:13 -05:00
Ted Patrick
856720da49 Simple 404 in _static (#1495)
* Simple 404 in _static

* Docs 404 error

* s3_error.html
2023-05-31 16:42:30 -05:00
Ted Patrick
8f2c150d1e Docs correction (#1494) 2023-05-31 16:19:36 -05:00
Ted Patrick
7d8b4c980a Error html to root of docs (#1493)
* Error html to root of docs

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-05-31 16:10:32 -05:00
Jeff Glass
932756c7a0 Add Option to make Py-Terminal and Xterm.js (#1317)
* Add 'xterm' attribute in py-config using new validation

* Use screen reader mode

* Add `xtermReady` promise to allow users to away xterm.js init

* Guard against initializing a tag twice

* Add tests and doc
2023-05-29 10:00:20 -05:00
Andrea Giammarchi
538aac9a28 Fix #1482 - Add utils.py to the list of modules (#1485) 2023-05-25 10:39:07 +02:00
Andrea Giammarchi
856bf8f5fb Fix #1474 - await both JS and Python plugins all at once (#1481) 2023-05-24 18:59:23 +02:00
Jeff Glass
e1758ae2e2 Upgrade to Pyodide 0.23 (#1347)
* Upgrade to Pyodide 0.23.2

* Update changelog

* Use @param decorator to fix kmeans examlpe

* Separate zz_example tests to run sequentially

* Remove pytest.raises from pyscript_src_not_found test, use check_js_errors instead

* Add 'check_js_errors' to wait_for_pyscript
2023-05-24 07:59:19 -05:00
Andrea Giammarchi
61b3154461 [chore] Improve current Error extends (#1467) 2023-05-22 13:22:53 +02:00
Jeff Glass
fb9b30d144 Fix zz_examples tests (pin dependencies) (#1477) 2023-05-18 19:27:47 -05:00
StefanoHiway
b0df96b13f html tag not closed (#1473) 2023-05-17 16:16:35 +05:30
Andrea Giammarchi
a469062a32 Simplified classList within Python code (#1459) 2023-05-15 12:45:11 +02:00
Andrea Giammarchi
89d5d5c7db Fix svg errors on the page caused by <py-script> (#1464) 2023-05-15 12:44:19 +02:00
Madhur Tandon
b8c2d6b05d fix panel kmeans test (#1465) 2023-05-15 15:24:08 +05:30
Madhur Tandon
b247864414 remove PyWidget and py-register-widget + refactor PyList as a Python Plugin (#1452)
* remove PyWidget and py-register-widget

* refactor py-list as Plugin

* add newline

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix eslint

* handle case if src not supplied

* move inside if

* - Remove src attribute for py-list
- Re-implement as a Python plugin
- Remove pylist.py from examples directory
- Remove PyListPlugin as one of the default ones

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* move PyItem and PyList classes to the plugin

* clean up PyListTemplate and PyItemTemplate from pyscript module

* fix linting

* use PyList instead of PyListTemplate instead

* fix example for todo-pylist

* re-enable and improve test

* move py-list plugin to examples

* fix py-list plugin link

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-05-10 20:17:07 +05:30
Andrea Giammarchi
d3bcd87cfa Allow nodes in shadow roots to be addressed via Element (#1454) 2023-05-09 17:42:09 +02:00
Antonio Cuni
82e5b64bad Make sure that tests fail in case there is an unhandled Python error (#1456)
Before this PR, the following test passed:

    def test_pyscript_hello(self):
        self.pyscript_run(
            """
            <script type="py">
                raise Exception("hello")
            </script>
            """)

What happens is that we intercept the Python exception and display a nice banner on the DOM, but the test itself passes. This is error prone: if we have Python exceptions on the page, the test should fail by default, and we should have a way to silence it in case those exceptions are expected.

This PR treats Python errors as we treat JS errors: unhandled exceptions cause the test to fail, but you can silence them by calling self.check_py_errors(), exactly as you can call self.check_js_errors().
2023-05-09 15:39:19 +02:00
Andrea Giammarchi
73e0271c23 Fix typo in py-events attributes handling (#1458) 2023-05-09 14:29:00 +02:00
Andrea Giammarchi
a2dabee0e9 Fix element.select tests (#1457) 2023-05-09 13:12:29 +02:00
Andrea Giammarchi
6a27c6d9f2 Cleanup some unnecessary utility (#1453) 2023-05-06 08:20:08 +02:00
woxtu
213ced0c7f Fix an error message when loading local modules failed (#1394)
* Fix an error message when loading local modules failed

* Fix an error message when loading local modules failed
2023-05-05 18:17:35 +05:30
Andrea Giammarchi
5086c23d47 Fix #1445 - Move the EditorView into ShadowDOM (#1449) 2023-05-05 10:41:05 +02:00
Ted Patrick
ee345a5206 Add Andrea Giammarchi as PyScript Maintainer (#1450) 2023-05-04 19:27:50 -05:00
Andrea Giammarchi
f74cddc3b1 Fix #1446 - Move pyscript defer after other dependencies (#1448) 2023-05-04 12:42:35 +02:00
Andrea Giammarchi
5b986b8b26 Fix #1425 - Move scripts within the tutor to make it explicit more files are needed (#1444) 2023-05-03 13:12:06 +02:00
Andrea Giammarchi
14887b9814 Fix #1341 - Use <script type="py"> instead to avoid entities (#1443) 2023-05-03 13:11:27 +02:00
Andrea Giammarchi
ecc40315b3 Fix #1427 - Avoid multiple initialization of the same node (#1433) 2023-05-03 10:00:35 +02:00
Andrea Giammarchi
e7aed7fcf0 Fix #1059 - Observe py-* attributes changes (#1435) 2023-05-03 09:50:21 +02:00
Madhur Tandon
cd1aa948f9 [Worker support] test for no cors headers (#1374)
* test for no cors headers

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix tests

* suggested changes

* disable directly

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* add error message

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* improve test

* improve error message

* remove py-config tag from cors test

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-05-02 18:43:11 +05:30
Andrea Giammarchi
82613d016a Fix #1429 - Use basic-devtools module (#1430)
This MR brings in `$`, `$$`, and `$x` browsers devtools' utilities to our code so we can use these whenever we find it convenient.
2023-05-02 15:12:28 +02:00
Jeff Glass
3a66be585f Add @when decorator (#1428)
* Add new _event_handling.py file with @when decorator

* @when decorate is in pyscript package namespace/_all__

* Write tests in new test_event_handling.py

* Add docs for @when decorator

------------

Co-authored-by: Mariana Meireles <marian.meireles@gmail.com>
2023-05-01 09:51:49 -05:00
Andrea Giammarchi
0a4e36ae09 Fix <script src> + test all py-script attributes (#1434) 2023-04-28 18:08:53 +02:00
Andrea Giammarchi
92643539cf Fix #735 - Test <script type="py"> against all special cases (#1431) 2023-04-27 17:46:42 +02:00
Andrea Giammarchi
a1281d1331 Fix #1326 - Allow <script type="py"> tag to work as <py-script> (#1396)
The goal of this MR is to unobtrusively allow the usage of `<script type="py">`, `<script type="pyscript">` or `<script type="py-script">` tags instead of `<py-script>` for all those case where the layout in custom elements get parsed and breaks users' expectations (including our SVG based tests).
2023-04-27 15:21:31 +02:00
Cameron Cairns
074ca0ef8f Adds missing dependency description (anaconda) to docs (#1409) 2023-04-27 12:06:31 +01:00
Fábio Rosado
464a9633dc Fix what is pyscript example h1 color (#1417) 2023-04-26 15:31:01 +01:00
Andrea Giammarchi
fc2d91c5bb Fix #801 - Simplified events listeners attachment: (#1403)
* always same listener, for easy removal and reduce RAM
  * avoid duplicated entries for smaller library outcome
  * use XPath to crawl all attributes names instead of CSS
2023-04-26 15:55:22 +02:00
MrValdez
d68169bffb Development documentation (#1410)
* Updated the instructions for setting up the documentation environment to be clearer

* Expanded on setting up the development and documentation environments.

Moved the documentation section to its own section as it's behavior and purpose is different from the development environment.

* Added alternative to git upstream

Indented list instead of using &nbsp;

* Add 'Using a Local Dev Server' to Getting Started Docs (#1400)

* Added alternative to git upstream

Indented list instead of using &nbsp;

Updated the test environment section

cleaned up "reviewing your work" section to make it easier to read.

* added "setting up environment" changes to changelog

* reverted additions to changelog

---------

Co-authored-by: Jeff Glass <glass.jeffrey@gmail.com>
2023-04-26 12:28:43 +01:00
Andrea Giammarchi
7efdb04e1e Increase commonly failing tests timeout (#1412) 2023-04-26 10:01:20 +02:00
Jeff Glass
0155e122fd Add nodejs=16 to docs conda env (#1413) 2023-04-25 11:36:27 -05:00
Jeff Glass
eb03f16a77 Improve PR template - Doc Changes Don't Need Changelogs(#1411) 2023-04-25 10:03:12 -05:00
Jeff Glass
5ac39641ab Add 'Using a Local Dev Server' to Getting Started Docs (#1400) 2023-04-25 08:17:57 -05:00
Ikko Eltociear Ashimine
8d1e48e400 Fix typo in py-config.md (#1399)
unspecifed -> unspecified
2023-04-21 10:01:59 -06:00
Andrea Giammarchi
0021ccb49f Remove redundant .shadow property as that is defined at the Custom Element level. (#1395) 2023-04-21 10:05:50 +02:00
woxtu
8590c7e5b8 Remove an unused dependency (#1390) 2023-04-16 14:23:52 +05:30
Antonio Cuni
8c5475f78f Move pyodide to a web worker (#1333)
This PR adds support for optionally running pyodide in a web worker:

- add a new option config.execution_thread, which can be `main` or `worker`. The default is `main`

- improve the test machinery so that we run all tests twice, once for `main` and once for `worker`

- add a new esbuild target which builds the code for the worker

The support for workers is not complete and many features are still missing: there are 71 tests which are marked as `@skip_worker`, but we can fix them in subsequent PRs.

The vast majority of tests fail because js.document is unavailable: for it to run transparently, we need the "auto-syncify" feature of synclink.


Co-authored-by: Hood Chatham <roberthoodchatham@gmail.com>
Co-authored-by: Madhur Tandon <20173739+madhur-tandon@users.noreply.github.com>
2023-04-14 10:55:31 +02:00
Jeff Glass
dfa116eb70 Improve validate() function for plugin options (#1323)
* Add `validateConfigParameter` and `validateConfigParameterFromArray` functions to validate user-provided parameters from py-config

* Add units tests for `validateConfigParameter` and `validateConfigParameterFromArray`

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-13 14:35:01 -05:00
Mike Chen
3a9fd3c074 Fix path errors on Windows systems (#1368)
* Add docs to repl with attr src

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix path errors on Windows systems

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix path errors on Windows systems

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-13 20:51:49 +05:30
Stefane Fermigier
5a92ef3c11 doc: remove duplicated link (#1386)
"How to write content to the page" appeared twice in the index.
2023-04-13 11:00:28 +05:30
woxtu
d3902f5c93 Update to the latest TypeScript (#1377) 2023-04-12 23:49:54 +05:30
Hood Chatham
c886f887ae Split pyscript into multiple files (#1338)
In the future this should help us leak fewer names into the pyscript
namespace.

Rather than assigning to the pyscript module from JavaScript, we
mount a separate private JS module with the extra names needed by
PyScript. I moved a bit more interpeter intialization into
remote_interpreter.

I added a deprecation warning for `pyscript.js`: the proper way to
access `js` is `import js`.

---------

Co-authored-by: Antonio Cuni <anto.cuni@gmail.com>
2023-04-12 14:49:47 +02:00
Hood Chatham
fc5089ac59 same thread syncify (#1372)
Switch to using the new version of synclink with support for same thread syncify (and also correct types). 
Uses syncify to replace one use of `_unwrapped_remote`.
2023-04-11 21:31:05 -07:00
Madhur Tandon
e3602f464b remove pys-on* and py-on* attributes (#1361)
* remove pys-on* and py-on* attributes

* update changelog

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix eslint

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-11 18:59:42 +05:30
Madhur Tandon
f3db6a339c remove PY_COMPLETE and the associated message of Python initialization complete (#1373)
* remove PY_COMPLETE

* fix test_multiple_async
2023-04-11 18:59:09 +05:30
cnelir98
c05195c045 DOCS Update display.md (#1375) 2023-04-10 18:44:08 +02:00
Antonio Cuni
af981fc719 Improve self.wait_for_console() (#1363)
- Previously, if the message appeared on the console immediately before the call to self.wait_for_console(), the call would hang forever until the timeout. Now, it returns immediately.

- `wait_for_pyscript` now logs the time actually taken for waiting

- `wait_for_pyscript` takes a `timeout` argument so that we can tweak it on a test-by-test basis
2023-04-05 16:00:17 +02:00
Mike Chen
088a264910 Add docs to repl with attr src (#1353)
* Add docs to repl with attr src

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-03 14:19:23 +02:00
Mike Chen
d7e80ad51b load code from the attr src of py-repl (#1292)
* load code from the attr src of py-repl

* load code from the attr src of py-repl

* load code from the attr src of py-repl

* load code from the attr src of py-repl

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-03 11:06:30 +02:00
Antonio Cuni
b53ddd401f re-enable source maps (#1340) 2023-03-31 16:42:19 +02:00
Hood Chatham
e9122bca9d Fix test_async and test_stdio_handling (#1319)
Resolves pyscript#1313 and pyscript#1314. On top of pyscript#1318.

The point of these tests is to define the execution order of Tasks
that are scheduled in <py-script> tags: first all the py-script tags
are executed and their related lifecycle events. Once all of this
is done, we schedule any enqueued tasks.

To delay the execution of these tasks, we use a custom event loop for
pyExec with this defer behavior. Until schedule_deferred_tasks is called,
we defer tasks started by user code. schedule_deferred_tasks starts all 
deferred user tasks and switches to immediately scheduling any further
user tasks.
2023-03-30 14:38:51 -07:00
Hood Chatham
b61e8435d1 Fix main.test.ts (#1320) 2023-03-30 10:52:39 -07:00
Hood Chatham
146afb6532 Remove all but 2 eslint-disable pragmas (#1335)
Turns off:
 - no-explicit-any (we need to handle any return values from runPython)
 - no-unsafe-assignment (noisy and pointless)

And resolves all but two remaining ones. The last two lints regard access to private Pyodide variables and so:
 - they are not easy to work around without an upstream Pyodide patch
 - they should trigger linter and require explicit override
2023-03-30 10:51:36 -07:00
Hood Chatham
854e9d1378 Refactor pyexec (#1318)
This is some refactoring I did on the way towards resolving pyscript#1313.
I added a new _run_pyscript Python function which executes the code
inside a context manager that sets the display target. We can then
return a JS object wrapper directly from Python.

I moved the "installation" of the pyscript module to loadInterpreter,
and pyimport pyscript_py there and give it a type. This avoids a bunch
of creating and deleting of proxies for pyscript_py and allows us to
give it a type once and for all.

I also did some minor logic cleanup in a few places.
2023-03-30 04:34:24 +02:00
Jeff Glass
689878ce32 Remove AWS upload (#1331)
Deleted final 2 steps of docs-review to un-break CI
2023-03-29 13:22:21 -05:00
Jeff Glass
d7ab177cc5 Deprecate py-mount Attribute (#1330)
* Deprecate py-mount attribute, with comments as to when it was deprecated

* Add changelog entry for deprecation

* Fix 'unused' examples that used py-mount (handtrack and mario)
2023-03-29 10:49:31 -05:00
aneesh98
f4c6093c47 Docs: make tests should be make test (#1332) 2023-03-29 07:33:32 -07:00
Hood Chatham
9fedfe3699 Use Promise.all to fetch files part of py-config (#1322)
This is a first step towards loading more stuff simultaneously rather
than sequentially.

The functional part of this is pretty small: call `calculateFetchPaths` and
then `Promise.all(fetchPaths.map(loadFileFromURL));`. I also transposed the
return type of `calculateFetchPaths` since it's more convenient to consume
this way.

I redid the logic in `calculateFetchPaths` a bit. I renamed `src/plugins/fetch.ts`
to `calculateFetchPaths.ts` since the file performs no fetching. I also
renamed `loadFromFile` to `loadFileFromURL`.
2023-03-29 07:32:09 -07:00
Hood Chatham
26f07246e1 Allow pyscript package to contain multiple files (#1309)
Followup to pyscript#1232. Closes pyscript#1226.

Use node to make a manifest of the src/python dir and then use an esbuild
plugin to resolve an import called `pyscript_python_package.esbuild_injected.json`
to an object indicating the directories and files in the package folder.
This object is then used to govern runtime installation of the package.
2023-03-29 07:31:14 -07:00
Antonio Cuni
3ae4b3c4de use a dynamic import for loading pyodide. This greatly simplifies the logic around interpreter loading and handling of UserError (#1306) 2023-03-27 18:46:50 +02:00
Madhur Tandon
c8f9f16791 synclink integration (#1258)
synclink integration + fixes for `py-repl` related tests and `display` tests
2023-03-27 20:56:31 +05:30
Antonio Cuni
88f0738500 re-enable blank issues
They were disabled by PR #1157 but without any discussion or consensus, so I guess it was a mistake. Personally I found them very useful and AFAIK we never had a problem of people abusing them, so I don't see why they should be disabled  (#1311)
2023-03-27 17:25:28 +02:00
Jeff Glass
03c79d5f2f Make repl hooks optional (#1310)
`beforeByReplExec()` and `afterPyReplExec()` only called if they exist on a plugin
2023-03-27 09:20:58 -05:00
zipperer
e7c3b7bcfe Update getting-started.md (#1307)
1. replace 'other then' with 'other than'
2. replace 'cherrie' with 'cherry'
2023-03-25 20:16:40 -05:00
Andrea Giammarchi
c8becca044 Slightly imporved pyrepl (#1296)
* removed unnecessary getAttribute
  * removed unnecessary shadow and ShadowDOM in general, as it was never used
  * dropped redundant constructor
  * removed unnecessary usage of the label element
  * fixed redundant always-same buttons IDs
2023-03-24 11:42:45 +01:00
Mariana Meireles
543a27271f Add docs for py-event* (#1300)
* Fix markdown
Add event-handlers.md

* Address changes from Jeff + Antonion and add it to index

* how tos don't exist anymore theyre now guides

* Restore p on contributing

* Adding changelog

* Aadd space

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: Carmelo <carmelofiorello@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-23 17:57:49 +01:00
Fábio Rosado
a62aba83a0 Update changelog date (#1289) 2023-03-23 11:20:20 +00:00
pre-commit-ci[bot]
53c6cf5f45 [pre-commit.ci] pre-commit autoupdate (#1281)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/charliermarsh/ruff-pre-commit: v0.0.254 → v0.0.257](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.254...v0.0.257)
- [github.com/codespell-project/codespell: v2.2.2 → v2.2.4](https://github.com/codespell-project/codespell/compare/v2.2.2...v2.2.4)
- [github.com/pre-commit/mirrors-eslint: v8.35.0 → v8.36.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.35.0...v8.36.0)

* Fixes lint

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: mariana <marianameireles@protonmail.com>
2023-03-23 11:58:25 +01:00
Hood Chatham
89842e20da Set pre-commit autoupdate to monthly (#1271)
* Set pre-commit autoupdate to quarterly

Weekly autoupdate creates a lot of noise

* Update .pre-commit-config.yaml

Co-authored-by: Christian Clauss <cclauss@me.com>

---------

Co-authored-by: Christian Clauss <cclauss@me.com>
2023-03-23 10:27:01 +01:00
Jeff Glass
ef793aecf3 Add REPL plugin hooks; Add output, output-mode, stderr attributes (#1106)
* Add before, after REPL hooks

* Re-introduce 'output-mode' attribute for py-repl

* Add plugin execution tests

* Documentation

* Changelog

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: mariana <marianameireles@protonmail.com>
2023-03-22 20:19:22 -05:00
Andrea Giammarchi
51d51409d3 Using esbuild instead of rollup (#1298) 2023-03-22 16:57:37 +01:00
Fábio Rosado
371b5eac45 Add tests for snippets in docs (#1264) 2023-03-22 15:34:23 +00:00
Mariana Meireles
5319bd13d5 Fix tests running on osx + remove auto-gen where doesnt make sense (#1297)
* Fix tests running on osx + remove auto-gen where doesnt make sense

* Linting

* Adds a changelog

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-21 14:39:51 +01:00
Andrea Giammarchi
e10d055453 Docked auto py-terminal (#1284) 2023-03-20 10:22:16 +00:00
Mariana Meireles
716254e655 Revert "Ruff: Add pylint (#1277)" (#1283)
This reverts commit 4c00b1683f.
2023-03-14 17:46:13 +01:00
Christian Clauss
4c00b1683f Ruff: Add pylint (#1277) 2023-03-13 18:03:15 +01:00
Hood Chatham
37c9db09c6 Fix many ESlint errors (#1265)
* Unvendor toml package

* Fix many ESlint errors

For mysterious reasons, these errors appear on my branch #1262 even
though they are not related to changes there. The eslint config seems
a bit unstable.

Anyways this fixes them.

* Put back Record

* Fix typescript compilation

* Fix lints

* Try @iarna/toml instead

* Fix import

* Use @ltd/j-toml

* Update test

* Use toml-j0.4

* Some changes

* Fix toml import

* Try adding eslint gha job

* Add forgotten checkout action

* Force CI to run

* Blah

* Fix

* Revert changes to github workflow

* Fix lints

* wget toml-j0.4 type definitions

* Add toml-j types workaround to eslint workflow

* Apply formatter

* Use @hoodmane/toml-j0.4

* Import from @hoodmane/toml-j0.4
2023-03-13 15:51:28 +01:00
Fábio Rosado
653e2c9be4 Revert changes to the sync workflow (#1276) 2023-03-13 13:39:17 +00:00
Kanishk Pachauri
a2a9613da1 Added Pull request template (#1279)
* Added Pull request template

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: you@example.com <you@example.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-13 13:03:47 +00:00
Antonio Cuni
e8d92d0d34 we cannot use ../build paths in the main examples/ directory (#1275) 2023-03-11 10:35:26 +00:00
Fábio Rosado
755b98a8c0 Update sync-examples CI to be run manually as well (#1274) 2023-03-11 10:34:59 +00:00
Hood Chatham
13e9252260 Run eslint in github actions, skip it in precommit.ci (#1268)
precommit.ci seems to have different results than when I run eslint
locally. At least at first glance, github seems to behave the same
as local. So this turns off eslint in pre-commit.ci and turns it on
in gha.
2023-03-09 14:48:24 +05:30
Hood Chatham
6a9c27325a Fix typo in workflow file (#1266) 2023-03-09 14:45:36 +05:30
Hood Chatham
a1cb78eb85 Fix paths in py-unit/conftest.py (#1269)
The path logic here assumes that the tests are being run from inside
the pyscriptjs directory. It is better to compute releative to `Path(__file__)`
so that it does not depend on the working directory
2023-03-09 14:44:33 +05:30
Hood Chatham
716b57ebd3 Pass --fix to ruff in pre-commit to enable autofixes (#1270) 2023-03-09 14:43:42 +05:30
pre-commit-ci[bot]
8e231313b8 [pre-commit.ci] pre-commit autoupdate (#1260)
updates:
- [github.com/charliermarsh/ruff-pre-commit: v0.0.247 → v0.0.254](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.247...v0.0.254)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Madhur Tandon <20173739+madhur-tandon@users.noreply.github.com>
2023-03-09 14:42:59 +05:30
Hood Chatham
84e4e361c5 Unvendor toml package (#1263)
* Unvendor toml package

* Try @iarna/toml instead

* Fix import

* Use @ltd/j-toml

* Update test

* Use toml-j0.4

* Fix toml import
2023-03-09 14:36:43 +05:30
Mariana Meireles
41a8d804e3 Attrs doc (#1125)
* adds attr ref

* lint

* address comments

* Addressing to Fabio's rewrite
2023-03-07 11:49:46 +01:00
Hood Chatham
03e798a079 Add prettier to pre-commit (#1255)
* Add prettier to pre-commit

* Apply prettier
2023-03-07 15:02:16 +05:30
Hood Chatham
34a0205757 Add missing </body> tags (#1256) 2023-03-06 22:11:46 +05:30
dependabot[bot]
ba145f04ea Bump json5 from 2.2.1 to 2.2.3 in /pyscriptjs (#1216) 2023-03-06 16:24:17 +00:00
Hood Chatham
22fd023635 More automatically generated formatter changes (#1254)
Apparently some of these were accidentally lost when generating #1210...
2023-03-06 15:06:30 +00:00
Hood Chatham
08f34f748b Apply prettier to css, html, js, md, ts, and yml (#1249)
* Apply prettier to css, js, html, md, ts, and yml

As a followup I will add prettier to the .pre-commit config.
This patch is 100% generated by prettier.
I used a forked version of prettier that understands the
py-script tag.
See https://github.com/hoodmane/pyscript-prettier-precommit
for more info.

* Apply old pre-commit

* Revert some problems

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Revert some changes

* More changes

* Fix pre-commit

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-06 14:20:21 +00:00
Christian Clauss
7ffe6a598e pre-commit: Add ruff to replace bandit, flake8, isort, and pyupgrade (#1210)
* pre-commit: Add ruff to replace bandit, flake8, isort, and pyupgrade

* Upgrade ruff

* Update .pre-commit-config.yaml

* Update .pre-commit-config.yaml

* Update .pre-commit-config.yaml
2023-03-06 14:20:08 +00:00
Kd-Here
71d24a445e Add description of src attribute to py-script (#1136)
* Added description of src attribute to py-script

* Description for py-script src attribute

* resolved mistake of raised by pre-commit

* Update docs/reference/elements/py-script.md

---------

Co-authored-by: Fábio Rosado <hello@fabiorosado.dev>
2023-03-06 12:39:55 +00:00
Fábio Rosado
6bcbbfb085 Add tutorial for using requests (#1164) 2023-03-06 12:36:52 +00:00
Hood Chatham
04fe1348d8 Fix test reporter again (#1247)
* Fix test reporter again

* Escape brackets
2023-03-06 17:15:01 +05:30
Madhur Tandon
3033c779b0 use mkdirTree in emscripten FS (#1245)
* try mkdirTree

* suggested changes

* fix pre-commit
2023-03-04 18:48:01 +05:30
Hood Chatham
4483f0db0f Move prism.js and prism.css to prism.min.* (#1248) 2023-03-03 17:32:06 +00:00
Madhur Tandon
727267ae22 split interpreter class (#1218)
* split interpreter class

* add new files

* add newlines

* disable eslint for run

* remove usage of interpreter from unit test

* delete fakeinterpreter class

* fix unit tests

* add comments

* remove interpreter.ts and pyodide.ts files

* suggested changes
2023-03-03 22:23:52 +05:30
Hood Chatham
b5d15c2f7e Actually generate test reports (#1246) 2023-03-03 14:10:48 +01:00
Hood Chatham
589c614e57 Fix syntax errors in html files, apply dos2unix, prettier everything (#1244)
* Fix syntax errors in html files

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-03 12:59:49 +01:00
Hood Chatham
4588e90226 Add test reporter (#1242)
* Add test reporter

* Fix indendation

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-02 12:38:51 +00:00
Hood Chatham
8665a14dec Begin making pyscript.py into a Python package (#1232)
* Begin making pyscript.py into a Python package

* Fix path
2023-03-02 12:30:43 +00:00
Jeff Glass
43d598d951 Fix Failing test_multiple_async (#1237) 2023-02-28 07:21:10 -06:00
Fábio Rosado
68018cf078 Add intersphinx and some docs (#1217)
* Add intersphinx and some docs

* Remove spaces

* Address Jeff comments
2023-02-28 11:37:21 +00:00
Jeff Glass
ef4ab0d7a8 Add typing for tagExecutionLock (#1235) 2023-02-28 12:27:49 +05:30
Hood Chatham
e66a2702df Fix error message (#1225) 2023-02-28 12:22:40 +05:30
Hood Chatham
c57d4a7054 Remove @staticmethod decorator from top level definitions (#1224) 2023-02-28 12:22:13 +05:30
Hood Chatham
a36f08f0f1 Try new import path and fall back to old one (#1223) 2023-02-28 12:21:55 +05:30
pre-commit-ci[bot]
760a8c75a5 [pre-commit.ci] pre-commit autoupdate (#1234)
updates:
- [github.com/pre-commit/mirrors-eslint: v8.34.0 → v8.35.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.34.0...v8.35.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-02-28 12:12:28 +05:30
Jeff Glass
740fd921e1 Make plugin methods optional (#1134)
* Make plugin methods optional using optional chaining on all methods.
2023-02-27 20:14:07 -06:00
Jeff Glass
065c697070 Fix by comparing to str(results)) (#1233) 2023-02-27 19:45:24 -06:00
Madhur Tandon
e2c2459290 wrap runPython in async (#1212) 2023-02-21 20:35:19 +00:00
pre-commit-ci[bot]
11c79a5344 [pre-commit.ci] pre-commit autoupdate (#1211)
updates:
- [github.com/macisamuele/language-formatters-pre-commit-hooks: v2.6.0 → v2.7.0](https://github.com/macisamuele/language-formatters-pre-commit-hooks/compare/v2.6.0...v2.7.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-02-21 09:16:17 +00:00
pre-commit-ci[bot]
429fe4c356 [pre-commit.ci] pre-commit autoupdate (#1202)
updates:
- [github.com/pre-commit/mirrors-eslint: v8.33.0 → v8.34.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.33.0...v8.34.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-02-14 11:14:02 +00:00
Russell Keith-Magee
a18b4edfc0 Updated freedom demo to make use of Toga 0.3.0 and briefcase web backend. (#1203) 2023-02-14 11:13:39 +00:00
Mariana Meireles
b14a2bba5f Marimeireles fix/#1081 (#1155)
* Improves repl id output

* Fix tests for new REPL output ids

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add new REPL tests

* Pre commit linting

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Remove mistake

* Fixing tests that i didn't notice were broken?

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-02-14 07:51:43 +01:00
Ted Patrick
1f825edc28 Issues Configuration (#1157)
* Issues Configuration

* format

* Remove issue staler
2023-02-09 17:05:54 -06:00
Mariana Meireles
6ed834807a Adding info on how to set up test env + rewording a few things (#1156)
* Adding info on how to set up test env + rewording a few things

* changelog entry

* lint
2023-02-08 11:25:12 -08:00
Madhur Tandon
9a908e5fd0 Upgrade Pyodide to v0.22.1 (#1144)
* upgrade to pyodide 0.22.1

* pin bokeh in panel examples

* fix typo

* fix tests by using custom bokeh wheel

* fix bokeh interactive test

* adhere to new loadPackage API
2023-02-08 22:01:51 +05:30
Mariana Meireles
4c30359b71 Missing one line to make test (#1154)
`make test` command was incomplete.
2023-02-07 11:18:45 +05:30
pre-commit-ci[bot]
34dfe2d80b [pre-commit.ci] pre-commit autoupdate (#1153)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/psf/black: 22.12.0 → 23.1.0](https://github.com/psf/black/compare/22.12.0...23.1.0)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-02-07 11:17:57 +05:30
Fábio Rosado
25bcff10b7 Fix import warning for js.document (#1151) 2023-02-06 16:11:47 +00:00
Fábio Rosado
81268d0545 Add config option to splashscreen so users can disable it plus docs (#1138)
* Allow disabling splashscreen

* Move disabled to if statement

* Add docs for py-splashscreen and test for logging

* Add entry to changelog

* Address Jeff's comments

* Rename disabled option to enabled and set to true by default

* Fix logic and tests

* Uncomment test

* Fix test
2023-02-04 15:56:48 +00:00
Madhur Tandon
8f0a7706d7 fix py-markdown plugin test (#1145) 2023-02-04 20:57:17 +05:30
Mariana Meireles
46150f9b80 complete install guide (#1140) 2023-01-31 08:18:58 -08:00
INC
247745b7e7 Fixing docs Issue #1033 (#1037)
* Move the info in /pyscriptjs/README.md to /docs/development/developing.md

* developing.md: Markdown linting

* Fixing docs issue #1033

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* setting-up-environment.md: Markdown Linting

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-31 12:28:25 +00:00
pre-commit-ci[bot]
94cc09b610 [pre-commit.ci] pre-commit autoupdate (#1139)
updates:
- [github.com/pycqa/isort: 5.11.4 → 5.12.0](https://github.com/pycqa/isort/compare/5.11.4...5.12.0)
- [github.com/pre-commit/mirrors-eslint: v8.32.0 → v8.33.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.32.0...v8.33.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-31 10:49:14 +05:30
Jeff Glass
a210b2d5f5 Improve typing on PyScriptTag (#1135) 2023-01-26 09:38:07 -06:00
Jeff Glass
12bf6db331 Change Plugin Hooks to Use options object/kwargs (#1132) 2023-01-26 07:20:04 -06:00
Madhur Tandon
697ac9de9a fix exception thrown but not shown in DOM in event handler (#1131)
* fix exception not thrown in event handler

* fix implicit display test

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-25 20:08:05 +05:30
Fábio Rosado
4124bb5edc Restructure the tutorials index page (#1090) 2023-01-25 12:28:23 +00:00
Fabio Pliger
d55340a817 Better test support for Python Plugins (#1108)
* add plugins testing utils module

* add plugins manager fixture and init plugins tests helper in conftest

* add _custom_elements attribute to pyscript.Plugin to allow plugins to track the CE they register

* add test for py_tutor

* remove unrelated code from prims js script

* ensure a Plugin always has the app attribute and improve tests

* add tests for py_tutor create_code_section

* implement PluginsManager reset and add teardown on plugins_manager fixture to clean it up after a test

* add test to check if plugin has been registered

* add docstrings to new tests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* add docstrings to plugins tester

* add changes from main

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* lint

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* add todo to add remaining PluginsManager lifecycle events

Co-authored-by: Fabio Pliger <fpliger@anaconda.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-24 13:32:16 -08:00
Mohammad Anash
0de8cd9ab7 Update README to point to development docs (#1129) 2023-01-24 19:57:00 +00:00
Madhur Tandon
4e8281c749 fix display multiple append with target (#1126)
* fix display multiple append with target

* flake8 ignore long lines

* fix img render test
2023-01-24 22:01:19 +05:30
pre-commit-ci[bot]
357fbc644d [pre-commit.ci] pre-commit autoupdate (#1127)
updates:
- [github.com/macisamuele/language-formatters-pre-commit-hooks: v2.5.0 → v2.6.0](https://github.com/macisamuele/language-formatters-pre-commit-hooks/compare/v2.5.0...v2.6.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-24 11:20:07 +05:30
Fábio Rosado
7947a8a2dc Add an id if the user forgot when using py-attribute (#1122) 2023-01-21 20:48:27 +00:00
Fábio Rosado
35de3aa154 Add f string formatting ignore (#1121) 2023-01-20 21:14:43 +05:30
pre-commit-ci[bot]
1ea687beb8 [pre-commit.ci] pre-commit autoupdate (#1111)
updates:
- [github.com/pre-commit/mirrors-eslint: v8.31.0 → v8.32.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.31.0...v8.32.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-20 21:14:32 +05:30
Fábio Rosado
bb5c59307a Rename runtimes with interpreter (#1082) 2023-01-16 18:52:31 +00:00
Mariana Meireles
5a3c414c8f we don't really use numpy here (#1101) 2023-01-11 19:43:26 -08:00
Fábio Rosado
cc4b460183 Allow fetching plugins from URL (#1065) 2023-01-11 17:03:53 +00:00
Jeff Glass
470c3489dd Restore output attribute of py-script tags, add py-script exec lifecycle hooks (#1063)
* Add beforePyScriptExec, afterPyScriptExec lifecycle hooks

* Add stdiodirector plugin for `output`, `stderr` attributes of py-script tag

* Add docs on `output` and `stderr` attributes of py-script tag

* Tests

* Removed output deprecation warning for `output` attribute

* Add createSingularWarning(), with createDeprecationWarning as alias
2023-01-10 13:00:29 -06:00
Jeff Glass
e1b4415193 Fix Flaky test_execute_on_shift_enter (#1097)
* Wait for text to render in DOM before 'assert', to fix timing issue.
2023-01-05 07:29:20 -06:00
Jeff Glass
77d98a565e In a Py-Repl, Shift Enter Shouldn't Insert Newline (#1094)
* Prevent shift-enter adding newlines in Py-Repl

* Add test
2023-01-04 12:26:46 -06:00
Fábio Rosado
412da2de08 Add version file (#1087) 2023-01-03 17:31:13 +00:00
Fábio Rosado
dbdcd0b3d0 Remove deprecated elements and adds deprecation banner to pys-on (#1084)
* Show deprecation banner

* Add test for deprecation warning

* Remove deprecated elements

* Add entry in changelog

* Update test_style

* Remove random color rule

* Add PR link to changelog
2023-01-03 13:14:20 +00:00
pre-commit-ci[bot]
5c67384fbf [pre-commit.ci] pre-commit autoupdate (#1086)
updates:
- [github.com/pre-commit/mirrors-eslint: v8.30.0 → v8.31.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.30.0...v8.31.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-03 10:55:37 +00:00
Fábio Rosado
35b0f9d377 pys-onClick shouldn't prevent splashscreen from closing (#1069) 2022-12-30 12:34:27 +00:00
Fábio Rosado
95783bc284 Add changelog to docs (#1066) 2022-12-29 23:07:39 +00:00
Ioannis Chrysostomakis
4b840f7cbd Minor refactoring in logger and pyconfig (#1072) 2022-12-28 10:51:21 +00:00
pre-commit-ci[bot]
f73d6cd9f2 [pre-commit.ci] pre-commit autoupdate (#1078)
updates:
- [github.com/pycqa/isort: v5.11.3 → 5.11.4](https://github.com/pycqa/isort/compare/v5.11.3...5.11.4)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-12-27 19:05:40 +00:00
Roman Kehr
15bb8f03ea Pandas example file for /examples folder (#1067)
* Add files via upload

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-12-26 10:38:16 +00:00
Jeff Glass
059dbc88c9 Don't add plugin to manager twice (#1064)
* Prevent Python plugins from being added to PluginManager twice
2022-12-21 12:10:01 -06:00
Fabio Pliger
c0f36aa047 examples inspector plugin (#1040)
* replace unnecessary elements from hello_world example and replace with py-tutor tag

* add py_tutor plugin

* port altair example

* add code for more granular tutor mode

* add support for including modules source in pytutor

* remove js dependencies in hello_world

* put antigravity on a diet ;)

* use py-tutor on antigravity example

* use py-tutor on d3 example

* use py-tutor on bokeh example

* use py-tutor on bokeh_interactive example

* fix issue when module_paths is undefined

* remove prism js dependency leftovers

* ooops, really remove prism js dependency leftovers

* port follium example to pytutor

* port pymarkdown and matplotlib example to pytutor

* port message_passing and numpy_convas_fractals examples to pytutor

* port the panel complex  examples to pytutor

* port the panel complex  examples to pytutor

* port last examples to py-tutor

* remove prism

* remore most debugging logs and replace log with info

* add new d3.py file

* add comments to connect method

* clean pyscript class from logs

* revert class pySrc attribute

* add check_tutor_generated_code to test code inspector plugin in examples

* add doctsting to PyTutor connect

* add check for tutor code inspection on all examples

* Update pyscriptjs/src/plugins/python/py_tutor.py

fix template indentation

Co-authored-by: Fábio Rosado <fabioglrosado@gmail.com>

* Update examples/todo-pylist.html

fix typo (stray = )

Co-authored-by: Fábio Rosado <fabioglrosado@gmail.com>

* fix pymarkdown example

Co-authored-by: Fabio Pliger <fpliger@anaconda.com>
Co-authored-by: Fábio Rosado <fabioglrosado@gmail.com>
2022-12-20 07:48:07 -08:00
pre-commit-ci[bot]
d4120d2af3 [pre-commit.ci] pre-commit autoupdate (#1056)
updates:
- [github.com/pycqa/isort: 5.11.1 → v5.11.3](https://github.com/pycqa/isort/compare/5.11.1...v5.11.3)
- [github.com/pre-commit/mirrors-eslint: v8.29.0 → v8.30.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.29.0...v8.30.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-12-20 12:09:44 +00:00
Nayan Khedkar
dd1c008447 missing closing > in img tag (#1058) 2022-12-20 10:26:22 +00:00
Jeff Glass
3721d2cd72 Add name:pyscript to minified output (#1054) 2022-12-16 19:11:58 -06:00
Roman Kehr
e0dda0e547 Fixed "Direct usage of document is deprecated." warning (#1052) 2022-12-16 20:28:12 +00:00
Jeff Glass
3c7568c72c Adjust version_info test regex - no dot (#1045)
Tweaked the `test_version_info` test to remove matching against an extraneous '.'
2022-12-13 16:10:06 -06:00
Ted Patrick
6be1758548 Back to development 2022-12-13 11:39:11 -06:00
262 changed files with 26866 additions and 24091 deletions

View File

@@ -2,62 +2,62 @@ name: Bug Report
description: Create a report to help us improve description: Create a report to help us improve
labels: ["type: bug", "needs-triage"] labels: ["type: bug", "needs-triage"]
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
Thanks for helping PyScript! 🐍 Thanks for helping PyScript! 🐍
Going through bugs and issues takes up a lot of time, so please be so kind and take a few minutes to fill out all the areas to the best of your ability. Going through bugs and issues takes up a lot of time, so please be so kind and take a few minutes to fill out all the areas to the best of your ability.
There will always be more issues than there is time to do them, and so we will need to selectively close issues that don't provide enough information, so we can focus our time on helping people like you who fill out the issue form completely. Thank you for your collaboration! There will always be more issues than there is time to do them, and so we will need to selectively close issues that don't provide enough information, so we can focus our time on helping people like you who fill out the issue form completely. Thank you for your collaboration!
There are also already a lot of open issues, so please take 2 minutes and search through existing ones to see if what you are experiencing already exists There are also already a lot of open issues, so please take 2 minutes and search through existing ones to see if what you are experiencing already exists
Thanks for helping PyScript be amazing. We are nothing without people like you helping build a better community 💐! Thanks for helping PyScript be amazing. We are nothing without people like you helping build a better community 💐!
- type: checkboxes - type: checkboxes
id: checks id: checks
attributes: attributes:
label: Checklist label: Checklist
description: Please confirm and check all the following options. description: Please confirm and check all the following options.
options: options:
- label: I added a descriptive title - label: I added a descriptive title
required: true required: true
- label: I searched for other issues and couldn't find a solution or duplication - label: I searched for other issues and couldn't find a solution or duplication
required: true required: true
- label: I already searched in Google and didn't find any good information or help - label: I already searched in Google and didn't find any good information or help
required: true required: true
- type: textarea - type: textarea
id: what-happened id: what-happened
attributes: attributes:
label: What happened? label: What happened?
description: And what should have happened instead? This really helps everyone review quicker and greatly increases the chance that someone can get around to solve your issue description: And what should have happened instead? This really helps everyone review quicker and greatly increases the chance that someone can get around to solve your issue
placeholder: Tell us what you see! placeholder: Tell us what you see!
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
id: browsers id: browsers
attributes: attributes:
label: What browsers are you seeing the problem on? (if applicable) label: What browsers are you seeing the problem on? (if applicable)
multiple: true multiple: true
options: options:
- Firefox - Firefox
- Chrome - Chrome
- Safari - Safari
- Microsoft Edge - Microsoft Edge
- Other - Other
validations: validations:
required: false required: false
- type: textarea - type: textarea
id: list id: list
attributes: attributes:
label: Console info label: Console info
description: | description: |
If there are errors in your browser console then its helpful to be able to troubleshoot. If there are errors in your browser console then its helpful to be able to troubleshoot.
- Chrome , Firefox, and Edge: Right-click on the page and select *Inspect*. Alternatively you can press F12 on your keyboard. - Chrome , Firefox, and Edge: Right-click on the page and select *Inspect*. Alternatively you can press F12 on your keyboard.
- Safari: Find instructions [here](https://support.apple.com/guide/safari/use-the-developer-tools-in-the-develop-menu-sfri20948/mac). - Safari: Find instructions [here](https://support.apple.com/guide/safari/use-the-developer-tools-in-the-develop-menu-sfri20948/mac).
render: shell render: shell
- type: textarea - type: textarea
id: context id: context
attributes: attributes:
label: Additional Context label: Additional Context
description: Add any additional context information or screenshots you think are useful. description: Add any additional context information or screenshots you think are useful.

View File

@@ -1,5 +1,8 @@
blank_issues_enabled: true blank_issues_enabled: true
contact_links: contact_links:
- name: Question - name: Feature Proposals
url: https://community.anaconda.cloud/c/tech-topics/pyscript url: https://github.com/pyscript/pyscript/discussions/new?category=proposals
about: Create a feature request to make PyScript even better
- name: Questions
url: https://github.com/pyscript/pyscript/discussions/new?category=q-a
about: For questions or discussions about pyscript about: For questions or discussions about pyscript

View File

@@ -1,67 +0,0 @@
name: Feature Request
description: Create a feature request to make PyScript even better
labels: ["type: enhancement", "needs-triage"]
body:
- type: markdown
attributes:
value: |
### Thanks for helping PyScript! 🐍
Going through feature requests and issues takes up a lot of time, so please be so kind and take a few minutes to fill out all the areas to the best of your ability.
There will always be more great ideas than there is time to do them, and so we will need to selectively close issues that don't provide enough information, so everyone can focus our time on helping people like you who fill out the form completely. Thank you for your collaboration!
There are also already a lot of open requests, so please take 2 minutes and search through existing ones to see if your idea already exists. If you find something close, please upvote that request and comment.
Thanks for helping PyScript be amazing. We are nothing without people like you helping build a better community 💐!
### Lets make sure you are in the right place. If you have an idea/request for:
- #### A specific package/library (such as pandas or scikit learn):
Search for that respective library on github repo or website. You will have much more success there.
- #### A general Python question/feature request:
Try out a forum post [here](https://discuss.python.org/c/users/7)
- type: checkboxes
id: checks
attributes:
label: Checklist
description: Please confirm and check all the following options
options:
- label: I added a descriptive title
required: true
- label: I searched for other feature requests and couldn't find a duplicate (including also the ``type-feature`` tag)
required: true
- label: I confirmed that it's not related to another project area (see the above section)
required: true
- type: textarea
id: request-idea
attributes:
label: What is the idea?
description: Describe what the feature is and the desired state
placeholder: This feature would allow any user of PyScript to type in a simple command in the console and show all variables currently in use
validations:
required: true
- type: textarea
id: why
attributes:
label: Why is this needed
description: |
Who would benefit from this and why would this add value to them? What problem does this solve?
placeholder: This would benefit users who would like to see what is being used so they can learn and debug faster
- type: textarea
id: what
attributes:
label: What should happen?
description: |
What should be the user experience with the feature? Describe from a user perpective what they would do and see
placeholder: A user would type in ``PyScript debug`` in the browser console and see a list of all variables created.
- type: textarea
id: context
attributes:
label: Additional Context
description: |
Is there any other information that you think would be valuable for the team to know?

View File

@@ -1,37 +0,0 @@
name: Miscellaneous
description: For issues that don't belong in other categories
labels: ["type: misc", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Thanks for helping PyScript! 🐍
This issue is for things that doesn't make sense to put into the other issue categories and we don't want it to get lost.
Going through issues takes up a lot of time, so please be so kind and take a few minutes to fill out all the areas to the best of your ability.
There will always be more issues than there is time to do them, and so we will need to selectively close issues that don't provide enough information, so we can focus our time on helping people like you who fill out the issue form completely. Thank you for your collaboration!
There are also already a lot of open issues, so please take 2 minutes and search through existing ones to see if what you are experiencing already exists
Thanks for helping PyScript be amazing. We are nothing without people like you helping build a better community 💐!
- type: checkboxes
id: checks
attributes:
label: Checklist
description: Please confirm and check all the following options.
options:
- label: I added a descriptive title
required: true
- label: I searched for other issues and couldn't find a duplication
required: true
- label: I already searched in Google and didn't find any good information or help
required: true
- type: textarea
id: what
attributes:
label: What is the issue/comment/problem?
description: This is a miscellaneous issue so this could be just about anything. We simply ask that you provide as many details as you can to help spur discussion or the outcome you want.
validations:
required: true

15
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,15 @@
## Description
<!--Please describe the changes in your pull request in few words here. -->
## Changes
<!-- List the changes done to fix a bug or introduce a new feature.Please note both user-facing changes and changes to internal API's here -->
## Checklist
<!-- Note: Only user-facing changes require a changelog entry. Internal-only API changes do not require a changelog entry. Changes in documentation do not require a changelog entry. -->
- [ ] All tests pass locally
- [ ] I have updated `docs/changelog.md`
- [ ] I have created documentation for this(if applicable)

6
.github/release.yml vendored
View File

@@ -1,5 +1,5 @@
changelog: changelog:
categories: categories:
- title: New Features - title: New Features
- title: Breaking Changes - title: Breaking Changes
- title: Known Issues - title: Known Issues

26
.github/stale.yaml vendored
View File

@@ -1,26 +0,0 @@
# 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.

View File

@@ -1,26 +1,26 @@
name: '[CI] Build Unstable' name: "[CI] Build Unstable"
on: on:
push: # Only run on merges into main that modify files under pyscriptjs/ and examples/ push: # Only run on merges into main that modify files under pyscriptjs/ and examples/
branches: branches:
- main - main
paths: paths:
- pyscriptjs/** - pyscriptjs/**
- examples/** - examples/**
- .github/workflows/build-latest.yml # Test that workflow works when changed - .github/workflows/build-unstable.yml # Test that workflow works when changed
pull_request: # Run on any PR that modifies files under pyscriptjs/ and examples/ pull_request: # Run on any PR that modifies files under pyscriptjs/ and examples/
branches: branches:
- main - main
paths: paths:
- pyscriptjs/** - pyscriptjs/**
- examples/** - examples/**
- .github/workflows/build-unstable.yml # Test that workflow works when changed - .github/workflows/build-unstable.yml # Test that workflow works when changed
workflow_dispatch: workflow_dispatch:
jobs: jobs:
BuildAndTest: BuildAndTest:
runs-on: ubuntu-latest runs-on: ubuntu-latest-8core
defaults: defaults:
run: run:
working-directory: pyscriptjs working-directory: pyscriptjs
@@ -28,56 +28,97 @@ jobs:
MINICONDA_PYTHON_VERSION: py38 MINICONDA_PYTHON_VERSION: py38
MINICONDA_VERSION: 4.11.0 MINICONDA_VERSION: 4.11.0
steps: steps:
- name: Checkout
uses: actions/checkout@v3
- name: Checkout - name: Install node
uses: actions/checkout@v3 uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Install node - name: Cache node modules
uses: actions/setup-node@v3 uses: actions/cache@v3
with: env:
node-version: 18.x cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Cache node modules - name: setup Miniconda
uses: actions/cache@v3 uses: conda-incubator/setup-miniconda@v2
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: setup Miniconda - name: Setup Environment
uses: conda-incubator/setup-miniconda@v2 run: make setup
- name: Setup Environment - name: Build
run: make setup run: make build
- name: Build - name: TypeScript Tests
run: make build run: make test-ts
- name: TypeScript Tests - name: Python Tests
run: make test-ts run: make test-py
- name: Python Tests - name: Integration Tests
run: make test-py run: make test-integration-parallel
- name: Integration Tests - name: Examples Tests
run: make test-integration-parallel run: make test-examples
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
name: pyscript name: pyscript
path: | path: |
pyscriptjs/build/ pyscriptjs/build/
if-no-files-found: error if-no-files-found: error
retention-days: 7 retention-days: 7
- uses: actions/upload-artifact@v3
if: success() || failure()
with:
name: test_results
path: pyscriptjs/test_results
if-no-files-found: error
eslint:
runs-on: ubuntu-latest-8core
defaults:
run:
working-directory: pyscriptjs
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install node
uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: npm install
run: npm install
- name: Eslint
run: npx eslint src -c .eslintrc.js
Deploy: Deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest-8core
needs: BuildAndTest needs: BuildAndTest
if: github.ref == 'refs/heads/main' # Only deploy on merge into main if: github.ref == 'refs/heads/main' # Only deploy on merge into main
permissions: permissions:
@@ -85,17 +126,17 @@ jobs:
id-token: write id-token: write
steps: steps:
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
with: with:
name: pyscript name: pyscript
path: ./build/ path: ./build/
# Deploy to S3 # Deploy to S3
- name: Configure AWS credentials - name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1.6.1 uses: aws-actions/configure-aws-credentials@v1.6.1
with: with:
aws-region: ${{ secrets.AWS_REGION }} aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }} role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
- name: Sync to S3 - name: Sync to S3
run: aws s3 sync --quiet ./build/ s3://pyscript.net/unstable/ run: aws s3 sync --quiet ./build/ s3://pyscript.net/unstable/

View File

@@ -1,4 +1,4 @@
name: '[Docs] Build Release' name: "[Docs] Build Release"
on: on:
release: release:
@@ -6,61 +6,57 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest-8core
permissions: permissions:
contents: read contents: read
id-token: write id-token: write
env: env:
SPHINX_HTML_BASE_URL: https://docs.pyscript.net/ SPHINX_HTML_BASE_URL: https://docs.pyscript.net/
steps: steps:
- name: Checkout
uses: actions/checkout@v3
with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
- name: Checkout - name: Setup
uses: actions/checkout@v3 uses: conda-incubator/setup-miniconda@v2
with: with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token. auto-update-conda: true
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. activate-environment: docs
environment-file: docs/environment.yml
python-version: "3.9"
- name: Setup - name: Build
uses: conda-incubator/setup-miniconda@v2 shell: bash -l {0}
with: run: |
auto-update-conda: true cd docs/
activate-environment: docs make html
environment-file: docs/environment.yml
python-version: '3.9'
- name: Build - name: Upload artifacts
shell: bash -l {0} uses: actions/upload-artifact@v3
run: | with:
cd docs/ name: pyscript-docs-${{ github.ref_name }}
make html path: docs/_build/html/
- name: Upload artifacts # Deploy to S3
uses: actions/upload-artifact@v3 - name: Configure AWS credentials
with: uses: aws-actions/configure-aws-credentials@v1.6.1
name: pyscript-docs-${{ github.ref_name }} with:
path: docs/_build/html/ aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
# Deploy to S3 - name: Copy redirect file
- name: Configure AWS credentials run: aws s3 cp --quiet ./docs/_build/html/_static/redirect.html s3://docs.pyscript.net/index.html
uses: aws-actions/configure-aws-credentials@v1.6.1
with:
aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
- name: Copy redirect file - name: Sync to S3
run: aws s3 cp --quiet ./docs/_build/html/_static/redirect.html s3://docs.pyscript.net/index.html run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/${{ github.ref_name }}/
# - name: Delete release directory # Make sure to remove the latest folder so we sync the full docs upon release
# run: aws s3 rm --recursive s3://docs.pyscript.net/${{ github.ref_name }}/ - name: Delete latest directory
run: aws s3 rm --recursive s3://docs.pyscript.net/latest/
- name: Sync to S3 # Note that the files are the same as above, but we want to have folders with
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/${{ github.ref_name }}/ # /<tag name>/ AND /latest/ which latest will always point to the latest release
- name: Sync to /latest
# Make sure to remove the latest folder so we sync the full docs upon release run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/latest/
- 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/

View File

@@ -1,74 +1,53 @@
name: '[Docs] Build Review' name: "[Docs] Build Review"
on: on:
pull_request: pull_request:
branches: branches:
- '*' - "*"
paths: paths:
- docs/** - docs/**
concurrency: concurrency:
# Concurrency group that uses the workflow name and PR number if available # Concurrency group that uses the workflow name and PR number if available
# or commit SHA as a fallback. If a new build is triggered under that # or commit SHA as a fallback. If a new build is triggered under that
# concurrency group while a previous build is running it will be canceled. # concurrency group while a previous build is running it will be canceled.
# Repeated pushes to a PR will cancel all previous builds, while multiple # Repeated pushes to a PR will cancel all previous builds, while multiple
# merges to main will not cancel. # merges to main will not cancel.
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
build: build:
if: github.repository_owner == 'pyscript' if: github.repository_owner == 'pyscript'
runs-on: ubuntu-latest runs-on: ubuntu-latest-8core
permissions: permissions:
contents: read contents: read
id-token: write id-token: write
env: env:
SPHINX_HTML_BASE_URL: https://docs.pyscript.net/ SPHINX_HTML_BASE_URL: https://docs.pyscript.net/
steps: steps:
- name: Checkout
uses: actions/checkout@v3
with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
- name: Checkout - name: Setup
uses: actions/checkout@v3 uses: conda-incubator/setup-miniconda@v2
with: with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token. auto-update-conda: true
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. activate-environment: docs
environment-file: docs/environment.yml
python-version: "3.9"
- name: Setup - name: Build
uses: conda-incubator/setup-miniconda@v2 shell: bash -l {0}
with: run: |
auto-update-conda: true cd docs/
activate-environment: docs make html
environment-file: docs/environment.yml
python-version: '3.9'
- name: Build - name: Upload artifacts
shell: bash -l {0} uses: actions/upload-artifact@v3
run: | with:
cd docs/ name: pyscript-docs-review-${{ github.event.number }}
make html path: docs/_build/html/
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: pyscript-docs-review-${{ github.event.number }}
path: docs/_build/html/
# Deploy to S3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1.6.1
with:
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: Sync to S3
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/review/${{ github.event.number }}/
- name: Adding step summary
run: |
echo "### Review documentation" >> $GITHUB_STEP_SUMMARY
echo "As with any pull request, you can find the rendered documentation version for pull request ${{ github.event.number }} here:"
echo "" >> $GITHUB_STEP_SUMMARY # this is a blank line
echo "https://docs.pyscript.net/review/${{ github.event.number }}/" >> $GITHUB_STEP_SUMMARY

View File

@@ -1,55 +1,58 @@
name: '[Docs] Build Latest' name: "[Docs] Build Latest"
on: on:
push: push:
branches: branches:
- main - main
paths: paths:
- docs/** - docs/**
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest-8core
permissions: permissions:
contents: read contents: read
id-token: write id-token: write
env: env:
SPHINX_HTML_BASE_URL: https://docs.pyscript.net/ SPHINX_HTML_BASE_URL: https://docs.pyscript.net/
steps: steps:
- name: Checkout
uses: actions/checkout@v3
with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
- name: Checkout - name: Setup
uses: actions/checkout@v3 uses: conda-incubator/setup-miniconda@v2
with: with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token. auto-update-conda: true
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. activate-environment: docs
environment-file: docs/environment.yml
python-version: "3.9"
- name: Setup - name: Build
uses: conda-incubator/setup-miniconda@v2 shell: bash -l {0}
with: run: |
auto-update-conda: true cd docs/
activate-environment: docs make html
environment-file: docs/environment.yml
python-version: '3.9'
- name: Build - name: Upload artifacts
shell: bash -l {0} uses: actions/upload-artifact@v3
run: | with:
cd docs/ name: pyscript-docs-latest
make html path: docs/_build/html/
- name: Upload artifacts # Deploy to S3
uses: actions/upload-artifact@v3 - name: Configure AWS credentials
with: uses: aws-actions/configure-aws-credentials@v1.6.1
name: pyscript-docs-latest with:
path: docs/_build/html/ aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
# Deploy to S3 # Sync will only copy changed files
- name: Configure AWS credentials - name: Sync Error
uses: aws-actions/configure-aws-credentials@v1.6.1 run: aws s3 cp --quiet ./docs/_static/s3_error.html s3://docs.pyscript.net/error.html
with:
aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
# Sync will only copy changed files # Sync will only copy changed files
- name: Sync to S3 - name: Sync to S3
run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/unstable/ run: aws s3 sync --quiet ./docs/_build/html/ s3://docs.pyscript.net/unstable/

View File

@@ -1,9 +1,9 @@
name: '[CI] Prepare Release' name: "[CI] Prepare Release"
on: on:
push: push:
tags: tags:
- '[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9]+' # YYYY.MM.MICRO - "[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9]+" # YYYY.MM.MICRO
env: env:
MINICONDA_PYTHON_VERSION: py38 MINICONDA_PYTHON_VERSION: py38
@@ -15,46 +15,57 @@ defaults:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest-8core
steps: steps:
- name: Checkout
uses: actions/checkout@v3
- name: Checkout - name: Install node
uses: actions/checkout@v3 uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Install node - name: Cache node modules
uses: actions/setup-node@v3 uses: actions/cache@v3
with: env:
node-version: 18.x cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Cache node modules - name: setup Miniconda
uses: actions/cache@v3 uses: conda-incubator/setup-miniconda@v2
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: setup Miniconda - name: Setup Environment
uses: conda-incubator/setup-miniconda@v2 run: make setup
- name: Setup Environment - name: Build
run: make setup run: make build
- name: Build and Test - name: TypeScript Tests
run: make test run: make test-ts
- name: Zip build folder - name: Python Tests
run: zip -r -q ./build.zip ./build run: make test-py
- name: Prepare Release - name: Integration Tests
uses: softprops/action-gh-release@v1 run: make test-integration-parallel
with:
draft: true - name: Examples Tests
prerelease: true run: make test-examples
generate_release_notes: true
files: ./build.zip - 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

View File

@@ -1,4 +1,4 @@
name: '[CI] Publish Release' name: "[CI] Publish Release"
on: on:
release: release:
@@ -14,49 +14,62 @@ defaults:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest-8core
permissions: permissions:
contents: read contents: read
id-token: write id-token: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install node - name: Install node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18.x node-version: 18.x
- name: Cache node modules - name: Cache node modules
uses: actions/cache@v3 uses: actions/cache@v3
env: env:
cache-name: cache-node-modules cache-name: cache-node-modules
with: with:
# npm cache files are stored in `~/.npm` on Linux/macOS # npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: | restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}- ${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build- ${{ runner.os }}-build-
${{ runner.os }}- ${{ runner.os }}-
- name: setup Miniconda - name: setup Miniconda
uses: conda-incubator/setup-miniconda@v2 uses: conda-incubator/setup-miniconda@v2
- name: Setup Environment - name: Setup Environment
run: make setup run: make setup
- name: Build and Test - name: Build
run: make test run: make build
# Upload to S3 - name: TypeScript Tests
- name: Configure AWS credentials run: make test-ts
uses: aws-actions/configure-aws-credentials@v1.6.1
with:
aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
- name: Sync to S3 - name: Python Tests
run: | # Update /latest and create an explicitly versioned directory under releases/YYYY.MM.MICRO/ run: make test-py
aws s3 sync --quiet ./build/ s3://pyscript.net/latest/
aws s3 sync --quiet ./build/ s3://pyscript.net/releases/${{ github.ref_name }}/ - name: Integration Tests
run: make test-integration-parallel
- name: Examples Tests
run: make test-examples
# Upload to S3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1.6.1
with:
aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
- name: Sync to S3
run:
| # Update /latest and create an explicitly versioned directory under releases/YYYY.MM.MICRO/
aws s3 sync --quiet ./build/ s3://pyscript.net/latest/
aws s3 sync --quiet ./build/ s3://pyscript.net/releases/${{ github.ref_name }}/

View File

@@ -1,26 +1,26 @@
name: '[CI] Publish Snapshot' name: "[CI] Publish Snapshot"
# Copy /unstable/ to /snapshots/2022.09.1.RC1/ # Copy /unstable/ to /snapshots/2022.09.1.RC1/
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
snapshot_version: snapshot_version:
description: 'The calver version of this snapshot: 2022.09.1 or 2022.09.1.RC1' description: "The calver version of this snapshot: 2022.09.1 or 2022.09.1.RC1"
type: string type: string
required: true required: true
jobs: jobs:
snapshot: snapshot:
runs-on: ubuntu-latest runs-on: ubuntu-latest-8core
permissions: permissions:
contents: read contents: read
id-token: write id-token: write
steps: steps:
- name: Configure AWS credentials - name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1.6.1 uses: aws-actions/configure-aws-credentials@v1.6.1
with: with:
aws-region: ${{ secrets.AWS_REGION }} aws-region: ${{ secrets.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }} role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
- name: Sync to S3 - name: Sync to S3
run: > run: >
aws s3 sync s3://pyscript.net/unstable/ s3://pyscript.net/snapshots/${{ inputs.snapshot_version }}/ aws s3 sync s3://pyscript.net/unstable/ s3://pyscript.net/snapshots/${{ inputs.snapshot_version }}/

View File

@@ -1,4 +1,4 @@
name: '[CI] Sync Examples' name: "[CI] Sync Examples"
on: on:
release: release:
@@ -6,7 +6,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest-8core
permissions: permissions:
contents: read contents: read
id-token: write id-token: write
@@ -15,15 +15,15 @@ jobs:
working-directory: examples working-directory: examples
steps: steps:
# Deploy to S3
# Deploy to S3 - name: Checkout
- name: Checkout uses: actions/checkout@v3
uses: actions/checkout@v3 - name: Configure AWS credentials
- name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1.6.1
uses: aws-actions/configure-aws-credentials@v1.6.1 with:
with: aws-region: ${{ secrets.AWS_REGION }}
aws-region: ${{ secrets.AWS_REGION }} role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }}
role-to-assume: ${{ secrets.AWS_OIDC_RUNNER_ROLE }} - name:
- name: Sync to S3 Sync to S3
# Sync outdated or new files, delete ones no longer in source # Sync outdated or new files, delete ones no longer in source
run: aws s3 sync --quiet --delete . s3://pyscript.net/examples/ # Sync directory, delete what is not in source run: aws s3 sync --quiet --delete . s3://pyscript.net/examples/ # Sync directory, delete what is not in source

128
.github/workflows/test-next.yml vendored Normal file
View File

@@ -0,0 +1,128 @@
name: "[CI] Test Next"
on:
push: # Only run on merges into main that modify files under pyscriptjs/ and examples/
branches:
- next
paths:
- pyscript.core/**
- pyscriptjs/**
- examples/**
- .github/workflows/test-next.yml # Test that workflow works when changed
pull_request: # Run on any PR that modifies files under pyscriptjs/ and examples/
branches:
- next
paths:
- pyscript.core/**
- pyscriptjs/**
- examples/**
- .github/workflows/test-next.yml # Test that workflow works when changed
workflow_dispatch:
jobs:
BuildAndTest:
runs-on: ubuntu-latest-8core
defaults:
run:
working-directory: pyscriptjs
env:
MINICONDA_PYTHON_VERSION: py38
MINICONDA_VERSION: 4.11.0
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install node
uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: setup Miniconda
uses: conda-incubator/setup-miniconda@v2
- name: Setup Environment
run: make setup
- name: Build
run: make build
- name: TypeScript Tests (core)
run: make test-ts
- name: Python Tests
run: make test-py
- name: install next deps
working-directory: pyscript.core
run: npm i
- name: Run next tests
working-directory: pyscript.core
run: npm test
- name: Integration Tests
run: make test-integration-parallel
- name: Examples Tests
run: make test-examples
- uses: actions/upload-artifact@v3
with:
name: pyscript
path: |
pyscriptjs/build/
if-no-files-found: error
retention-days: 7
- uses: actions/upload-artifact@v3
if: success() || failure()
with:
name: test_results
path: pyscriptjs/test_results
if-no-files-found: error
eslint:
runs-on: ubuntu-latest-8core
defaults:
run:
working-directory: pyscriptjs
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install node
uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: npm install
run: npm install
- name: Eslint
run: npx eslint src -c .eslintrc.js

16
.github/workflows/test_report.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: Test Report
on:
workflow_run:
workflows: ['\[CI\] Build Unstable']
types:
- completed
jobs:
report:
runs-on: ubuntu-latest-8core
steps:
- uses: dorny/test-reporter@v1.6.0
with:
artifact: test_results
name: Test reports
path: "*.xml"
reporter: java-junit

3
.gitignore vendored
View File

@@ -138,3 +138,6 @@ dmypy.json
node_modules/ node_modules/
coverage/ coverage/
# junit xml for test results
test_results

View File

@@ -1,89 +1,60 @@
# This is the configuration for pre-commit, a local framework for managing pre-commit hooks # This is the configuration for pre-commit, a local framework for managing pre-commit hooks
# Check out the docs at: https://pre-commit.com/ # Check out the docs at: https://pre-commit.com/
ci:
skip: [eslint]
autoupdate_schedule: monthly
default_stages: [commit] default_stages: [commit]
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0 rev: v4.4.0
hooks: hooks:
- id: check-builtin-literals - id: check-builtin-literals
- id: check-case-conflict - id: check-case-conflict
- id: check-docstring-first - id: check-docstring-first
- id: check-executables-have-shebangs - id: check-executables-have-shebangs
- id: check-json - id: check-json
exclude: tsconfig.json exclude: tsconfig.json
- id: check-toml - id: check-toml
- id: check-xml - id: check-xml
- id: check-yaml - id: check-yaml
- id: detect-private-key - id: detect-private-key
- id: end-of-file-fixer - id: end-of-file-fixer
exclude: \.min\.js$ exclude: \.min\.js$
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 1.7.4 rev: v0.0.257
hooks: hooks:
- id: bandit - id: ruff
args: args: [--fix]
- --skip=B101,B201
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 22.12.0 rev: 23.1.0
hooks: hooks:
- id: black - id: black
- repo: https://github.com/codespell-project/codespell
rev: v2.2.4
hooks:
- id: codespell # See 'pyproject.toml' for args
additional_dependencies:
- tomli
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/hoodmane/pyscript-prettier-precommit
rev: v2.2.2 rev: "v3.0.0-alpha.6"
hooks: hooks:
- id: codespell # See 'setup.cfg' for args - id: prettier
args: [--tab-width, "4"]
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/pre-commit/mirrors-eslint
rev: 6.0.0 rev: v8.36.0
hooks: hooks:
- id: flake8 # See 'setup.cfg' for args - id: eslint
additional_dependencies: [flake8-bugbear, flake8-comprehensions] files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
types: [file]
- repo: https://github.com/pycqa/isort additional_dependencies:
rev: 5.11.1 - eslint@8.25.0
hooks: - typescript@5.0.4
- id: isort - "@typescript-eslint/eslint-plugin@5.58.0"
name: isort (python) - "@typescript-eslint/parser@5.58.0"
args: [--profile, black]
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
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: v3.3.1
hooks:
- id: pyupgrade
args:
- --py310-plus
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.29.0
hooks:
- id: eslint
files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
types: [file]
additional_dependencies:
- 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
# - repo: https://github.com/executablebooks/mdformat
# rev: 0.7.14 # Use the ref you want to point at
# hooks:
# - id: mdformat
# additional_dependencies:
# - mdformat-gfm
# - mdformat-myst
# - mdformat-black

5
.prettierignore Normal file
View File

@@ -0,0 +1,5 @@
ISSUE_TEMPLATE
*.min.*
package-lock.json
docs
examples/panel.html

View File

@@ -25,4 +25,4 @@ conda:
# Optionally declare the Python requirements required to build your docs # Optionally declare the Python requirements required to build your docs
python: python:
install: install:
- requirements: docs/requirements.txt - requirements: docs/requirements.txt

View File

@@ -4,17 +4,24 @@ Thank you for wanting to contribute to the PyScript project!
## Table of contents ## Table of contents
* [Code of Conduct](#code-of-conduct) - [Contributing to PyScript](#contributing-to-pyscript)
* [Contributing](#contributing) - [Table of contents](#table-of-contents)
* [Reporting bugs](#reporting-bugs) - [Code of Conduct](#code-of-conduct)
* [Reporting security issues](#reporting-security-issues) - [Contributing](#contributing)
* [Asking questions](#asking-questions) - [Reporting bugs](#reporting-bugs)
* [Setting up your local environment and developing](#setting-up-your-local-environment-and-developing) - [Creating useful issues](#creating-useful-issues)
* [Places to start](#places-to-start) - [Reporting security issues](#reporting-security-issues)
* [Submitting a change](#submitting-a-change) - [Asking questions](#asking-questions)
* [License terms for contributions](#license-terms-for-contributions) - [Setting up your local environment and developing](#setting-up-your-local-environment-and-developing)
* [Becoming a maintainer](#becoming-a-maintainer) - [Developing](#developing)
* [Trademarks](#trademarks) - [Rebasing changes](#rebasing-changes)
- [Building the docs](#building-the-docs)
- [Places to start](#places-to-start)
- [Setting up your local environment and developing](#setting-up-your-local-environment-and-developing)
- [Submitting a change](#submitting-a-change)
- [License terms for contributions](#license-terms-for-contributions)
- [Becoming a maintainer](#becoming-a-maintainer)
- [Trademarks](#trademarks)
# Code of Conduct # Code of Conduct
@@ -28,10 +35,10 @@ Bugs are tracked on the [project issues page](https://github.com/pyscript/pyscri
## Creating useful issues ## Creating useful issues
* Use a clear and descriptive title. - Use a clear and descriptive title.
* Describe the specific steps that reproduce the problem with as many details as possible so that someone can verify the issue. - Describe the specific steps that reproduce the problem with as many details as possible so that someone can verify the issue.
* Describe the behavior you observed, and the behavior you had expected. - Describe the behavior you observed, and the behavior you had expected.
* Include screenshots if they help make the issue clear. - Include screenshots if they help make the issue clear.
## Reporting security issues ## Reporting security issues
@@ -43,18 +50,18 @@ If you have questions about the project, using PyScript, or anything else, pleas
## Places to start ## Places to start
If you would like to contribute to PyScript, but you aren't sure where to begin, here are some suggestions. If you would like to contribute to PyScript, but you aren't sure where to begin, here are some suggestions:
* **Read over the existing documentation.** Are there things missing, or could they be clearer? Make some changes/additions to those documents. - **Read over the existing documentation.** Are there things missing, or could they be clearer? Make some changes/additions to those documents.
* **Review the open issues.** Are they clear? Can you reproduce them? You can add comments, clarifications, or additions to those issues. If you think you have an idea of how to address the issue, submit a fix! - **Review the open issues.** Are they clear? Can you reproduce them? You can add comments, clarifications, or additions to those issues. If you think you have an idea of how to address the issue, submit a fix!
* **Look over the open pull requests.** Do you have comments or suggestions for the proposed changes? Add them. - **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. - **Check out the examples.** Is there a use case that would be good to have sample code for? Create an example for it.
## Setting up your local environment and developing ## Setting up your local environment and developing
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. 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.
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. You can also read about PyScript's [development process](https://docs.pyscript.net/latest/development/developing.html) to learn how to contribute code to PyScript, how to run tests and what's the PR etiquette of the community!
## License terms for contributions ## License terms for contributions
@@ -69,5 +76,6 @@ Contributors are invited to be maintainers of the project by demonstrating good
The Project abides by the Organization's [trademark policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md). The Project abides by the Organization's [trademark policy](https://github.com/pyscript/governance/blob/main/TRADEMARKS.md).
--- ---
Part of MVG-0.1-beta. Part of MVG-0.1-beta.
Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/). Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/).

View File

@@ -41,5 +41,6 @@ Any names, trademarks, logos, or goodwill developed by and associated with the P
Amendments to this governance policy may be made by affirmative vote of 2/3 of all Maintainers, with approval by the Organization's Steering Committee. Amendments to this governance policy may be made by affirmative vote of 2/3 of all Maintainers, with approval by the Organization's Steering Committee.
--- ---
Part of MVG-0.1-beta. Part of MVG-0.1-beta.
Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/). Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/).

View File

@@ -9,15 +9,16 @@ This document lists the Maintainers of the Project. Maintainers may be added onc
| Philipp Rudiger | Anaconda, Inc | | Philipp Rudiger | Anaconda, Inc |
| Peter Wang | Anaconda, Inc | | Peter Wang | Anaconda, Inc |
| Kevin Goldsmith | Anaconda, Inc | | Kevin Goldsmith | Anaconda, Inc |
| Mariana Meireles | Anaconda, Inc | | Mariana Meireles | |
| Nicholas H.Tollervey | Anaconda, Inc | | Nicholas H.Tollervey | Anaconda, Inc |
| Madhur Tandon | Anaconda, Inc | | Madhur Tandon | Anaconda, Inc |
| Ted Patrick | Anaconda, Inc | | Ted Patrick | Anaconda, Inc |
| Jeff Glass | --- | | Jeff Glass | |
| Paul Everitt | --- | | Paul Everitt | |
| Fabio Rosado | --- | | Fabio Rosado | Anaconda, Inc |
| Andrea Giammarchi | Anaconda, Inc |
______________________________________________________________________ ---
Part of MVG-0.1-beta. Part of MVG-0.1-beta.
Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/). Made with love by GitHub. Licensed under the [CC-BY 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/).

View File

@@ -11,21 +11,24 @@ To get started see the [getting started tutorial](docs/tutorials/getting-started
For examples see [here](examples). For examples see [here](examples).
### Longer Version ### Longer Version
PyScript is a meta project that aims to combine multiple open technologies into a framework that allows users to create sophisticated browser applications with Python. It integrates seamlessly with the way the DOM works in the browser and allows users to add Python logic in a way that feels natural both to web and Python developers. PyScript is a meta project that aims to combine multiple open technologies into a framework that allows users to create sophisticated browser applications with Python. It integrates seamlessly with the way the DOM works in the browser and allows users to add Python logic in a way that feels natural both to web and Python developers.
## Try PyScript ## Try PyScript
To try PyScript, import the appropriate pyscript files into the ```<head>``` tag of your html page with: To try PyScript, import the appropriate pyscript files into the `<head>` tag of your html page with:
```html ```html
<head> <head>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head> </head>
``` ```
You can then use PyScript components in your html page. PyScript currently implements the following elements: You can then use PyScript components in your html page. PyScript currently implements the following elements:
* `<py-script>`: can be used to define python code that is executable within the web page. The element itself is not rendered to the page and is only used to add logic - `<py-script>`: can be used to define python code that is executable within the web page. The element itself is not rendered to the page and is only used to add logic
* `<py-repl>`: creates a REPL component that is rendered to the page as a code editor and allows users to write executable code - `<py-repl>`: creates a REPL component that is rendered to the page as a code editor and allows users to write executable code
Check out the [the examples directory](examples) folder for more examples on how to use it, all you need to do is open them in Chrome. Check out the [the examples directory](examples) folder for more examples on how to use it, all you need to do is open them in Chrome.
@@ -33,18 +36,20 @@ Check out the [the examples directory](examples) folder for more examples on how
Read the [contributing guide](CONTRIBUTING.md) to learn about our development process, reporting bugs and improvements, creating issues and asking questions. Read the [contributing guide](CONTRIBUTING.md) to learn about our development process, reporting bugs and improvements, creating issues and asking questions.
Check out the [developing process](https://docs.pyscript.net/latest/development/developing.html) documentation for more information on how to setup your development environment.
## Resources ## Resources
* [Official docs](https://docs.pyscript.net) - [Official docs](https://docs.pyscript.net)
* [Discussion board](https://community.anaconda.cloud/c/tech-topics/pyscript) - [Discussion board](https://community.anaconda.cloud/c/tech-topics/pyscript)
* [Home Page](https://pyscript.net/) - [Home Page](https://pyscript.net/)
* [Blog Post](https://engineering.anaconda.com/2022/04/welcome-pyscript.html) - [Blog Post](https://engineering.anaconda.com/2022/04/welcome-pyscript.html)
* [Discord Channel](https://discord.gg/BYB2kvyFwm) - [Discord Channel](https://discord.gg/BYB2kvyFwm)
## Notes ## Notes
* This is an extremely experimental project, so expect things to break! - This is an extremely experimental project, so expect things to break!
* PyScript has been only tested on Chrome at the moment. - PyScript has been only tested on Chrome at the moment.
## Governance ## Governance

View File

@@ -3,7 +3,8 @@
This page is meant for troubleshooting common problems with PyScript. This page is meant for troubleshooting common problems with PyScript.
## Table of contents: ## Table of contents:
* [Make Setup](#make-setup)
- [Make Setup](#make-setup)
## Make setup ## Make setup

View File

@@ -30,7 +30,7 @@ shell:
@export CONDA_ENV_PROMPT='<{name}>' @export CONDA_ENV_PROMPT='<{name}>'
@echo 'conda activate $(env)' @echo 'conda activate $(env)'
htmlserve: htmlserve: html
@echo 'visit docs at http://localhost:8080' @echo 'visit docs at http://localhost:8080'
python -m http.server -d "$(BUILDDIR)/html/" 8080 python -m http.server -d "$(BUILDDIR)/html/" 8080

View File

@@ -29,3 +29,26 @@ static files like templates and themes, to build the static end result.
### Build ### Build
To learn how to build the docs, head over the [CONTRIBUTING](../CONTRIBUTING.md) page. To learn how to build the docs, head over the [CONTRIBUTING](../CONTRIBUTING.md) page.
## Cross-referencing
You can link to other pages in the documentation by using the `{doc}` role. For example, to link to the `docs/README.md` file, you would use:
```markdown
{doc}`docs/README.md`
```
You can also cross-reference the python glossary by using the `{term}` role. For example, to link to the `iterable` term, you would use:
```markdown
{term}`iterable`
```
You can also cross-reference functions, methods or data attributes by using the `{attr}` for example:
```markdown
{py:func}`repr`
```
This would link to the `repr` function in the python builtins.

View File

@@ -3,6 +3,10 @@
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
<style> <style>
h1 {
color: #459db9;
}
.pulse { .pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
} }

4
docs/_static/s3_error.html vendored Normal file
View File

@@ -0,0 +1,4 @@
<html><head><meta http-equiv="refresh" content="5; URL='/latest/'" /></head><body>
<h1>404 - File not found</h1>
<p>You will be redirected to the latest documentation in 5 seconds.</p>
</body></html>

95
docs/changelog.md Normal file
View File

@@ -0,0 +1,95 @@
# Release Notes
2023.XX.X
=========
Features
--------
- Added the `xterm` attribute to `py-config`. When set to `True` or `xterm`, an (output-only) [xterm.js](http://xtermjs.org/) terminal will be used in place of the default py-terminal.
- The default version of Pyodide is now `0.23.2`. See the [Pyodide Changelog](https://pyodide.org/en/stable/project/changelog.html#version-0-23-2) for a detailed list of changes.
- Added the `@when` decorator for attaching Python functions as event handlers
- The `py-mount` attribute on HTML elements has been deprecated, and will be removed in a future release.
### Runtime py- attributes
- Added logic to react to `py-*` attributes changes, removal, `py-*` attributes added to already live nodes but also `py-*` attributes added or defined via injected nodes (either appended or via `innerHTML` operations). ([#1435](https://github.com/pyscript/pyscript/pull/1435))
### &lt;script type="py"&gt;
- Added the ability to optionally use `<script type="py">`, `<script type="pyscript">` or `<script type="py-script">` instead of a `<py-script>` custom element, in order to tackle cases where the content of the `<py-script>` tag, inevitably parsed by browsers, could accidentally contain *HTML* able to break the surrounding page layout. ([#1396](https://github.com/pyscript/pyscript/pull/1396))
### &lt;py-terminal&gt;
- Added a `docked` field and attribute for the `<py-terminal>` custom element, enabled by default when the terminal is in `auto` mode, and able to dock the terminal at the bottom of the page with auto scroll on new code execution.
### &lt;py-script&gt;
- Restored the `output` attribute of `py-script` tags to route `sys.stdout` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
- Added a `stderr` attribute of `py-script` tags to route `sys.stderr` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
### &lt;py-repl&gt;
- The `output` attribute of `py-repl` tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()`
- Added a `stderr` attribute of `py-repl` tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
- Resored the `output-mode` attribute of `py-repl` tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results.
- Load code from the attribute src of py-repl and preload it into the corresponding py-repl tag by use the attribute `str` in your `py-repl` tag([#1292](https://github.com/pyscript/pyscript/pull/1292))
- &lt;py-repl&gt; elements now have a `getPySrc()` method, which returns the code inside the REPL as a string.([#1516](https://github.com/pyscript/pyscript/pull/1292))
### Plugins
- Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a `py-repl` tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
### Web worker support
- introduced the new experimental `execution_thread` config option: if you set `execution_thread = "worker"`, the python interpreter runs inside a web worker
- worker support is still **very** experimental: not everything works, use it at your own risk
Bug fixes
---------
- Fixes [#1280](https://github.com/pyscript/pyscript/issues/1280), which describes the errors on the PyRepl tests related to having auto-gen tags that shouldn't be there.
Enhancements
------------
- Py-REPL tests now run on both osx and non osx OSs
- migrated from *rollup* to *esbuild* to create artifacts
- updated `@codemirror` dependency to its latest
Docs
----
- Add docs for event handlers
2023.03.1
=========
Features
--------
Bug fixes
---------
- Fixed an issue where `pyscript` would not be available when using the minified version of PyScript. ([#1054](https://github.com/pyscript/pyscript/pull/1054))
- Fixed missing closing tag when rendering an image with `display`. ([#1058](https://github.com/pyscript/pyscript/pull/1058))
- Fixed a bug where Python plugins methods were being executed twice. ([#1064](https://github.com/pyscript/pyscript/pull/1064))
Enhancements
------------
- When adding a `py-` attribute to an element but didn't added an `id` attribute, PyScript will now generate a random ID for the element instead of throwing an error which caused the splash screen to not shutdown. ([#1122](https://github.com/pyscript/pyscript/pull/1122))
- You can now disable the splashscreen by setting `enabled = false` in your `py-config` under the `[splashscreen]` configuration section. ([#1138](https://github.com/pyscript/pyscript/pull/1138))
Documentation
-------------
- Fixed 'Direct usage of document is deprecated' warning in the getting started guide. ([#1052](https://github.com/pyscript/pyscript/pull/1052))
- Added reference documentation for the `py-splashscreen` plugin ([#1138](https://github.com/pyscript/pyscript/pull/1138))
- Adds doc for installing tests ([#1156](https://github.com/pyscript/pyscript/pull/1156))
- Adds docs for custom Pyscript attributes (`py-*`) that allow you to add event listeners to an element ([#1125](https://github.com/pyscript/pyscript/pull/1125))
Deprecations and Removals
-------------------------
- The py-config `runtimes` to specify an interpreter has been deprecated. The `interpreters` config should be used instead. ([#1082](https://github.com/pyscript/pyscript/pull/1082))
- The attributes `pys-onClick` and `pys-onKeyDown` have been deprecated, but the warning was only shown in the console. An alert banner will now be shown on the page if the attributes are used. They will be removed in the next release. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
- The pyscript elements `py-button`, `py-inputbox`, `py-box` and `py-title` have now completed their deprecation cycle and have been removed. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
- The attributes `pys-onClick` and `pys-onKeyDown` have been removed. Use `py-click` and `py-keydown` instead ([#1361](https://github.com/pyscript/pyscript/pull/1361))

View File

@@ -19,7 +19,7 @@ import os
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = "PyScript" project = "PyScript"
copyright = "(c) 2022, Anaconda, Inc." copyright = "(c) 2023, Anaconda, Inc."
author = "Anaconda, Inc." author = "Anaconda, Inc."
language = "en" language = "en"
@@ -36,8 +36,13 @@ extensions = [
"sphinx_sitemap", "sphinx_sitemap",
"sphinxemoji.sphinxemoji", "sphinxemoji.sphinxemoji",
"sphinxcontrib.youtube", "sphinxcontrib.youtube",
"sphinx.ext.intersphinx",
] ]
intersphinx_mapping = {
"python": ("https://docs.python.org/3.10", None),
}
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"] templates_path = ["_templates"]

View File

@@ -28,3 +28,11 @@ showWarning(`
</p> </p>
`, "html") `, "html")
``` ```
## Deprecation History
This section tracks deprecations of specific features, both for historical record and to help the development team remember to actually remove deprecated features in future releases.
|Attribute/Object/Functionality|Deprecated In|Removed In|
|-|-|-|
|`py-mount` attribute | (Release following 2023.03.1) | -|

View File

@@ -1,6 +1,6 @@
# Developing Process # Development 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). This document is intended to help you get started in developing software for the PyScript project. It assumes that you have [a working development environment](setting-up-environment.md). It also assumes you have a remote named `upstream` pointing to PyScript's repository and one named `origin` pointing to your own repository.
* First, make sure you are using the latest version of the pyscript main branch * First, make sure you are using the latest version of the pyscript main branch
@@ -34,29 +34,57 @@ pre-commit install
git checkout -b <your branch name> git checkout -b <your branch name>
``` ```
* Work on your change * Work on your changes
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **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). &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **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 * Run tests before pushing the changes
``` ```
make tests make test
``` ```
* 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 To learn more about tests please refer to the session [Quick guide to pytest](## Quick guide to pytest).
* 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 the linter you should fix it <u>**before**</u> creating a pull request.
## Rebasing changes #### 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). Sometimes you might be asked to rebase the main branch into your local 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! If you need help with anything, feel free to reach out and ask for help!
## pytest quick guide ## Updating the changelog
We make a heavy usage of `pytest`. Here is a quick guide and collection of As you work on your changes, please update the changelog file `changelog.md` with a short description of the changes you made. This will help us keep track of what has changed in each release.
You can look at the [changelog](../changelog.md) for examples on how to add your changes to the changelog. But here's a quick example:
```
2023.02.01
=========
Bug fixes
---------
- Fixed a bug that was causing the app to crash when you tried to do something #PR_NUMBER
Enhancements
------------
- Made awesome new feature #PR_NUMBER
Documentation
-------------
- Added a new section to the docs #PR_NUMBER
```
## Quick guide to pytest
We make heavy usage of `pytest`. Here is a quick guide and collection of
useful options: useful options:
- To run all tests in the current directory and subdirectories: `pytest` - To run all tests in the current directory and subdirectories: `pytest`
@@ -79,7 +107,7 @@ useful options:
- `-k 'foo and not bar'` - `-k 'foo and not bar'`
## Running integration tests under pytest ### Running integration tests under pytest
`make test` is useful to run all the tests, but during the development is `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 useful to have more control on how tests are run. The following guide assumes
@@ -118,8 +146,8 @@ $ pytest test_01_basic.py -k test_pyscript_hello -s
[ 0.00 page.goto ] pyscript_hello.html [ 0.00 page.goto ] pyscript_hello.html
[ 0.01 request ] 200 - fake_server - http://fake_server/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.17 console.info ] [py-loader] Downloading pyodide-x.y.z...
[ 0.18 request ] 200 - CACHED - https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js [ 0.18 request ] 200 - CACHED - https://cdn.jsdelivr.net/pyodide/vx.y.z/full/pyodide.js
... ...
[ 3.59 console.info ] [pyscript/main] PyScript page fully initialized [ 3.59 console.info ] [pyscript/main] PyScript page fully initialized
[ 3.60 console.log ] hello pyscript [ 3.60 console.log ] hello pyscript
@@ -162,8 +190,18 @@ $ pytest test_01_basic.py -k test_pyscript_hello -s --dev
`--dev` implies `--headed --no-fake-server`. In addition, it also `--dev` implies `--headed --no-fake-server`. In addition, it also
automatically open chrome dev tools. automatically open chrome dev tools.
#### To run only main thread or worker tests
#### Fake server, HTTP cache By default, we run each test twice: one with `execution_thread = "main"` and
one with `execution_thread = "worker"`. If you want to run only half of them,
you can use `-m`:
```
$ pytest -m main # run only the tests in the main thread
$ pytest -m worker # ron only the tests in the web worker
```
## Fake server, HTTP cache
By default, our test machinery uses a playwright router which intercepts and 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 cache HTTP requests, so that for example you don't have to download pyodide
@@ -179,25 +217,6 @@ If you want to temporarily disable the cache, the easiest thing is to use
If you want to clear the cache, you can use the special option If you want to clear the cache, you can use the special option
`--clear-http-cache`: `--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 **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 explicitly specify `tests/integration` from the command line. This is due to
how `pytest` decides to search for and load the various `conftest.py`. how `pytest` decides to search for and load the various `conftest.py`.

View File

@@ -1,69 +1,285 @@
# Setting up your development environment # Setting up your development environment
* Fork the repository - [quicklink](https://github.com/pyscript/pyscript/fork) These steps will help you set up your development environment. We suggest completing each step before going to the next step, as some parts have dependencies on previous commands.
* Clone your fork of the project ## Prepare your repository
``` * Create a fork of the [PyScript github repository](https://github.com/pyscript/pyscript/fork) to your github.
* In your development machine, clone your fork of PyScript. Use this command in your terminal.
```sh
git clone https://github.com/<your username>/pyscript git clone https://github.com/<your username>/pyscript
``` ```
* Add the original project as your upstream (this will allow you to pull the latest changes) * With the following command, add the original project as your upstream. This will allow you to pull the latest changes.
```sh ```sh
git remote add upstream https://github.com/pyscript/pyscript.git
git pull upstream main
```
* If the above fails, try this alternative:
```sh
git remote remove upstream
git remote add upstream git@github.com:pyscript/pyscript.git git remote add upstream git@github.com:pyscript/pyscript.git
git pull upstream main
``` ```
* cd into the `pyscriptjs` folder using the line below in your terminal (if your terminal is already in pyscript then use **cd pyscriptjs** instead) ## Install the dependencies
``` * change directory into `pyscriptjs` using this command:
```sh
cd pyscript/pyscriptjs cd pyscript/pyscriptjs
``` ```
* Install the dependencies with the command below (you must have node >=16) We need to ensure that we have installed `conda`, `nodejs` >= 16 and `make`, before we can continue.
* Install `conda` by downloading one of the following packages that include it [MiniConda](https://docs.conda.io/en/latest/miniconda.html) or [Anaconda](https://www.anaconda.com/download/).
* Install `nodejs` with at least version 16. This can be downloaded at [https://nodejs.org](https://nodejs.org)
* Ensure that `make` is available on your system:
* *Linux*. `make` is usually installed by default in most Linux distributions. In the case it is not installed, run the terminal command `sudo apt install make`.
* *OS X*. Install Apple Developer Tools. `make` is included in this package.
* *Windows*. It is recommended to use either Windows Subsystem for Linux (WSL) or GNUWin32 for installing `make`. Instructions can be found [in this StackOverflow question](https://stackoverflow.com/questions/32127524/how-to-install-and-use-make-in-windows).
* The following command will download and install the rest of the PyScript dependencies:
``` ```
make setup make setup
``` ```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **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 * **NOTE**: If `make setup` gives an error on an incompatible version for node or npm, please refer to [troubleshooting](https://github.com/pyscript/pyscript/blob/main/TROUBLESHOOTING.md).
## Activating the environment
* After the above `make setup` command is completed, it will print out the command for activating the environment using the following format. Use this to work on the development environment:
```
conda activate <environment name>
```
## Deactivating the environment
* To deactivate the environment, use the following command:
```
conda deactivate
```
# Running PyScript examples server
The examples server is used to view and edit the example files.
* change directory into `pyscriptjs` using this command:
```sh
cd pyscript/pyscriptjs
```
* To build the examples, run this command:
``` ```
make examples 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. * To serve the examples, run this command:
```sh
python -m http.server 8080 --directory examples
```
* Alternately, you can also run this command if conda is not activated:
```sh
conda run -p <environment name> python -m http.server 8080 --directory examples
```
* You can access the examples server by visiting the following url in your browser: [http://localhost:8080](http://localhost:8080)
# Running the PyScript development server
The PyScript development server will regularly check for any changes in the src directory. If any changes were detected, the server will rebuild itself to reflect the changes. This is useful for development with PyScript.
* change directory into `pyscriptjs` using this command:
```sh
cd pyscript/pyscriptjs
```
* Use the following command to build and run the PyScript dev server.
``` ```
npm run dev npm run dev
``` ```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **NOTE**: To access your local build paste `http://localhost:8080` into your browser
* You can access the PyScript development server by visiting the following url in your browser: [http://localhost:8080](http://localhost:8080)
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 the test environment
A key to good development is to perform tests before sending a Pull Request for your changes.
## Setting up and building the docs ## Install the dependencies
To build the documentation locally first make sure you are in the `docs` directory. * change directory into `pyscriptjs` using this command:
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: ```sh
cd pyscript/pyscriptjs
```
* The following command will download the dependencies needed for running the tests:
``` ```
make setup make setup
``` ```
Use `conda activate $environment_name` to activate your environment. * If you are not using a conda environment, or wish to install the dependencies manually, here are the packages needed:
* `pillow`
* `requests`
* `numpy`
* `playwright`
* `pytest-playwright`. Note that this is only available as a `pip` package.
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. ## Activating the environment
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. * After the above `make setup` command is completed, it will print out the command for activating the environment using the following format:
Once done, initialize a server to check your work: ```
conda activate <environment name>
```
## Deactivating the environment
* To deactivate the environment, use the following command:
```
conda deactivate
```
## Running the tests
* After setting up the test environment and while the environment is activated, you can run the tests with the following command:
```
make test
```
For more information about PyScript's testing framework, head over to the [development process](developing.md) page.
# Setting up your documentation environment
The documentation environment is separate from the development environment. It is used for updating and reviewing the documentation before deployment.
## Installing the dependencies
* change directory into the `docs` using this command:
```sh
cd pyscript/docs
```
* The following command will download, install the dependencies, and create the environment for you:
```
make setup
```
(activating-documentation-environment)=
## Activating the environment
* After the above `make setup` command is completed, it will print out the command for activating the environment using the following format:
```
conda activate <docs environment name>
```
Note that the docs environment path is different from the developer's environment path.
## Deactivating the environment
* To deactivate the environment, use the following command:
```
conda deactivate
```
## Contributing to the documentation
* Before sending a pull request, we recommend that your documentation conforms with [PyScript's code of conduct](https://github.com/pyscript/governance/blob/main/CODE-OF-CONDUCT.md) and with the general principles of [Diataxis](https://diataxis.fr/). 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). This is similar to Markdown but with some addons to create the documentation infrastructure.
## Reviewing your work
* Before sending a Pull Request, review your work by starting the documentation server. To do this, use the following command:
``` ```
make livehtml make livehtml
``` ```
Visible here: [http://127.0.0.1:8000](http://127.0.0.1:8000) You can visit the documentation server by opening a browser and visiting [http://127.0.0.1:8000](http://127.0.0.1:8000).
* Alternately, you can open a static documentation server. Unlike the above, this will not automatically update any changes done after running this server. To see the changes done, you will need to manually stop and restart the server. To do this, use the following command:
```
make htmlserve
```
You can visit the documentation server by opening a browser and visiting [http://127.0.0.1:8080](http://127.0.0.1:8080).
* To stop either server, press `ctrl+C` or `command+C` while the shell running the command is active.
* Note: If the above make commands failed, you need to activate the documentation environment first before running any of the commands. Refer to [Activating the environment](#activating-documentation-environment) section above.
# PyScript Demonstrator
A simple webapp to demonstrate the capabilities of PyScript.
## Getting started
1. If you don't already have Node.js, install it. The official installer for the
LTS version of Node is available from [nodejs.org](https://nodejs.org/).
2. If you don't already have Rollup, install it. Rollup can be installed as a
global resource using:
$ npm install --global rollup
3. Install the demo apps requirements:
$ npm install
4. Start the server:
$ npm run dev
This will compile the resources for the app, and start the development server.
5. When the compilation completes, it will display something like:
Your application is ready~! 🚀
- Local: http://localhost:8080
- Network: Add `--host` to expose
────────────────── LOGS ──────────────────
Once this is visible, open a browser at
[http://localhost:8080](http://localhost:8080). This will provide a list of
demos that you can run.
## More information
For more information:
* [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)
We use Discord as the main place for our discussions

View File

@@ -10,6 +10,7 @@ dependencies:
- sphinx-copybutton - sphinx-copybutton
- sphinx-design - sphinx-design
- sphinx-togglebutton - sphinx-togglebutton
- nodejs=16
- pip: - pip:
- sphinxemoji - sphinxemoji

View File

@@ -0,0 +1,225 @@
# Creating custom pyscript plugins
Pyscript has a few built-in plugins, but you can also create your own ones. This guide will show you how to develop both Javascript and Python plugins.
```{warning}
Pyscript plugins are currently under active development. The API is likely to go through breaking changes between releases.
```
You can add your custom plugins to the `<py-config>` tag on your page. For example:
```html
<py-config>
plugins = ["http://example.com/hello-world.py"]
</py-config>
```
Currently, only single files with the extension `.py` and `.js` files can be used as plugins.
## Python plugins
Python plugins allow you to write plugins in pure Python. We first need to import `Plugin` from `pyscript` and create a new instance of it.
```python
from pyscript import Plugin
plugin = Plugin("PyHelloWorld")
```
We can now create a new class containing our plugin code to add the text "Hello World" to the page.
```python
from pyscript import Plugin, js
plugin = Plugin("PyHelloWorld")
class PyHelloWorld:
def __init__(self, element):
self.element = element
def connect(self):
self.element.innerHTML = "<h1>Hello World!</h1>"
```
Let's now create our `index.html` page and add the plugin.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Python Plugin</title>
<link rel="stylesheet" href="https://pyscript.net/unstable/pyscript.css" />
<script defer src="https://pyscript.net/unstable/pyscript.js"></script>
</head>
<body>
<py-config>
plugins = ["./hello-world.py"]
</py-config>
</body>
</html>
```
Now we need to start a live server to serve our page. You can use Python's `http.server` module for this.
```bash
python -m http.server
```
Now you can open your browser and go to `http://localhost:8000` to see the page. You might be surprised that the text "Hello World" is not on the page. This is because we need to do a few more things to make our plugin work.
First, we must create a custom element that our plugin will use. We can use a decorator in our `PyHelloWorld` class.
```python
from pyscript import Plugin, js
plugin = Plugin("PyHelloWorld")
@plugin.register_custom_element("py-hello-world")
class PyHelloWorld:
def __init__(self, element):
self.element = element
def connect(self):
self.element.innerHTML = "<div id='hello'>Hello World!</div>"
```
Now that we have registered our custom element, we can use the custom tag `<py-hello-world>` to add our plugin 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>Python Plugin</title>
<link rel="stylesheet" href="https://pyscript.net/unstable/pyscript.css" />
<script defer src="https://pyscript.net/unstable/pyscript.js"></script>
</head>
<body>
<py-config>
plugins = ["./hello-world.py"]
</py-config>
<py-hello-world></py-hello-world>
</body>
</html>
```
Now, if you go to `http://localhost:8000` you should see the text "Hello World" on the page.
Writing plugins in Python is an excellent way if you want to use PyScript's API's. However, if you want to write plugins in Javascript, you can do that too.
## Javascript plugins
Javascript plugins need to have a specific structure to be loaded by PyScript. The plugin export a default class with the following method, which may implement any, all, or none of the [Plugin lifecycle methods](https://github.com/pyscript/pyscript/blob/main/pyscriptjs/src/plugin.ts#L9-L65). These method will be called at the corresponding points in lifecycle of PyScript as it loads, configures itself and its Python interpreter, and executes `<py-script>` and `<py-repl>` tags.
```{note}
You need to specify the file extension `.js` when adding your custom plugin to the `<py-config>` tag.
```
### Creating a Hello World plugin
Let's create a simple plugin that will add the text "Hello World" to the page. We will create a `hello-world.js` file and write the plugin class.
```js
export default class HelloWorldPlugin {
afterStartup(runtime) {
// Code goes here
}
}
```
Now we need to add the code that will add the text to the page.
```js
export default class HelloWorldPlugin {
afterStartup(runtime) {
const elem = document.createElement("h1");
elem.innerText = "Hello World";
document.body.appendChild(elem);
}
}
```
Finally, we need to add the plugin to our page's `<py-config>` tag.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Javascript Plugin</title>
<link rel="stylesheet" href="https://pyscript.net/unstable/pyscript.css" />
<script defer src="https://pyscript.net/unstable/pyscript.js"></script>
</head>
<body>
<py-config>
plugins = ["./hello-world.js"]
</py-config>
</body>
</html>
```
Now we need to start a live server to serve our page. You can use Python's `http.server` module for this.
```bash
python -m http.server
```
Now you can open your browser and go to `http://localhost:8000` to see the page. You should see the text "Hello World" on the page.
```{note}
Because we are using a local file, you must start a live server. Otherwise, Pyscript will not be able to fetch the file.
```
### Expanding the Hello World plugin
As you can see, we could build all our plugin logic inside the `afterStartup` method. You may also want to create a custom html element for your plugin. Let's see how we can do that.
First, we need to create a custom html element. Let's start by creating our `PyHelloWorld` class that extends the `HTMLElement` class.
```js
class PyHelloWorld extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = `<h1>Hello, world!</h1>`;
this.mount_name = this.id;
}
}
```
We can now register our custom element in the `afterStartup` method of our `HelloWorldPlugin` class. We will also add the custom tag `py-hello-world` to the page.
```js
export default class HelloWorldPlugin {
afterStartup(runtime) {
// Create a custom element called <py-hello-world>
customElements.define("py-hello-world", PyHelloWorld);
// Add the custom element to the page so we can see it
const elem = document.createElement('py-hello-world');
document.body.append(elem);
}
}
```
Now we can open our page and see the custom element on the page.
By now, you should have a good idea for creating a custom plugin. Also, how powerful it can be to create custom elements that other users could use in their PyScript pages.

View File

@@ -0,0 +1,179 @@
# Event handlers in PyScript
PyScript offer two ways to subscribe to Javascript event handlers:
## Subscribe to event with `py-*` attributes
The value of the attribute contains python code which will be executed when the event is fired. A very common pattern is to call a function which does further work, for example:
```html
<button id="noParam" py-click="say_hello_no_param()">
No Event - No Params py-click
</button>
<button id="withParam" py-click="say_hello_with_param('World')">
No Event - With Params py-click
</button>
```
```python
<py-script>
def say_hello_no_param():
print("Hello!")
def say_hello_with_param(name):
print("Hello " + name + "!")
</py-script>
```
Note that py-\* attributes need a _function call_
Supported py-\* attributes can be seen in the [PyScript API reference](<[../api-reference.md](https://github.com/pyscript/pyscript/blob/66b57bf812dcc472ed6ffee075ace5ced89bbc7c/pyscriptjs/src/components/pyscript.ts#L119-L260)>).
## Subscribe to event with `addEventListener`
You can also subscribe to an event using the `addEventListener` method of the DOM element. This is useful if you want to pass event object to the event handler.
```html
<button id="two">add_event_listener passes event</button>
```
```python
<py-script>
from js import console, document
from pyodide.ffi.wrappers import add_event_listener
def hello_args(*args):
console.log(f"Hi! I got some args! {args}")
add_event_listener(document.getElementById("two"), "click", hello_args)
</py-script>
```
or using the `addEventListener` method of the DOM element:
```html
<button id="three">add_event_listener passes event</button>
```
```python
<py-script>
from js import console, document
from pyodide.ffi import create_proxy
def hello_args(*args):
console.log(f"Hi! I got some args! {args}")
document.getElementById("three").addEventListener("click", create_proxy(hello_args))
</py-script>
```
or using the PyScript Element class:
```html
<button id="four">add_event_listener passes event</button>
```
```python
<py-script>
from js import console
from pyodide.ffi import create_proxy
def hello_args(*args):
console.log(f"Hi! I got some args! {args}")
Element("four").element.addEventListener("click", create_proxy(hello_args))
</py-script>
```
## JavaScript to PyScript and From PyScript to JavaScript
If you're wondering about how to pass objects from JavaScript to PyScript and/or the other way around head over to the [Passing Objects](passing-objects.md) page.
### 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>
```

View File

@@ -1,6 +1,6 @@
# How to make HTTP requests using `PyScript`, in pure Python # How to make HTTP requests using `PyScript`, in pure Python
[Pyodide](https://pyodide.org), the runtime that underlies `PyScript`, does not have the `requests` module [Pyodide](https://pyodide.org), the interpreter that underlies `PyScript`, does not have the `requests` module
(or other similar modules) available by default, which are traditionally used to make HTTP requests in Python. (or other similar modules) available by default, which are traditionally used to make HTTP requests in Python.
However, it is possible to make HTTP requests in Pyodide using the modern `JavaScript` `fetch` API However, it is possible to make HTTP requests in Pyodide using the modern `JavaScript` `fetch` API
([docs](https://developer.mozilla.org/en-US/docs/Web/API/fetch)). This example shows how to make common HTTP request ([docs](https://developer.mozilla.org/en-US/docs/Web/API/fetch)). This example shows how to make common HTTP request
@@ -35,7 +35,8 @@ for dealing with the response, such as `json()` or `status`. See the
[FetchResponse documentation](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.FetchResponse) [FetchResponse documentation](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.FetchResponse)
for more information. for more information.
# Example ## Example
We will make async HTTP requests to [JSONPlaceholder](https://jsonplaceholder.typicode.com/)'s fake API using `pyfetch`. We will make async HTTP requests to [JSONPlaceholder](https://jsonplaceholder.typicode.com/)'s fake API using `pyfetch`.
First we write a helper function in pure Python that makes a request and returns the response. This function First we write a helper function in pure Python that makes a request and returns the response. This function
makes it easier to make specific types of requests with the most common parameters. makes it easier to make specific types of requests with the most common parameters.
@@ -70,6 +71,7 @@ async def request(url: str, method: str = "GET", body: Optional[str] = None,
response = await pyfetch(url, **kwargs) response = await pyfetch(url, **kwargs)
return response return response
``` ```
This function is a wrapper for `pyfetch`, which is a wrapper for the `fetch` API. It is a coroutine function, This function is a wrapper for `pyfetch`, which is a wrapper for the `fetch` API. It is a coroutine function,
so it must be awaited. It also has type hints, which are not required, but are useful for IDEs and other tools. so it must be awaited. It also has type hints, which are not required, but are useful for IDEs and other tools.
The basic idea is that the `PyScript` will import and call this function, then await the response. Therefore, The basic idea is that the `PyScript` will import and call this function, then await the response. Therefore,
@@ -160,7 +162,8 @@ concluding html code.
The very first thing to notice is the `py-config` tag. This tag is used to import Python files into the `PyScript`. 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. 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. ### `py-script` tag for making async HTTP requests
Next, the `py-script` tag contains the actual Python code where we import `asyncio` and `json`, Next, the `py-script` tag contains the actual Python code where we import `asyncio` and `json`,
which are required or helpful for the `request` function. which are required or helpful for the `request` function.
The `# GET`, `# POST`, `# PUT`, `# DELETE` blocks show examples of how to use the `request` function to make basic The `# GET`, `# POST`, `# PUT`, `# DELETE` blocks show examples of how to use the `request` function to make basic
@@ -169,6 +172,7 @@ HTTP requests. The `await` keyword is required not only for the `request` functi
faster ones. faster ones.
### HTTP Requests ### HTTP Requests
HTTP requests are a very common way to communicate with a server. They are used for everything from getting data from HTTP requests are a very common way to communicate with a server. They are used for everything from getting data from
a database, to sending emails, to authorization, and more. Due to safety concerns, files loaded from the a database, to sending emails, to authorization, and more. Due to safety concerns, files loaded from the
local file system are not accessible by `PyScript`. Therefore, the proper way to load data into `PyScript` is also local file system are not accessible by `PyScript`. Therefore, the proper way to load data into `PyScript` is also
@@ -182,31 +186,38 @@ function or to `pyfetch`. See the
HTTP requests are defined by standards-setting bodies in [RFC 1945](https://www.rfc-editor.org/info/rfc1945) and HTTP requests are defined by standards-setting bodies in [RFC 1945](https://www.rfc-editor.org/info/rfc1945) and
[RFC 9110](https://www.rfc-editor.org/info/rfc9110). [RFC 9110](https://www.rfc-editor.org/info/rfc9110).
# Conclusion ## Conclusion
This tutorial demonstrates how to make HTTP requests using `pyfetch` and the `FetchResponse` objects. Importing Python This tutorial demonstrates how to make HTTP requests using `pyfetch` and the `FetchResponse` objects. Importing Python
code/files into the `PyScript` using the `py-config` 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`, 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! or load data into `PyScript` for use by an application, all served as a static HTML page, which is pretty amazing!
## API Quick Reference
# API Quick Reference
## pyodide.http.pyfetch ## pyodide.http.pyfetch
### Usage
### pyfetch Usage
```python ```python
await pyodide.http.pyfetch(url: str, **kwargs: Any) -> FetchResponse 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`. 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 ## pyodide.http.FetchResponse
### Usage
### FetchResponse Usage
```python ```python
response: pyodide.http.FetchResponse = await <pyfetch call> response: pyodide.http.FetchResponse = await <pyfetch call>
status = response.status status = response.status
json = await response.json() json = await response.json()
``` ```
Class for handling HTTP responses. This is a wrapper around the `JavaScript` fetch `Response`. Contains common (async) 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. methods and properties for handling HTTP responses, such as `json()`, `url`, `status`, `headers`, etc.

View File

@@ -17,4 +17,6 @@ caption: 'Contents:'
passing-objects passing-objects
http-requests http-requests
asyncio asyncio
custom-plugins
event-handlers
``` ```

View File

@@ -1,6 +1,6 @@
# How to Pass Objects from PyScript to Javascript (and Vice Versa) # 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. [Pyodide](https://pyodide.org), the interpreter 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). 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).
@@ -36,7 +36,7 @@ We can use the syntax `from js import ...` to import JavaScript objects directly
### Using Pyodide's globals access ### 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')` The [PyScript JavaScript module](../reference/modules/pyscript.md) exposes its underlying Pyodide interpreter as `PyScript.interpreter`, 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.interpreter.globals.get('my_variable_name')`
```html ```html
<body> <body>
@@ -45,7 +45,7 @@ The [PyScript JavaScript module](../reference/modules/pyscript.md) exposes its
<button onclick="showX()">Click Me to Get 'x' from Python</button> <button onclick="showX()">Click Me to Get 'x' from Python</button>
<script> <script>
function showX(){ function showX(){
console.log(`In Python right now, x = ${pyscript.runtime.globals.get('x')}`) console.log(`In Python right now, x = ${pyscript.interpreter.globals.get('x')}`)
} }
</script> </script>
</body> </body>
@@ -59,7 +59,7 @@ Since [everything is an object](https://docs.python.org/3/reference/datamodel.ht
<button onclick="sortInPython(['Candy', 'Donut', 'Apple', 'Banana'])">Sort In Python And Log</button> <button onclick="sortInPython(['Candy', 'Donut', 'Apple', 'Banana'])">Sort In Python And Log</button>
<script> <script>
function sortInPython(data){ function sortInPython(data){
js_sorted = pyscript.runtime.globals.get('sorted') //grab python's 'sorted' function js_sorted = pyscript.interpreter.globals.get('sorted') //grab python's 'sorted' function
const sorted_data = js_sorted(data) //apply the function to the 'data' argument const sorted_data = js_sorted(data) //apply the function to the 'data' argument
for (const item of sorted_data){ for (const item of sorted_data){
console.log(item) console.log(item)
@@ -71,7 +71,7 @@ Since [everything is an object](https://docs.python.org/3/reference/datamodel.ht
### Using JavaScript's eval() ### 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). There may be some situations where it isn't possible or ideal to use `PyScript.interpreter.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. 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.

View File

@@ -54,4 +54,5 @@ tutorials/index
guides/index guides/index
concepts/index concepts/index
reference/index reference/index
changelog
``` ```

View File

@@ -0,0 +1,85 @@
# List of PyScript Attributes to Events:
PyScript provides a convenient syntax for mapping JavaScript events to PyScript events, making it easy to connect events to HTML tags.
For example, you can use the following code to connect the click event to a button:
```
<button id="py-click" py-click="foo()">Click me</button>
```
Here is a list of all the available event mappings:
| PyScript Event Name | DOM Event Name |
|-------------------|----------------|
| py-afterprint | afterprint |
| py-beforeprint | beforeprint |
| py-beforeunload | beforeunload |
| py-error | error |
| py-hashchange | hashchange |
| py-load | load |
| py-message | message |
| py-offline | offline |
| py-online | online |
| py-pagehide | pagehide |
| py-pageshow | pageshow |
| py-popstate | popstate |
| py-resize | resize |
| py-storage | storage |
| py-unload | unload |
| py-blur | blur |
| py-change | change |
| py-contextmenu | contextmenu |
| py-focus | focus |
| py-input | input |
| py-invalid | invalid |
| py-reset | reset |
| py-search | search |
| py-select | select |
| py-submit | submit |
| py-keydown | keydown |
| py-keypress | keypress |
| py-keyup | keyup |
| py-click | click |
| py-dblclick | dblclick |
| py-mousedown | mousedown |
| py-mousemove | mousemove |
| py-mouseout | mouseout |
| py-mouseover | mouseover |
| py-mouseup | mouseup |
| py-mousewheel | mousewheel |
| py-wheel | wheel |
| py-drag | drag |
| py-dragend | dragend |
| py-dragenter | dragenter |
| py-dragleave | dragleave |
| py-dragover | dragover |
| py-dragstart | dragstart |
| py-drop | drop |
| py-scroll | scroll |
| py-copy | copy |
| py-cut | cut |
| py-paste | paste |
| py-abort | abort |
| py-canplay | canplay |
| py-canplaythrough | canplaythrough |
| py-cuechange | cuechange |
| py-durationchange | durationchange |
| py-emptied | emptied |
| py-ended | ended |
| py-loadeddata | loadeddata |
| py-loadedmetadata | loadedmetadata |
| py-loadstart | loadstart |
| py-pause | pause |
| py-play | play |
| py-playing | playing |
| py-progress | progress |
| py-ratechange | ratechange |
| py-seeked | seeked |
| py-seeking | seeking |
| py-stalled | stalled |
| py-suspend | suspend |
| py-timeupdate | timeupdate |
| py-volumechange | volumechange |
| py-waiting | waiting |
| py-toggle | toggle |

View File

@@ -2,7 +2,8 @@
## Parameters ## 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: `*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 {py:func}`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 | | Method | Inferred MIME type |
|---------------------|------------------------| |---------------------|------------------------|
@@ -34,7 +35,7 @@ Display will throw an exception if the target is not clear. E.g. the following c
# from event handlers # from event handlers
display('hello') display('hello')
</py-script> </py-script>
<button id="my-button" py-onClick="display_hello()">Click me</button> <button id="my-button" py-click="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. Because it's considered unclear if the `hello` string should be displayed underneath the `<py-script>` tag or the `<button>` tag.
@@ -44,12 +45,11 @@ To write compliant code, make sure to specify the target using the `target` para
```html ```html
<py-script> <py-script>
def display_hello(): def display_hello():
# this fails because we don't have any implicit target # this works because we give an explicit target
# from event handlers
display('hello', target="helloDiv") display('hello', target="helloDiv")
</py-script> </py-script>
<div id="helloDiv"></div> <div id="helloDiv"></div>
<button id="my-button" py-onClick="display_hello()">Click me</button> <button id="my-button" py-click="display_hello()">Click me</button>
``` ```
#### Using matplotlib with display #### Using matplotlib with display

View File

@@ -0,0 +1,51 @@
# `@when`
`@when(event_type:str = None, selector:str = None)`
The `@when` decorator attaches the decorated function or Callable as an event handler for selected objects on the page. That is, when the named event is emitted by the selected DOM elements, the decorated Python function will be called.
If the decorated function takes a single (non-self) argument, it will be passed the [Event object](https://developer.mozilla.org/en-US/docs/Web/API/Event) corresponding to the triggered event. If the function takes no (non-self) argument, it will be called with no arguments.
## Parameters
`event_type` - A string representing the event type to match against. This can be any of the [https://developer.mozilla.org/en-US/docs/Web/Events#event_listing](https://developer.mozilla.org/en-US/docs/Web/Events) that HTML elements may emit, as appropriate to their element type.
`selector` = A string containing one or more [CSS selectors](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors). The selected DOM elements will have the decorated function attacehed as an event handler.
## Examples:
The following example prints "Hello, world!" whenever the button is clicked. It demonstrates using the `@when` decorator on a Callable which takes no arguments:
```html
<button id="my_btn">Click Me to Say Hi</button>
<py-script>
from pyscript import when
@when("click", selector="#my_btn")
def say_hello():
print(f"Hello, world!")
</py-script>
```
The following example includes three buttons - when any of the buttons is clicked, that button turns green, and the remaining two buttons turn red. This demonstrates using the `@when` decorator on a Callable which takes one argument, which is then passed the Event object from the associated event. When combined with the ability to look at other elements in on the page, this is quite a powerful feature.
```html
<div id="container">
<button>First</button>
<button>Second</button>
<button>Third</button>
</div>
<py-script>
from pyscript import when
import js
@when("click", selector="#container button")
def highlight(evt):
#Set the clicked button's background to green
evt.target.style.backgroundColor = 'green'
#Set the background of all buttons to red
other_buttons = (button for button in js.document.querySelectorAll('button') if button != evt.target)
for button in other_buttons:
button.style.backgroundColor = 'red'
</py-script>
```

View File

@@ -8,10 +8,10 @@ The `<py-config>` element should be placed within the `<body>` element.
## Attributes ## Attributes
| attribute | type | default | description | | 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. | | **type** | string | "toml" | Syntax type of the `<py-config>`. Value can be `json` or `toml`. Default: "toml" if type is unspecified. |
| **src** | url | | Source url to an external configuration file. | | **src** | url | | Source url to an external configuration file. |
## Examples ## Examples
@@ -20,7 +20,7 @@ The `<py-config>` element should be placed within the `<body>` element.
- `<py-config>` using TOML (default) - `<py-config>` using TOML (default)
```{note} ```{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=...`) Reminder: when using TOML, any Arrays of Tables defined with double-brackets (like `[[interpreters]]` and `[[fetch]]` must come after individual keys (like `plugins = ...` and `packages=...`)
``` ```
```html ```html
@@ -28,7 +28,7 @@ Reminder: when using TOML, any Arrays of Tables defined with double-brackets (li
[splashscreen] [splashscreen]
autoclose = true autoclose = true
[[runtimes]] [[interpreters]]
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js" src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
name = "pyodide-0.21.2" name = "pyodide-0.21.2"
lang = "python" lang = "python"
@@ -43,7 +43,7 @@ Reminder: when using TOML, any Arrays of Tables defined with double-brackets (li
"splashscreen": { "splashscreen": {
"autoclose": true "autoclose": true
}, },
"runtimes": [{ "interpreters": [{
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js", "src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
"name": "pyodide-0.21.2", "name": "pyodide-0.21.2",
"lang": "python" "lang": "python"
@@ -65,7 +65,7 @@ where `custom.toml` contains
[splashscreen] [splashscreen]
autoclose = true autoclose = true
[[runtimes]] [[interpreters]]
src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js" src = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js"
name = "pyodide-0.21.2" name = "pyodide-0.21.2"
lang = "python" lang = "python"
@@ -83,7 +83,7 @@ where `custom.json` contains
"splashscreen": { "splashscreen": {
"autoclose": true, "autoclose": true,
}, },
"runtimes": [{ "interpreters": [{
"src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js", "src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js",
"name": "pyodide-0.21.2", "name": "pyodide-0.21.2",
"lang": "python" "lang": "python"
@@ -127,7 +127,7 @@ The keys supplied through `inline` override the values present in config supplie
One can also declare dependencies so as to get access to many 3rd party OSS packages that are supported by PyScript. 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). 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. 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 interpreter) 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 interpreter 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) 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)
@@ -246,7 +246,8 @@ The following optional values are supported by `<py-config>`:
| `packages` | List of Packages | Dependencies on 3rd party OSS packages are specified here. The default value is an empty list. | | `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. | | `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. | | `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. | | `interpreters` | List of Interpreters| List of Interpreter configurations, described below. The default value contains a single Pyodide based interpreter. **Note:** Currently, only a single interpreter is supported. |
| `runtimes` {bdg-warning-line}`Deprecated` | List of Runtimes | This value is deprecated, please use `interpreters`. List of runtime configurations, described below. The default value contains a single Pyodide based interpreter. |
### <a name="fetch">Fetch</a> ### <a name="fetch">Fetch</a>
@@ -440,28 +441,33 @@ content/
</py-script> </py-script>
``` ```
### Runtime ### Interpreter
An interpreter configuration consists of the following:
A runtime configuration consists of the following:
| Value | Type | Description | | Value | Type | Description |
|--------|-------------------|-------------| |--------|-------------------|-------------|
| `src` | string (Required) | URL to the runtime source. | | `src` | string (Required) | URL to the interpreter 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 | | `name` | string | Name of the interpreter. 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. | | `lang` | string | Programming language supported by the interpreter. This field can be used by the application author to provide clarification. It currently has no implications on how PyScript behaves. |
#### Example #### Example
- The default runtime is `pyodide`, another version of which can be specified as following - The default interpreter is `pyodide`, another version of which can be specified as following
```html ```html
<py-config> <py-config>
[[runtimes]] [[interpreters]]
src = "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js" src = "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"
name = "pyodide-0.20.0" name = "pyodide-0.20.0"
lang = "python" lang = "python"
</py-config> </py-config>
``` ```
```{note}
Currently, PyScript supports a single interpreter, this may change in the future.
```
## Supplying extra information (or metadata) ## 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. 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.

View File

@@ -4,27 +4,67 @@ The `<py-repl>` element provides a REPL(Read Eval Print Loop) to evaluate multi-
## Attributes ## Attributes
| attribute | type | default | description | | attribute | type | default | description |
|-------------------|---------|---------|---------------------------------------| |-------------------|---------|---------|--------------------------------------|
| **auto-generate** | boolean | | Auto-generates REPL after evaluation | | **auto-generate** | boolean | | Auto-generates REPL after evaluation |
| **output** | string | | The element to write output into | | **output-mode** | string | "" | Determines whether the output element is cleared prior to writing output |
| **output** | string | | The id of the element to write `stdout` and `stderr` to |
| **stderr** | string | | The id of the element to write `stderr` to |
| **src** | string | | Resource to be preloaded into the REPL |
### Examples
#### `<py-repl>` element set to auto-generate ### `auto-generate`
If a \<py-repl\> tag has the `auto-generate` attribute, upon execution, another \<pr-repl\> tag will be created and added to the DOM as a sibling of the current tag.
### `output-mode`
By default, the element which displays the output from a REPL is cleared (`innerHTML` set to "") prior to each new execution of the REPL. If `output-mode` == "append", that element is not cleared, and the output is appended instead.
### `output`
The ID of an element in the DOM that `stdout` (e.g. `print()`), `stderr`, and the results of executing the repl are written to. Defaults to an automatically-generated \<div\> as the next sibling of the REPL itself.
### `stderr`
The ID of an element in the DOM that `stderr` will be written to. Defaults to None, though writes to `stderr` will still appear in the location specified by `output`.
### `src`
If a \<py-repl\> tag has the `src` attribute, during page initialization, resource in the `src` will be preloaded into the REPL. Please note that this will not run in advance. If there is content in the \<py-repl\> tag, it will be cleared and replaced with preloaded resource.
## Methods
The following are methods that can be called on the \<py-repl\> element, from within Python or JavaScript
### `getPySrc()`
Returns the current code contents of the REPL as a string.
## Examples
### `<py-repl>` element set to auto-generate
```html ```html
<py-repl auto-generate="true"> </py-repl> <py-repl auto-generate="true"> </py-repl>
``` ```
#### `<py-repl>` element with output ### `<py-repl>` element with output
The following will write "Hello! World!" to the div with id `replOutput`.
```html ```html
<div id="replOutput"></div> <div id="replOutput"></div>
<py-repl output="replOutput"> <py-repl output="replOutput">
hello = "Hello world!" print("Hello!")
hello = "World!"
hello hello
</py-repl> </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. Note that if we `print` from the REPL (or otherwise write to `sys.stdout`), the output will be printed in the [`py-terminal`](../plugins/py-terminal.md) if is enabled.
### `<py-repl>` element with src
Preload resource from src into the REPL
```html
<py-repl id="py-repl" output="replOutput" src="./src/py/py_code.py">
If a py-repl tag has the src attribute,
the content here will be cleared and replaced.
</py-repl>
<div id="replOutput"></div>
```

View File

@@ -1,12 +1,27 @@
# &lt;py-script&gt; # &lt;py-script&gt;
The `<py-script>` element lets you execute multi-line Python scripts both inline and via a src attribute. The `<py-script>` element, also available as `<script type="py-script">`, lets you execute multi-line Python scripts both inline and via a src attribute.
## Attributes ## Attributes
| attribute | type | default | description | | attribute | type | default | description |
|-----------|------|---------|------------------------------| |-----------|--------|---------|------------------------------|
| **src** | url | | Url of a python source file. | | **src** | url | | You don't need to add long python code in py-script, you can provide url of the python source file in the py-script tag with the `src` attribute. When a Python file is referred with the `src` attribute it is executed, and then added to the namespace where it was referred. |
| **output**| string | | The id of a DOM element to route `sys.stdout` and `stderr` to, in addition to sending it to the `<py-terminal>`|
| **stderr**| string | | The id of a DOM element to route just `sys.stderr` to, in addition to sending it to the `<py-terminal>`|
### output
If the `output` attribute is provided, any output to [sys.stdout](https://docs.python.org/3/library/sys.html#sys.stdout) or [sys.stderr](https://docs.python.org/3/library/sys.html#sys.stderr) is written to the DOM element with the ID matching the attribute. If no DOM element is found with a matching ID, a warning is shown. The msg is output to the `innerHTML` of the HTML Element, with newlines (`\n'`) converted to breaks (`<br/>`).
This output is in addition to the output being written to the developer console and the `<py-terminal>` if it is being used.
### stderr
If the `stderr` attribute is provided, any output to [sys.stderr](https://docs.python.org/3/library/sys.html#sys.stderr) is written to the DOM element with the ID matching the attribute. If no DOM element is found with a matching ID, a warning is shown. The msg is output to the `innerHTML` of the HTML Element, with newlines (`\n'`) converted to breaks (`<br/>`).
This output is in addition to the output being written to the developer console and the `<py-terminal>` if it is being used.
## Examples ## Examples

View File

@@ -9,6 +9,8 @@ This reference guide contains the error codes you might find and a description o
|------------|--------------------------------|--------------------| |------------|--------------------------------|--------------------|
| 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. | | 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. | | PY1001 | Unable to install package(s) | Confirm that the package contains a pure Python 3 wheel or the name of the package is correct. |
| PY2000 | Invalid plugin file extension | Only `.js` and `.py` files can be used when loading user plugins. Please confirm your path contains the file extension. |
| PY2001 | Plugin doesn't contain a default export | Please add `export default` to the main plugin class. |
| PY9000 | Top level await is deprecated | Create a coroutine with your code and schedule it with `asyncio.ensure_future` or similar | | PY9000 | Top level await is deprecated | Create a coroutine with your code and schedule it with `asyncio.ensure_future` or similar |
@@ -37,3 +39,14 @@ Pyscript cannot install the package(s) you specified in your `py-config` tag. Th
- An error occurred while trying to install the package - 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. 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.
## PY2001
Javascript plugins must export a default class. This is required for PyScript to be able to load the plugin. Please add `export default` to the main plugin class. For example:
```js
export default class HelloWorldPlugin {
afterStartup(runtime) {
console.log("Hello World from the plugin!");
}
```

View File

@@ -155,6 +155,6 @@ Requests and Black do not work out of the box because they werent meant for t
For Black, its a design choice that can be patched. This is currently being addressed by the team at Pyodide. For Black, its a design choice that can be patched. This is currently being addressed by the team at Pyodide.
Requests do not work because of the sockets issue (sockets and websockets are two different things) and requests are blocking—which you dont want in the browser. Itll require putting the runtime on a webworker and utilizing an assistant, but on the main thread its unlikely that itll work. Requests do not work because of the sockets issue (sockets and websockets are two different things) and requests are blocking—which you dont want in the browser. Itll require putting the interpreter on a webworker and utilizing an assistant, but on the main thread its unlikely that itll work.
There are options as a path forward. For example, Requests can be leveraged using javascript libraries, or building a python async version of Requests API or a python wrapper for fetch (pyfetch), etc. The websockets library has a client side that could be made to work—given that it has all asynchronous APIs, theres nothing fundamentally difficult about getting it to work. There are options as a path forward. For example, Requests can be leveraged using javascript libraries, or building a python async version of Requests API or a python wrapper for fetch (pyfetch), etc. The websockets library has a client side that could be made to work—given that it has all asynchronous APIs, theres nothing fundamentally difficult about getting it to work.

View File

@@ -1,6 +1,6 @@
# pyscript # 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 code underlying PyScript is a 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: The module is exported to the browser as `pyscript`. The exports from this module are:
@@ -15,35 +15,35 @@ Once `pyscript.js` has loaded, the version of PyScript that is currently running
Object { year: 2022, month: 11, patch: 1, releaselevel: "dev" } Object { year: 2022, month: 11, patch: 1, releaselevel: "dev" }
``` ```
## pyscript.runtime ## pyscript.interpreter
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 Interpreter object which is responsible for executing Python code in the Browser. Currently, all interpreters are assumed to be Pyodide interpreters, but there is flexibility to expand this to other web-based Python interpreters in future versions.
The RunTime object has the following attributes The Interpreter object has the following attributes
| attribute | type | description | | attribute | type | description |
|---------------------|---------------------|-----------------------------------------------------------------------------| |---------------------|-----------------------|---------------------------------------------------------------------------------|
| **src** | string | The URL from which the current runtime was fetched | | **src** | string | The URL from which the current interpreter was fetched |
| **interpreter** | RuntimeInterpreter | A reference to the runtime object itself | | **interface** | InterpreterInterface | A reference to the interpreter object itself |
| **globals** | any | The globals dictionary of the runtime, if applicable/accessible | | **globals** | any | The globals dictionary of the interpreter, if applicable/accessible |
| **name (optional)** | string | A user-designated name for the runtime | | **name (optional)** | string | A user-designated name for the interpreter |
| **lang (optional)** | string | A user-designation for the language the runtime runs ('Python', 'C++', etc) | | **lang (optional)** | string | A user-designation for the language the interpreter runs ('Python', 'C++', etc) |
### pyscript.runtime.src ### pyscript.interpreter.src
The URL from which the current runtime was fetched. The URL from which the current interpreter was fetched.
### pyscript.runtime.interpreter ### pyscript.interpreter.interface
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. A reference to the Interpreter wrapper that PyScript uses to execute code. object itself. This allows other frameworks, modules etc to interact with the same [(Pyodide) interpreter 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: For example, assuming we've loaded Pyodide, we can access the methods of the Pyodide interpreter as follows:
```html ```html
<button onclick="logFromPython()">Click Me to Run Some Python</button> <button onclick="logFromPython()">Click Me to Run Some Python</button>
<script> <script>
function logFromPython(){ function logFromPython(){
pyscript.runtime.interpreter.runPython(` pyscript.interpreter.interface.runPython(`
animal = "Python" animal = "Python"
sound = "sss" sound = "sss"
console.warn(f"{animal}s go " + sound * 5) console.warn(f"{animal}s go " + sound * 5)
@@ -52,9 +52,9 @@ For example, assuming we've loaded Pyodide, we can access the methods of the Pyo
</script> </script>
``` ```
### pyscript.runtime.globals ### pyscript.interpreter.globals
A proxy for the runtime's `globals()` dictionary. For example: A proxy for the interpreter's `globals()` dictionary. For example:
```html ```html
<body> <body>
@@ -63,15 +63,15 @@ A proxy for the runtime's `globals()` dictionary. For example:
<button onclick="showX()">Click Me to Get 'x' from Python</button> <button onclick="showX()">Click Me to Get 'x' from Python</button>
<script> <script>
function showX(){ function showX(){
console.log(`In Python right now, x = ${pyscript.runtime.globals.get('x')}`) console.log(`In Python right now, x = ${pyscript.interpreter.globals.get('x')}`)
} }
</script> </script>
</body> </body>
``` ```
### pyscript.runtime.name ### pyscript.interpreter.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. A user-supplied string for the interpreter given at its creation. For user reference only - does not affect the operation of the interpreter or PyScript.
### PyScript.runtime.lang ### PyScript.interpreter.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. A user-supplied string for the language the interpreter uses given at its creation. For user reference only - does not affect the operation of the interpreter or PyScript.

View File

@@ -0,0 +1,65 @@
# &lt;py-splashscreen&gt;
This is one of the core plugins in PyScript, which is active by default. The splashscreen is the first thing you see when you open a page with Pyscript while it is loading itself and all the necessary resources.
## Configuration
You can control how `<py-splashscreen>` behaves by setting the value of the `splashscreen` configuration in your `<py-config>`.
| parameter | default | description |
|-------------|-----------|-------------|
| `autoclose` | `true` | Whether to close the splashscreen automatically when the page is ready or not |
| `enabled` | `true` | Whether to show the splashscreen or not |
### Examples
#### Disabling the splashscreen
If you don't want the splashscreen to show and log any loading messages, you can disable it by setting the splashscreen option `enabled` to `false`.
```html
<py-config>
[splashscreen]
enabled = false
</py-config>
```
#### Disabling autoclose
If you want to keep the splashscreen open even after the page is ready, you can disable autoclose by setting `autoclose` to `false`.
```html
<py-config>
[splashscreen]
autoclose = false
</py-config>
```
## Adding custom messages
You can add custom messages to the splashscreen. This is useful if you want to show the user that something is happening in the background for your PyScript app.
There are two ways to add your custom messages to the splashscreen, either by dispatching a new custom event, `py-status-message` to the document:
```js
document.dispatchEvent(new CustomEvent("py-status-message", {detail: "Hello, world!"}))
```
Or by using the `log` method of the `py-splashscreen` tag directly:
```js
const splashscreen = document.querySelector("py-splashscreen")
splashscreen.log("Hello, world!")
```
If you wish, you can also send messages directly to the splashscreen from your python code:
```python
from js import document
splashscreen = document.querySelector("py-splashscreen")
splashscreen.log("Hello, world!")
```

View File

@@ -4,7 +4,9 @@ This is one of the core plugins in PyScript, which is active by default. With it
## Configuration ## Configuration
You can control how `<py-terminal>` behaves by setting the value of the `terminal` configuration in your `<py-config>`. You can control how `<py-terminal>` behaves by setting the values of the `terminal`, `docked`, and `xterm` fields in your configuration in your `<py-config>`.
For the **terminal** field, these are the values:
| value | description | | value | description |
|-------|-------------| |-------|-------------|
@@ -12,11 +14,50 @@ You can control how `<py-terminal>` behaves by setting the value of the `termin
| `true` | Automatically add a `<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` | | `"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` |
For the **docked** field, these are the values:
| value | description |
|-------|-------------|
| `false` | Don't dock `<py-terminal>` to the page |
| `true` | Automatically dock a `<py-terminal>` to the page |
| `"docked"` | This is the default. Automatically add a `<py-terminal docked>`, to the page. The terminal, once visible, is automatically shown at the bottom of the page, covering the width of such page |
Please note that **docked** mode is currently used as default only when `terminal="auto"`, or *terminal* default, is used.
In all other cases it's up to the user decide if a terminal should be docked or not.
For the **xterm** field, these are the values:
| value | description |
|-------|-------------|
| `false` | This is the default. The `<py-terminal>` is a simple `<pre>` tag with some CSS styling. |
| `true` or `xterm` | The [xtermjs](http://xtermjs.org/) library is loaded and its Terminal object is used as the `<py-terminal>`. It's visibility and position are determined by the `docked` and `auto` keys in the same way as the default `<py-terminal>` |
The xterm.js [Terminal object](http://xtermjs.org/docs/api/terminal/classes/terminal/) can be accessed directly if you want to adjust its properties, add [custom parser hooks](http://xtermjs.org/docs/guides/hooks/), introduce [xterm.js addons](http://xtermjs.org/docs/guides/using-addons/), etc. Access is best achieved by awaiting the `xtermReady` attribute of the `<py-terminal>` HTML element itself:
```python
import js
import asyncio
async def adjust_term_size(columns, rows):
xterm = await js.document.querySelector('py-terminal').xtermReady
xterm.resize(columns, rows)
asyncio.ensure_future(adjust_term_size(40,10))
```
Some terminal-formatting packages read from specific environment variables to determine whether they should emit formatted output; PyScript does not set these variables explicitly - you may need to set them yourself, or force your terminal-formatting package into a state where it outputs correctly formatted output.
A couple of specific examples:
- the [rich](https://github.com/Textualize/rich) will not, by default, output colorful text, but passing `256` or `truecolor` as an argument as the `color_system` parameter to the [Console constructor](https://rich.readthedocs.io/en/stable/reference/console.html#rich.console.Console) will force it to do so. (As of rich v13)
- [termcolor](https://github.com/termcolor/termcolor) will not, by default, output colorful text, but setting `os.environ["FORCE_COLOR"] = "True"` or by passing `force_color=True` to the `colored()` function will force it to do so. (As of termcolor v2.3)
### Examples ### Examples
```html ```html
<py-config> <py-config>
terminal = true terminal = true
docked = false
</py-config> </py-config>
<py-script> <py-script>

View File

@@ -5,7 +5,7 @@ This page will guide you through getting started with PyScript.
## Development setup ## Development setup
PyScript does not require any development environment other PyScript does not require any development environment other
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. 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.
If you're using [VSCode](https://code.visualstudio.com/), the If you're using [VSCode](https://code.visualstudio.com/), the
[Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) [Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
@@ -42,6 +42,12 @@ open an HTML by double-clicking it in your file explorer.
</html> </html>
``` ```
### Using a Local Server
In some situations, your browser may forbid loading remote resources like `pyscript.js` and `pyscript.css` when you open an HTML file directly. When this is the case, you may see your Python code in the text of the webpage, and the [browser developer console](https://balsamiq.com/support/faqs/browserconsole/) may show an error like *"Cross origin requests are only supported for HTTP."* The fix for this is to use a [simple local server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/set_up_a_local_testing_server) to make your html file available to the browser.
If you have python installed on your system, you can use it's basic built-in server for this purpose via the command line. Change the current working directory of your terminal or command line to the folder where your HTML file is stored. From this folder, run `python -m http.server 8080 --bind 127.0.0.1` in your terminal or command line. With the server program running, point your browser to `http://localhost:8080` to view the contents of that folder. (If a file in that folder is called `index.html`, it will be displayed by default.)
## A more complex example ## A more complex example
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. 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.
@@ -197,6 +203,7 @@ Now that we have a way to explore the data using `py-repl` and a way to create t
</py-config> </py-config>
<py-script> <py-script>
import js
import pandas as pd import pandas as pd
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
@@ -209,7 +216,7 @@ Now that we have a way to explore the data using `py-repl` and a way to create t
ice_data = pd.read_csv(open_url(url)) ice_data = pd.read_csv(open_url(url))
current_selected = [] current_selected = []
flavour_elements = document.getElementsByName("flavour") flavour_elements = js.document.getElementsByName("flavour")
def plot(data): def plot(data):
plt.rcParams["figure.figsize"] = (22,20) plt.rcParams["figure.figsize"] = (22,20)
@@ -248,8 +255,8 @@ Now that we have a way to explore the data using `py-repl` and a way to create t
<label for="all"> All 🍧</label> <label for="all"> All 🍧</label>
<input type="radio" id="chocolate" name="flavour" value="COCOA"> <input type="radio" id="chocolate" name="flavour" value="COCOA">
<label for="chocolate"> Chocolate 🍫</label> <label for="chocolate"> Chocolate 🍫</label>
<input type="radio" id="cherrie" name="flavour" value="CHERRIES"> <input type="radio" id="cherry" name="flavour" value="CHERRIES">
<label for="cherrie"> Cherries 🍒</label> <label for="cherry"> Cherries 🍒</label>
<input type="radio" id="berries" name="flavour" value="BERRY"> <input type="radio" id="berries" name="flavour" value="BERRY">
<label for="berries"> Berries 🍓</label> <label for="berries"> Berries 🍓</label>
<input type="radio" id="cheese" name="flavour" value="CHEESE"> <input type="radio" id="cheese" name="flavour" value="CHEESE">

View File

@@ -1,13 +1,38 @@
# Tutorials # Tutorials
This is the tutorials section for beginners. This section contains pyscript tutorials. Each tutorial is a self-contained document that will guide you through a specific topic.
## Getting Started
This tutorial will guide you through getting started with PyScript, from installation to writing your first PyScript application. The getting started will show you how to specify dependencies, read a csv file from the web, use `pandas` and `matplotlib` and how to handle user input.
[Read the get started tutorial](getting-started.md)
## Basics
This section contains tutorials about the basics of PyScript.
```{toctree} ```{toctree}
--- ---
maxdepth: 2 maxdepth: 1
glob:
--- ---
getting-started
py-config-fetch
py-config-runtime
writing-to-page writing-to-page
py-click
requests
```
## PyScript Configuration
This section contains tutorials about the PyScript configuration using the `<py-config>` tag.
```{toctree}
---
maxdepth: 1
glob:
---
py-config-fetch
py-config-interpreter
``` ```

127
docs/tutorials/py-click.md Normal file
View File

@@ -0,0 +1,127 @@
# Handling click events
This tutorial will show you how to use the `py-click` attribute to handle mouse clicks on elements on your page. The `py-click` attribute is a special attribute that allows you to specify a Python function that will be called when the element is clicked. There are many other events such as py-mouseover, py-focus, py-input, py-keyress etc, which can be used as well. They are listed here [Attr-to-Event](../reference/API/attr_to_event.html)
## Development setup
Let's start by building the base HTML page. We will create an HTML page with a button and a paragraph. When the button is clicked, the paragraph will show the current time.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Current Time</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>
```
## Adding elements to the page
Let's add a button and a paragraph to the page. The button will have the `py-click` attribute, and the paragraph will have the `id` attribute. The `id` attribute is used to identify the element on the page, and the `py-click` attribute will be used to specify the function that will be called when the button is clicked.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Current Time</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<button py-click="current_time()" id="get-time" class="py-button">Get current time</button>
<p id="current-time"></p>
</body>
</html>
```
There are two things to note here:
- You must specify an id for an element that uses any `py-*` attribute
- We used the `py-button` class to style the button, this is optional, and these rules are coming from the pyscript.css that we added in the `<head>` section.
## Creating the Python function
In this step, we will create the Python function that will be called when the button is clicked. This function will get the current time and update the paragraph with the current time. We will use a `<py-script>` tag to specify the Python code that will be executed when the page is loaded.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Current Time</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<button py-click="current_time()" id="get-time" class="py-button">Get current time</button>
<p id="current-time"></p>
<py-script>
import datetime
def current_time():
now = datetime.datetime.now()
</py-script>
</body>
</html>
```
## Writing the time to the page
If you run the example, you will notice that nothing happened. This is because we still need to update the paragraph with the current time. We can do this by using the [`Element` API](../reference/API/element.md) to get the paragraph element and then update it with the current time with the `write` method.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Current Time</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
<button py-click="current_time()" id="get-time" class="py-button">Get current time</button>
<p id="current-time"></p>
<py-script>
from pyscript import Element
import datetime
def current_time():
now = datetime.datetime.now()
# Get paragraph element by id
paragraph = Element("current-time")
# Add current time to the paragraph element
paragraph.write(now.strftime("%Y-%m-%d %H:%M:%S"))
</py-script>
</body>
</html>
```
Now, if you refresh the page and click the button, the paragraph will be updated with the current time.

View File

@@ -1,6 +1,6 @@
# Setting a pyodide runtime # Setting a pyodide interpreter
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. Pyscript will automatically set the interpreter 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 ## Development setup
@@ -13,7 +13,7 @@ To get started, let's create a new `index.html` file and import `pyscript.js`.
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Runtime</title> <title>Interpreter</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
@@ -25,11 +25,11 @@ To get started, let's create a new `index.html` file and import `pyscript.js`.
</html> </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. We are using the pyodide CDN to setup our interpreter, but you can also download the files from [the pyodide GitHub releases](https://github.com/pyodide/pyodide/releases/), unzip them and use the `pyodide.js` file as your interpreter.
## Setting the runtime ## Setting the interpreter
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. To set the interpreter, you can use the `interpreter` 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 ```html
<!DOCTYPE html> <!DOCTYPE html>
@@ -38,7 +38,7 @@ To set the runtime, you can use the `runtime` configuration in the `py-config` e
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Runtime</title> <title>Interpreter</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
@@ -46,18 +46,18 @@ To set the runtime, you can use the `runtime` configuration in the `py-config` e
<body> <body>
<py-config> <py-config>
[[runtimes]] [[interpreters]]
src = "https://cdn.jsdelivr.net/pyodide/v0.22.0a3/full/pyodide.js" src = "https://cdn.jsdelivr.net/pyodide/v0.23.0/full/pyodide.js"
name = "pyodide-0.22.0a3" name = "pyodide-0.23.0"
lang = "python" lang = "python"
</py-config> </py-config>
</body> </body>
</html> </html>
``` ```
## Confirming the runtime version ## Confirming the interpreter 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. To confirm that the interpreter 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 ```html
<!DOCTYPE html> <!DOCTYPE html>
@@ -66,7 +66,7 @@ To confirm that the runtime is set correctly, you can open the DevTools and chec
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Runtime</title> <title>Interpreter</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
@@ -74,9 +74,9 @@ To confirm that the runtime is set correctly, you can open the DevTools and chec
<body> <body>
<py-config> <py-config>
[[runtimes]] [[interpreters]]
src = "https://cdn.jsdelivr.net/pyodide/v0.22.0a3/full/pyodide.js" src = "https://cdn.jsdelivr.net/pyodide/v0.23.0/full/pyodide.js"
name = "pyodide-0.22.0a3" name = "pyodide-0.23.0"
lang = "python" lang = "python"
</py-config> </py-config>
<py-script> <py-script>

123
docs/tutorials/requests.md Normal file
View File

@@ -0,0 +1,123 @@
# Calling an API using Requests
This tutorial will show you how to interact with an API using the [Requests](https://requests.readthedocs.io/en/master/) library. Requests is a popular library, but it doesn't work out of the box with Pyscript. We will use the [pyodide-http](https://github.com/koenvo/pyodide-http) library to patch the Requests library, so it works with Pyscript.
We will use the [JSON Placeholder API](https://jsonplaceholder.typicode.com/), a free fake API that returns fake data.
## Development Setup
Let's build the base HTML page to add our `py-config` and `py-script` tags in the next steps.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Requests Tutorial</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>
```
## Installing the dependencies
In this step, we will install the dependencies we need to use the Requests library. We will use the `py-config` tag to specify the dependencies we need to install.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Requests Tutorial</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>
packages = ["requests", "pyodide-http"]
</py-config>
</body>
</html>
```
## Patching the Requests library
Now that we have installed the dependencies, we need to patch the Requests library to work with Pyscript. We will use the `py-script` tag to specify the code that will be executed when the page is loaded.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Requests Tutorial</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>
packages = ["requests", "pyodide-http"]
</py-config>
<py-script>
import pyodide_http
pyodide_http.patch_all()
</py-script>
</body>
</html>
```
## Making a request
Finally, let's make a request to the JSON Placeholder API to confirm that everything is working.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Requests Tutorial</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>
packages = ["requests", "pyodide-http"]
</py-config>
<py-script>
import requests
import pyodide_http
# Patch the Requests library so it works with Pyscript
pyodide_http.patch_all()
# Make a request to the JSON Placeholder API
response = requests.get("https://jsonplaceholder.typicode.com/todos")
print(response.json())
</py-script>
</body>
</html>
```
## Conclusion
In this tutorial, we learned how to use the Requests library to make requests to an API. We also learned how to use the `py-config` and `py-script` tags to install dependencies and execute code when the page is loaded.
Depending on the API you use, you may need to add additional headers to your request. You can read the [Requests documentation](https://requests.readthedocs.io/en/master/user/quickstart/#custom-headers) to learn more about how to do this.
You may also be interested in creating your module to make requests. You can read the in-depth guide on [How to make HTTP requests using `PyScript`, in pure Python](../guides/http-requests.md) to learn more about how to do this.

View File

@@ -1,154 +1,81 @@
<html> <html>
<head> <head>
<title>Altair</title> <title>Altair</title>
<meta charset="utf-8"> <meta charset="utf-8" />
<link rel="icon" type="image/x-icon" href="./favicon.png"> <link rel="icon" type="image/x-icon" href="./favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
<link rel="stylesheet" href="./assets/css/examples.css" /> rel="stylesheet"
<link rel="stylesheet" href="./assets/prism/prism.css" /> href="https://pyscript.net/latest/pyscript.css"
<script defer src="https://pyscript.net/latest/pyscript.js"></script> />
<script defer src="./assets/prism/prism.js"></script> <link rel="stylesheet" href="./assets/css/examples.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head> </head>
<body> <body>
<nav class="navbar" style="background-color: #000000;"> <nav class="navbar" style="background-color: #000000">
<div class="app-header"> <div class="app-header">
<a href="/"> <a href="/">
<img src="./logo.png" class="logo"> <img src="./logo.png" class="logo" />
</a> </a>
<a class="title" href="" style="color: #f0ab3c;">Altair</a> <a class="title" href="" style="color: #f0ab3c">Altair</a>
</div> </div>
</nav> </nav>
<section class="pyscript"> <section class="pyscript">
<div id="altair"></div> <div id="altair"></div>
<py-config> <py-tutor>
packages = [ <py-config>
"altair", packages = [
"pandas", "altair",
"vega_datasets" "pandas",
] "vega_datasets"
</py-config> ]
<py-script> plugins = [
import altair as alt "https://pyscript.net/latest/plugins/python/py_tutor.py"
from vega_datasets import data ]
</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( rect = alt.Chart(data.movies.url).mark_rect().encode(
alt.X('IMDB_Rating:Q', bin=True), alt.X('IMDB_Rating:Q', bin=True),
alt.Y('Rotten_Tomatoes_Rating:Q', bin=True), alt.Y('Rotten_Tomatoes_Rating:Q', bin=True),
alt.Color('count()', alt.Color('count()',
scale=alt.Scale(scheme='greenblue'), scale=alt.Scale(scheme='greenblue'),
legend=alt.Legend(title='Total Records') legend=alt.Legend(title='Total Records')
) )
) )
circ = rect.mark_point().encode( circ = rect.mark_point().encode(
alt.ColorValue('grey'), alt.ColorValue('grey'),
alt.Size('count()', alt.Size('count()',
legend=alt.Legend(title='Records in Selection') legend=alt.Legend(title='Records in Selection')
) )
).transform_filter( ).transform_filter(
pts pts
) )
bar = alt.Chart(source).mark_bar().encode( bar = alt.Chart(source).mark_bar().encode(
x='Major_Genre:N', x='Major_Genre:N',
y='count()', y='count()',
color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey")) color=alt.condition(pts, alt.ColorValue("steelblue"), alt.ColorValue("grey"))
).properties( ).properties(
width=550, width=550,
height=200 height=200
).add_selection(pts) ).add_selection(pts)
display(alt.vconcat( display(alt.vconcat(
rect + circ, rect + circ,
bar bar
).resolve_legend( ).resolve_legend(
color="independent", color="independent",
size="independent" size="independent"
), target="altair") ), target="altair")
</py-script> </py-script>
</py-tutor>
</section> </section>
<section class="code"> </body>
<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">
&lt;py-config&gt;
packages = [
"altair",
"pandas",
"vega_datasets"
]
&lt;/py-config&gt;
&lt;py-script&gt;
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")
&lt;/py-script&gt;
</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> </html>

View File

@@ -1,118 +1,39 @@
<html> <html>
<head> <head>
<title>Antigravity</title> <title>Antigravity</title>
<meta charset="utf-8"> <meta charset="utf-8" />
<link rel="icon" type="image/x-icon" href="./favicon.png"> <link rel="icon" type="image/x-icon" href="./favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
<link rel="stylesheet" href="./assets/css/examples.css" /> rel="stylesheet"
<link rel="stylesheet" href="./assets/prism/prism.css" /> href="https://pyscript.net/latest/pyscript.css"
<script defer src="https://pyscript.net/latest/pyscript.js"></script> />
<script defer src="./assets/prism/prism.js"></script> <link rel="stylesheet" href="./assets/css/examples.css" />
</head> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
<body> </head>
<nav class="navbar" style="background-color: #000000;"> <body>
<div class="app-header"> <nav class="navbar" style="background-color: #000000">
<a href="/"> <div class="app-header">
<img src="./logo.png" class="logo"> <a href="/">
</a> <img src="./logo.png" class="logo" />
<a class="title" href="" style="color: #f0ab3c;">Antigravity</a> </a>
</div> <a class="title" href="" style="color: #f0ab3c">Antigravity</a>
</nav> </div>
<section class="pyscript"> </nav>
<py-config> <py-tutor modules="antigravity.py">
[[fetch]] <section class="pyscript">
files = ["./antigravity.py"] <py-config>
</py-config> plugins = [
<b>Based on xkcd: antigravity https://xkcd.com/353/.</b> "https://pyscript.net/latest/plugins/python/py_tutor.py"
<py-script> ]
import antigravity [[fetch]]
antigravity.fly() files = ["./antigravity.py"]
</py-script> </py-config>
</section> <b>Based on xkcd: antigravity https://xkcd.com/353/.</b>
<section class="code"> <py-script>
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div> import antigravity
<div id="code-section" class="code-section-hidden"> antigravity.fly()
<p>index.html</p> </py-script>
<pre class="prism-code language-html"> </section>
<code class="language-html"> </py-tutor>
&lt;py-config&gt; </body>
[[fetch]]
files = ["./antigravity.py"]
&lt;/py-config&gt;
&lt;py-script&gt;
import antigravity
antigravity.fly()
&lt;/py-script&gt;
</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> </html>

View File

@@ -6,7 +6,6 @@ from pyodide.http import open_url
class Antigravity: class Antigravity:
url = "./antigravity.svg" url = "./antigravity.svg"
def __init__(self, target=None, interval=10, append=True, fly=False): def __init__(self, target=None, interval=10, append=True, fly=False):

View File

@@ -36,20 +36,21 @@ body {
border-radius: 10px 0px 0px 10px; border-radius: 10px 0px 0px 10px;
color: #c6c6c8; color: #c6c6c8;
} }
.code-section-visible p{ .code-section-visible p {
margin: 0; margin: 0;
font-style: italic; font-style: italic;
font-size: small; font-size: small;
} }
.language-html, .language-python { .language-html,
.language-python {
float: left; float: left;
} }
#view-code-button { #view-code-button {
writing-mode: tb-rl; writing-mode: tb-rl;
text-orientation: sideways-right; text-orientation: sideways-right;
background-color: #1D1D22; background-color: #1d1d22;
color: white; color: white;
padding: 0.5rem; padding: 0.5rem;
border-radius: 5px; border-radius: 5px;

View File

@@ -2,7 +2,7 @@
margin-bottom: 5rem; margin-bottom: 5rem;
} }
.example h2{ .example h2 {
/* color: #000000; */ /* color: #000000; */
font-family: "Inconsolata", monospace; font-family: "Inconsolata", monospace;
font-size: 2.25rem; font-size: 2.25rem;
@@ -16,9 +16,12 @@
border-radius: 10px; border-radius: 10px;
} }
.card:hover, .card:hover a, .card:hover a:visited, .card:hover h2 { .card:hover,
.card:hover a,
.card:hover a:visited,
.card:hover h2 {
background-color: var(--color-primary); background-color: var(--color-primary);
color: #1D1D22 color: #1d1d22;
} }
.card a h2 { .card a h2 {
@@ -46,7 +49,8 @@ a .card {
justify-content: space-between; justify-content: space-between;
} }
.card-content a, .card-content a:visited { .card-content a,
.card-content a:visited {
color: var(--color-primary); color: var(--color-primary);
} }

View File

@@ -2,12 +2,13 @@
@import "./variables.css"; @import "./variables.css";
@import "./reset.css"; @import "./reset.css";
body { 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: #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; background-attachment: fixed;
overflow-x: hidden; overflow-x: hidden;
color: var(--text-color) color: var(--text-color);
} }
.container { .container {

View File

@@ -1,6 +1,6 @@
:root { :root {
--color-primary: #FDA703; --color-primary: #fda703;
--color-secondary: #1D1D22; --color-secondary: #1d1d22;
--text-color: white; --text-color: white;
--card-shadow: 0px 5px 11px 0px rgb(0 0 0 / 15%); --card-shadow: 0px 5px 11px 0px rgb(0 0 0 / 15%);
} }

View File

@@ -1,26 +1,29 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Async Await BLOCKING LOOP Pyscript Twice</title> <title>Async Await BLOCKING LOOP Pyscript Twice</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
<script defer src="https://pyscript.net/latest/pyscript.js"></script> rel="stylesheet"
</head> href="https://pyscript.net/latest/pyscript.css"
<body> />
<py-script> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
import js </head>
import asyncio <body>
for i in range(3): <py-script>
js.console.log('A', i) import js
await asyncio.sleep(0.1) import asyncio
</py-script> for i in range(3):
<py-script> js.console.log('A', i)
import js await asyncio.sleep(0.1)
import asyncio </py-script>
for i in range(3): <py-script>
js.console.log('B', i) import js
await asyncio.sleep(0.1) import asyncio
</py-script> for i in range(3):
</body> js.console.log('B', i)
await asyncio.sleep(0.1)
</py-script>
</body>
</html> </html>

View File

@@ -1,40 +1,45 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Async Await BLOCKING LOOP Pyscript Twice</title> <title>Async Await BLOCKING LOOP Pyscript Twice</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
<script defer src="https://pyscript.net/latest/pyscript.js"></script> rel="stylesheet"
</head> href="https://pyscript.net/latest/pyscript.css"
<body> />
<div> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
Pyscript - FIRST ASYNC WITH INVOKED LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log: </head>
<py-script> <body>
import js <div>
import asyncio Pyscript - FIRST ASYNC WITH INVOKED LOOP BLOCKING AWAIT AT SAME
LEVEL AS LOOP Pyscript writing to console.log:
<py-script>
import js
import asyncio
async def asyncCallLoop1(): async def asyncCallLoop1():
for i in range(3): for i in range(3):
js.console.log('A', i) js.console.log('A', i)
await asyncio.sleep(2) await asyncio.sleep(2)
asyncCallLoop1() asyncCallLoop1()
</py-script> </py-script>
</div> </div>
<div> <div>
Pyscript - SECOND ASYNC WITH INVOKED LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log: Pyscript - SECOND ASYNC WITH INVOKED LOOP BLOCKING AWAIT AT SAME
<py-script> LEVEL AS LOOP Pyscript writing to console.log:
import js <py-script>
import asyncio import js
import asyncio
async def asyncCallLoop2(): async def asyncCallLoop2():
for i in range(3): for i in range(3):
js.console.log('B', i) js.console.log('B', i)
await asyncio.sleep(2) await asyncio.sleep(2)
asyncCallLoop2() asyncCallLoop2()
</py-script> </py-script>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,37 +1,42 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Async Await BLOCKING LOOP Pyscript Twice</title> <title>Async Await BLOCKING LOOP Pyscript Twice</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
<script defer src="https://pyscript.net/latest/pyscript.js"></script> rel="stylesheet"
</head> href="https://pyscript.net/latest/pyscript.css"
<body> />
<div> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
Pyscript - FIRST ASYNC WITH INVOKED LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log: </head>
<py-script> <body>
import js <div>
import asyncio Pyscript - FIRST ASYNC WITH INVOKED LOOP BLOCKING AWAIT AT SAME
LEVEL AS LOOP Pyscript writing to console.log:
<py-script>
import js
import asyncio
async def asyncCallLoop1(): async def asyncCallLoop1():
for i in range(3): for i in range(3):
js.console.log('A', i) js.console.log('A', i)
await asyncio.sleep(2) await asyncio.sleep(2)
asyncCallLoop1() asyncCallLoop1()
</py-script> </py-script>
</div> </div>
<div> <div>
Pyscript - SECOND ASYNC WITH TOP-LEVEL LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log: Pyscript - SECOND ASYNC WITH TOP-LEVEL LOOP BLOCKING AWAIT AT SAME
<py-script> LEVEL AS LOOP Pyscript writing to console.log:
import js <py-script>
import asyncio import js
import asyncio
for i in range(3): for i in range(3):
js.console.log('B', i) js.console.log('B', i)
await asyncio.sleep(2) await asyncio.sleep(2)
</py-script> </py-script>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,40 +1,45 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Async Await NON-BLOCKING Pyscript Twice</title> <title>Async Await NON-BLOCKING Pyscript Twice</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
<script defer src="https://pyscript.net/latest/pyscript.js"></script> rel="stylesheet"
</head> href="https://pyscript.net/latest/pyscript.css"
<body> />
<div> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
Pyscript - FIRST ASYNC WITH NON-BLOCKING AWAIT AT ONE LEVEL LOWER THAN LOOP Pyscript writing to console.log: </head>
<py-script> <body>
import js <div>
import asyncio Pyscript - FIRST ASYNC WITH NON-BLOCKING AWAIT AT ONE LEVEL LOWER
THAN LOOP Pyscript writing to console.log:
<py-script>
import js
import asyncio
async def asyncCall1(): async def asyncCall1():
await asyncio.sleep(2) await asyncio.sleep(2)
for i in range(3): for i in range(3):
js.console.log('A', i) js.console.log('A', i)
asyncCall1() asyncCall1()
</py-script> </py-script>
</div> </div>
<div> <div>
Pyscript - SECOND ASYNC WITH NON-BLOCKING AWAIT AT ONE LEVEL LOWER THAN LOOP Pyscript writing to console.log: Pyscript - SECOND ASYNC WITH NON-BLOCKING AWAIT AT ONE LEVEL LOWER
<py-script> THAN LOOP Pyscript writing to console.log:
import js <py-script>
import asyncio import js
import asyncio
async def asyncCall2(): async def asyncCall2():
await asyncio.sleep(2) await asyncio.sleep(2)
for i in range(3): for i in range(3):
js.console.log('B', i) js.console.log('B', i)
asyncCall2() asyncCall2()
</py-script> </py-script>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,34 +1,39 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Async Await BLOCKING LOOP Pyscript Twice</title> <title>Async Await BLOCKING LOOP Pyscript Twice</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
<script defer src="https://pyscript.net/latest/pyscript.js"></script> rel="stylesheet"
</head> href="https://pyscript.net/latest/pyscript.css"
<body> />
<div> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
Pyscript - FIRST ASYNC WITH TOP-LEVEL LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log: </head>
<py-script> <body>
import js <div>
import asyncio Pyscript - FIRST ASYNC WITH TOP-LEVEL LOOP BLOCKING AWAIT AT SAME
LEVEL AS LOOP Pyscript writing to console.log:
<py-script>
import js
import asyncio
for i in range(3): for i in range(3):
js.console.log('A', i) js.console.log('A', i)
await asyncio.sleep(2) await asyncio.sleep(2)
</py-script> </py-script>
</div> </div>
<div> <div>
Pyscript - SECOND ASYNC WITH TOP-LEVEL LOOP BLOCKING AWAIT AT SAME LEVEL AS LOOP Pyscript writing to console.log: Pyscript - SECOND ASYNC WITH TOP-LEVEL LOOP BLOCKING AWAIT AT SAME
<py-script> LEVEL AS LOOP Pyscript writing to console.log:
import js <py-script>
import asyncio import js
import asyncio
for i in range(3): for i in range(3):
js.console.log('B', i) js.console.log('B', i)
await asyncio.sleep(2) await asyncio.sleep(2)
</py-script> </py-script>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,19 +1,22 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Async Await BLOCKING LOOP Pyscript Twice</title> <title>Async Await BLOCKING LOOP Pyscript Twice</title>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
<script defer src="https://pyscript.net/latest/pyscript.js"></script> rel="stylesheet"
</head> href="https://pyscript.net/latest/pyscript.css"
<body> />
<py-script> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
import asyncio </head>
from itertools import count <body>
for i in count(): <py-script>
print(f"Count: {i}") import asyncio
await asyncio.sleep(1) from itertools import count
</py-script> for i in count():
</body> print(f"Count: {i}")
await asyncio.sleep(1)
</py-script>
</body>
</html> </html>

View File

@@ -1,115 +1,86 @@
<html><head> <html>
<title>Bokeh Example</title> <head>
<meta charset="iso-8859-1"> <title>Bokeh Example</title>
<link rel="icon" type="image/x-icon" href="./favicon.png"> <meta charset="iso-8859-1" />
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.min.js"></script> <link rel="icon" type="image/x-icon" href="./favicon.png" />
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.2.min.js"></script> <script
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script> type="text/javascript"
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script> src="https://cdn.bokeh.org/bokeh/release/bokeh-3.0.3.min.js"
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.2.min.js"></script> ></script>
<script
type="text/javascript"
src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.0.3.min.js"
></script>
<script
type="text/javascript"
src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.0.3.min.js"
></script>
<script
type="text/javascript"
src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.0.3.min.js"
></script>
<script
type="text/javascript"
src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-3.0.3.min.js"
></script>
<script type="text/javascript"> <script type="text/javascript">
Bokeh.set_log_level("info"); Bokeh.set_log_level("info");
</script> </script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
rel="stylesheet"
href="https://pyscript.net/latest/pyscript.css"
/>
<script defer src="https://pyscript.net/latest/pyscript.js"></script> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
<link rel="stylesheet" href="./assets/css/examples.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>
</head> </head>
<body> <body>
<nav class="navbar" style="background-color: #000000;"> <nav class="navbar" style="background-color: #000000">
<div class="app-header"> <div class="app-header">
<a href="/"> <a href="/">
<img src="./logo.png" class="logo"> <img src="./logo.png" class="logo" />
</a> </a>
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a> <a class="title" href="" style="color: #f0ab3c"
</div> >Bokeh Example</a
</nav> >
<section class="pyscript"> </div>
<div id="myplot"></div> </nav>
<py-tutor>
<section class="pyscript">
<div id="myplot"></div>
<py-config> <py-config>
packages = [ packages = [
"bokeh", "pandas",
"numpy" "bokeh",
] "xyzservices"
</py-config> ]
plugins = [
"https://pyscript.net/latest/plugins/python/py_tutor.py"
]
</py-config>
<py-script id="main"> <py-script id="main">
import json import json
import pyodide import pyodide
from js import Bokeh, console, JSON from js import Bokeh, console, JSON
from bokeh.embed import json_item from bokeh.embed import json_item
from bokeh.plotting import figure from bokeh.plotting import figure
from bokeh.resources import CDN from bokeh.resources import CDN
# create a new plot with default tools, using figure # create a new plot with default tools, using figure
p = figure(plot_width=400, plot_height=400) p = figure(width=400, height=400)
# add a circle renderer with x and y coordinates, size, color, and alpha # 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.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")) p_json = json.dumps(json_item(p, "myplot"))
Bokeh.embed.embed_item(JSON.parse(p_json)) Bokeh.embed.embed_item(JSON.parse(p_json))
</py-script> </py-script>
</section> </section>
<section class="code"> </py-tutor>
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div> </body>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy"
]
&lt;/py-config&gt;
&lt;py-script id="main"&gt;
import json
import pyodide
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))
&lt;/py-script&gt;
</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> </html>

View File

@@ -1,216 +1,136 @@
<html><head> <html>
<title>Bokeh Example</title> <head>
<meta charset="iso-8859-1"> <title>Bokeh Example</title>
<link rel="icon" type="image/x-icon" href="./favicon.png"> <meta charset="iso-8859-1" />
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.js"></script> <link rel="icon" type="image/x-icon" href="./favicon.png" />
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.2.min.js"></script> <script
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script> type="text/javascript"
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script> src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.js"
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.2.min.js"></script> ></script>
<link rel="stylesheet" href="./assets/css/examples.css" /> <script
<link rel="stylesheet" href="./assets/prism/prism.css" /> type="text/javascript"
<script defer src="./assets/prism/prism.js"></script> src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.3.min.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.bokeh.org/bokeh/release/bokeh-mathjax-2.4.3.min.js"
></script>
<link rel="stylesheet" href="./assets/css/examples.css" />
<script type="text/javascript"> <script type="text/javascript">
Bokeh.set_log_level("info"); Bokeh.set_log_level("info");
</script> </script>
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
rel="stylesheet"
href="https://pyscript.net/latest/pyscript.css"
/>
<script defer src="https://pyscript.net/latest/pyscript.js"></script> <script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head> </head>
<body> <body>
<nav class="navbar" style="background-color: #000000;"> <nav class="navbar" style="background-color: #000000">
<div class="app-header"> <div class="app-header">
<a href="/"> <a href="/">
<img src="./logo.png" class="logo"> <img src="./logo.png" class="logo" />
</a> </a>
<a class="title" href="" style="color: #f0ab3c;">Bokeh Example</a> <a class="title" href="" style="color: #f0ab3c"
</div> >Bokeh Example</a
</nav> >
<section class="pyscript"> </div>
<h1>Bokeh Example</h1> </nav>
<div id="myplot"></div> <py-tutor>
<section class="pyscript">
<h1>Bokeh Example</h1>
<div id="myplot"></div>
<py-config> <py-config>
packages = [ packages = [
"bokeh", "https://cdn.holoviz.org/panel/0.14.3/dist/wheels/bokeh-2.4.3-py3-none-any.whl",
"numpy" "numpy",
] ]
</py-config> plugins = [
"https://pyscript.net/latest/plugins/python/py_tutor.py"
]
</py-config>
<py-script id="main"> <py-script id="main">
import asyncio import asyncio
import json import json
import pyodide import pyodide
from js import Bokeh, console, JSON from js import Bokeh, console, JSON
from bokeh import __version__ from bokeh import __version__
from bokeh.document import Document from bokeh.document import Document
from bokeh.embed.util import OutputDocumentFor, standalone_docs_json_and_render_items from bokeh.embed.util import OutputDocumentFor, standalone_docs_json_and_render_items
from bokeh.models import Slider, Div from bokeh.models import Slider, Div
from bokeh.layouts import Row from bokeh.layouts import Row
from bokeh.protocol.messages.patch_doc import process_document_events from bokeh.protocol.messages.patch_doc import process_document_events
# create a new plot with default tools, using figure # create a new plot with default tools, using figure
p = Slider(start=0.1, end=10, value=1, step=.1, title="Amplitude") p = Slider(start=0.1, end=10, value=1, step=.1, title="Amplitude")
div = Div(text=f'Amplitude is: {p.value}') div = Div(text=f'Amplitude is: {p.value}')
def callback(attr, old, new): def callback(attr, old, new):
div.text = f'Amplitude is: {new}' div.text = f'Amplitude is: {new}'
p.on_change('value', callback) p.on_change('value', callback)
row = Row(children=[p, div]) row = Row(children=[p, div])
def doc_json(model, target): def doc_json(model, target):
with OutputDocumentFor([model]) as doc: with OutputDocumentFor([model]) as doc:
doc.title = "" doc.title = ""
docs_json, _ = standalone_docs_json_and_render_items( docs_json, _ = standalone_docs_json_and_render_items(
[model], suppress_callback_warning=True [model], suppress_callback_warning=True
) )
doc_json = list(docs_json.values())[0] doc_json = list(docs_json.values())[0]
root_id = doc_json['roots']['root_ids'][0] root_id = doc_json['roots']['root_ids'][0]
return doc, json.dumps(dict( return doc, json.dumps(dict(
target_id = target, target_id = target,
root_id = root_id, root_id = root_id,
doc = doc_json, doc = doc_json,
version = __version__, version = __version__,
)) ))
def _link_docs(pydoc, jsdoc): def _link_docs(pydoc, jsdoc):
def jssync(event): def jssync(event):
if getattr(event, 'setter_id', None) is not None: if getattr(event, 'setter_id', None) is not None:
return return
events = [event] events = [event]
json_patch = jsdoc.create_json_patch_string(pyodide.ffi.to_js(events)) json_patch = jsdoc.create_json_patch_string(pyodide.ffi.to_js(events))
pydoc.apply_json_patch(json.loads(json_patch)) pydoc.apply_json_patch(json.loads(json_patch))
jsdoc.on_change(pyodide.ffi.create_proxy(jssync), pyodide.ffi.to_js(False)) jsdoc.on_change(pyodide.ffi.create_proxy(jssync), pyodide.ffi.to_js(False))
def pysync(event): def pysync(event):
json_patch, buffers = process_document_events([event], use_buffers=True) json_patch, buffers = process_document_events([event], use_buffers=True)
buffer_map = {} buffer_map = {}
for (ref, buffer) in buffers: for (ref, buffer) in buffers:
buffer_map[ref['id']] = buffer buffer_map[ref['id']] = buffer
jsdoc.apply_json_patch(JSON.parse(json_patch), pyodide.ffi.to_js(buffer_map), setter_id='js') jsdoc.apply_json_patch(JSON.parse(json_patch), pyodide.ffi.to_js(buffer_map), setter_id='js')
pydoc.on_change(pysync) pydoc.on_change(pysync)
async def show(plot, target): async def show(plot, target):
pydoc, model_json = doc_json(plot, target) pydoc, model_json = doc_json(plot, target)
views = await Bokeh.embed.embed_item(JSON.parse(model_json)) views = await Bokeh.embed.embed_item(JSON.parse(model_json))
jsdoc = views[0].model.document jsdoc = views[0].model.document
_link_docs(pydoc, jsdoc) _link_docs(pydoc, jsdoc)
asyncio.ensure_future(show(row, 'myplot')) asyncio.ensure_future(show(row, 'myplot'))
</py-script> </py-script>
</section> </section>
<section class="code"> </py-tutor>
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div> </body>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;py-config&gt;
packages = [
"bokeh",
"numpy"
]
&lt;/py-config&gt;
&lt;py-script id="main"&gt;
import asyncio
import json
import pyodide
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'))
&lt;/py-script&gt;
</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> </html>

View File

@@ -1,314 +1,138 @@
<html> <html>
<head> <head>
<title>d3: JavaScript & PyScript visualizations side-by-side</title> <title>d3: JavaScript & PyScript visualizations side-by-side</title>
<meta charset="utf-8"> <meta charset="utf-8" />
<link rel="icon" type="image/x-icon" href="./favicon.png"> <link rel="icon" type="image/x-icon" href="./favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
<script defer src="https://pyscript.net/latest/pyscript.js"></script> rel="stylesheet"
<link rel="stylesheet" href="./assets/css/examples.css" /> href="https://pyscript.net/latest/pyscript.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>
<script src="https://d3js.org/d3.v7.min.js"></script> <link rel="stylesheet" href="./assets/css/examples.css" />
<style> <script src="https://d3js.org/d3.v7.min.js"></script>
.loading { <style>
display: inline-block; .loading {
width: 50px; display: inline-block;
height: 50px; width: 50px;
border: 3px solid rgba(255, 255, 255, 0.3); height: 50px;
border-radius: 50%; border: 3px solid rgba(255, 255, 255, 0.3);
border-top-color: black; border-radius: 50%;
animation: spin 1s ease-in-out infinite; border-top-color: black;
} animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
</head> </head>
<body> <body>
<nav class="navbar" style="background-color: #000000;"> <nav class="navbar" style="background-color: #000000">
<div class="app-header"> <div class="app-header">
<a href="/"> <a href="/">
<img src="./logo.png" class="logo"> <img src="./logo.png" class="logo" />
</a> </a>
<a class="title" href="" style="color: #f0ab3c;">Simple d3 visualization</a> <a class="title" href="" style="color: #f0ab3c"
</div> >Simple d3 visualization</a
</nav> >
<section class="pyscript"> </div>
<b> </nav>
Based on <i><a href="https://observablehq.com/@d3/learn-d3-shapes?collection=@d3/learn-d3>">Learn D3: Shapes</a></i> tutorial.
</b>
<div style="display: flex; flex-direction: row">
<div>
<div style="text-align: center">JavaScript version</div>
<div id="js" style="width: 400px; height: 400px">
<div class="loading"></div>
</div>
</div>
<div>
<div style="text-align: center">PyScript version</div>
<div id="py" style="width: 400px; height: 400px">
<div class="loading"></div>
</div>
</div>
</div>
<script type="module"> <section class="pyscript">
<py-tutor modules="d3.py">
const fruits = [ <py-config>
{name: "🍊", count: 21}, plugins = [
{name: "🍇", count: 13}, "https://pyscript.net/latest/plugins/python/py_tutor.py"
{name: "🍏", count: 8}, ]
{name: "🍌", count: 5}, [[fetch]]
{name: "🍐", count: 3}, files = ["./d3.py"]
{name: "🍋", count: 2}, </py-config>
{name: "🍎", count: 1}, </py-tutor>
{name: "🍉", count: 1}, <b>
] Based on
<i
const fn = (d) => d.count ><a
const data = d3.pie().value(fn)(fruits) href="https://observablehq.com/@d3/learn-d3-shapes?collection=@d3/learn-d3>"
>Learn D3: Shapes</a
const arc = d3.arc() ></i
.innerRadius(210) >
.outerRadius(310) tutorial.
.padRadius(300) </b>
.padAngle(2 / 300) <div style="display: flex; flex-direction: row">
.cornerRadius(8) <div>
<div style="text-align: center">JavaScript version</div>
const js = d3.select("#js") <div id="js" style="width: 400px; height: 400px">
js.select(".loading").remove() <div class="loading"></div>
</div>
const svg = js </div>
.append("svg") <div>
.attr("viewBox", "-320 -320 640 640") <div style="text-align: center">PyScript version</div>
.attr("width", "400") <div id="py" style="width: 400px; height: 400px">
.attr("height", "400") <div class="loading"></div>
</div>
for (const d of data) { </div>
svg.append("path") </div>
.style("fill", "steelblue") <py-script src="d3.py"></py-script>
.attr("d", arc(d)) </section>
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>
import js
from pyodide.ffi import create_proxy, to_js
d3 = js.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>
</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">
&lt;script&gt;
import * as d3 from "https://cdn.skypack.dev/pin/d3@v7.6.1-1Q0NZ0WZnbYeSjDusJT3/mode=imports,min/optimized/d3.js";
<script type="module">
const fruits = [ const fruits = [
{name: "🍊", count: 21}, { name: "🍊", count: 21 },
{name: "🍇", count: 13}, { name: "🍇", count: 13 },
{name: "🍏", count: 8}, { name: "🍏", count: 8 },
{name: "🍌", count: 5}, { name: "🍌", count: 5 },
{name: "🍐", count: 3}, { name: "🍐", count: 3 },
{name: "🍋", count: 2}, { name: "🍋", count: 2 },
{name: "🍎", count: 1}, { name: "🍎", count: 1 },
{name: "🍉", count: 1}, { name: "🍉", count: 1 },
] ];
const fn = (d) => d.count const fn = (d) => d.count;
const data = d3.pie().value(fn)(fruits) const data = d3.pie().value(fn)(fruits);
const arc = d3.arc() const arc = d3
.innerRadius(210) .arc()
.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)
}
&lt;/script&gt;
&lt;py-script&gt;
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) .innerRadius(210)
.outerRadius(310) .outerRadius(310)
.padRadius(300) .padRadius(300)
.padAngle(2 / 300) .padAngle(2 / 300)
.cornerRadius(8)) .cornerRadius(8);
py = d3.select("#py") const js = d3.select("#js");
py.select(".loading").remove() js.select(".loading").remove();
svg = (py const svg = js
.append("svg") .append("svg")
.attr("viewBox", "-320 -320 640 640") .attr("viewBox", "-320 -320 640 640")
.attr("width", "400") .attr("width", "400")
.attr("height", "400")) .attr("height", "400");
for d in data: for (const d of data) {
d_py = d.to_py() svg.append("path").style("fill", "steelblue").attr("d", arc(d));
(svg.append("path") const text = svg
.style("fill", "steelblue") .append("text")
.attr("d", arc(d))) .style("fill", "white")
.attr(
"transform",
`translate(${arc.centroid(d).join(",")})`,
)
.attr("text-anchor", "middle");
text = (svg.append("text") text.append("tspan")
.style("fill", "white") .style("font-size", "24")
.attr("transform", f"translate({arc.centroid(d).join(',')})") .attr("x", "0")
.attr("text-anchor", "middle")) .text(d.data.name);
(text.append("tspan") text.append("tspan")
.style("font-size", "24") .style("font-size", "18")
.attr("x", "0") .attr("x", "0")
.text(d_py["data"]["name"])) .attr("dy", "1.3em")
.text(d.value);
(text.append("tspan") }
.style("font-size", "18") </script>
.attr("x", "0") </body>
.attr("dy", "1.3em")
.text(d_py["value"]))
&lt;/py-script&gt;
</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> </html>

64
examples/d3.py Normal file
View File

@@ -0,0 +1,64 @@
import js
from pyodide.ffi import create_proxy, to_js
d3 = js.d3
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},
]
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"])
)

View File

@@ -1,136 +1,72 @@
<html> <html>
<head> <head>
<title>Folium</title> <title>Folium</title>
<meta charset="utf-8"> <meta charset="utf-8" />
<link rel="icon" type="image/x-icon" href="./favicon.png"> <link rel="icon" type="image/x-icon" href="./favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
<script defer src="https://pyscript.net/latest/pyscript.js"></script> rel="stylesheet"
<link rel="stylesheet" href="./assets/css/examples.css" /> href="https://pyscript.net/latest/pyscript.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>
<link rel="stylesheet" href="./assets/css/examples.css" />
</head> </head>
<body> <body>
<nav class="navbar" style="background-color: #000000;"> <nav class="navbar" style="background-color: #000000">
<div class="app-header"> <div class="app-header">
<a href="/"> <a href="/">
<img src="./logo.png" class="logo"> <img src="./logo.png" class="logo" />
</a> </a>
<a class="title" href="" style="color: #f0ab3c;">Folium</a> <a class="title" href="" style="color: #f0ab3c">Folium</a>
</div> </div>
</nav> </nav>
<section class="pyscript"> <section class="pyscript">
<div id="folium"></div> <div id="folium"></div>
<py-config> <py-tutor>
packages = [ <py-config>
"folium", packages = [
"pandas" "folium",
] "pandas"
</py-config> ]
plugins = [
"https://pyscript.net/latest/plugins/python/py_tutor.py"
]
</py-config>
<py-script> <py-script>
import folium import folium
import json import json
import pandas as pd import pandas as pd
from pyodide.http import open_url from pyodide.http import open_url
url = ( url = (
"https://raw.githubusercontent.com/python-visualization/folium/master/examples/data" "https://raw.githubusercontent.com/python-visualization/folium/master/examples/data"
) )
state_geo = f"{url}/us-states.json" state_geo = f"{url}/us-states.json"
state_unemployment = f"{url}/US_Unemployment_Oct2012.csv" state_unemployment = f"{url}/US_Unemployment_Oct2012.csv"
state_data = pd.read_csv(open_url(state_unemployment)) state_data = pd.read_csv(open_url(state_unemployment))
geo_json = json.loads(open_url(state_geo).read()) geo_json = json.loads(open_url(state_geo).read())
m = folium.Map(location=[48, -102], zoom_start=3) m = folium.Map(location=[48, -102], zoom_start=3)
folium.Choropleth( folium.Choropleth(
geo_data=geo_json, geo_data=geo_json,
name="choropleth", name="choropleth",
data=state_data, data=state_data,
columns=["State", "Unemployment"], columns=["State", "Unemployment"],
key_on="feature.id", key_on="feature.id",
fill_color="YlGn", fill_color="YlGn",
fill_opacity=0.7, fill_opacity=0.7,
line_opacity=0.2, line_opacity=0.2,
legend_name="Unemployment Rate (%)", legend_name="Unemployment Rate (%)",
).add_to(m) ).add_to(m)
folium.LayerControl().add_to(m) folium.LayerControl().add_to(m)
display(m, target="folium") display(m, target="folium")
</py-script> </py-script>
</section> </py-tutor>
<section class="code"> </section>
<div id="view-code-button" role="button" aria-pressed="false" tabindex="0">View Code</div> </body>
<div id="code-section" class="code-section-hidden">
<p>index.html</p>
<pre class="prism-code language-html">
<code class="language-html">
&lt;div id="folium"&gt;&lt;/div&gt;
&lt;py-config&gt;
packages = [
"folium",
"pandas"
]
&lt;/py-config&gt;
&lt;py-script&gt;
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")
&lt;/py-script&gt;
</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> </html>

View File

@@ -1,122 +1,138 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Say Hello</title> <title>Say Hello</title>
<link rel="icon" type="image/png" href="../favicon.png" /> <link rel="icon" type="image/png" href="../favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
rel="stylesheet"
href="https://pyscript.net/latest/pyscript.css"
/>
<script defer src="../../pyscriptjs/build/pyscript.js"></script>
<!-- <script defer src="https://pyscript.net/latest/pyscript.js"></script> -->
</head>
<script defer src="https://pyscript.net/latest/pyscript.js"></script> <body>
</head> <py-script>
from js import handTrack, requestAnimationFrame, console
from pyodide import create_once_callable
import asyncio
<body> update_note = Element("update-note")
<py-script> canvas = Element("canvas")
from js import handTrack, requestAnimationFrame video = Element("myvideo")
from pyodide import create_once_callable context = canvas.element.getContext("2d")
import asyncio
context = canvas.element.getContext("2d") isVideo = False
model = None
isVideo = False modelParams = {
model = None "flipHorizontal": True, # flip e.g for video
"maxNumBoxes": 20, # maximum number of boxes to detect
"iouThreshold": 0.5, # ioU threshold for non-max suppression
"scoreThreshold": 0.6, # confidence threshold for predictions.
}
modelParams = { def toggle_video():
"flipHorizontal": True, # flip e.g for video global isVideo
"maxNumBoxes": 20, # maximum number of boxes to detect if (not isVideo):
"iouThreshold": 0.5, # ioU threshold for non-max suppression update_note.write("Starting video")
"scoreThreshold": 0.6, # confidence threshold for predictions. pyscript.run_until_complete(start_video())
} else:
update_note.write("Stopping video")
handTrack.stopVideo(video.element)
isVideo = False
update_note.write("Video stopped")
def toggle_video(evt): async def start_video():
global isVideo global isVideo
if (not isVideo): update_note.write("Inside sstart video")
update_note.write("Starting video") status = await handTrack.startVideo(video.element)
pyscript.run_until_complete(start_video()) console.log("video started", status)
else: if status:
update_note.write("Stopping video") update_note.write("Video started. Now tracking")
handTrack.stopVideo(video.element) isVideo = True
isVideo = False console.log( "Calling RUN DETECTION")
update_note.write("Video stopped") y = await run_detection()
else:
update_note.write( "Please enable video")
async def start_video(): def sync_run_detection(evt):
global isVideo pyscript.run_until_complete(run_detection())
update_note.write("Inside sstart video")
status = await handTrack.startVideo(video.element)
console.log("video started", status)
if status:
update_note.write("Video started. Now tracking")
isVideo = True
console.log( "Calling RUN DETECTION")
y = await run_detection()
else:
update_note.write( "Please enable video")
def sync_run_detection(evt): async def run_detection():
pyscript.run_until_complete(run_detection()) console.log("in RUN DETECTION: ");
global model
global isVideo
async def run_detection(): console.log("...1")
console.log("in RUN DETECTION: ");
global model
global isVideo
console.log("...1") predictions = await model.detect(video.element)
console.log("done...1")
console.log("Predictions: ", predictions);
model.renderPredictions(predictions, canvas.element, context, video.element);
console.log("is Video?", isVideo)
if (isVideo):
console.log("requestingAnimation!")
await requestAnimationFrame(create_once_callable(sync_run_detection));
console.log("...2")
predictions = await model.detect(video.element) def run_detection_image(img):
console.log("done...1") console.log("in RUN DETECTION IMAGE", predictions);
console.log("Predictions: ", predictions); global model
model.renderPredictions(predictions, canvas.element, context, video.element); def detect(predition):
console.log("is Video?", isVideo) console.log("Predictions: ", predictions);
if (isVideo): model.renderPredictions(predictions, canvas, context, img);
console.log("requestingAnimation!") console.log("...3")
await requestAnimationFrame(create_once_callable(sync_run_detection)); model.detect(img).then(detect)
console.log("...2") console.log("...4")
def run_detection_image(img): def handle_model(lmodel):
console.log("in RUN DETECTION IMAGE", predictions); global model
global model model = lmodel
def detect(predition): update_note.write("Loaded Model!")
console.log("Predictions: ", predictions);
model.renderPredictions(predictions, canvas, context, img);
console.log("...3")
model.detect(img).then(detect)
console.log("...4")
def handle_model(lmodel): async def start():
global model console.log("creating x")
model = lmodel console.log("calling x")
update_note.write("Loaded Model!") model = await handTrack.load(modelParams)#.then(handle_model)
console.log("loaded model!")
console.log(model)
handle_model(model)
print(dir(x))
print(x)
async def start(): pyscript.run_until_complete(start())
console.log("creating x")
console.log("calling x")
model = await handTrack.load(modelParams)#.then(handle_model)
console.log("loaded model!")
console.log(model)
handle_model(model)
print(dir(x))
print(x)
pyscript.run_until_complete(start()) #});
</py-script>
#}); <div class="mb10">
<button
</py-script> id="trackbutton"
class="bx--btn bx--btn--secondary"
<div class="mb10"> type="button"
<button id="trackbutton" class="bx--btn bx--btn--secondary" type="button" py-onClick="toggle_video()"> py-click="toggle_video()"
Toggle Video >
</button> Toggle Video
<button id="nextimagebutton" class="mt10 bx--btn bx--btn--secondary" type="button" disabled> </button>
Next Image <button
</button> id="nextimagebutton"
<div id="update-note" py-mount class="updatenote mt10">loading model ..</div> class="mt10 bx--btn bx--btn--secondary"
</div> type="button"
<div> disabled
<video autoplay="autoplay" id="myvideo" py-mount="video"></video> >
<canvas id="canvas" py-mount class="border canvasbox"></canvas> Next Image
</div> </button>
<script src="lib/handtrack.min.js"> </script> <div id="update-note" class="updatenote mt10">loading model ..</div>
</div>
<div>
<video autoplay="autoplay" id="myvideo" py-mount="video"></video>
<canvas id="canvas" class="border canvasbox"></canvas>
</div>
<script src="lib/handtrack.min.js"></script>
</body>
</html> </html>

View File

@@ -1,71 +1,46 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>PyScript Hello World</title> <title>PyScript Hello World</title>
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" /> <link
<link rel="stylesheet" href="./assets/css/examples.css" /> rel="stylesheet"
<link rel="stylesheet" href="./assets/prism/prism.css" /> href="https://pyscript.net/latest/pyscript.css"
<script defer src="https://pyscript.net/latest/pyscript.js"></script> />
<script defer src="./assets/prism/prism.js"></script> <link rel="stylesheet" href="./assets/css/examples.css" />
</head> <script defer src="https://pyscript.net/latest/pyscript.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">Hello World</a>
</div>
</nav>
<body> <py-tutor>
<nav class="navbar" style="background-color: #000000;"> <py-config>
<div class="app-header"> plugins = [
<a href="/"> "https://pyscript.net/latest/plugins/python/py_tutor.py"
<img src="./logo.png" class="logo"> ]
</a> </py-config>
<a class="title" href="" style="color: #f0ab3c;">Hello World</a>
</div> <section class="pyscript">
</nav> Hello world! <br />
<section class="pyscript"> This is the current date and time, as computed by Python:
Hello world! <br> <py-script>
This is the current date and time, as computed by Python: from datetime import datetime
<py-script> now = datetime.now()
from datetime import datetime display(now.strftime("%m/%d/%Y, %H:%M:%S"))
now = datetime.now() </py-script>
display(now.strftime("%m/%d/%Y, %H:%M:%S")) </section>
</py-script> </py-tutor>
</section> </body>
<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">
&lt;py-script&gt;
from datetime import datetime
now = datetime.now()
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
&lt;/py-script&gt;
</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> </html>

View File

@@ -1,276 +1,315 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>PyScript demo</title> <title>PyScript demo</title>
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="./assets/css/main.css" /> <link rel="stylesheet" href="./assets/css/main.css" />
<link rel="stylesheet" href="./assets/css/index.css" /> <link rel="stylesheet" href="./assets/css/index.css" />
</head> </head>
<body class="container"> <body class="container">
<h1 class="title-main">PyScript demos</h1> <h1 class="title-main">PyScript demos</h1>
<section class="example"> <section class="example">
<h2>Basic examples</h2> <h2>Basic examples</h2>
<div class="container-card"> <div class="container-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./hello_world.html" target="_blank"> <a href="./hello_world.html" target="_blank">
<h2>Hello world</h2> <h2>Hello world</h2>
</a> </a>
<p> <p>
A static demo of the <code>&lt;py-script&gt;</code> tag A static demo of the
</p> <code>&lt;py-script&gt;</code> tag
</div> </p>
</div> </div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./simple_clock.html" target="_blank"> <a href="./simple_clock.html" target="_blank">
<h2>Simple clock</h2> <h2>Simple clock</h2>
</a> </a>
<p> <p>
A dynamic demo of the <code>&lt;py-script&gt;</code> tag A dynamic demo of the
</p> <code>&lt;py-script&gt;</code> tag
</div> </p>
</div> </div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./repl.html" target="_blank"> <a href="./repl.html" target="_blank">
<h2>REPL</h2> <h2>REPL</h2>
</a> </a>
<p> <p>A Python REPL (Read Eval Print Loop)</p>
A Python REPL (Read Eval Print Loop) </div>
</p> </div>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./repl2.html" target="_blank"> <a href="./repl2.html" target="_blank">
<h2>REPL2</h2> <h2>REPL2</h2>
</a> </a>
<p> <p>
A Python REPL (Read Eval Print Loop) with slightly better formatting A Python REPL (Read Eval Print Loop) with slightly
</p> better formatting
</div> </p>
</div> </div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./todo.html" target="_blank"> <a href="./todo.html" target="_blank">
<h2>TODO App</h2> <h2>TODO App</h2>
</a> </a>
<p> <p>Simple TODO App</p>
Simple TODO App </div>
</p> </div>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./todo-pylist.html" target="_blank"> <a href="./todo-pylist.html" target="_blank">
<h2>PyScript Native TODO App</h2> <h2>PyScript Native TODO App</h2>
</a> </a>
<p> <p>
Simple TODO App using <code>&lt;py-list&gt;</code> Simple TODO App using <code>&lt;py-list&gt;</code>
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<section class="example"> <section class="example">
<h2>MIME Rendering</h2> <h2>MIME Rendering</h2>
<div class="container-card"> <div class="container-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./matplotlib.html" target="_blank"> <a href="./matplotlib.html" target="_blank">
<h2>Matplotlib</h2> <h2>Matplotlib</h2>
</a> </a>
<p> <p>
Demonstrates rendering a <a href="https://matplotlib.org/" target="_blank">Matplotlib</a> figure as output of the py-script tag Demonstrates rendering a
</p> <a href="https://matplotlib.org/" target="_blank"
</div> >Matplotlib</a
</div> >
figure as output of the py-script tag
</p>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./altair.html" target="_blank"> <a href="./altair.html" target="_blank">
<h2> <h2>Altair</h2>
Altair </a>
</h2> <p>
</a> Demonstrates rendering a
<p> <a
Demonstrates rendering a <a href="https://altair-viz.github.io/" target="_blank">Altair</a> plot as output of the py-script tag href="https://altair-viz.github.io/"
</p> target="_blank"
</div> >Altair</a
</div> >
plot as output of the py-script tag
</p>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./folium.html" target="_blank"> <a href="./folium.html" target="_blank">
<h2> <h2>Folium</h2>
Folium </a>
</h2> <p>
</a> Demonstrates rendering a
<p> <a
Demonstrates rendering a href="https://python-visualization.github.io/folium/"
<a href="https://python-visualization.github.io/folium/" target="_blank">Folium</a> target="_blank"
map as output of the py-script tag >Folium</a
</p> >
</div> map as output of the py-script tag
</div> </p>
</div> </div>
</section> </div>
</div>
</section>
<section class="example"> <section class="example">
<h2>JS Interaction</h2> <h2>JS Interaction</h2>
<div class="container-card"> <div class="container-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./d3.html" target="_blank"> <a href="./d3.html" target="_blank">
<h2> <h2>Simple d3 visualization</h2>
Simple d3 visualization </a>
</h2> <p>
</a> Minimal
<p> <a href="https://d3js.org/" target="_blank">D3</a>
Minimal <a href="https://d3js.org/" target="_blank">D3</a> demo demonstrating how to create a visualization
demo demonstrating how to create a visualization </p>
</p> </div>
</div> </div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./webgl/raycaster/index.html" target="_blank"> <a href="./webgl/raycaster/index.html" target="_blank">
<h2> <h2>Webgl Icosahedron Example</h2>
Webgl Icosahedron Example </a>
</h2> <p>
</a> Demo showing how a Simple
<p> <a
Demo showing how a Simple <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API" target="_blank">WebGL</a> href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API"
scene would work in PyScript</code> tag target="_blank"
</p> >WebGL</a
</div> >
</div> scene would work in the
</div> <code>&lt;py-script&gt;</code> tag
</section> </p>
</div>
</div>
</div>
</section>
<section class="example"> <section class="example">
<h2>Visualizations & Dashboards</h2> <h2>Visualizations & Dashboards</h2>
<div class="container-card"> <div class="container-card">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./bokeh.html" target="_blank"> <a href="./bokeh.html" target="_blank">
<h2> <h2>Simple Static Bokeh Plot</h2>
Simple Static Bokeh Plot </a>
</h2> <p>
</a> Minimal Bokeh demo demonstrating how to create a
<p> simple
Minimal Bokeh demo demonstrating how to create a simple <a href="https://bokeh.org/" target="_blank"
<a href="https://bokeh.org/" target="_blank">Bokeh</a> >Bokeh</a
plot from code >
</p> plot from code
</div> </p>
</div> </div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./bokeh_interactive.html" target="_blank"> <a href="./bokeh_interactive.html" target="_blank">
<h2 class="text-2xl font-bold text-blue-600"> <h2 class="text-2xl font-bold text-blue-600">
Bokeh Interactive Bokeh Interactive
</h2> </h2>
</a> </a>
<p> <p>
Interactive demo using a Interactive demo using a
<a href="https://bokeh.org/" target="_blank">Bokeh</a> <a href="https://bokeh.org/" target="_blank"
slider widget to dynamically change a value in the page >Bokeh</a
WARNING: This examples takes a little longer to load. So be patient :) >
</p> slider widget to dynamically change a value in the
</div> page WARNING: This examples takes a little longer to
</div> load. So be patient :)
</p>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./panel_kmeans.html" target="_blank"> <a href="./panel_kmeans.html" target="_blank">
<h2 class="text-2xl font-bold text-blue-600"> <h2 class="text-2xl font-bold text-blue-600">
KMeans Demo in Panel KMeans Demo in Panel
</h2> </h2>
</a> </a>
<p> <p>
Interactive KMeans Chart using Interactive KMeans Chart using
<a href="https://panel.holoviz.org/" target="_blank">Panel</a> <a href="https://panel.holoviz.org/" target="_blank"
WARNING: This examples takes a little longer to load. So be patient :) >Panel</a
</p> >
</div> WARNING: This examples takes a little longer to
</div> load. So be patient :)
</p>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./panel_stream.html" target="_blank"> <a href="./panel_stream.html" target="_blank">
<h2 class="text-2xl font-bold text-blue-600"> <h2 class="text-2xl font-bold text-blue-600">
Streaming Demo in Panel Streaming Demo in Panel
</h2> </h2>
</a> </a>
<p> <p>
Interactive Streaming Table and Bokeh plot using Interactive Streaming Table and Bokeh plot using
<a href="https://panel.holoviz.org/" target="_blank">Panel</a> <a href="https://panel.holoviz.org/" target="_blank"
WARNING: This examples takes a little longer to load. So be patient :) >Panel</a
</p> >
</div> WARNING: This examples takes a little longer to
</div> load. So be patient :)
</p>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./panel.html" target="_blank"> <a href="./panel.html" target="_blank">
<h2 class="text-3xl font-bold text-blue-600"> <h2 class="text-3xl font-bold text-blue-600">
Simple Panel Demo Simple Panel Demo
</h2> </h2>
</a> </a>
<p> <p>
Simple demo showing Simple demo showing
<a href="https://panel.holoviz.org/" target="_blank">Panel</a> <a href="https://panel.holoviz.org/" target="_blank"
widgets interacting with parts of the page >Panel</a
WARNING: This examples takes a little longer to load. So be patient :) >
</p> widgets interacting with parts of the page WARNING:
</div> This examples takes a little longer to load. So be
</div> patient :)
</p>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./panel_deckgl.html" target="_blank"> <a href="./panel_deckgl.html" target="_blank">
<h2 class="text-2xl font-bold text-blue-600"> <h2 class="text-2xl font-bold text-blue-600">
NYC Taxi Data Panel DeckGL Demo NYC Taxi Data Panel DeckGL Demo
</h2> </h2>
</a> </a>
<p> <p>
Interactive application exploring the NYC Taxi dataset using Interactive application exploring the NYC Taxi
<a href="https://panel.holoviz.org/" target="_blank">Panel</a> and <a href="https://deck.gl/" target="_blank">DeckGL</a> dataset using
WARNING: This examples takes a little longer to load. So be patient :) <a href="https://panel.holoviz.org/" target="_blank"
</p> >Panel</a
</div> >
</div> and
<a href="https://deck.gl/" target="_blank"
>DeckGL</a
>
WARNING: This examples takes a little longer to
load. So be patient :)
</p>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<a href="./numpy_canvas_fractals.html" target="_blank"> <a href="./numpy_canvas_fractals.html" target="_blank">
<h2 class="text-2xl font-bold text-blue-600"> <h2 class="text-2xl font-bold text-blue-600">
Fractals with NumPy and canvas Fractals with NumPy and canvas
</h2> </h2>
</a> </a>
<p> <p>
Visualization of Mandelbrot and Julia sets with Visualization of Mandelbrot and Julia sets with
<a href="https://numpy.org/" target="_blank">Numpy</a> and <a href="https://numpy.org/" target="_blank"
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API" target="_blank"> >Numpy</a
HTML5 canvas >
</a> and
</p> <a
</div> href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API"
</div> target="_blank"
</div> >
</section> HTML5 canvas
</body> </a>
</p>
</div>
</div>
</div>
</section>
</body>
</html> </html>

View File

@@ -1,32 +1,35 @@
html, body, ul, li { html,
margin: 0; body,
border: 0; ul,
padding: 0; li {
margin: 0;
border: 0;
padding: 0;
} }
canvas { canvas {
display: block; display: block;
width: 762; width: 762;
margin: 0 auto; margin: 0 auto;
background-color: blue; background-color: blue;
} }
p { p {
text-align: center; text-align: center;
} }
body { body {
overflow: hidden; overflow: hidden;
height: 100%; height: 100%;
} }
html { html {
overflow: hidden; overflow: hidden;
height: 100%; height: 100%;
} }
.info { .info {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
} }

View File

@@ -1,45 +1,43 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
var Bcoin = Mario.Bcoin = function(pos) { var Bcoin = (Mario.Bcoin = function (pos) {
Mario.Entity.call(this, { Mario.Entity.call(this, {
pos: pos, pos: pos,
sprite: level.bcoinSprite(), sprite: level.bcoinSprite(),
hitbox: [0,0,16,16] hitbox: [0, 0, 16, 16],
});
}); });
}
Mario.Util.inherits(Bcoin, Mario.Entity); Mario.Util.inherits(Bcoin, Mario.Entity);
//I'm not sure whether it makes sense to use an array for vel and acc here //I'm not sure whether it makes sense to use an array for vel and acc here
//in order to keep with convention, or to just use a single value, since //in order to keep with convention, or to just use a single value, since
//it's literally impossible for these to move left or right. //it's literally impossible for these to move left or right.
Bcoin.prototype.spawn = function() { Bcoin.prototype.spawn = function () {
sounds.coin.currentTime = 0.05; sounds.coin.currentTime = 0.05;
sounds.coin.play(); sounds.coin.play();
this.idx = level.items.length; this.idx = level.items.length;
level.items.push(this); level.items.push(this);
this.active = true; this.active = true;
this.vel = -12; this.vel = -12;
this.targetpos = this.pos[1] - 32; this.targetpos = this.pos[1] - 32;
} };
Bcoin.prototype.update = function(dt) { Bcoin.prototype.update = function (dt) {
if (!this.active) return; if (!this.active) return;
if (this.vel > 0 && this.pos[1] >= this.targetpos) { if (this.vel > 0 && this.pos[1] >= this.targetpos) {
player.coins += 1; player.coins += 1;
//spawn a score thingy. //spawn a score thingy.
delete level.items[this.idx]; delete level.items[this.idx];
} }
this.acc = 0.75; this.acc = 0.75;
this.vel += this.acc; this.vel += this.acc;
this.pos[1] += this.vel; this.pos[1] += this.vel;
this.sprite.update(dt); this.sprite.update(dt);
} };
Bcoin.prototype.checkCollisions = function() {;}
Bcoin.prototype.checkCollisions = function () {};
})(); })();

View File

@@ -1,81 +1,84 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
//TODO: clean up the logic for sprite switching. //TODO: clean up the logic for sprite switching.
//TODO: There's a weird bug with the collision logic. Look into it. //TODO: There's a weird bug with the collision logic. Look into it.
var Block = Mario.Block = function(options) { var Block = (Mario.Block = function (options) {
this.item = options.item; this.item = options.item;
this.usedSprite = options.usedSprite; this.usedSprite = options.usedSprite;
this.bounceSprite = options.bounceSprite; this.bounceSprite = options.bounceSprite;
this.breakable = options.breakable; this.breakable = options.breakable;
Mario.Entity.call(this, { Mario.Entity.call(this, {
pos: options.pos, pos: options.pos,
sprite: options.sprite, sprite: options.sprite,
hitbox: [0,0,16,16] hitbox: [0, 0, 16, 16],
});
this.standing = true;
}); });
this.standing = true; Mario.Util.inherits(Block, Mario.Floor);
}
Mario.Util.inherits(Block, Mario.Floor); Block.prototype.break = function () {
sounds.breakBlock.play();
Block.prototype.break = function() { new Mario.Rubble().spawn(this.pos);
sounds.breakBlock.play(); var x = this.pos[0] / 16,
(new Mario.Rubble()).spawn(this.pos); y = this.pos[1] / 16;
var x = this.pos[0] / 16, y = this.pos[1] / 16;
delete level.blocks[y][x];
}
Block.prototype.bonk = function(power) {
sounds.bump.play();
if (power > 0 && this.breakable) {
this.break();
} else if (this.standing){
this.standing = false;
if (this.item) {
this.item.spawn();
this.item = null;
}
this.opos = [];
this.opos[0] = this.pos[0];
this.opos[1] = this.pos[1];
if (this.bounceSprite) {
this.osprite = this.sprite;
this.sprite = this.bounceSprite;
} else {
this.sprite = this.usedSprite;
}
this.vel[1] = -2;
}
}
Block.prototype.update = function(dt, gameTime) {
if (!this.standing) {
if (this.pos[1] < this.opos[1] - 8) {
this.vel[1] = 2;
}
if (this.pos[1] > this.opos[1]) {
this.vel[1] = 0;
this.pos = this.opos;
if (this.osprite) {
this.sprite = this.osprite;
}
this.standing = true;
}
} else {
if (this.sprite === this.usedSprite) {
var x = this.pos[0] / 16, y = this.pos[1] / 16;
level.statics[y][x] = new Mario.Floor(this.pos, this.usedSprite);
delete level.blocks[y][x]; delete level.blocks[y][x];
} };
}
this.pos[1] += this.vel[1]; Block.prototype.bonk = function (power) {
this.sprite.update(dt, gameTime); sounds.bump.play();
} if (power > 0 && this.breakable) {
this.break();
} else if (this.standing) {
this.standing = false;
if (this.item) {
this.item.spawn();
this.item = null;
}
this.opos = [];
this.opos[0] = this.pos[0];
this.opos[1] = this.pos[1];
if (this.bounceSprite) {
this.osprite = this.sprite;
this.sprite = this.bounceSprite;
} else {
this.sprite = this.usedSprite;
}
this.vel[1] = -2;
}
};
Block.prototype.update = function (dt, gameTime) {
if (!this.standing) {
if (this.pos[1] < this.opos[1] - 8) {
this.vel[1] = 2;
}
if (this.pos[1] > this.opos[1]) {
this.vel[1] = 0;
this.pos = this.opos;
if (this.osprite) {
this.sprite = this.osprite;
}
this.standing = true;
}
} else {
if (this.sprite === this.usedSprite) {
var x = this.pos[0] / 16,
y = this.pos[1] / 16;
level.statics[y][x] = new Mario.Floor(
this.pos,
this.usedSprite,
);
delete level.blocks[y][x];
}
}
this.pos[1] += this.vel[1];
this.sprite.update(dt, gameTime);
};
})(); })();

View File

@@ -1,47 +1,62 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
var Coin = Mario.Coin = function(pos, sprite) { var Coin = (Mario.Coin = function (pos, sprite) {
Mario.Entity.call(this, { Mario.Entity.call(this, {
pos: pos, pos: pos,
sprite: sprite, sprite: sprite,
hitbox: [0,0,16,16] hitbox: [0, 0, 16, 16],
});
this.idx = level.items.length;
}); });
this.idx = level.items.length
}
Mario.Util.inherits(Coin, Mario.Entity); Mario.Util.inherits(Coin, Mario.Entity);
Coin.prototype.isPlayerCollided = function() { Coin.prototype.isPlayerCollided = function () {
//the first two elements of the hitbox array are an offset, so let's do this now. //the first two elements of the hitbox array are an offset, so let's do this now.
var hpos1 = [this.pos[0] + this.hitbox[0], this.pos[1] + this.hitbox[1]]; var hpos1 = [
var hpos2 = [player.pos[0] + player.hitbox[0], player.pos[1] + player.hitbox[1]]; this.pos[0] + this.hitbox[0],
this.pos[1] + this.hitbox[1],
];
var hpos2 = [
player.pos[0] + player.hitbox[0],
player.pos[1] + player.hitbox[1],
];
//if the hitboxes actually overlap //if the hitboxes actually overlap
if (!(hpos1[0] > hpos2[0]+player.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) { if (
if (!(hpos1[1] > hpos2[1]+player.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) { !(
this.collect(); hpos1[0] > hpos2[0] + player.hitbox[2] ||
} hpos1[0] + this.hitbox[2] < hpos2[0]
} )
} ) {
if (
!(
hpos1[1] > hpos2[1] + player.hitbox[3] ||
hpos1[1] + this.hitbox[3] < hpos2[1]
)
) {
this.collect();
}
}
};
Coin.prototype.render = function(ctx, vX, vY) { Coin.prototype.render = function (ctx, vX, vY) {
this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY); this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY);
} };
//money is not affected by gravity, you see. //money is not affected by gravity, you see.
Coin.prototype.update = function(dt) { Coin.prototype.update = function (dt) {
this.sprite.update(dt); this.sprite.update(dt);
} };
Coin.prototype.checkCollisions = function() { Coin.prototype.checkCollisions = function () {
this.isPlayerCollided(); this.isPlayerCollided();
} };
Coin.prototype.collect = function() { Coin.prototype.collect = function () {
sounds.coin.currentTime = 0.05; sounds.coin.currentTime = 0.05;
sounds.coin.play(); sounds.coin.play();
player.coins += 1; player.coins += 1;
delete level.items[this.idx] delete level.items[this.idx];
} };
})(); })();

View File

@@ -1,34 +1,34 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
var Entity = Mario.Entity = function(options) { var Entity = (Mario.Entity = function (options) {
this.vel = [0,0]; this.vel = [0, 0];
this.acc = [0,0]; this.acc = [0, 0];
this.standing = true; this.standing = true;
this.pos = options.pos; this.pos = options.pos;
this.sprite = options.sprite; this.sprite = options.sprite;
this.hitbox = options.hitbox; this.hitbox = options.hitbox;
this.left = false; this.left = false;
} });
Entity.prototype.render = function(ctx, vX, vY) { Entity.prototype.render = function (ctx, vX, vY) {
this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY) this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY);
} };
Entity.prototype.collideWall = function(wall) { Entity.prototype.collideWall = function (wall) {
//the wall will always be a 16x16 block with hitbox = [0,0,16,16]. //the wall will always be a 16x16 block with hitbox = [0,0,16,16].
if (this.pos[0] > wall.pos[0]) { if (this.pos[0] > wall.pos[0]) {
//from the right //from the right
this.pos[0] = wall.pos[0] + wall.hitbox[2] - this.hitbox[0]; this.pos[0] = wall.pos[0] + wall.hitbox[2] - this.hitbox[0];
this.vel[0] = Math.max(0, this.vel[0]); this.vel[0] = Math.max(0, this.vel[0]);
this.acc[0] = Math.max(0, this.acc[0]); this.acc[0] = Math.max(0, this.acc[0]);
} else { } else {
this.pos[0] = wall.pos[0] + wall.hitbox[0] - this.hitbox[2] - this.hitbox[0]; this.pos[0] =
this.vel[0] = Math.min(0, this.vel[0]); wall.pos[0] + wall.hitbox[0] - this.hitbox[2] - this.hitbox[0];
this.acc[0] = Math.min(0, this.acc[0]); this.vel[0] = Math.min(0, this.vel[0]);
} this.acc[0] = Math.min(0, this.acc[0]);
} }
};
Entity.prototype.bump = function() {;} Entity.prototype.bump = function () {};
})(); })();

View File

@@ -1,126 +1,145 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
var Fireball = Mario.Fireball = function(pos) { var Fireball = (Mario.Fireball = function (pos) {
this.hit = 0; this.hit = 0;
this.standing = false; this.standing = false;
Mario.Entity.call(this, { Mario.Entity.call(this, {
pos: pos, pos: pos,
sprite: new Mario.Sprite('sprites/items.png', [96, 144], [8,8], 5, [0,1,2,3]), sprite: new Mario.Sprite(
hitbox: [0,0,8,8] "sprites/items.png",
[96, 144],
[8, 8],
5,
[0, 1, 2, 3],
),
hitbox: [0, 0, 8, 8],
});
}); });
}
Mario.Util.inherits(Fireball, Mario.Entity); Mario.Util.inherits(Fireball, Mario.Entity);
Fireball.prototype.spawn = function(left) { Fireball.prototype.spawn = function (left) {
sounds.fireball.currentTime = 0; sounds.fireball.currentTime = 0;
sounds.fireball.play(); sounds.fireball.play();
if (fireballs[0]) { if (fireballs[0]) {
this.idx = 1; this.idx = 1;
fireballs[1] = this; fireballs[1] = this;
} else { } else {
this.idx = 0; this.idx = 0;
fireballs[0] = this; fireballs[0] = this;
}
this.vel[0] = (left ? -5 : 5);
this.standing = false;
this.vel[1] = 0;
}
Fireball.prototype.render = function(ctx, vX, vY) {
this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY);
}
Fireball.prototype.update = function(dt) {
if (this.hit == 1) {
this.sprite.pos = [96, 160];
this.sprite.size = [16,16];
this.sprite.frames = [0,1,2];
this.sprite.speed = 8;
this.hit += 1;
return;
} else if (this.hit == 5) {
delete fireballs[this.idx];
player.fireballs -= 1;
return;
} else if (this.hit) {
this.hit += 1;
return;
}
//In retrospect, the way collision is being handled is RIDICULOUS
//but I don't have to use some horrible kludge for this.
if (this.standing) {
this.standing = false;
this.vel[1] = -4;
}
this.acc[1] = 0.5;
this.vel[1] += this.acc[1];
this.pos[0] += this.vel[0];
this.pos[1] += this.vel[1];
if (this.pos[0] < vX || this.pos[0] > vX + 256) {
this.hit = 1;
}
this.sprite.update(dt);
}
Fireball.prototype.collideWall = function() {
if (!this.hit) this.hit = 1;
}
Fireball.prototype.checkCollisions = function() {
if (this.hit) return;
var h = this.pos[1] % 16 < 8 ? 1 : 2;
var w = this.pos[0] % 16 < 8 ? 1 : 2;
var baseX = Math.floor(this.pos[0] / 16);
var baseY = Math.floor(this.pos[1] / 16);
if (baseY + h > 15) {
delete fireballs[this.idx];
player.fireballs -= 1;
return;
}
for (var i = 0; i < h; i++) {
for (var j = 0; j < w; j++) {
if (level.statics[baseY + i][baseX + j]) {
level.statics[baseY + i][baseX + j].isCollideWith(this);
} }
if (level.blocks[baseY + i][baseX + j]) { this.vel[0] = left ? -5 : 5;
level.blocks[baseY + i][baseX + j].isCollideWith(this); this.standing = false;
this.vel[1] = 0;
};
Fireball.prototype.render = function (ctx, vX, vY) {
this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY);
};
Fireball.prototype.update = function (dt) {
if (this.hit == 1) {
this.sprite.pos = [96, 160];
this.sprite.size = [16, 16];
this.sprite.frames = [0, 1, 2];
this.sprite.speed = 8;
this.hit += 1;
return;
} else if (this.hit == 5) {
delete fireballs[this.idx];
player.fireballs -= 1;
return;
} else if (this.hit) {
this.hit += 1;
return;
} }
}
}
var that = this; //In retrospect, the way collision is being handled is RIDICULOUS
level.enemies.forEach(function(enemy){ //but I don't have to use some horrible kludge for this.
if (enemy.flipping || enemy.pos[0] - vX > 336){ //stop checking once we get to far away dudes. if (this.standing) {
return; this.standing = false;
} else { this.vel[1] = -4;
that.isCollideWith(enemy); }
}
});
}
Fireball.prototype.isCollideWith = function(ent) { this.acc[1] = 0.5;
//the first two elements of the hitbox array are an offset, so let's do this now.
var hpos1 = [this.pos[0] + this.hitbox[0], this.pos[1] + this.hitbox[1]];
var hpos2 = [ent.pos[0] + ent.hitbox[0], ent.pos[1] + ent.hitbox[1]];
//if the hitboxes actually overlap this.vel[1] += this.acc[1];
if (!(hpos1[0] > hpos2[0]+ent.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) { this.pos[0] += this.vel[0];
if (!(hpos1[1] > hpos2[1]+ent.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) { this.pos[1] += this.vel[1];
this.hit = 1; if (this.pos[0] < vX || this.pos[0] > vX + 256) {
ent.bump(); this.hit = 1;
} }
} this.sprite.update(dt);
}; };
Fireball.prototype.bump = function() {;} Fireball.prototype.collideWall = function () {
if (!this.hit) this.hit = 1;
};
Fireball.prototype.checkCollisions = function () {
if (this.hit) return;
var h = this.pos[1] % 16 < 8 ? 1 : 2;
var w = this.pos[0] % 16 < 8 ? 1 : 2;
var baseX = Math.floor(this.pos[0] / 16);
var baseY = Math.floor(this.pos[1] / 16);
if (baseY + h > 15) {
delete fireballs[this.idx];
player.fireballs -= 1;
return;
}
for (var i = 0; i < h; i++) {
for (var j = 0; j < w; j++) {
if (level.statics[baseY + i][baseX + j]) {
level.statics[baseY + i][baseX + j].isCollideWith(this);
}
if (level.blocks[baseY + i][baseX + j]) {
level.blocks[baseY + i][baseX + j].isCollideWith(this);
}
}
}
var that = this;
level.enemies.forEach(function (enemy) {
if (enemy.flipping || enemy.pos[0] - vX > 336) {
//stop checking once we get to far away dudes.
return;
} else {
that.isCollideWith(enemy);
}
});
};
Fireball.prototype.isCollideWith = function (ent) {
//the first two elements of the hitbox array are an offset, so let's do this now.
var hpos1 = [
this.pos[0] + this.hitbox[0],
this.pos[1] + this.hitbox[1],
];
var hpos2 = [ent.pos[0] + ent.hitbox[0], ent.pos[1] + ent.hitbox[1]];
//if the hitboxes actually overlap
if (
!(
hpos1[0] > hpos2[0] + ent.hitbox[2] ||
hpos1[0] + this.hitbox[2] < hpos2[0]
)
) {
if (
!(
hpos1[1] > hpos2[1] + ent.hitbox[3] ||
hpos1[1] + this.hitbox[3] < hpos2[1]
)
) {
this.hit = 1;
ent.bump();
}
}
};
Fireball.prototype.bump = function () {};
})(); })();

View File

@@ -1,74 +1,90 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
var Fireflower = Mario.Fireflower = function(pos) { var Fireflower = (Mario.Fireflower = function (pos) {
this.spawning = false; this.spawning = false;
this.waiting = 0; this.waiting = 0;
Mario.Entity.call(this, { Mario.Entity.call(this, {
pos: pos, pos: pos,
sprite: level.fireFlowerSprite, sprite: level.fireFlowerSprite,
hitbox: [0,0,16,16] hitbox: [0, 0, 16, 16],
});
}); });
}
Mario.Util.inherits(Fireflower, Mario.Entity); Mario.Util.inherits(Fireflower, Mario.Entity);
Fireflower.prototype.render = function(ctx, vX, vY) { Fireflower.prototype.render = function (ctx, vX, vY) {
if (this.spawning > 1) return; if (this.spawning > 1) return;
this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY); this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY);
} };
Fireflower.prototype.spawn = function() { Fireflower.prototype.spawn = function () {
sounds.itemAppear.play(); sounds.itemAppear.play();
this.idx = level.items.length; this.idx = level.items.length;
level.items.push(this); level.items.push(this);
this.spawning = 12; this.spawning = 12;
this.targetpos = []; this.targetpos = [];
this.targetpos[0] = this.pos[0]; this.targetpos[0] = this.pos[0];
this.targetpos[1] = this.pos[1] - 16; this.targetpos[1] = this.pos[1] - 16;
} };
Fireflower.prototype.update = function(dt) { Fireflower.prototype.update = function (dt) {
if (this.spawning > 1) { if (this.spawning > 1) {
this.spawning -= 1; this.spawning -= 1;
if (this.spawning == 1) this.vel[1] = -.5; if (this.spawning == 1) this.vel[1] = -0.5;
return; return;
} }
if (this.spawning) { if (this.spawning) {
if (this.pos[1] <= this.targetpos[1]) { if (this.pos[1] <= this.targetpos[1]) {
this.pos[1] = this.targetpos[1]; this.pos[1] = this.targetpos[1];
this.vel[1] = 0; this.vel[1] = 0;
this.spawning = 0; this.spawning = 0;
} }
} }
this.vel[1] += this.acc[1]; this.vel[1] += this.acc[1];
this.pos[0] += this.vel[0]; this.pos[0] += this.vel[0];
this.pos[1] += this.vel[1]; this.pos[1] += this.vel[1];
this.sprite.update(dt); this.sprite.update(dt);
} };
Fireflower.prototype.checkCollisions = function() { Fireflower.prototype.checkCollisions = function () {
if (this.spawning) {return;} if (this.spawning) {
this.isPlayerCollided(); return;
} }
this.isPlayerCollided();
};
Fireflower.prototype.isPlayerCollided = function() { Fireflower.prototype.isPlayerCollided = function () {
//the first two elements of the hitbox array are an offset, so let's do this now. //the first two elements of the hitbox array are an offset, so let's do this now.
var hpos1 = [this.pos[0] + this.hitbox[0], this.pos[1] + this.hitbox[1]]; var hpos1 = [
var hpos2 = [player.pos[0] + player.hitbox[0], player.pos[1] + player.hitbox[1]]; this.pos[0] + this.hitbox[0],
this.pos[1] + this.hitbox[1],
];
var hpos2 = [
player.pos[0] + player.hitbox[0],
player.pos[1] + player.hitbox[1],
];
//if the hitboxes actually overlap //if the hitboxes actually overlap
if (!(hpos1[0] > hpos2[0]+player.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) { if (
if (!(hpos1[1] > hpos2[1]+player.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) { !(
player.powerUp(this.idx); hpos1[0] > hpos2[0] + player.hitbox[2] ||
} hpos1[0] + this.hitbox[2] < hpos2[0]
} )
} ) {
if (
//This should never be called, but just in case. !(
Fireflower.prototype.bump = function() {;} hpos1[1] > hpos2[1] + player.hitbox[3] ||
hpos1[1] + this.hitbox[3] < hpos2[1]
)
) {
player.powerUp(this.idx);
}
}
};
//This should never be called, but just in case.
Fireflower.prototype.bump = function () {};
})(); })();

View File

@@ -1,47 +1,51 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
Flag = Mario.Flag = function(pos) { Flag = Mario.Flag = function (pos) {
//afaik flags always have the same height and Y-position //afaik flags always have the same height and Y-position
this.pos = [pos, 49]; this.pos = [pos, 49];
this.hitbox = [0,0,0,0]; this.hitbox = [0, 0, 0, 0];
this.vel = [0,0]; this.vel = [0, 0];
this.acc = [0,0]; this.acc = [0, 0];
} };
Flag.prototype.collideWall = function() {; Flag.prototype.collideWall = function () {};
}
Flag.prototype.update = function(dt){ Flag.prototype.update = function (dt) {
if (!this.done && this.pos[1] >= 170) { if (!this.done && this.pos[1] >= 170) {
this.vel = [0,0]; this.vel = [0, 0];
this.pos[1] = 170; this.pos[1] = 170;
player.exit(); player.exit();
this.done = true; this.done = true;
} }
this.pos[1] += this.vel[1]; this.pos[1] += this.vel[1];
} };
Flag.prototype.checkCollisions = function() { Flag.prototype.checkCollisions = function () {
this.isPlayerCollided(); this.isPlayerCollided();
} };
Flag.prototype.isPlayerCollided = function() { Flag.prototype.isPlayerCollided = function () {
if (this.hit) return; if (this.hit) return;
if (player.pos[0] + 8 >= this.pos[0]) { if (player.pos[0] + 8 >= this.pos[0]) {
music.overworld.pause(); music.overworld.pause();
sounds.flagpole.play(); sounds.flagpole.play();
setTimeout(function() { setTimeout(function () {
music.clear.play(); music.clear.play();
}, 2000); }, 2000);
this.hit = true; this.hit = true;
player.flag(); player.flag();
this.vel = [0, 2]; this.vel = [0, 2];
} }
} };
Flag.prototype.render = function() { Flag.prototype.render = function () {
level.flagpoleSprites[2].render(ctx, this.pos[0]-8, this.pos[1], vX, vY); level.flagpoleSprites[2].render(
} ctx,
this.pos[0] - 8,
this.pos[1],
vX,
vY,
);
};
})(); })();

View File

@@ -1,56 +1,83 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
var Floor = Mario.Floor = function(pos, sprite) { var Floor = (Mario.Floor = function (pos, sprite) {
Mario.Entity.call(this, {
pos: pos,
sprite: sprite,
hitbox: [0, 0, 16, 16],
});
});
Mario.Entity.call(this, { Mario.Util.inherits(Floor, Mario.Entity);
pos: pos,
sprite: sprite,
hitbox: [0,0,16,16]
});
}
Mario.Util.inherits(Floor, Mario.Entity); Floor.prototype.isCollideWith = function (ent) {
//the first two elements of the hitbox array are an offset, so let's do this now.
var hpos1 = [
Math.floor(this.pos[0] + this.hitbox[0]),
Math.floor(this.pos[1] + this.hitbox[1]),
];
var hpos2 = [
Math.floor(ent.pos[0] + ent.hitbox[0]),
Math.floor(ent.pos[1] + ent.hitbox[1]),
];
Floor.prototype.isCollideWith = function (ent) { //if the hitboxes actually overlap
//the first two elements of the hitbox array are an offset, so let's do this now. if (
var hpos1 = [Math.floor(this.pos[0] + this.hitbox[0]), Math.floor(this.pos[1] + this.hitbox[1])]; !(
var hpos2 = [Math.floor(ent.pos[0] + ent.hitbox[0]), Math.floor(ent.pos[1] + ent.hitbox[1])]; hpos1[0] > hpos2[0] + ent.hitbox[2] ||
hpos1[0] + this.hitbox[2] < hpos2[0]
)
) {
if (
!(
hpos1[1] > hpos2[1] + ent.hitbox[3] ||
hpos1[1] + this.hitbox[3] < hpos2[1]
)
) {
if (!this.standing) {
ent.bump();
} else {
//if the entity is over the block, it's basically floor
var center = hpos2[0] + ent.hitbox[2] / 2;
if (
Math.abs(hpos2[1] + ent.hitbox[3] - hpos1[1]) <=
ent.vel[1]
) {
if (
level.statics[this.pos[1] / 16 - 1][
this.pos[0] / 16
]
) {
return;
}
ent.vel[1] = 0;
ent.pos[1] = hpos1[1] - ent.hitbox[3] - ent.hitbox[1];
ent.standing = true;
if (ent instanceof Mario.Player) {
ent.jumping = 0;
}
} else if (
Math.abs(hpos2[1] - hpos1[1] - this.hitbox[3]) >
ent.vel[1] &&
center + 2 >= hpos1[0] &&
center - 2 <= hpos1[0] + this.hitbox[2]
) {
//ent is under the block.
ent.vel[1] = 0;
ent.pos[1] = hpos1[1] + this.hitbox[3];
if (ent instanceof Mario.Player) {
this.bonk(ent.power);
ent.jumping = 0;
}
} else {
//entity is hitting it from the side, we're a wall
ent.collideWall(this);
}
}
}
}
};
//if the hitboxes actually overlap Floor.prototype.bonk = function () {};
if (!(hpos1[0] > hpos2[0]+ent.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) {
if (!(hpos1[1] > hpos2[1]+ent.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) {
if (!this.standing) {
ent.bump();
} else {
//if the entity is over the block, it's basically floor
var center = hpos2[0] + ent.hitbox[2] / 2;
if (Math.abs(hpos2[1] + ent.hitbox[3] - hpos1[1]) <= ent.vel[1]) {
if (level.statics[(this.pos[1] / 16) - 1][this.pos[0] / 16]) {return};
ent.vel[1] = 0;
ent.pos[1] = hpos1[1] - ent.hitbox[3] - ent.hitbox[1];
ent.standing = true;
if (ent instanceof Mario.Player) {
ent.jumping = 0;
}
} else if (Math.abs(hpos2[1] - hpos1[1] - this.hitbox[3]) > ent.vel[1] &&
center + 2 >= hpos1[0] && center - 2 <= hpos1[0] + this.hitbox[2]) {
//ent is under the block.
ent.vel[1] = 0;
ent.pos[1] = hpos1[1] + this.hitbox[3];
if (ent instanceof Mario.Player) {
this.bonk(ent.power);
ent.jumping = 0;
}
} else {
//entity is hitting it from the side, we're a wall
ent.collideWall(this);
}
}
}
}
}
Floor.prototype.bonk = function() {;}
})(); })();

View File

@@ -1,20 +1,22 @@
var requestAnimFrame = (function(){ var requestAnimFrame = (function () {
return window.requestAnimationFrame || return (
window.webkitRequestAnimationFrame || window.requestAnimationFrame ||
window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
window.oRequestAnimationFrame || window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame || window.oRequestAnimationFrame ||
function(callback){ window.msRequestAnimationFrame ||
window.setTimeout(callback, 1000 / 60); function (callback) {
}; window.setTimeout(callback, 1000 / 60);
}
);
})(); })();
//create the canvas //create the canvas
var canvas = document.createElement("canvas"); var canvas = document.createElement("canvas");
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext("2d");
var updateables = []; var updateables = [];
var fireballs = []; var fireballs = [];
var player = new Mario.Player([0,0]); var player = new Mario.Player([0, 0]);
//we might have to get the size and calculate the scaling //we might have to get the size and calculate the scaling
//but this method should let us make it however big. //but this method should let us make it however big.
@@ -23,7 +25,7 @@ var player = new Mario.Player([0,0]);
//TODO: fiddling with scaled sprites looks BETTER, but not perfect. Hmm. //TODO: fiddling with scaled sprites looks BETTER, but not perfect. Hmm.
canvas.width = 762; canvas.width = 762;
canvas.height = 720; canvas.height = 720;
ctx.scale(3,3); ctx.scale(3, 3);
document.body.appendChild(canvas); document.body.appendChild(canvas);
//viewport //viewport
@@ -34,12 +36,12 @@ var vX = 0,
//load our images //load our images
resources.load([ resources.load([
'sprites/player.png', "sprites/player.png",
'sprites/enemy.png', "sprites/enemy.png",
'sprites/tiles.png', "sprites/tiles.png",
'sprites/playerl.png', "sprites/playerl.png",
'sprites/items.png', "sprites/items.png",
'sprites/enemyr.png', "sprites/enemyr.png",
]); ]);
resources.onReady(init); resources.onReady(init);
@@ -50,192 +52,202 @@ var music;
//initialize //initialize
var lastTime; var lastTime;
function init() { function init() {
music = { music = {
overworld: new Audio('sounds/aboveground_bgm.ogg'), overworld: new Audio("sounds/aboveground_bgm.ogg"),
underground: new Audio('sounds/underground_bgm.ogg'), underground: new Audio("sounds/underground_bgm.ogg"),
clear: new Audio('sounds/stage_clear.wav'), clear: new Audio("sounds/stage_clear.wav"),
death: new Audio('sounds/mariodie.wav') death: new Audio("sounds/mariodie.wav"),
}; };
sounds = { sounds = {
smallJump: new Audio('sounds/jump-small.wav'), smallJump: new Audio("sounds/jump-small.wav"),
bigJump: new Audio('sounds/jump-super.wav'), bigJump: new Audio("sounds/jump-super.wav"),
breakBlock: new Audio('sounds/breakblock.wav'), breakBlock: new Audio("sounds/breakblock.wav"),
bump: new Audio('sounds/bump.wav'), bump: new Audio("sounds/bump.wav"),
coin: new Audio('sounds/coin.wav'), coin: new Audio("sounds/coin.wav"),
fireball: new Audio('sounds/fireball.wav'), fireball: new Audio("sounds/fireball.wav"),
flagpole: new Audio('sounds/flagpole.wav'), flagpole: new Audio("sounds/flagpole.wav"),
kick: new Audio('sounds/kick.wav'), kick: new Audio("sounds/kick.wav"),
pipe: new Audio('sounds/pipe.wav'), pipe: new Audio("sounds/pipe.wav"),
itemAppear: new Audio('sounds/itemAppear.wav'), itemAppear: new Audio("sounds/itemAppear.wav"),
powerup: new Audio('sounds/powerup.wav'), powerup: new Audio("sounds/powerup.wav"),
stomp: new Audio('sounds/stomp.wav') stomp: new Audio("sounds/stomp.wav"),
}; };
Mario.oneone(); Mario.oneone();
lastTime = Date.now(); lastTime = Date.now();
main(); main();
} }
var gameTime = 0; var gameTime = 0;
//set up the game loop //set up the game loop
function main() { function main() {
var now = Date.now(); var now = Date.now();
var dt = (now - lastTime) / 1000.0; var dt = (now - lastTime) / 1000.0;
update(dt); update(dt);
render(); render();
lastTime = now; lastTime = now;
requestAnimFrame(main); requestAnimFrame(main);
} }
function update(dt) { function update(dt) {
gameTime += dt; gameTime += dt;
handleInput(dt); handleInput(dt);
updateEntities(dt, gameTime); updateEntities(dt, gameTime);
checkCollisions(); checkCollisions();
} }
function handleInput(dt) { function handleInput(dt) {
if (player.piping || player.dying || player.noInput) return; //don't accept input if (player.piping || player.dying || player.noInput) return; //don't accept input
if (input.isDown('RUN')){ if (input.isDown("RUN")) {
player.run(); player.run();
} else { } else {
player.noRun(); player.noRun();
} }
if (input.isDown('JUMP')) { if (input.isDown("JUMP")) {
player.jump(); player.jump();
} else { } else {
//we need this to handle the timing for how long you hold it //we need this to handle the timing for how long you hold it
player.noJump(); player.noJump();
} }
if (input.isDown('DOWN')) { if (input.isDown("DOWN")) {
player.crouch(); player.crouch();
} else { } else {
player.noCrouch(); player.noCrouch();
} }
if (input.isDown('LEFT')) { // 'd' or left arrow if (input.isDown("LEFT")) {
player.moveLeft(); // 'd' or left arrow
} player.moveLeft();
else if (input.isDown('RIGHT')) { // 'k' or right arrow } else if (input.isDown("RIGHT")) {
player.moveRight(); // 'k' or right arrow
} else { player.moveRight();
player.noWalk(); } else {
} player.noWalk();
}
} }
//update all the moving stuff //update all the moving stuff
function updateEntities(dt, gameTime) { function updateEntities(dt, gameTime) {
player.update(dt, vX); player.update(dt, vX);
updateables.forEach (function(ent) { updateables.forEach(function (ent) {
ent.update(dt, gameTime); ent.update(dt, gameTime);
}); });
//This should stop the jump when he switches sides on the flag. //This should stop the jump when he switches sides on the flag.
if (player.exiting) { if (player.exiting) {
if (player.pos[0] > vX + 96) if (player.pos[0] > vX + 96) vX = player.pos[0] - 96;
vX = player.pos[0] - 96 } else if (level.scrolling && player.pos[0] > vX + 80) {
}else if (level.scrolling && player.pos[0] > vX + 80) { vX = player.pos[0] - 80;
vX = player.pos[0] - 80; }
}
if (player.powering.length !== 0 || player.dying) { return; } if (player.powering.length !== 0 || player.dying) {
level.items.forEach (function(ent) { return;
ent.update(dt); }
}); level.items.forEach(function (ent) {
ent.update(dt);
});
level.enemies.forEach (function(ent) { level.enemies.forEach(function (ent) {
ent.update(dt, vX); ent.update(dt, vX);
}); });
fireballs.forEach(function(fireball) { fireballs.forEach(function (fireball) {
fireball.update(dt); fireball.update(dt);
}); });
level.pipes.forEach (function(pipe) { level.pipes.forEach(function (pipe) {
pipe.update(dt); pipe.update(dt);
}); });
} }
//scan for collisions //scan for collisions
function checkCollisions() { function checkCollisions() {
if (player.powering.length !== 0 || player.dying) { return; } if (player.powering.length !== 0 || player.dying) {
player.checkCollisions(); return;
}
player.checkCollisions();
//Apparently for each will just skip indices where things were deleted. //Apparently for each will just skip indices where things were deleted.
level.items.forEach(function(item) { level.items.forEach(function (item) {
item.checkCollisions(); item.checkCollisions();
}); });
level.enemies.forEach (function(ent) { level.enemies.forEach(function (ent) {
ent.checkCollisions(); ent.checkCollisions();
}); });
fireballs.forEach(function(fireball){ fireballs.forEach(function (fireball) {
fireball.checkCollisions(); fireball.checkCollisions();
}); });
level.pipes.forEach (function(pipe) { level.pipes.forEach(function (pipe) {
pipe.checkCollisions(); pipe.checkCollisions();
}); });
} }
//draw the game! //draw the game!
function render() { function render() {
updateables = []; updateables = [];
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = level.background; ctx.fillStyle = level.background;
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
//scenery gets drawn first to get layering right. //scenery gets drawn first to get layering right.
for(var i = 0; i < 15; i++) { for (var i = 0; i < 15; i++) {
for (var j = Math.floor(vX / 16) - 1; j < Math.floor(vX / 16) + 20; j++){ for (
if (level.scenery[i][j]) { var j = Math.floor(vX / 16) - 1;
renderEntity(level.scenery[i][j]); j < Math.floor(vX / 16) + 20;
} j++
) {
if (level.scenery[i][j]) {
renderEntity(level.scenery[i][j]);
}
}
} }
}
//then items //then items
level.items.forEach (function (item) { level.items.forEach(function (item) {
renderEntity(item); renderEntity(item);
}); });
level.enemies.forEach (function(enemy) { level.enemies.forEach(function (enemy) {
renderEntity(enemy); renderEntity(enemy);
}); });
fireballs.forEach(function (fireball) {
renderEntity(fireball);
});
//then we draw every static object.
fireballs.forEach(function(fireball) { for (var i = 0; i < 15; i++) {
renderEntity(fireball); for (
}) var j = Math.floor(vX / 16) - 1;
j < Math.floor(vX / 16) + 20;
//then we draw every static object. j++
for(var i = 0; i < 15; i++) { ) {
for (var j = Math.floor(vX / 16) - 1; j < Math.floor(vX / 16) + 20; j++){ if (level.statics[i][j]) {
if (level.statics[i][j]) { renderEntity(level.statics[i][j]);
renderEntity(level.statics[i][j]); }
} if (level.blocks[i][j]) {
if (level.blocks[i][j]) { renderEntity(level.blocks[i][j]);
renderEntity(level.blocks[i][j]); updateables.push(level.blocks[i][j]);
updateables.push(level.blocks[i][j]); }
} }
} }
}
//then the player //then the player
if (player.invincibility % 2 === 0) { if (player.invincibility % 2 === 0) {
renderEntity(player); renderEntity(player);
} }
//Mario goes INTO pipes, so naturally they go after. //Mario goes INTO pipes, so naturally they go after.
level.pipes.forEach (function(pipe) { level.pipes.forEach(function (pipe) {
renderEntity(pipe); renderEntity(pipe);
}); });
} }
function renderEntity(entity) { function renderEntity(entity) {
entity.render(ctx, vX, vY); entity.render(ctx, vX, vY);
} }

View File

@@ -1,129 +1,147 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
//TODO: On console the hitbox is smaller. Measure it and edit this. //TODO: On console the hitbox is smaller. Measure it and edit this.
var Goomba = Mario.Goomba = function(pos, sprite) { var Goomba = (Mario.Goomba = function (pos, sprite) {
this.dying = false; this.dying = false;
Mario.Entity.call(this, { Mario.Entity.call(this, {
pos: pos, pos: pos,
sprite: sprite, sprite: sprite,
hitbox: [0,0,16,16] hitbox: [0, 0, 16, 16],
});
this.vel[0] = -0.5;
this.idx = level.enemies.length;
}); });
this.vel[0] = -0.5;
this.idx = level.enemies.length;
};
Goomba.prototype.render = function(ctx, vX, vY) { Goomba.prototype.render = function (ctx, vX, vY) {
this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY); this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY);
}; };
Goomba.prototype.update = function(dt, vX) { Goomba.prototype.update = function (dt, vX) {
if (this.pos[0] - vX > 336) { //if we're too far away, do nothing. if (this.pos[0] - vX > 336) {
return; //if we're too far away, do nothing.
} else if (this.pos[0] - vX < -32) { return;
delete level.enemies[this.idx]; } else if (this.pos[0] - vX < -32) {
} delete level.enemies[this.idx];
if (this.dying) {
this.dying -= 1;
if (!this.dying) {
delete level.enemies[this.idx];
}
}
this.acc[1] = 0.2;
this.vel[1] += this.acc[1];
this.pos[0] += this.vel[0];
this.pos[1] += this.vel[1];
this.sprite.update(dt);
};
Goomba.prototype.collideWall = function() {
this.vel[0] = -this.vel[0];
};
Goomba.prototype.checkCollisions = function() {
if (this.flipping) {
return;
}
var h = this.pos[1] % 16 === 0 ? 1 : 2;
var w = this.pos[0] % 16 === 0 ? 1 : 2;
var baseX = Math.floor(this.pos[0] / 16);
var baseY = Math.floor(this.pos[1] / 16);
if (baseY + h > 15) {
delete level.enemies[this.idx];
return;
}
for (var i = 0; i < h; i++) {
for (var j = 0; j < w; j++) {
if (level.statics[baseY + i][baseX + j]) {
level.statics[baseY + i][baseX + j].isCollideWith(this);
} }
if (level.blocks[baseY + i][baseX + j]) {
level.blocks[baseY + i][baseX + j].isCollideWith(this); if (this.dying) {
this.dying -= 1;
if (!this.dying) {
delete level.enemies[this.idx];
}
} }
} this.acc[1] = 0.2;
} this.vel[1] += this.acc[1];
var that = this; this.pos[0] += this.vel[0];
level.enemies.forEach(function(enemy){ this.pos[1] += this.vel[1];
if (enemy === that) { //don't check collisions with ourselves. this.sprite.update(dt);
return; };
} else if (enemy.pos[0] - vX > 336){ //stop checking once we get to far away dudes.
return;
} else {
that.isCollideWith(enemy);
}
});
this.isCollideWith(player);
};
Goomba.prototype.isCollideWith = function(ent) { Goomba.prototype.collideWall = function () {
if (ent instanceof Mario.Player && (this.dying || ent.invincibility)) { this.vel[0] = -this.vel[0];
return; };
}
//the first two elements of the hitbox array are an offset, so let's do this now. Goomba.prototype.checkCollisions = function () {
var hpos1 = [this.pos[0] + this.hitbox[0], this.pos[1] + this.hitbox[1]]; if (this.flipping) {
var hpos2 = [ent.pos[0] + ent.hitbox[0], ent.pos[1] + ent.hitbox[1]]; return;
//if the hitboxes actually overlap
if (!(hpos1[0] > hpos2[0]+ent.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) {
if (!(hpos1[1] > hpos2[1]+ent.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) {
if (ent instanceof Mario.Player) { //if we hit the player
if (ent.vel[1] > 0) { //then the goomba dies
this.stomp();
} else if (ent.starTime) {
this.bump();
} else { //or the player gets hit
ent.damage();
}
} else {
this.collideWall();
} }
}
}
};
Goomba.prototype.stomp = function() { var h = this.pos[1] % 16 === 0 ? 1 : 2;
sounds.stomp.play(); var w = this.pos[0] % 16 === 0 ? 1 : 2;
player.bounce = true;
this.sprite.pos[0] = 32;
this.sprite.speed = 0;
this.vel[0] = 0;
this.dying = 10;
};
Goomba.prototype.bump = function() { var baseX = Math.floor(this.pos[0] / 16);
sounds.kick.play(); var baseY = Math.floor(this.pos[1] / 16);
this.sprite.img = 'sprites/enemyr.png';
this.flipping = true; if (baseY + h > 15) {
this.pos[1] -= 1; delete level.enemies[this.idx];
this.vel[0] = 0; return;
this.vel[1] = -2.5; }
};
for (var i = 0; i < h; i++) {
for (var j = 0; j < w; j++) {
if (level.statics[baseY + i][baseX + j]) {
level.statics[baseY + i][baseX + j].isCollideWith(this);
}
if (level.blocks[baseY + i][baseX + j]) {
level.blocks[baseY + i][baseX + j].isCollideWith(this);
}
}
}
var that = this;
level.enemies.forEach(function (enemy) {
if (enemy === that) {
//don't check collisions with ourselves.
return;
} else if (enemy.pos[0] - vX > 336) {
//stop checking once we get to far away dudes.
return;
} else {
that.isCollideWith(enemy);
}
});
this.isCollideWith(player);
};
Goomba.prototype.isCollideWith = function (ent) {
if (ent instanceof Mario.Player && (this.dying || ent.invincibility)) {
return;
}
//the first two elements of the hitbox array are an offset, so let's do this now.
var hpos1 = [
this.pos[0] + this.hitbox[0],
this.pos[1] + this.hitbox[1],
];
var hpos2 = [ent.pos[0] + ent.hitbox[0], ent.pos[1] + ent.hitbox[1]];
//if the hitboxes actually overlap
if (
!(
hpos1[0] > hpos2[0] + ent.hitbox[2] ||
hpos1[0] + this.hitbox[2] < hpos2[0]
)
) {
if (
!(
hpos1[1] > hpos2[1] + ent.hitbox[3] ||
hpos1[1] + this.hitbox[3] < hpos2[1]
)
) {
if (ent instanceof Mario.Player) {
//if we hit the player
if (ent.vel[1] > 0) {
//then the goomba dies
this.stomp();
} else if (ent.starTime) {
this.bump();
} else {
//or the player gets hit
ent.damage();
}
} else {
this.collideWall();
}
}
}
};
Goomba.prototype.stomp = function () {
sounds.stomp.play();
player.bounce = true;
this.sprite.pos[0] = 32;
this.sprite.speed = 0;
this.vel[0] = 0;
this.dying = 10;
};
Goomba.prototype.bump = function () {
sounds.kick.play();
this.sprite.img = "sprites/enemyr.png";
this.flipping = true;
this.pos[1] -= 1;
this.vel[0] = 0;
this.vel[1] = -2.5;
};
})(); })();

View File

@@ -1,54 +1,61 @@
(function() { (function () {
var pressedKeys = {}; var pressedKeys = {};
function setKey(event, status) { function setKey(event, status) {
var code = event.keyCode; var code = event.keyCode;
var key; var key;
switch(code) { switch (code) {
case 32: case 32:
key = 'SPACE'; break; key = "SPACE";
case 37: break;
key = 'LEFT'; break; case 37:
case 38: key = "LEFT";
key = 'UP'; break; break;
case 39: case 38:
key = 'RIGHT'; break; key = "UP";
case 40: break;
key = 'DOWN'; break; case 39:
case 88: key = "RIGHT";
key = 'JUMP'; break; break;
case 90: case 40:
key = 'RUN'; break; key = "DOWN";
default: break;
key = String.fromCharCode(code); case 88:
key = "JUMP";
break;
case 90:
key = "RUN";
break;
default:
key = String.fromCharCode(code);
} }
pressedKeys[key] = status; pressedKeys[key] = status;
} }
document.addEventListener('keydown', function(e) { document.addEventListener("keydown", function (e) {
setKey(e, true); setKey(e, true);
}); });
document.addEventListener('keyup', function(e) { document.addEventListener("keyup", function (e) {
setKey(e, false); setKey(e, false);
}); });
window.addEventListener('blur', function() { window.addEventListener("blur", function () {
pressedKeys = {}; pressedKeys = {};
}); });
window.input = { window.input = {
isDown: function(key) { isDown: function (key) {
return pressedKeys[key.toUpperCase()]; return pressedKeys[key.toUpperCase()];
}, },
reset: function() { reset: function () {
pressedKeys['RUN'] = false; pressedKeys["RUN"] = false;
pressedKeys['LEFT'] = false; pressedKeys["LEFT"] = false;
pressedKeys['RIGHT'] = false; pressedKeys["RIGHT"] = false;
pressedKeys['DOWN'] = false; pressedKeys["DOWN"] = false;
pressedKeys['JUMP'] = false; pressedKeys["JUMP"] = false;
} },
}; };
})(); })();

View File

@@ -1,211 +1,228 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
var Koopa = Mario.Koopa = function(pos, sprite, para) { var Koopa = (Mario.Koopa = function (pos, sprite, para) {
this.dying = false; this.dying = false;
this.shell = false; this.shell = false;
this.para = para; //para. As in, is it a paratroopa? this.para = para; //para. As in, is it a paratroopa?
//So, funny story. The actual hitboxes don't reach all the way to the ground. //So, funny story. The actual hitboxes don't reach all the way to the ground.
//What that means is, as long as I use them to keep things on the floor //What that means is, as long as I use them to keep things on the floor
//making the hitboxes accurate will make enemies sink into the ground. //making the hitboxes accurate will make enemies sink into the ground.
Mario.Entity.call(this, { Mario.Entity.call(this, {
pos: pos, pos: pos,
sprite: sprite, sprite: sprite,
hitbox: [2,8,12,24] hitbox: [2, 8, 12, 24],
});
this.vel[0] = -0.5;
this.idx = level.enemies.length;
}); });
this.vel[0] = -0.5;
this.idx = level.enemies.length;
};
Koopa.prototype.render = function(ctx, vX, vY) { Koopa.prototype.render = function (ctx, vX, vY) {
this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY); this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY);
}; };
Koopa.prototype.update = function(dt, vX) { Koopa.prototype.update = function (dt, vX) {
if (this.turn) { if (this.turn) {
this.vel[0] = -this.vel[0]; this.vel[0] = -this.vel[0];
if (this.shell) sounds.bump.play(); if (this.shell) sounds.bump.play();
this.turn = false; this.turn = false;
}
if (this.vel[0] != 0) {
this.left = (this.vel[0] < 0);
}
if (this.left) {
this.sprite.img = 'sprites/enemy.png';
} else {
this.sprite.img = 'sprites/enemyr.png';
}
if (this.pos[0] - vX > 336) { //if we're too far away, do nothing.
return;
} else if (this.pos[0] - vX < -32) {
delete level.enemies[this.idx];
}
if (this.dying) {
this.dying -= 1;
if (!this.dying) {
delete level.enemies[this.idx];
}
}
if (this.shell) {
if (this.vel[0] == 0) {
this.shell -= 1;
if (this.shell < 120) {
this.sprite.speed = 5;
} }
if (this.shell == 0) { if (this.vel[0] != 0) {
this.sprite = level.koopaSprite(); this.left = this.vel[0] < 0;
this.hitbox = [2,8,12,24]
if (this.left) {
this.sprite.img = 'sprites/enemyr.png';
this.vel[0] = 0.5;
this.left = false;
} else {
this.vel[0] = -0.5;
this.left = true;
}
this.pos[1] -= 16;
} }
} else {
this.shell = 360;
this.sprite.speed = 0;
this.sprite.setFrame(0);
}
}
this.acc[1] = 0.2;
this.vel[1] += this.acc[1];
this.pos[0] += this.vel[0];
this.pos[1] += this.vel[1];
this.sprite.update(dt);
};
Koopa.prototype.collideWall = function() { if (this.left) {
//This stops us from flipping twice on the same frame if we collide this.sprite.img = "sprites/enemy.png";
//with multiple wall tiles simultaneously.
this.turn = true;
};
Koopa.prototype.checkCollisions = function() {
var h = this.shell ? 1 : 2;
if (this.pos[1] % 16 !== 0) {
h += 1;
}
var w = this.pos[0] % 16 === 0 ? 1 : 2;
var baseX = Math.floor(this.pos[0] / 16);
var baseY = Math.floor(this.pos[1] / 16);
if (baseY + h > 15) {
delete level.enemies[this.idx];
return;
}
if (this.flipping) {
return;
}
for (var i = 0; i < h; i++) {
for (var j = 0; j < w; j++) {
if (level.statics[baseY + i][baseX + j]) {
level.statics[baseY + i][baseX + j].isCollideWith(this);
}
if (level.blocks[baseY + i][baseX + j]) {
level.blocks[baseY + i][baseX + j].isCollideWith(this);
}
}
}
var that = this;
level.enemies.forEach(function(enemy){
if (enemy === that) { //don't check collisions with ourselves.
return;
} else if (enemy.pos[0] - vX > 336){ //stop checking once we get to far away dudes.
return;
} else {
that.isCollideWith(enemy);
}
});
this.isCollideWith(player);
};
Koopa.prototype.isCollideWith = function(ent) {
if (ent instanceof Mario.Player && (this.dying || ent.invincibility)) {
return;
}
//the first two elements of the hitbox array are an offset, so let's do this now.
var hpos1 = [this.pos[0] + this.hitbox[0], this.pos[1] + this.hitbox[1]];
var hpos2 = [ent.pos[0] + ent.hitbox[0], ent.pos[1] + ent.hitbox[1]];
//if the hitboxes actually overlap
if (!(hpos1[0] > hpos2[0]+ent.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) {
if (!(hpos1[1] > hpos2[1]+ent.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) {
if (ent instanceof Mario.Player) {
if (ent.vel[1] > 0) {
player.bounce = true;
}
if (this.shell) {
sounds.kick.play();
if (this.vel[0] === 0) {
if (ent.left) { //I'm pretty sure this isn't the real logic.
this.vel[0] = -4;
} else {
this.vel[0] = 4;
}
} else {
if (ent.bounce) {
this.vel[0] = 0;
} else ent.damage();
}
} else if (ent.vel[1] > 0) { //then we get BOPPED.
this.stomp();
} else { //or the player gets hit
ent.damage();
}
} else { } else {
if (this.shell && (ent instanceof Mario.Goomba)) { this.sprite.img = "sprites/enemyr.png";
ent.bump();
} else this.collideWall();
} }
}
}
};
Koopa.prototype.stomp = function() { if (this.pos[0] - vX > 336) {
//Turn this thing into a shell if it isn't already. Kick it if it is. //if we're too far away, do nothing.
player.bounce = true; return;
if (this.para) { } else if (this.pos[0] - vX < -32) {
this.para = false; delete level.enemies[this.idx];
this.sprite.pos[0] -= 32; }
} else {
sounds.stomp.play();
this.shell = 360;
this.sprite.pos[0] += 64;
this.sprite.pos[1] += 16;
this.sprite.size = [16,16];
this.hitbox = [2,0,12,16];
this.sprite.speed = 0;
this.frames = [0,1];
this.vel = [0,0];
this.pos[1] += 16;
}
}; if (this.dying) {
this.dying -= 1;
if (!this.dying) {
delete level.enemies[this.idx];
}
}
Koopa.prototype.bump = function() { if (this.shell) {
sounds.kick.play(); if (this.vel[0] == 0) {
if (this.flipping) return; this.shell -= 1;
this.flipping = true; if (this.shell < 120) {
this.sprite.pos = [160, 0]; this.sprite.speed = 5;
this.sprite.size = [16,16]; }
this.hitbox = [2, 0, 12, 16]; if (this.shell == 0) {
this.sprite.speed = 0; this.sprite = level.koopaSprite();
this.vel[0] = 0; this.hitbox = [2, 8, 12, 24];
this.vel[1] = -2.5; if (this.left) {
}; this.sprite.img = "sprites/enemyr.png";
this.vel[0] = 0.5;
this.left = false;
} else {
this.vel[0] = -0.5;
this.left = true;
}
this.pos[1] -= 16;
}
} else {
this.shell = 360;
this.sprite.speed = 0;
this.sprite.setFrame(0);
}
}
this.acc[1] = 0.2;
this.vel[1] += this.acc[1];
this.pos[0] += this.vel[0];
this.pos[1] += this.vel[1];
this.sprite.update(dt);
};
Koopa.prototype.collideWall = function () {
//This stops us from flipping twice on the same frame if we collide
//with multiple wall tiles simultaneously.
this.turn = true;
};
Koopa.prototype.checkCollisions = function () {
var h = this.shell ? 1 : 2;
if (this.pos[1] % 16 !== 0) {
h += 1;
}
var w = this.pos[0] % 16 === 0 ? 1 : 2;
var baseX = Math.floor(this.pos[0] / 16);
var baseY = Math.floor(this.pos[1] / 16);
if (baseY + h > 15) {
delete level.enemies[this.idx];
return;
}
if (this.flipping) {
return;
}
for (var i = 0; i < h; i++) {
for (var j = 0; j < w; j++) {
if (level.statics[baseY + i][baseX + j]) {
level.statics[baseY + i][baseX + j].isCollideWith(this);
}
if (level.blocks[baseY + i][baseX + j]) {
level.blocks[baseY + i][baseX + j].isCollideWith(this);
}
}
}
var that = this;
level.enemies.forEach(function (enemy) {
if (enemy === that) {
//don't check collisions with ourselves.
return;
} else if (enemy.pos[0] - vX > 336) {
//stop checking once we get to far away dudes.
return;
} else {
that.isCollideWith(enemy);
}
});
this.isCollideWith(player);
};
Koopa.prototype.isCollideWith = function (ent) {
if (ent instanceof Mario.Player && (this.dying || ent.invincibility)) {
return;
}
//the first two elements of the hitbox array are an offset, so let's do this now.
var hpos1 = [
this.pos[0] + this.hitbox[0],
this.pos[1] + this.hitbox[1],
];
var hpos2 = [ent.pos[0] + ent.hitbox[0], ent.pos[1] + ent.hitbox[1]];
//if the hitboxes actually overlap
if (
!(
hpos1[0] > hpos2[0] + ent.hitbox[2] ||
hpos1[0] + this.hitbox[2] < hpos2[0]
)
) {
if (
!(
hpos1[1] > hpos2[1] + ent.hitbox[3] ||
hpos1[1] + this.hitbox[3] < hpos2[1]
)
) {
if (ent instanceof Mario.Player) {
if (ent.vel[1] > 0) {
player.bounce = true;
}
if (this.shell) {
sounds.kick.play();
if (this.vel[0] === 0) {
if (ent.left) {
//I'm pretty sure this isn't the real logic.
this.vel[0] = -4;
} else {
this.vel[0] = 4;
}
} else {
if (ent.bounce) {
this.vel[0] = 0;
} else ent.damage();
}
} else if (ent.vel[1] > 0) {
//then we get BOPPED.
this.stomp();
} else {
//or the player gets hit
ent.damage();
}
} else {
if (this.shell && ent instanceof Mario.Goomba) {
ent.bump();
} else this.collideWall();
}
}
}
};
Koopa.prototype.stomp = function () {
//Turn this thing into a shell if it isn't already. Kick it if it is.
player.bounce = true;
if (this.para) {
this.para = false;
this.sprite.pos[0] -= 32;
} else {
sounds.stomp.play();
this.shell = 360;
this.sprite.pos[0] += 64;
this.sprite.pos[1] += 16;
this.sprite.size = [16, 16];
this.hitbox = [2, 0, 12, 16];
this.sprite.speed = 0;
this.frames = [0, 1];
this.vel = [0, 0];
this.pos[1] += 16;
}
};
Koopa.prototype.bump = function () {
sounds.kick.play();
if (this.flipping) return;
this.flipping = true;
this.sprite.pos = [160, 0];
this.sprite.size = [16, 16];
this.hitbox = [2, 0, 12, 16];
this.sprite.speed = 0;
this.vel[0] = 0;
this.vel[1] = -2.5;
};
})(); })();

View File

@@ -1,219 +1,341 @@
var oneone = Mario.oneone = function() { var oneone = (Mario.oneone = function () {
//The things that need to be passed in are basically just dependent on what //The things that need to be passed in are basically just dependent on what
//tileset we're in, so it makes more sense to just make one variable for that, so //tileset we're in, so it makes more sense to just make one variable for that, so
//TODO: put as much of this in the Level object definition as possible. //TODO: put as much of this in the Level object definition as possible.
level = new Mario.Level({ level = new Mario.Level({
playerPos: [56,192], playerPos: [56, 192],
loader: Mario.oneone, loader: Mario.oneone,
background: "#7974FF", background: "#7974FF",
scrolling: true, scrolling: true,
invincibility: [144, 192, 240], invincibility: [144, 192, 240],
exit: 204, exit: 204,
floorSprite: new Mario.Sprite('sprites/tiles.png', [0,0],[16,16],0), floorSprite: new Mario.Sprite("sprites/tiles.png", [0, 0], [16, 16], 0),
cloudSprite: new Mario.Sprite('sprites/tiles.png', [0,320],[48,32],0), cloudSprite: new Mario.Sprite(
wallSprite: new Mario.Sprite('sprites/tiles.png', [0, 16],[16,16],0), "sprites/tiles.png",
brickSprite: new Mario.Sprite('sprites/tiles.png', [16, 0], [16,16], 0), [0, 320],
brickBounceSprite: new Mario.Sprite('sprites/tiles.png',[32,0],[16,16],0), [48, 32],
rubbleSprite: function () { 0,
return new Mario.Sprite('sprites/items.png', [64,0], [8,8], 3, [0,1]) ),
}, wallSprite: new Mario.Sprite("sprites/tiles.png", [0, 16], [16, 16], 0),
ublockSprite: new Mario.Sprite('sprites/tiles.png', [48, 0], [16,16],0), brickSprite: new Mario.Sprite(
superShroomSprite: new Mario.Sprite('sprites/items.png', [0,0], [16,16], 0), "sprites/tiles.png",
fireFlowerSprite: new Mario.Sprite('sprites/items.png', [0,32], [16,16], 20, [0,1,2,3]), [16, 0],
starSprite: new Mario.Sprite('sprites/items.png', [0,48], [16,16], 20, [0,1,2,3]), [16, 16],
pipeLEndSprite: new Mario.Sprite('sprites/tiles.png', [0, 128], [16,16], 0), 0,
pipeREndSprite: new Mario.Sprite('sprites/tiles.png', [16, 128], [16,16], 0), ),
pipeLMidSprite: new Mario.Sprite('sprites/tiles.png', [0, 144], [16,16], 0), brickBounceSprite: new Mario.Sprite(
pipeRMidSprite: new Mario.Sprite('sprites/tiles.png', [16, 144], [16,16], 0), "sprites/tiles.png",
[32, 0],
[16, 16],
0,
),
rubbleSprite: function () {
return new Mario.Sprite(
"sprites/items.png",
[64, 0],
[8, 8],
3,
[0, 1],
);
},
ublockSprite: new Mario.Sprite(
"sprites/tiles.png",
[48, 0],
[16, 16],
0,
),
superShroomSprite: new Mario.Sprite(
"sprites/items.png",
[0, 0],
[16, 16],
0,
),
fireFlowerSprite: new Mario.Sprite(
"sprites/items.png",
[0, 32],
[16, 16],
20,
[0, 1, 2, 3],
),
starSprite: new Mario.Sprite(
"sprites/items.png",
[0, 48],
[16, 16],
20,
[0, 1, 2, 3],
),
pipeLEndSprite: new Mario.Sprite(
"sprites/tiles.png",
[0, 128],
[16, 16],
0,
),
pipeREndSprite: new Mario.Sprite(
"sprites/tiles.png",
[16, 128],
[16, 16],
0,
),
pipeLMidSprite: new Mario.Sprite(
"sprites/tiles.png",
[0, 144],
[16, 16],
0,
),
pipeRMidSprite: new Mario.Sprite(
"sprites/tiles.png",
[16, 144],
[16, 16],
0,
),
pipeUpMid: new Mario.Sprite('sprites/tiles.png', [0, 144], [32,16], 0), pipeUpMid: new Mario.Sprite("sprites/tiles.png", [0, 144], [32, 16], 0),
pipeSideMid: new Mario.Sprite('sprites/tiles.png', [48, 128], [16,32], 0), pipeSideMid: new Mario.Sprite(
pipeLeft: new Mario.Sprite('sprites/tiles.png', [32, 128], [16,32], 0), "sprites/tiles.png",
pipeTop: new Mario.Sprite('sprites/tiles.png', [0, 128], [32,16], 0), [48, 128],
qblockSprite: new Mario.Sprite('sprites/tiles.png', [384, 0], [16,16], 8, [0,0,0,0,1,2,1]), [16, 32],
bcoinSprite: function() { 0,
return new Mario.Sprite('sprites/items.png', [0,112],[16,16], 20,[0,1,2,3]); ),
}, pipeLeft: new Mario.Sprite("sprites/tiles.png", [32, 128], [16, 32], 0),
cloudSprites:[ pipeTop: new Mario.Sprite("sprites/tiles.png", [0, 128], [32, 16], 0),
new Mario.Sprite('sprites/tiles.png', [0,320],[16,32],0), qblockSprite: new Mario.Sprite(
new Mario.Sprite('sprites/tiles.png', [16,320],[16,32],0), "sprites/tiles.png",
new Mario.Sprite('sprites/tiles.png', [32,320],[16,32],0) [384, 0],
], [16, 16],
hillSprites: [ 8,
new Mario.Sprite('sprites/tiles.png', [128,128],[16,16],0), [0, 0, 0, 0, 1, 2, 1],
new Mario.Sprite('sprites/tiles.png', [144,128],[16,16],0), ),
new Mario.Sprite('sprites/tiles.png', [160,128],[16,16],0), bcoinSprite: function () {
new Mario.Sprite('sprites/tiles.png', [128,144],[16,16],0), return new Mario.Sprite(
new Mario.Sprite('sprites/tiles.png', [144,144],[16,16],0), "sprites/items.png",
new Mario.Sprite('sprites/tiles.png', [160,144],[16,16],0) [0, 112],
], [16, 16],
bushSprite: new Mario.Sprite('sprites/tiles.png', [176, 144], [48, 16], 0), 20,
bushSprites: [ [0, 1, 2, 3],
new Mario.Sprite('sprites/tiles.png', [176,144], [16,16],0), );
new Mario.Sprite('sprites/tiles.png', [192,144], [16,16],0), },
new Mario.Sprite('sprites/tiles.png', [208,144], [16,16],0)], cloudSprites: [
goombaSprite: function() { new Mario.Sprite("sprites/tiles.png", [0, 320], [16, 32], 0),
return new Mario.Sprite('sprites/enemy.png', [0, 16], [16,16], 3, [0,1]); new Mario.Sprite("sprites/tiles.png", [16, 320], [16, 32], 0),
}, new Mario.Sprite("sprites/tiles.png", [32, 320], [16, 32], 0),
koopaSprite: function() { ],
return new Mario.Sprite('sprites/enemy.png', [96,0], [16,32], 2, [0,1]); hillSprites: [
}, new Mario.Sprite("sprites/tiles.png", [128, 128], [16, 16], 0),
flagPoleSprites: [ new Mario.Sprite("sprites/tiles.png", [144, 128], [16, 16], 0),
new Mario.Sprite('sprites/tiles.png', [256, 128], [16,16], 0), new Mario.Sprite("sprites/tiles.png", [160, 128], [16, 16], 0),
new Mario.Sprite('sprites/tiles.png', [256, 144], [16,16], 0), new Mario.Sprite("sprites/tiles.png", [128, 144], [16, 16], 0),
new Mario.Sprite('sprites/items.png', [128, 32], [16,16], 0) new Mario.Sprite("sprites/tiles.png", [144, 144], [16, 16], 0),
] new Mario.Sprite("sprites/tiles.png", [160, 144], [16, 16], 0),
}); ],
ground = [[0,69],[71,86],[89,153],[155,212]]; bushSprite: new Mario.Sprite(
player.pos[0] = level.playerPos[0]; "sprites/tiles.png",
player.pos[1] = level.playerPos[1]; [176, 144],
vX = 0; [48, 16],
0,
),
bushSprites: [
new Mario.Sprite("sprites/tiles.png", [176, 144], [16, 16], 0),
new Mario.Sprite("sprites/tiles.png", [192, 144], [16, 16], 0),
new Mario.Sprite("sprites/tiles.png", [208, 144], [16, 16], 0),
],
goombaSprite: function () {
return new Mario.Sprite(
"sprites/enemy.png",
[0, 16],
[16, 16],
3,
[0, 1],
);
},
koopaSprite: function () {
return new Mario.Sprite(
"sprites/enemy.png",
[96, 0],
[16, 32],
2,
[0, 1],
);
},
flagPoleSprites: [
new Mario.Sprite("sprites/tiles.png", [256, 128], [16, 16], 0),
new Mario.Sprite("sprites/tiles.png", [256, 144], [16, 16], 0),
new Mario.Sprite("sprites/items.png", [128, 32], [16, 16], 0),
],
});
ground = [
[0, 69],
[71, 86],
[89, 153],
[155, 212],
];
player.pos[0] = level.playerPos[0];
player.pos[1] = level.playerPos[1];
vX = 0;
//build THE GROUND //build THE GROUND
ground.forEach(function(loc) { ground.forEach(function (loc) {
level.putFloor(loc[0],loc[1]); level.putFloor(loc[0], loc[1]);
}); });
//build scenery //build scenery
clouds = [[7,3],[19, 2],[56, 3],[67, 2],[87, 2],[103, 2],[152, 3],[163, 2],[200, 3]]; clouds = [
clouds.forEach(function(cloud){ [7, 3],
level.putCloud(cloud[0],cloud[1]); [19, 2],
}); [56, 3],
[67, 2],
[87, 2],
[103, 2],
[152, 3],
[163, 2],
[200, 3],
];
clouds.forEach(function (cloud) {
level.putCloud(cloud[0], cloud[1]);
});
twoClouds = [[36,2],[132,2],[180,2]]; twoClouds = [
twoClouds.forEach(function(cloud){ [36, 2],
level.putTwoCloud(cloud[0],cloud[1]); [132, 2],
}); [180, 2],
];
twoClouds.forEach(function (cloud) {
level.putTwoCloud(cloud[0], cloud[1]);
});
threeClouds = [[27,3],[75,3],[123,3],[171,3]]; threeClouds = [
threeClouds.forEach(function(cloud){ [27, 3],
level.putThreeCloud(cloud[0],cloud[1]); [75, 3],
}); [123, 3],
[171, 3],
];
threeClouds.forEach(function (cloud) {
level.putThreeCloud(cloud[0], cloud[1]);
});
bHills = [0,48,96,144,192] bHills = [0, 48, 96, 144, 192];
bHills.forEach(function(hill) { bHills.forEach(function (hill) {
level.putBigHill(hill, 12); level.putBigHill(hill, 12);
}); });
sHills = [16,64,111,160]; sHills = [16, 64, 111, 160];
sHills.forEach(function(hill) { sHills.forEach(function (hill) {
level.putSmallHill(hill, 12); level.putSmallHill(hill, 12);
}); });
bushes = [23,71,118,167]; bushes = [23, 71, 118, 167];
bushes.forEach(function(bush) { bushes.forEach(function (bush) {
level.putBush(bush, 12); level.putBush(bush, 12);
}); });
twoBushes = [41,89,137]; twoBushes = [41, 89, 137];
twoBushes.forEach(function(bush) { twoBushes.forEach(function (bush) {
level.putTwoBush(bush, 12); level.putTwoBush(bush, 12);
}); });
threeBushes = [11,59,106]; threeBushes = [11, 59, 106];
threeBushes.forEach(function(bush) { threeBushes.forEach(function (bush) {
level.putThreeBush(bush, 12); level.putThreeBush(bush, 12);
}); });
//interactable terrain //interactable terrain
level.putQBlock(16, 9, new Mario.Bcoin([256, 144])); level.putQBlock(16, 9, new Mario.Bcoin([256, 144]));
level.putBrick(20, 9, null); level.putBrick(20, 9, null);
level.putQBlock(21, 9, new Mario.Mushroom([336, 144])); level.putQBlock(21, 9, new Mario.Mushroom([336, 144]));
level.putBrick(22, 9, null); level.putBrick(22, 9, null);
level.putQBlock(22, 5, new Mario.Bcoin([352, 80])); level.putQBlock(22, 5, new Mario.Bcoin([352, 80]));
level.putQBlock(23, 9, new Mario.Bcoin([368, 144])); level.putQBlock(23, 9, new Mario.Bcoin([368, 144]));
level.putBrick(24, 9, null); level.putBrick(24, 9, null);
level.putPipe(28, 13, 2); level.putPipe(28, 13, 2);
level.putPipe(38, 13, 3); level.putPipe(38, 13, 3);
level.putPipe(46, 13, 4); level.putPipe(46, 13, 4);
level.putRealPipe(57, 9, 4, "DOWN", Mario.oneonetunnel); level.putRealPipe(57, 9, 4, "DOWN", Mario.oneonetunnel);
level.putBrick(77, 9, null); level.putBrick(77, 9, null);
level.putQBlock(78, 9, new Mario.Mushroom([1248, 144])); level.putQBlock(78, 9, new Mario.Mushroom([1248, 144]));
level.putBrick(79, 9, null); level.putBrick(79, 9, null);
level.putBrick(80, 5, null); level.putBrick(80, 5, null);
level.putBrick(81, 5, null); level.putBrick(81, 5, null);
level.putBrick(82, 5, null); level.putBrick(82, 5, null);
level.putBrick(83, 5, null); level.putBrick(83, 5, null);
level.putBrick(84, 5, null); level.putBrick(84, 5, null);
level.putBrick(85, 5, null); level.putBrick(85, 5, null);
level.putBrick(86, 5, null); level.putBrick(86, 5, null);
level.putBrick(87, 5, null); level.putBrick(87, 5, null);
level.putBrick(91, 5, null); level.putBrick(91, 5, null);
level.putBrick(92, 5, null); level.putBrick(92, 5, null);
level.putBrick(93, 5, null); level.putBrick(93, 5, null);
level.putQBlock(94, 5, new Mario.Bcoin([1504, 80])); level.putQBlock(94, 5, new Mario.Bcoin([1504, 80]));
level.putBrick(94, 9, null); level.putBrick(94, 9, null);
level.putBrick(100, 9, new Mario.Star([1600, 144])); level.putBrick(100, 9, new Mario.Star([1600, 144]));
level.putBrick(101, 9, null); level.putBrick(101, 9, null);
level.putQBlock(105, 9, new Mario.Bcoin([1680, 144])); level.putQBlock(105, 9, new Mario.Bcoin([1680, 144]));
level.putQBlock(108, 9, new Mario.Bcoin([1728, 144])); level.putQBlock(108, 9, new Mario.Bcoin([1728, 144]));
level.putQBlock(108, 5, new Mario.Mushroom([1728, 80])); level.putQBlock(108, 5, new Mario.Mushroom([1728, 80]));
level.putQBlock(111, 9, new Mario.Bcoin([1776, 144])); level.putQBlock(111, 9, new Mario.Bcoin([1776, 144]));
level.putBrick(117, 9, null); level.putBrick(117, 9, null);
level.putBrick(120, 5, null); level.putBrick(120, 5, null);
level.putBrick(121, 5, null); level.putBrick(121, 5, null);
level.putBrick(122, 5, null); level.putBrick(122, 5, null);
level.putBrick(123, 5, null); level.putBrick(123, 5, null);
level.putBrick(128, 5, null); level.putBrick(128, 5, null);
level.putQBlock(129, 5, new Mario.Bcoin([2074, 80])); level.putQBlock(129, 5, new Mario.Bcoin([2074, 80]));
level.putBrick(129, 9, null); level.putBrick(129, 9, null);
level.putQBlock(130, 5, new Mario.Bcoin([2080, 80])); level.putQBlock(130, 5, new Mario.Bcoin([2080, 80]));
level.putBrick(130, 9, null); level.putBrick(130, 9, null);
level.putBrick(131, 5, null); level.putBrick(131, 5, null);
level.putWall(134, 13, 1); level.putWall(134, 13, 1);
level.putWall(135, 13, 2); level.putWall(135, 13, 2);
level.putWall(136, 13, 3); level.putWall(136, 13, 3);
level.putWall(137, 13, 4); level.putWall(137, 13, 4);
level.putWall(140, 13, 4); level.putWall(140, 13, 4);
level.putWall(141, 13, 3); level.putWall(141, 13, 3);
level.putWall(142, 13, 2); level.putWall(142, 13, 2);
level.putWall(143, 13, 1); level.putWall(143, 13, 1);
level.putWall(148, 13, 1); level.putWall(148, 13, 1);
level.putWall(149, 13, 2); level.putWall(149, 13, 2);
level.putWall(150, 13, 3); level.putWall(150, 13, 3);
level.putWall(151, 13, 4); level.putWall(151, 13, 4);
level.putWall(152, 13, 4); level.putWall(152, 13, 4);
level.putWall(155, 13, 4); level.putWall(155, 13, 4);
level.putWall(156, 13, 3); level.putWall(156, 13, 3);
level.putWall(157, 13, 2); level.putWall(157, 13, 2);
level.putWall(158, 13, 1); level.putWall(158, 13, 1);
level.putPipe(163, 13, 2); level.putPipe(163, 13, 2);
level.putBrick(168, 9, null); level.putBrick(168, 9, null);
level.putBrick(169, 9, null); level.putBrick(169, 9, null);
level.putQBlock(170, 9, new Mario.Bcoin([2720, 144])); level.putQBlock(170, 9, new Mario.Bcoin([2720, 144]));
level.putBrick(171, 9, null); level.putBrick(171, 9, null);
level.putPipe(179, 13, 2); level.putPipe(179, 13, 2);
level.putWall(181, 13, 1); level.putWall(181, 13, 1);
level.putWall(182, 13, 2); level.putWall(182, 13, 2);
level.putWall(183, 13, 3); level.putWall(183, 13, 3);
level.putWall(184, 13, 4); level.putWall(184, 13, 4);
level.putWall(185, 13, 5); level.putWall(185, 13, 5);
level.putWall(186, 13, 6); level.putWall(186, 13, 6);
level.putWall(187, 13, 7); level.putWall(187, 13, 7);
level.putWall(188, 13, 8); level.putWall(188, 13, 8);
level.putWall(189, 13, 8); level.putWall(189, 13, 8);
level.putFlagpole(198); level.putFlagpole(198);
//and enemies //and enemies
level.putGoomba(22, 12); level.putGoomba(22, 12);
level.putGoomba(40, 12); level.putGoomba(40, 12);
level.putGoomba(50, 12); level.putGoomba(50, 12);
level.putGoomba(51, 12); level.putGoomba(51, 12);
level.putGoomba(82, 4); level.putGoomba(82, 4);
level.putGoomba(84, 4); level.putGoomba(84, 4);
level.putGoomba(100, 12); level.putGoomba(100, 12);
level.putGoomba(102, 12); level.putGoomba(102, 12);
level.putGoomba(114, 12); level.putGoomba(114, 12);
level.putGoomba(115, 12); level.putGoomba(115, 12);
level.putGoomba(122, 12); level.putGoomba(122, 12);
level.putGoomba(123, 12); level.putGoomba(123, 12);
level.putGoomba(125, 12); level.putGoomba(125, 12);
level.putGoomba(126, 12); level.putGoomba(126, 12);
level.putGoomba(170, 12); level.putGoomba(170, 12);
level.putGoomba(172, 12); level.putGoomba(172, 12);
level.putKoopa(35, 11); level.putKoopa(35, 11);
music.underground.pause(); music.underground.pause();
// music.overworld.currentTime = 0; // music.overworld.currentTime = 0;
music.overworld.play(); music.overworld.play();
}; });

View File

@@ -1,65 +1,138 @@
var oneonetunnel = Mario.oneonetunnel = function() { var oneonetunnel = (Mario.oneonetunnel = function () {
level = new Mario.Level({ level = new Mario.Level({
playerPos: [40,16], playerPos: [40, 16],
loader: Mario.oneonetunnel, loader: Mario.oneonetunnel,
background: "#000000", background: "#000000",
scrolling: false, scrolling: false,
coinSprite: function() { coinSprite: function () {
return new Mario.Sprite('sprites/items.png', [0,96],[16,16], 6,[0,0,0,0,1,2,1]); return new Mario.Sprite(
}, "sprites/items.png",
floorSprite: new Mario.Sprite('sprites/tiles.png', [0,32],[16,16],0), [0, 96],
wallSprite: new Mario.Sprite('sprites/tiles.png', [32, 32],[16,16],0), [16, 16],
brickSprite: new Mario.Sprite('sprites/tiles.png', [16, 0], [16,16], 0), 6,
brickBounceSprite: new Mario.Sprite('sprites/tiles.png',[32,0],[16,16],0), [0, 0, 0, 0, 1, 2, 1],
ublockSprite: new Mario.Sprite('sprites/tiles.png', [48, 0], [16,16],0), );
pipeLMidSprite: new Mario.Sprite('sprites/tiles.png', [0, 144], [16,16], 0), },
pipeRMidSprite: new Mario.Sprite('sprites/tiles.png', [16, 144], [16,16], 0), floorSprite: new Mario.Sprite(
pipeLEndSprite: new Mario.Sprite('sprites/tiles.png', [0, 128], [16,16], 0), "sprites/tiles.png",
pipeREndSprite: new Mario.Sprite('sprites/tiles.png', [16, 128], [16,16], 0), [0, 32],
pipeUpMid: new Mario.Sprite('sprites/tiles.png', [0, 144], [32,16], 0), [16, 16],
pipeSideMid: new Mario.Sprite('sprites/tiles.png', [48, 128], [16,32], 0), 0,
pipeLeft: new Mario.Sprite('sprites/tiles.png', [32, 128], [16,32], 0), ),
pipeTop: new Mario.Sprite('sprites/tiles.png', [0, 128], [32,16], 0), wallSprite: new Mario.Sprite(
"sprites/tiles.png",
[32, 32],
[16, 16],
0,
),
brickSprite: new Mario.Sprite(
"sprites/tiles.png",
[16, 0],
[16, 16],
0,
),
brickBounceSprite: new Mario.Sprite(
"sprites/tiles.png",
[32, 0],
[16, 16],
0,
),
ublockSprite: new Mario.Sprite(
"sprites/tiles.png",
[48, 0],
[16, 16],
0,
),
pipeLMidSprite: new Mario.Sprite(
"sprites/tiles.png",
[0, 144],
[16, 16],
0,
),
pipeRMidSprite: new Mario.Sprite(
"sprites/tiles.png",
[16, 144],
[16, 16],
0,
),
pipeLEndSprite: new Mario.Sprite(
"sprites/tiles.png",
[0, 128],
[16, 16],
0,
),
pipeREndSprite: new Mario.Sprite(
"sprites/tiles.png",
[16, 128],
[16, 16],
0,
),
pipeUpMid: new Mario.Sprite("sprites/tiles.png", [0, 144], [32, 16], 0),
pipeSideMid: new Mario.Sprite(
"sprites/tiles.png",
[48, 128],
[16, 32],
0,
),
pipeLeft: new Mario.Sprite("sprites/tiles.png", [32, 128], [16, 32], 0),
pipeTop: new Mario.Sprite("sprites/tiles.png", [0, 128], [32, 16], 0),
LPipeSprites:[ LPipeSprites: [
new Mario.Sprite('sprites/tiles.png', [32,128],[16,16],0), new Mario.Sprite("sprites/tiles.png", [32, 128], [16, 16], 0),
new Mario.Sprite('sprites/tiles.png', [32,144],[16,16],0), new Mario.Sprite("sprites/tiles.png", [32, 144], [16, 16], 0),
new Mario.Sprite('sprites/tiles.png', [48,128],[16,16],0), new Mario.Sprite("sprites/tiles.png", [48, 128], [16, 16], 0),
new Mario.Sprite('sprites/tiles.png', [48,144],[16,16],0), new Mario.Sprite("sprites/tiles.png", [48, 144], [16, 16], 0),
new Mario.Sprite('sprites/tiles.png', [64,128],[16,16],0), new Mario.Sprite("sprites/tiles.png", [64, 128], [16, 16], 0),
new Mario.Sprite('sprites/tiles.png', [64,144],[16,16],0), new Mario.Sprite("sprites/tiles.png", [64, 144], [16, 16], 0),
] ],
});
}); player.pos[0] = level.playerPos[0];
player.pos[1] = level.playerPos[1];
vX = 0;
level.putFloor(0, 16);
level.putWall(0, 13, 11);
walls = [4, 5, 6, 7, 8, 9, 10];
walls.forEach(function (loc) {
level.putWall(loc, 13, 3);
level.putWall(loc, 3, 1);
});
player.pos[0] = level.playerPos[0]; coins = [
player.pos[1] = level.playerPos[1]; [5, 5],
vX = 0; [6, 5],
level.putFloor(0,16); [7, 5],
level.putWall(0,13,11); [8, 5],
walls = [4,5,6,7,8,9,10]; [9, 5],
walls.forEach(function(loc){ [4, 7],
level.putWall(loc,13,3); [5, 7],
level.putWall(loc,3,1); [6, 7],
}); [7, 7],
[8, 7],
[9, 7],
[10, 7],
[4, 9],
[5, 9],
[6, 9],
[7, 9],
[8, 9],
[9, 9],
[10, 9],
];
coins.forEach(function (pos) {
level.putCoin(pos[0], pos[1]);
});
coins = [[5,5], [6,5], [7,5], [8,5], [9,5], //level.putLeftPipe(13,11);
[4,7], [5,7], [6,7], [7,7], [8,7], [9,7], [10,7], level.putRealPipe(13, 11, 3, "RIGHT", function () {
[4,9], [5,9], [6,9], [7,9], [8,9], [9,9], [10,9]]; Mario.oneone.call();
coins.forEach(function(pos){ player.pos = [2616, 177];
level.putCoin(pos[0],pos[1]); player.pipe("UP", function () {});
}); });
//level.putLeftPipe(13,11); level.putPipe(15, 13, 13);
level.putRealPipe(13,11,3,"RIGHT", function() {
Mario.oneone.call();
player.pos = [2616, 177]
player.pipe("UP", function() {;});
});
level.putPipe(15,13,13); music.overworld.pause();
music.underground.currentTime = 0;
music.overworld.pause(); music.underground.play();
music.underground.currentTime = 0; });
music.underground.play();
};

View File

@@ -1,218 +1,348 @@
(function() { (function () {
var Level = Mario.Level = function(options) { var Level = (Mario.Level = function (options) {
this.playerPos = options.playerPos; this.playerPos = options.playerPos;
this.scrolling = options.scrolling; this.scrolling = options.scrolling;
this.loader = options.loader; this.loader = options.loader;
this.background = options.background; this.background = options.background;
this.exit = options.exit; this.exit = options.exit;
this.floorSprite = options.floorSprite; this.floorSprite = options.floorSprite;
this.cloudSprite = options.cloudSprite; this.cloudSprite = options.cloudSprite;
this.wallSprite = options.wallSprite; this.wallSprite = options.wallSprite;
this.brickSprite = options.brickSprite; this.brickSprite = options.brickSprite;
this.rubbleSprite = options.rubbleSprite; this.rubbleSprite = options.rubbleSprite;
this.brickBounceSprite = options.brickBounceSprite; this.brickBounceSprite = options.brickBounceSprite;
this.ublockSprite = options.ublockSprite; this.ublockSprite = options.ublockSprite;
this.superShroomSprite = options.superShroomSprite; this.superShroomSprite = options.superShroomSprite;
this.fireFlowerSprite = options.fireFlowerSprite; this.fireFlowerSprite = options.fireFlowerSprite;
this.starSprite = options.starSprite; this.starSprite = options.starSprite;
this.coinSprite = options.coinSprite; this.coinSprite = options.coinSprite;
this.bcoinSprite = options.bcoinSprite; this.bcoinSprite = options.bcoinSprite;
this.goombaSprite = options.goombaSprite; this.goombaSprite = options.goombaSprite;
this.koopaSprite = options.koopaSprite; this.koopaSprite = options.koopaSprite;
//prop pipe sprites, to be phased out //prop pipe sprites, to be phased out
this.pipeLEndSprite = options.pipeLEndSprite; this.pipeLEndSprite = options.pipeLEndSprite;
this.pipeREndSprite = options.pipeREndSprite; this.pipeREndSprite = options.pipeREndSprite;
this.pipeLMidSprite = options.pipeLMidSprite; this.pipeLMidSprite = options.pipeLMidSprite;
this.pipeRMidSprite = options.pipeRMidSprite; this.pipeRMidSprite = options.pipeRMidSprite;
//real pipe sprites, use these. //real pipe sprites, use these.
this.pipeUpMid = options.pipeUpMid; this.pipeUpMid = options.pipeUpMid;
this.pipeSideMid = options.pipeSideMid; this.pipeSideMid = options.pipeSideMid;
this.pipeLeft = options.pipeLeft; this.pipeLeft = options.pipeLeft;
this.pipeTop = options.pipeTop; this.pipeTop = options.pipeTop;
this.flagpoleSprites = options.flagPoleSprites; this.flagpoleSprites = options.flagPoleSprites;
this.LPipeSprites = options.LPipeSprites; this.LPipeSprites = options.LPipeSprites;
this.cloudSprites = options.cloudSprites; this.cloudSprites = options.cloudSprites;
this.hillSprites = options.hillSprites; this.hillSprites = options.hillSprites;
this.bushSprite = options.bushSprite; this.bushSprite = options.bushSprite;
this.bushSprites = options.bushSprites; this.bushSprites = options.bushSprites;
this.qblockSprite = options.qblockSprite; this.qblockSprite = options.qblockSprite;
this.invincibility = options.invincibility; this.invincibility = options.invincibility;
this.statics = []; this.statics = [];
this.scenery = []; this.scenery = [];
this.blocks = []; this.blocks = [];
this.enemies = []; this.enemies = [];
this.items = []; this.items = [];
this.pipes = []; this.pipes = [];
for (var i = 0; i < 15; i++) { for (var i = 0; i < 15; i++) {
this.statics[i] = []; this.statics[i] = [];
this.scenery[i] = []; this.scenery[i] = [];
this.blocks[i] = []; this.blocks[i] = [];
} }
};
Level.prototype.putFloor = function(start, end) {
for (var i = start; i < end; i++) {
this.statics[13][i] = new Mario.Floor([16*i,208], this.floorSprite);
this.statics[14][i] = new Mario.Floor([16*i,224], this.floorSprite);
}
};
Level.prototype.putGoomba = function(x, y) {
this.enemies.push(new Mario.Goomba([16*x, 16*y], this.goombaSprite() ));
};
Level.prototype.putKoopa = function(x, y) {
this.enemies.push(new Mario.Koopa([16*x, 16*y], this.koopaSprite(), false));
};
Level.prototype.putWall = function(x, y, height) {
//y is the bottom of the wall in this case.
for (var i = y-height; i < y; i++) {
this.statics[i][x] = new Mario.Floor([16*x, 16*i], this.wallSprite);
}
};
Level.prototype.putPipe = function(x, y, height) {
for (var i = y - height; i < y; i++) {
if (i === y - height) {
this.statics[i][x] = new Mario.Floor([16*x, 16*i], this.pipeLEndSprite);
this.statics[i][x+1] = new Mario.Floor([16*x+16, 16*i], this.pipeREndSprite);
} else {
this.statics[i][x] = new Mario.Floor([16*x, 16*i], this.pipeLMidSprite);
this.statics[i][x+1] = new Mario.Floor([16*x+16, 16*i], this.pipeRMidSprite);
}
}
};
//sometimes, pipes don't go straight up and down.
Level.prototype.putLeftPipe = function(x,y) {
this.statics[y][x] = new Mario.Floor([16*x, 16*y], this.LPipeSprites[0]);
this.statics[y+1][x] = new Mario.Floor([16*x,16*(y+1)], this.LPipeSprites[1]);
this.statics[y][x+1] = new Mario.Floor([16*(x+1),16*y], this.LPipeSprites[2]);
this.statics[y+1][x+1] = new Mario.Floor([16*(x+1),16*(y+1)], this.LPipeSprites[3]);
this.statics[y][x+2] = new Mario.Floor([16*(x+2),16*y], this.LPipeSprites[4]);
this.statics[y+1][x+2] = new Mario.Floor([16*(x+2),16*(y+1)], this.LPipeSprites[5]);
};
Level.prototype.putCoin = function(x, y) {
this.items.push(new Mario.Coin(
[x*16, y*16],
this.coinSprite()
));
};
Level.prototype.putCloud = function(x, y) {
this.scenery[y][x] = new Mario.Prop([x*16, y*16], this.cloudSprite);
};
Level.prototype.putQBlock = function(x, y, item) {
this.blocks[y][x] = new Mario.Block( {
pos: [x*16, y*16],
item: item,
sprite: this.qblockSprite,
usedSprite: this.ublockSprite
}); });
};
Level.prototype.putBrick = function(x,y,item) { Level.prototype.putFloor = function (start, end) {
this.blocks[y][x] = new Mario.Block({ for (var i = start; i < end; i++) {
pos: [x*16, y*16], this.statics[13][i] = new Mario.Floor(
item: item, [16 * i, 208],
sprite: this.brickSprite, this.floorSprite,
bounceSprite: this.brickBounceSprite, );
usedSprite: this.ublockSprite, this.statics[14][i] = new Mario.Floor(
breakable: !item [16 * i, 224],
}); this.floorSprite,
}; );
}
};
Level.prototype.putBigHill = function(x, y) { Level.prototype.putGoomba = function (x, y) {
var px = x*16, py = y*16; this.enemies.push(
this.scenery[y][x] = new Mario.Prop([px, py], this.hillSprites[0]); new Mario.Goomba([16 * x, 16 * y], this.goombaSprite()),
this.scenery[y][x+1] = new Mario.Prop([px+16, py], this.hillSprites[3]); );
this.scenery[y-1][x+1] = new Mario.Prop([px+16, py-16], this.hillSprites[0]); };
this.scenery[y][x+2] = new Mario.Prop([px+32, py], this.hillSprites[4]);
this.scenery[y-1][x+2] = new Mario.Prop([px+32, py-16], this.hillSprites[3]);
this.scenery[y-2][x+2] = new Mario.Prop([px+32, py-32], this.hillSprites[1]);
this.scenery[y][x+3] = new Mario.Prop([px+48, py], this.hillSprites[5]);
this.scenery[y-1][x+3] = new Mario.Prop([px+48, py-16], this.hillSprites[2]);
this.scenery[y][x+4] = new Mario.Prop([px+64, py], this.hillSprites[2]);
};
Level.prototype.putBush = function(x, y) { Level.prototype.putKoopa = function (x, y) {
this.scenery[y][x] = new Mario.Prop([x*16, y*16], this.bushSprite); this.enemies.push(
}; new Mario.Koopa([16 * x, 16 * y], this.koopaSprite(), false),
);
};
Level.prototype.putThreeBush = function(x,y) { Level.prototype.putWall = function (x, y, height) {
px = x*16; //y is the bottom of the wall in this case.
py = y*16; for (var i = y - height; i < y; i++) {
this.scenery[y][x] = new Mario.Prop([px, py], this.bushSprites[0]); this.statics[i][x] = new Mario.Floor(
this.scenery[y][x+1] = new Mario.Prop([px+16, py], this.bushSprites[1]); [16 * x, 16 * i],
this.scenery[y][x+2] = new Mario.Prop([px+32, py], this.bushSprites[1]); this.wallSprite,
this.scenery[y][x+3] = new Mario.Prop([px+48, py], this.bushSprites[1]); );
this.scenery[y][x+4] = new Mario.Prop([px+64, py], this.bushSprites[2]); }
}; };
Level.prototype.putTwoBush = function(x,y) { Level.prototype.putPipe = function (x, y, height) {
px = x*16; for (var i = y - height; i < y; i++) {
py = y*16; if (i === y - height) {
this.scenery[y][x] = new Mario.Prop([px, py], this.bushSprites[0]); this.statics[i][x] = new Mario.Floor(
this.scenery[y][x+1] = new Mario.Prop([px+16, py], this.bushSprites[1]); [16 * x, 16 * i],
this.scenery[y][x+2] = new Mario.Prop([px+32, py], this.bushSprites[1]); this.pipeLEndSprite,
this.scenery[y][x+3] = new Mario.Prop([px+48, py], this.bushSprites[2]); );
}; this.statics[i][x + 1] = new Mario.Floor(
[16 * x + 16, 16 * i],
this.pipeREndSprite,
);
} else {
this.statics[i][x] = new Mario.Floor(
[16 * x, 16 * i],
this.pipeLMidSprite,
);
this.statics[i][x + 1] = new Mario.Floor(
[16 * x + 16, 16 * i],
this.pipeRMidSprite,
);
}
}
};
Level.prototype.putSmallHill = function(x, y) { //sometimes, pipes don't go straight up and down.
var px = x*16, py = y*16; Level.prototype.putLeftPipe = function (x, y) {
this.scenery[y][x] = new Mario.Prop([px, py], this.hillSprites[0]); this.statics[y][x] = new Mario.Floor(
this.scenery[y][x+1] = new Mario.Prop([px+16, py], this.hillSprites[3]); [16 * x, 16 * y],
this.scenery[y-1][x+1] = new Mario.Prop([px+16, py-16], this.hillSprites[1]); this.LPipeSprites[0],
this.scenery[y][x+2] = new Mario.Prop([px+32, py], this.hillSprites[2]); );
}; this.statics[y + 1][x] = new Mario.Floor(
[16 * x, 16 * (y + 1)],
this.LPipeSprites[1],
);
this.statics[y][x + 1] = new Mario.Floor(
[16 * (x + 1), 16 * y],
this.LPipeSprites[2],
);
this.statics[y + 1][x + 1] = new Mario.Floor(
[16 * (x + 1), 16 * (y + 1)],
this.LPipeSprites[3],
);
this.statics[y][x + 2] = new Mario.Floor(
[16 * (x + 2), 16 * y],
this.LPipeSprites[4],
);
this.statics[y + 1][x + 2] = new Mario.Floor(
[16 * (x + 2), 16 * (y + 1)],
this.LPipeSprites[5],
);
};
Level.prototype.putTwoCloud = function(x,y) { Level.prototype.putCoin = function (x, y) {
px = x*16; this.items.push(new Mario.Coin([x * 16, y * 16], this.coinSprite()));
py = y*16; };
this.scenery[y][x] = new Mario.Prop([px, py], this.cloudSprites[0]);
this.scenery[y][x+1] = new Mario.Prop([px+16, py], this.cloudSprites[1]);
this.scenery[y][x+2] = new Mario.Prop([px+32, py], this.cloudSprites[1]);
this.scenery[y][x+3] = new Mario.Prop([px+48, py], this.cloudSprites[2]);
};
Level.prototype.putThreeCloud = function(x,y) { Level.prototype.putCloud = function (x, y) {
px = x*16; this.scenery[y][x] = new Mario.Prop([x * 16, y * 16], this.cloudSprite);
py = y*16; };
this.scenery[y][x] = new Mario.Prop([px, py], this.cloudSprites[0]);
this.scenery[y][x+1] = new Mario.Prop([px+16, py], this.cloudSprites[1]);
this.scenery[y][x+2] = new Mario.Prop([px+32, py], this.cloudSprites[1]);
this.scenery[y][x+3] = new Mario.Prop([px+48, py], this.cloudSprites[1]);
this.scenery[y][x+4] = new Mario.Prop([px+64, py], this.cloudSprites[2]);
};
Level.prototype.putRealPipe = function(x, y, length, direction, destination) { Level.prototype.putQBlock = function (x, y, item) {
px = x*16; this.blocks[y][x] = new Mario.Block({
py = y*16; pos: [x * 16, y * 16],
this.pipes.push(new Mario.Pipe({ item: item,
pos: [px, py], sprite: this.qblockSprite,
length: length, usedSprite: this.ublockSprite,
direction: direction, });
destination: destination };
}));
}
Level.prototype.putFlagpole = function(x) { Level.prototype.putBrick = function (x, y, item) {
this.statics[12][x] = new Mario.Floor([16*x, 192], this.wallSprite); this.blocks[y][x] = new Mario.Block({
for (i=3; i < 12; i++) { pos: [x * 16, y * 16],
this.scenery[i][x] = new Mario.Prop([16*x, 16*i], this.flagpoleSprites[1]) item: item,
} sprite: this.brickSprite,
this.scenery[2][x] = new Mario.Prop([16*x, 32], this.flagpoleSprites[0]); bounceSprite: this.brickBounceSprite,
this.items.push(new Mario.Flag(16*x)); usedSprite: this.ublockSprite,
} breakable: !item,
});
};
Level.prototype.putBigHill = function (x, y) {
var px = x * 16,
py = y * 16;
this.scenery[y][x] = new Mario.Prop([px, py], this.hillSprites[0]);
this.scenery[y][x + 1] = new Mario.Prop(
[px + 16, py],
this.hillSprites[3],
);
this.scenery[y - 1][x + 1] = new Mario.Prop(
[px + 16, py - 16],
this.hillSprites[0],
);
this.scenery[y][x + 2] = new Mario.Prop(
[px + 32, py],
this.hillSprites[4],
);
this.scenery[y - 1][x + 2] = new Mario.Prop(
[px + 32, py - 16],
this.hillSprites[3],
);
this.scenery[y - 2][x + 2] = new Mario.Prop(
[px + 32, py - 32],
this.hillSprites[1],
);
this.scenery[y][x + 3] = new Mario.Prop(
[px + 48, py],
this.hillSprites[5],
);
this.scenery[y - 1][x + 3] = new Mario.Prop(
[px + 48, py - 16],
this.hillSprites[2],
);
this.scenery[y][x + 4] = new Mario.Prop(
[px + 64, py],
this.hillSprites[2],
);
};
Level.prototype.putBush = function (x, y) {
this.scenery[y][x] = new Mario.Prop([x * 16, y * 16], this.bushSprite);
};
Level.prototype.putThreeBush = function (x, y) {
px = x * 16;
py = y * 16;
this.scenery[y][x] = new Mario.Prop([px, py], this.bushSprites[0]);
this.scenery[y][x + 1] = new Mario.Prop(
[px + 16, py],
this.bushSprites[1],
);
this.scenery[y][x + 2] = new Mario.Prop(
[px + 32, py],
this.bushSprites[1],
);
this.scenery[y][x + 3] = new Mario.Prop(
[px + 48, py],
this.bushSprites[1],
);
this.scenery[y][x + 4] = new Mario.Prop(
[px + 64, py],
this.bushSprites[2],
);
};
Level.prototype.putTwoBush = function (x, y) {
px = x * 16;
py = y * 16;
this.scenery[y][x] = new Mario.Prop([px, py], this.bushSprites[0]);
this.scenery[y][x + 1] = new Mario.Prop(
[px + 16, py],
this.bushSprites[1],
);
this.scenery[y][x + 2] = new Mario.Prop(
[px + 32, py],
this.bushSprites[1],
);
this.scenery[y][x + 3] = new Mario.Prop(
[px + 48, py],
this.bushSprites[2],
);
};
Level.prototype.putSmallHill = function (x, y) {
var px = x * 16,
py = y * 16;
this.scenery[y][x] = new Mario.Prop([px, py], this.hillSprites[0]);
this.scenery[y][x + 1] = new Mario.Prop(
[px + 16, py],
this.hillSprites[3],
);
this.scenery[y - 1][x + 1] = new Mario.Prop(
[px + 16, py - 16],
this.hillSprites[1],
);
this.scenery[y][x + 2] = new Mario.Prop(
[px + 32, py],
this.hillSprites[2],
);
};
Level.prototype.putTwoCloud = function (x, y) {
px = x * 16;
py = y * 16;
this.scenery[y][x] = new Mario.Prop([px, py], this.cloudSprites[0]);
this.scenery[y][x + 1] = new Mario.Prop(
[px + 16, py],
this.cloudSprites[1],
);
this.scenery[y][x + 2] = new Mario.Prop(
[px + 32, py],
this.cloudSprites[1],
);
this.scenery[y][x + 3] = new Mario.Prop(
[px + 48, py],
this.cloudSprites[2],
);
};
Level.prototype.putThreeCloud = function (x, y) {
px = x * 16;
py = y * 16;
this.scenery[y][x] = new Mario.Prop([px, py], this.cloudSprites[0]);
this.scenery[y][x + 1] = new Mario.Prop(
[px + 16, py],
this.cloudSprites[1],
);
this.scenery[y][x + 2] = new Mario.Prop(
[px + 32, py],
this.cloudSprites[1],
);
this.scenery[y][x + 3] = new Mario.Prop(
[px + 48, py],
this.cloudSprites[1],
);
this.scenery[y][x + 4] = new Mario.Prop(
[px + 64, py],
this.cloudSprites[2],
);
};
Level.prototype.putRealPipe = function (
x,
y,
length,
direction,
destination,
) {
px = x * 16;
py = y * 16;
this.pipes.push(
new Mario.Pipe({
pos: [px, py],
length: length,
direction: direction,
destination: destination,
}),
);
};
Level.prototype.putFlagpole = function (x) {
this.statics[12][x] = new Mario.Floor([16 * x, 192], this.wallSprite);
for (i = 3; i < 12; i++) {
this.scenery[i][x] = new Mario.Prop(
[16 * x, 16 * i],
this.flagpoleSprites[1],
);
}
this.scenery[2][x] = new Mario.Prop(
[16 * x, 32],
this.flagpoleSprites[0],
);
this.items.push(new Mario.Flag(16 * x));
};
})(); })();

View File

@@ -1,118 +1,132 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
var Mushroom = Mario.Mushroom = function(pos) { var Mushroom = (Mario.Mushroom = function (pos) {
this.spawning = false; this.spawning = false;
this.waiting = 0; this.waiting = 0;
Mario.Entity.call(this, { Mario.Entity.call(this, {
pos: pos, pos: pos,
sprite: level.superShroomSprite, sprite: level.superShroomSprite,
hitbox: [0,0,16,16] hitbox: [0, 0, 16, 16],
});
}); });
}
Mario.Util.inherits(Mushroom, Mario.Entity); Mario.Util.inherits(Mushroom, Mario.Entity);
Mushroom.prototype.render = function(ctx, vX, vY) { Mushroom.prototype.render = function (ctx, vX, vY) {
if (this.spawning > 1) return; if (this.spawning > 1) return;
this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY); this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY);
} };
Mushroom.prototype.spawn = function() { Mushroom.prototype.spawn = function () {
if (player.power > 0) { if (player.power > 0) {
//replace this with a fire flower //replace this with a fire flower
var ff = new Mario.Fireflower(this.pos) var ff = new Mario.Fireflower(this.pos);
ff.spawn(); ff.spawn();
return; return;
}
sounds.itemAppear.play();
this.idx = level.items.length;
level.items.push(this);
this.spawning = 12;
this.targetpos = [];
this.targetpos[0] = this.pos[0];
this.targetpos[1] = this.pos[1] - 16;
}
Mushroom.prototype.update = function(dt) {
if (this.spawning > 1) {
this.spawning -= 1;
if (this.spawning == 1) this.vel[1] = -.5;
return;
}
if (this.spawning) {
if (this.pos[1] <= this.targetpos[1]) {
this.pos[1] = this.targetpos[1];
this.vel[1] = 0;
this.waiting = 5;
this.spawning = 0;
this.vel[0] = 1;
}
} else {
this.acc[1] = 0.2;
}
if (this.waiting) {
this.waiting -= 1;
} else {
this.vel[1] += this.acc[1];
this.pos[0] += this.vel[0];
this.pos[1] += this.vel[1];
this.sprite.update(dt);
}
}
Mushroom.prototype.collideWall = function() {
this.vel[0] = -this.vel[0];
}
Mushroom.prototype.checkCollisions = function() {
if(this.spawning) {
return;
}
var h = this.pos[1] % 16 == 0 ? 1 : 2;
var w = this.pos[0] % 16 == 0 ? 1 : 2;
var baseX = Math.floor(this.pos[0] / 16);
var baseY = Math.floor(this.pos[1] / 16);
if (baseY + h > 15) {
delete level.items[this.idx];
return;
}
for (var i = 0; i < h; i++) {
for (var j = 0; j < w; j++) {
if (level.statics[baseY + i][baseX + j]) {
level.statics[baseY + i][baseX + j].isCollideWith(this);
} }
if (level.blocks[baseY + i][baseX + j]) { sounds.itemAppear.play();
level.blocks[baseY + i][baseX + j].isCollideWith(this); this.idx = level.items.length;
level.items.push(this);
this.spawning = 12;
this.targetpos = [];
this.targetpos[0] = this.pos[0];
this.targetpos[1] = this.pos[1] - 16;
};
Mushroom.prototype.update = function (dt) {
if (this.spawning > 1) {
this.spawning -= 1;
if (this.spawning == 1) this.vel[1] = -0.5;
return;
}
if (this.spawning) {
if (this.pos[1] <= this.targetpos[1]) {
this.pos[1] = this.targetpos[1];
this.vel[1] = 0;
this.waiting = 5;
this.spawning = 0;
this.vel[0] = 1;
}
} else {
this.acc[1] = 0.2;
} }
}
}
this.isPlayerCollided(); if (this.waiting) {
} this.waiting -= 1;
} else {
this.vel[1] += this.acc[1];
this.pos[0] += this.vel[0];
this.pos[1] += this.vel[1];
this.sprite.update(dt);
}
};
//we have access to player everywhere, so let's just do this. Mushroom.prototype.collideWall = function () {
Mushroom.prototype.isPlayerCollided = function() { this.vel[0] = -this.vel[0];
//the first two elements of the hitbox array are an offset, so let's do this now. };
var hpos1 = [this.pos[0] + this.hitbox[0], this.pos[1] + this.hitbox[1]];
var hpos2 = [player.pos[0] + player.hitbox[0], player.pos[1] + player.hitbox[1]];
//if the hitboxes actually overlap Mushroom.prototype.checkCollisions = function () {
if (!(hpos1[0] > hpos2[0]+player.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) { if (this.spawning) {
if (!(hpos1[1] > hpos2[1]+player.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) { return;
player.powerUp(this.idx); }
} var h = this.pos[1] % 16 == 0 ? 1 : 2;
} var w = this.pos[0] % 16 == 0 ? 1 : 2;
}
Mushroom.prototype.bump = function() { var baseX = Math.floor(this.pos[0] / 16);
this.vel[1] = -2; var baseY = Math.floor(this.pos[1] / 16);
}
if (baseY + h > 15) {
delete level.items[this.idx];
return;
}
for (var i = 0; i < h; i++) {
for (var j = 0; j < w; j++) {
if (level.statics[baseY + i][baseX + j]) {
level.statics[baseY + i][baseX + j].isCollideWith(this);
}
if (level.blocks[baseY + i][baseX + j]) {
level.blocks[baseY + i][baseX + j].isCollideWith(this);
}
}
}
this.isPlayerCollided();
};
//we have access to player everywhere, so let's just do this.
Mushroom.prototype.isPlayerCollided = function () {
//the first two elements of the hitbox array are an offset, so let's do this now.
var hpos1 = [
this.pos[0] + this.hitbox[0],
this.pos[1] + this.hitbox[1],
];
var hpos2 = [
player.pos[0] + player.hitbox[0],
player.pos[1] + player.hitbox[1],
];
//if the hitboxes actually overlap
if (
!(
hpos1[0] > hpos2[0] + player.hitbox[2] ||
hpos1[0] + this.hitbox[2] < hpos2[0]
)
) {
if (
!(
hpos1[1] > hpos2[1] + player.hitbox[3] ||
hpos1[1] + this.hitbox[3] < hpos2[1]
)
) {
player.powerUp(this.idx);
}
}
};
Mushroom.prototype.bump = function () {
this.vel[1] = -2;
};
})(); })();

View File

@@ -1,160 +1,228 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
//there are too many possible configurations of pipe to capture in a reasonable
//set of simple variables. Joints, etc. are just too much.
//To that end, the pipe class handles simple pipes, and we'll put together
//anything more complex with individual props. OK? OK.
Pipe = Mario.Pipe = function (options) {
this.pos = options.pos;
//there are too many possible configurations of pipe to capture in a reasonable //NOTE: direction is the direction you move INTO the pipe.
//set of simple variables. Joints, etc. are just too much. this.direction = options.direction;
//To that end, the pipe class handles simple pipes, and we'll put together this.destination = options.destination;
//anything more complex with individual props. OK? OK. this.length = options.length;
Pipe = Mario.Pipe = function(options) {
this.pos = options.pos
//NOTE: direction is the direction you move INTO the pipe. if (this.direction === "UP" || this.direction === "DOWN") {
this.direction = options.direction this.hitbox = [0, 0, 32, this.length * 16];
this.destination = options.destination this.midsection = level.pipeUpMid;
this.length = options.length; this.endsection = level.pipeTop;
if (this.direction === "UP" || this.direction === "DOWN") {
this.hitbox = [0,0, 32, this.length * 16];
this.midsection = level.pipeUpMid;
this.endsection = level.pipeTop;
} else {
this.hitbox = [0,0, 16*this.length, 32];
this.midsection = level.pipeSideMid;
this.endsection = level.pipeLeft;
}
}
Pipe.prototype.checkPipe = function() {
if (this.destination === undefined || !input.isDown(this.direction)) return;
var h = player.power===0 ? 16 : 32;
var x = Math.floor(player.pos[0]);
var y = Math.floor(player.pos[1]);
switch (this.direction) {
case 'RIGHT': if (x === this.pos[0]-16 &&
y >= this.pos[1] &&
y+h <= this.pos[1]+32) {
player.pipe(this.direction, this.destination)
}
break;
case 'LEFT': if (x === this.pos[0]+16*this.length &&
y >= this.pos[1] &&
y+h <= this.pos[1]+32) {
player.pipe(this.direction, this.destination)
}
break;
case 'UP': if (y === this.pos[1] + 16*this.length &&
x >= this.pos[0] &&
x+16 <= this.pos[0]+32) {
player.pipe(this.direction, this.destination)
}
break;
case 'DOWN': if (y+h === this.pos[1] &&
x >= this.pos[0] &&
x+16 <= this.pos[0]+32) {
player.pipe(this.direction, this.destination);
}
break;
}
}
//Note to self: next time, decide on a convention for which thing checks for collisions
//and stick to it. This is a pain.
Pipe.prototype.checkCollisions = function() {
var that = this;
level.enemies.forEach (function(ent) {
that.isCollideWith(ent);
});
level.items.forEach (function(ent) {
that.isCollideWith(ent);
});
fireballs.forEach(function(ent){
that.isCollideWith(ent)
});
if (!player.piping) this.isCollideWith(player);
}
Pipe.prototype.isCollideWith = function (ent) {
//long story short: because we scan every item, and and one 'rubble' item is four things with separate positions
//we'll crash without this line as soon as we destroy a block. OOPS.
if (ent.pos === undefined) return;
//the first two elements of the hitbox array are an offset, so let's do this now.
var hpos1 = [Math.floor(this.pos[0] + this.hitbox[0]), Math.floor(this.pos[1] + this.hitbox[1])];
var hpos2 = [Math.floor(ent.pos[0] + ent.hitbox[0]), Math.floor(ent.pos[1] + ent.hitbox[1])];
//if the hitboxes actually overlap
if (!(hpos1[0] > hpos2[0]+ent.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) {
if (!(hpos1[1] > hpos2[1]+ent.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) {
//if the entity is over the block, it's basically floor
var center = hpos2[0] + ent.hitbox[2] / 2;
if (Math.abs(hpos2[1] + ent.hitbox[3] - hpos1[1]) <= ent.vel[1]) {
ent.vel[1] = 0;
ent.pos[1] = hpos1[1] - ent.hitbox[3] - ent.hitbox[1];
ent.standing = true;
if (ent instanceof Mario.Player) {
ent.jumping = 0;
}
} else if (Math.abs(hpos2[1] - hpos1[1] - this.hitbox[3]) > ent.vel[1] &&
center + 2 >= hpos1[0] && center - 2 <= hpos1[0] + this.hitbox[2]) {
//ent is under the block.
ent.vel[1] = 0;
ent.pos[1] = hpos1[1] + this.hitbox[3];
if (ent instanceof Mario.Player) {
ent.jumping = 0;
}
} else { } else {
//entity is hitting it from the side, we're a wall this.hitbox = [0, 0, 16 * this.length, 32];
ent.collideWall(this); this.midsection = level.pipeSideMid;
this.endsection = level.pipeLeft;
} }
} };
}
}
//we COULD try to write some shenanigans so that the check gets put into the Pipe.prototype.checkPipe = function () {
//collision code, but there won't ever be more than a handful of pipes in a level if (this.destination === undefined || !input.isDown(this.direction))
//so the performance hit of scanning all of them is miniscule. return;
Pipe.prototype.update = function(dt) {
if (this.destination) this.checkPipe();
}
//http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array var h = player.power === 0 ? 16 : 32;
//I honestly have no idea if javascript does this, but I feel like it makes sense var x = Math.floor(player.pos[0]);
//stylistically to prefer branching outside of loops when possible as convention var y = Math.floor(player.pos[1]);
switch (this.direction) {
case "RIGHT":
if (
x === this.pos[0] - 16 &&
y >= this.pos[1] &&
y + h <= this.pos[1] + 32
) {
player.pipe(this.direction, this.destination);
}
break;
case "LEFT":
if (
x === this.pos[0] + 16 * this.length &&
y >= this.pos[1] &&
y + h <= this.pos[1] + 32
) {
player.pipe(this.direction, this.destination);
}
break;
case "UP":
if (
y === this.pos[1] + 16 * this.length &&
x >= this.pos[0] &&
x + 16 <= this.pos[0] + 32
) {
player.pipe(this.direction, this.destination);
}
break;
case "DOWN":
if (
y + h === this.pos[1] &&
x >= this.pos[0] &&
x + 16 <= this.pos[0] + 32
) {
player.pipe(this.direction, this.destination);
}
break;
}
};
//TODO: edit the spritesheet so UP and LEFT pipes aren't backwards. //Note to self: next time, decide on a convention for which thing checks for collisions
Pipe.prototype.render = function(ctx, vX, vY) { //and stick to it. This is a pain.
switch (this.direction) { Pipe.prototype.checkCollisions = function () {
case "DOWN": var that = this;
this.endsection.render(ctx, this.pos[0], this.pos[1], vX, vY); level.enemies.forEach(function (ent) {
for (var i = 1; i < this.length; i++) { that.isCollideWith(ent);
this.midsection.render(ctx, this.pos[0], this.pos[1]+i*16, vX, vY) });
level.items.forEach(function (ent) {
that.isCollideWith(ent);
});
fireballs.forEach(function (ent) {
that.isCollideWith(ent);
});
if (!player.piping) this.isCollideWith(player);
};
Pipe.prototype.isCollideWith = function (ent) {
//long story short: because we scan every item, and and one 'rubble' item is four things with separate positions
//we'll crash without this line as soon as we destroy a block. OOPS.
if (ent.pos === undefined) return;
//the first two elements of the hitbox array are an offset, so let's do this now.
var hpos1 = [
Math.floor(this.pos[0] + this.hitbox[0]),
Math.floor(this.pos[1] + this.hitbox[1]),
];
var hpos2 = [
Math.floor(ent.pos[0] + ent.hitbox[0]),
Math.floor(ent.pos[1] + ent.hitbox[1]),
];
//if the hitboxes actually overlap
if (
!(
hpos1[0] > hpos2[0] + ent.hitbox[2] ||
hpos1[0] + this.hitbox[2] < hpos2[0]
)
) {
if (
!(
hpos1[1] > hpos2[1] + ent.hitbox[3] ||
hpos1[1] + this.hitbox[3] < hpos2[1]
)
) {
//if the entity is over the block, it's basically floor
var center = hpos2[0] + ent.hitbox[2] / 2;
if (
Math.abs(hpos2[1] + ent.hitbox[3] - hpos1[1]) <= ent.vel[1]
) {
ent.vel[1] = 0;
ent.pos[1] = hpos1[1] - ent.hitbox[3] - ent.hitbox[1];
ent.standing = true;
if (ent instanceof Mario.Player) {
ent.jumping = 0;
}
} else if (
Math.abs(hpos2[1] - hpos1[1] - this.hitbox[3]) >
ent.vel[1] &&
center + 2 >= hpos1[0] &&
center - 2 <= hpos1[0] + this.hitbox[2]
) {
//ent is under the block.
ent.vel[1] = 0;
ent.pos[1] = hpos1[1] + this.hitbox[3];
if (ent instanceof Mario.Player) {
ent.jumping = 0;
}
} else {
//entity is hitting it from the side, we're a wall
ent.collideWall(this);
}
}
} }
break; };
case "UP":
this.endsection.render(ctx, this.pos[0], this.pos[1]+16*(this.length-1), vX, vY) //we COULD try to write some shenanigans so that the check gets put into the
for (var i=0; i < this.length - 1; i++) { //collision code, but there won't ever be more than a handful of pipes in a level
this.midsection.render(ctx, this.pos[0], this.pos[1]+i*16, vX, vY) //so the performance hit of scanning all of them is miniscule.
Pipe.prototype.update = function (dt) {
if (this.destination) this.checkPipe();
};
//http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array
//I honestly have no idea if javascript does this, but I feel like it makes sense
//stylistically to prefer branching outside of loops when possible as convention
//TODO: edit the spritesheet so UP and LEFT pipes aren't backwards.
Pipe.prototype.render = function (ctx, vX, vY) {
switch (this.direction) {
case "DOWN":
this.endsection.render(ctx, this.pos[0], this.pos[1], vX, vY);
for (var i = 1; i < this.length; i++) {
this.midsection.render(
ctx,
this.pos[0],
this.pos[1] + i * 16,
vX,
vY,
);
}
break;
case "UP":
this.endsection.render(
ctx,
this.pos[0],
this.pos[1] + 16 * (this.length - 1),
vX,
vY,
);
for (var i = 0; i < this.length - 1; i++) {
this.midsection.render(
ctx,
this.pos[0],
this.pos[1] + i * 16,
vX,
vY,
);
}
break;
case "RIGHT":
this.endsection.render(ctx, this.pos[0], this.pos[1], vX, vY);
for (var i = 1; i < this.length; i++) {
this.midsection.render(
ctx,
this.pos[0] + 16 * i,
this.pos[1],
vX,
vY,
);
}
break;
case "LEFT":
this.endsection.render(
ctx,
this.pos[0] + 16 * (this.length - 1),
this.pos[1],
vX,
vY,
);
for (var i = 0; i < this.legth - 1; i++) {
this.midsection.render(
ctx,
this.pos[0],
this.pos[1] + i * 16,
vX,
vY,
);
}
break;
} }
break; };
case "RIGHT":
this.endsection.render(ctx, this.pos[0], this.pos[1], vX, vY)
for (var i = 1; i < this.length; i++) {
this.midsection.render(ctx, this.pos[0]+16*i, this.pos[1], vX, vY)
}
break;
case "LEFT":
this.endsection.render(ctx, this.pos[0]+16*(this.length-1), this.pos[1], vX, vY)
for (var i = 0; i < this.legth-1; i++) {
this.midsection.render(ctx, this.pos[0], this.pos[1]+i*16, vX, vY)
}
break;
}
}
})(); })();

View File

@@ -1,446 +1,505 @@
(function() { (function () {
if (typeof Mario === 'undefined') if (typeof Mario === "undefined") window.Mario = {};
window.Mario = {};
var Player = Mario.Player = function(pos) { var Player = (Mario.Player = function (pos) {
//I know, I know, there are a lot of variables tracking Mario's state. //I know, I know, there are a lot of variables tracking Mario's state.
//Maybe these can be consolidated some way? We'll see once they're all in. //Maybe these can be consolidated some way? We'll see once they're all in.
this.power = 0; this.power = 0;
this.coins = 0; this.coins = 0;
this.powering = []; this.powering = [];
this.bounce = false; this.bounce = false;
this.jumping = 0; this.jumping = 0;
this.canJump = true; this.canJump = true;
this.invincibility = 0; this.invincibility = 0;
this.crouching = false; this.crouching = false;
this.fireballs = 0; this.fireballs = 0;
this.runheld = false; this.runheld = false;
this.noInput = false; this.noInput = false;
this.targetPos = []; this.targetPos = [];
Mario.Entity.call(this, { Mario.Entity.call(this, {
pos: pos, pos: pos,
sprite: new Mario.Sprite('sprites/player.png', [80,32],[16,16],0), sprite: new Mario.Sprite(
hitbox: [0,0,16,16] "sprites/player.png",
}); [80, 32],
}; [16, 16],
0,
),
hitbox: [0, 0, 16, 16],
});
});
Mario.Util.inherits(Player, Mario.Entity); Mario.Util.inherits(Player, Mario.Entity);
Player.prototype.run = function() { Player.prototype.run = function () {
this.maxSpeed = 2.5; this.maxSpeed = 2.5;
if (this.power == 2 && !this.runheld) { if (this.power == 2 && !this.runheld) {
this.shoot(); this.shoot();
} }
this.runheld = true; this.runheld = true;
} };
Player.prototype.shoot = function() { Player.prototype.shoot = function () {
if (this.fireballs >= 2) return; //Projectile limit! if (this.fireballs >= 2) return; //Projectile limit!
this.fireballs += 1; this.fireballs += 1;
var fb = new Mario.Fireball([this.pos[0]+8,this.pos[1]]); //I hate you, Javascript. var fb = new Mario.Fireball([this.pos[0] + 8, this.pos[1]]); //I hate you, Javascript.
fb.spawn(this.left); fb.spawn(this.left);
this.shooting = 2; this.shooting = 2;
} };
Player.prototype.noRun = function() { Player.prototype.noRun = function () {
this.maxSpeed = 1.5; this.maxSpeed = 1.5;
this.moveAcc = 0.07; this.moveAcc = 0.07;
this.runheld = false; this.runheld = false;
} };
Player.prototype.moveRight = function() { Player.prototype.moveRight = function () {
//we're on the ground //we're on the ground
if (this.vel[1] === 0 && this.standing) { if (this.vel[1] === 0 && this.standing) {
if (this.crouching) { if (this.crouching) {
this.noWalk(); this.noWalk();
return; return;
} }
this.acc[0] = this.moveAcc; this.acc[0] = this.moveAcc;
this.left = false; this.left = false;
} else { } else {
this.acc[0] = this.moveAcc; this.acc[0] = this.moveAcc;
} }
}; };
Player.prototype.moveLeft = function() { Player.prototype.moveLeft = function () {
if (this.vel[1] === 0 && this.standing) { if (this.vel[1] === 0 && this.standing) {
if (this.crouching) { if (this.crouching) {
this.noWalk(); this.noWalk();
return; return;
} }
this.acc[0] = -this.moveAcc; this.acc[0] = -this.moveAcc;
this.left = true; this.left = true;
} else { } else {
this.acc[0] = -this.moveAcc; this.acc[0] = -this.moveAcc;
} }
}; };
Player.prototype.noWalk = function() { Player.prototype.noWalk = function () {
this.maxSpeed = 0; this.maxSpeed = 0;
if (this.vel[0] === 0) return; if (this.vel[0] === 0) return;
if (Math.abs(this.vel[0]) <= 0.1) { if (Math.abs(this.vel[0]) <= 0.1) {
this.vel[0] = 0; this.vel[0] = 0;
this.acc[0] = 0; this.acc[0] = 0;
} }
};
}; Player.prototype.crouch = function () {
if (this.power === 0) {
this.crouching = false;
return;
}
Player.prototype.crouch = function() { if (this.standing) this.crouching = true;
if (this.power === 0) { };
this.crouching = false;
return;
}
if (this.standing) this.crouching = true; Player.prototype.noCrouch = function () {
} this.crouching = false;
};
Player.prototype.noCrouch = function() { Player.prototype.jump = function () {
this.crouching = false; if (this.vel[1] > 0) {
} return;
}
if (this.jumping) {
this.jumping -= 1;
} else if (this.standing && this.canJump) {
this.jumping = 20;
this.canJump = false;
this.standing = false;
this.vel[1] = -6;
if (this.power === 0) {
sounds.smallJump.currentTime = 0;
sounds.smallJump.play();
} else {
sounds.bigJump.currentTime = 0;
sounds.bigJump.play();
}
}
};
Player.prototype.jump = function() { Player.prototype.noJump = function () {
if (this.vel[1] > 0) { this.canJump = true;
return; if (this.jumping) {
} if (this.jumping <= 16) {
if (this.jumping) { this.vel[1] = 0;
this.jumping -= 1; this.jumping = 0;
} else if (this.standing && this.canJump) { } else this.jumping -= 1;
this.jumping = 20; }
this.canJump = false; };
this.standing = false;
this.vel[1] = -6;
if (this.power === 0) {
sounds.smallJump.currentTime = 0;
sounds.smallJump.play();
} else {
sounds.bigJump.currentTime = 0;
sounds.bigJump.play();
}
}
};
Player.prototype.noJump = function() { Player.prototype.setAnimation = function () {
this.canJump = true; if (this.dying) return;
if (this.jumping) {
if (this.jumping <= 16) {
this.vel[1] = 0;
this.jumping = 0;
} else this.jumping -= 1;
}
};
Player.prototype.setAnimation = function() { if (this.starTime) {
if (this.dying) return; var index;
if (this.starTime > 60) index = Math.floor(this.starTime / 2) % 3;
else index = Math.floor(this.starTime / 8) % 3;
if (this.starTime) { this.sprite.pos[1] = level.invincibility[index];
var index; if (this.power == 0) {
if (this.starTime > 60) this.sprite.pos[1] += 32;
index = Math.floor(this.starTime / 2) % 3; }
else index = Math.floor(this.starTime / 8) % 3; this.starTime -= 1;
if (this.starTime == 0) {
switch (this.power) {
case 0:
this.sprite.pos[1] = 32;
break;
case 1:
this.sprite.pos[1] = 0;
break;
case 2:
this.sprite.pos[1] = 96;
break;
}
}
}
//okay cool, now set the sprite
if (this.crouching) {
this.sprite.pos[0] = 176;
this.sprite.speed = 0;
return;
}
this.sprite.pos[1] = level.invincibility[index]; if (this.jumping) {
if (this.power == 0) { this.sprite.pos[0] = 160;
this.sprite.pos[1] += 32; this.sprite.speed = 0;
} } else if (this.standing) {
this.starTime -= 1; if (Math.abs(this.vel[0]) > 0) {
if (this.starTime == 0) { if (this.vel[0] * this.acc[0] >= 0) {
switch(this.power) { this.sprite.pos[0] = 96;
case 0: this.sprite.pos[1] = 32; break; this.sprite.frames = [0, 1, 2];
case 1: this.sprite.pos[1] = 0; break; if (this.vel[0] < 0.2) {
case 2: this.sprite.pos[1] = 96; break; this.sprite.speed = 5;
} } else {
} this.sprite.speed = Math.abs(this.vel[0]) * 8;
} }
//okay cool, now set the sprite } else if (
if (this.crouching) { (this.vel[0] > 0 && this.left) ||
this.sprite.pos[0] = 176; (this.vel[0] < 0 && !this.left)
this.sprite.speed = 0; ) {
return; this.sprite.pos[0] = 144;
} this.sprite.speed = 0;
}
} else {
this.sprite.pos[0] = 80;
this.sprite.speed = 0;
}
if (this.shooting) {
this.sprite.pos[0] += 160;
this.shooting -= 1;
}
}
if (this.jumping) { if (this.flagging) {
this.sprite.pos[0] = 160; this.sprite.pos[0] = 192;
this.sprite.speed = 0; this.sprite.frames = [0, 1];
} else if (this.standing) { this.sprite.speed = 10;
if (Math.abs(this.vel[0]) > 0) { if (this.vel[1] === 0) this.sprite.frames = [0];
if (this.vel[0] * this.acc[0] >= 0) { }
this.sprite.pos[0] = 96;
this.sprite.frames = [0,1,2];
if (this.vel[0] < 0.2) {
this.sprite.speed = 5;
} else {
this.sprite.speed = Math.abs(this.vel[0]) * 8;
}
} else if ((this.vel[0] > 0 && this.left) || (this.vel[0] < 0 && !this.left)){
this.sprite.pos[0] = 144;
this.sprite.speed = 0;
}
} else {
this.sprite.pos[0] = 80;
this.sprite.speed = 0;
}
if (this.shooting) {
this.sprite.pos[0] += 160;
this.shooting -= 1;
}
}
if (this.flagging) { //which way are we facing?
this.sprite.pos[0] = 192; if (this.left) {
this.sprite.frames = [0,1]; this.sprite.img = "sprites/playerl.png";
this.sprite.speed = 10; } else {
if (this.vel[1] === 0) this.sprite.frames = [0]; this.sprite.img = "sprites/player.png";
} }
};
//which way are we facing? Player.prototype.update = function (dt, vX) {
if (this.left) { if (this.powering.length !== 0) {
this.sprite.img = 'sprites/playerl.png'; var next = this.powering.shift();
} else { if (next == 5) return;
this.sprite.img = 'sprites/player.png'; this.sprite.pos = this.powerSprites[next];
} this.sprite.size = this.powerSizes[next];
}; this.pos[1] += this.shift[next];
if (this.powering.length === 0) {
delete level.items[this.touchedItem];
}
return;
}
Player.prototype.update = function(dt, vX) { if (this.invincibility) {
if (this.powering.length !== 0) { this.invincibility -= Math.round(dt * 60);
var next = this.powering.shift(); }
if (next == 5) return;
this.sprite.pos = this.powerSprites[next];
this.sprite.size = this.powerSizes[next];
this.pos[1] += this.shift[next];
if (this.powering.length === 0) {
delete level.items[this.touchedItem];
}
return;
}
if (this.invincibility) { if (this.waiting) {
this.invincibility -= Math.round(dt * 60); this.waiting -= dt;
} if (this.waiting <= 0) {
this.waiting = 0;
} else return;
}
if (this.waiting) { if (this.bounce) {
this.waiting -= dt; this.bounce = false;
if (this.waiting <= 0) { this.standing = false;
this.waiting = 0; this.vel[1] = -3;
} else return; }
}
if (this.bounce) { if (this.pos[0] <= vX) {
this.bounce = false; this.pos[0] = vX;
this.standing = false; this.vel[0] = Math.max(this.vel[0], 0);
this.vel[1] = -3; }
}
if (this.pos[0] <= vX) { if (Math.abs(this.vel[0]) > this.maxSpeed) {
this.pos[0] = vX; this.vel[0] -= (0.05 * this.vel[0]) / Math.abs(this.vel[0]);
this.vel[0] = Math.max(this.vel[0], 0); this.acc[0] = 0;
} }
if (Math.abs(this.vel[0]) > this.maxSpeed) { if (this.dying) {
this.vel[0] -= 0.05 * this.vel[0] / Math.abs(this.vel[0]); if (this.pos[1] < this.targetPos[1]) {
this.acc[0] = 0; this.vel[1] = 1;
} }
this.dying -= 1 * dt;
if (this.dying <= 0) {
player = new Mario.Player(level.playerPos);
level.loader.call();
input.reset();
}
} else {
this.acc[1] = 0.25;
if (this.pos[1] > 240) {
this.die();
}
}
if (this.dying){ if (this.piping) {
if (this.pos[1] < this.targetPos[1]) { this.acc = [0, 0];
this.vel[1] = 1; var pos = [Math.round(this.pos[0]), Math.round(this.pos[1])];
} if (pos[0] === this.targetPos[0] && pos[1] === this.targetPos[1]) {
this.dying -= 1 * dt; this.piping = false;
if (this.dying <= 0) { this.pipeLoc.call();
player = new Mario.Player(level.playerPos); }
level.loader.call(); }
input.reset();
}
}
else {
this.acc[1] = 0.25
if (this.pos[1] > 240) {
this.die();
}
}
if (this.piping) { if (this.flagging) {
this.acc = [0,0]; this.acc = [0, 0];
var pos = [Math.round(this.pos[0]), Math.round(this.pos[1])] }
if (pos[0] === this.targetPos[0] && pos[1] === this.targetPos[1]) {
this.piping = false;
this.pipeLoc.call();
}
}
if (this.flagging) { if (this.exiting) {
this.acc = [0,0]; this.left = false;
} this.flagging = false;
this.vel[0] = 1.5;
if (this.pos[0] >= this.targetPos[0]) {
this.sprite.size = [0, 0];
this.vel = [0, 0];
window.setTimeout(function () {
player.sprite.size =
player.power === 0 ? [16, 16] : [16, 32];
player.exiting = false;
player.noInput = false;
level.loader();
if (player.power !== 0) player.pos[1] -= 16;
music.overworld.currentTime = 0;
}, 5000);
}
}
if (this.exiting) { //approximate acceleration
this.left = false; this.vel[0] += this.acc[0];
this.flagging = false; this.vel[1] += this.acc[1];
this.vel[0] = 1.5; this.pos[0] += this.vel[0];
if (this.pos[0] >= this.targetPos[0]) { this.pos[1] += this.vel[1];
this.sprite.size = [0,0];
this.vel = [0,0];
window.setTimeout(function() {
player.sprite.size = player.power===0 ? [16,16] : [16,32];
player.exiting = false;
player.noInput = false;
level.loader();
if (player.power !== 0) player.pos[1] -= 16;
music.overworld.currentTime = 0;
}, 5000);
}
}
//approximate acceleration this.setAnimation();
this.vel[0] += this.acc[0]; this.sprite.update(dt);
this.vel[1] += this.acc[1]; };
this.pos[0] += this.vel[0];
this.pos[1] += this.vel[1];
this.setAnimation(); Player.prototype.checkCollisions = function () {
this.sprite.update(dt); if (this.piping || this.dying) return;
}; //x-axis first!
var h = this.power > 0 ? 2 : 1;
var w = 1;
if (this.pos[1] % 16 !== 0) {
h += 1;
}
if (this.pos[0] % 16 !== 0) {
w += 1;
}
var baseX = Math.floor(this.pos[0] / 16);
var baseY = Math.floor(this.pos[1] / 16);
Player.prototype.checkCollisions = function() { for (var i = 0; i < h; i++) {
if (this.piping || this.dying) return; if (baseY + i < 0 || baseY + i >= 15) continue;
//x-axis first! for (var j = 0; j < w; j++) {
var h = this.power > 0 ? 2 : 1; if (baseY < 0) {
var w = 1; i++;
if (this.pos[1] % 16 !== 0) { }
h += 1; if (level.statics[baseY + i][baseX + j]) {
} level.statics[baseY + i][baseX + j].isCollideWith(this);
if (this.pos[0] % 16 !== 0) { }
w += 1; if (level.blocks[baseY + i][baseX + j]) {
} level.blocks[baseY + i][baseX + j].isCollideWith(this);
var baseX = Math.floor(this.pos[0] / 16); }
var baseY = Math.floor(this.pos[1] / 16); }
}
};
for (var i = 0; i < h; i++) { Player.prototype.powerUp = function (idx) {
if (baseY + i < 0 || baseY + i >= 15) continue; sounds.powerup.play();
for (var j = 0; j < w; j++) { this.powering = [
if (baseY < 0) { i++;} 0, 5, 2, 5, 1, 5, 2, 5, 1, 5, 2, 5, 3, 5, 1, 5, 2, 5, 3, 5, 1, 5, 4,
if (level.statics[baseY + i][baseX + j]) { ];
level.statics[baseY + i][baseX + j].isCollideWith(this); this.touchedItem = idx;
}
if (level.blocks[baseY + i][baseX + j]) {
level.blocks[baseY + i][baseX + j].isCollideWith(this);
}
}
}
};
Player.prototype.powerUp = function(idx) { if (this.power === 0) {
sounds.powerup.play(); this.sprite.pos[0] = 80;
this.powering = [0,5,2,5,1,5,2,5,1,5,2,5,3,5,1,5,2,5,3,5,1,5,4]; var newy = this.sprite.pos[1] - 32;
this.touchedItem = idx; this.powerSprites = [
[80, newy + 32],
[80, newy + 32],
[320, newy],
[80, newy],
[128, newy],
];
this.powerSizes = [
[16, 16],
[16, 16],
[16, 32],
[16, 32],
[16, 32],
];
this.shift = [0, 16, -16, 0, -16];
this.power = 1;
this.hitbox = [0, 0, 16, 32];
} else if (this.power == 1) {
var curx = this.sprite.pos[0];
this.powerSprites = [
[curx, 96],
[curx, level.invincibility[0]],
[curx, level.invincibility[1]],
[curx, level.invincibility[2]],
[curx, 96],
];
this.powerSizes[([16, 32], [16, 32], [16, 32], [16, 32], [16, 32])];
this.shift = [0, 0, 0, 0, 0];
this.power = 2;
} else {
this.powering = [];
delete level.items[idx];
//no animation, but we play the sound and you get 5000 points.
}
};
if (this.power === 0) { Player.prototype.damage = function () {
this.sprite.pos[0] = 80; if (this.power === 0) {
var newy = this.sprite.pos[1] - 32; //if you're already small, you dead!
this.powerSprites = [[80, newy+32], [80, newy+32], [320, newy], [80, newy], [128, newy]]; this.die();
this.powerSizes = [[16,16],[16,16],[16,32],[16,32],[16,32]]; } else {
this.shift = [0,16,-16,0,-16]; //otherwise, you get turned into small mario
this.power = 1; sounds.pipe.play();
this.hitbox = [0,0,16,32]; this.powering = [
} else if (this.power == 1) { 0, 5, 1, 5, 2, 5, 1, 5, 2, 5, 1, 5, 2, 5, 1, 5, 2, 5, 1, 5, 2,
var curx = this.sprite.pos[0]; 5, 3,
this.powerSprites = [[curx, 96], [curx, level.invincibility[0]], ];
[curx, level.invincibility[1]], [curx, level.invincibility[2]], this.shift = [0, 16, -16, 16];
[curx, 96]]; this.sprite.pos = [160, 0];
this.powerSizes[[16,32],[16,32],[16,32],[16,32],[16,32]]; this.powerSprites = [
this.shift = [0,0,0,0,0]; [160, 0],
this.power = 2; [240, 32],
} else { [240, 0],
this.powering = []; [160, 32],
delete level.items[idx]; ];
//no animation, but we play the sound and you get 5000 points. this.powerSizes = [
} [16, 32],
}; [16, 16],
[16, 32],
[16, 16],
];
this.invincibility = 120;
this.power = 0;
this.hitbox = [0, 0, 16, 16];
}
};
Player.prototype.damage = function() { Player.prototype.die = function () {
if (this.power === 0) { //if you're already small, you dead! //TODO: rewrite the way sounds work to emulate the channels of an NES.
this.die(); music.overworld.pause();
} else { //otherwise, you get turned into small mario music.underground.pause();
sounds.pipe.play(); music.overworld.currentTime = 0;
this.powering = [0,5,1,5,2,5,1,5,2,5,1,5,2,5,1,5,2,5,1,5,2,5,3]; music.death.play();
this.shift = [0,16,-16,16]; this.noWalk();
this.sprite.pos = [160, 0]; this.noRun();
this.powerSprites = [[160,0], [240, 32], [240, 0], [160, 32]]; this.noJump();
this.powerSizes = [[16, 32], [16,16], [16,32], [16,16]];
this.invincibility = 120;
this.power = 0;
this.hitbox = [0,0,16,16];
}
};
Player.prototype.die = function () { this.acc[0] = 0;
//TODO: rewrite the way sounds work to emulate the channels of an NES. this.sprite.pos = [176, 32];
music.overworld.pause(); this.sprite.speed = 0;
music.underground.pause(); this.power = 0;
music.overworld.currentTime = 0; this.waiting = 0.5;
music.death.play(); this.dying = 2;
this.noWalk();
this.noRun();
this.noJump();
this.acc[0] = 0; if (this.pos[1] < 240) {
this.sprite.pos = [176, 32]; //falling into a pit doesn't do the animation.
this.sprite.speed = 0; this.targetPos = [this.pos[0], this.pos[1] - 128];
this.power = 0; this.vel = [0, -5];
this.waiting = 0.5; } else {
this.dying = 2; this.vel = [0, 0];
this.targetPos = [this.pos[0], this.pos[1] - 16];
}
};
if (this.pos[1] < 240) { //falling into a pit doesn't do the animation. Player.prototype.star = function (idx) {
this.targetPos = [this.pos[0], this.pos[1]-128]; delete level.items[idx];
this.vel = [0,-5]; this.starTime = 660;
} else { };
this.vel = [0,0];
this.targetPos = [this.pos[0], this.pos[1] - 16];
}
};
Player.prototype.star = function(idx) { Player.prototype.pipe = function (direction, destination) {
delete level.items[idx]; sounds.pipe.play();
this.starTime = 660; this.piping = true;
} this.pipeLoc = destination;
switch (direction) {
case "LEFT":
this.vel = [-1, 0];
this.targetPos = [
Math.round(this.pos[0] - 16),
Math.round(this.pos[1]),
];
break;
case "RIGHT":
this.vel = [1, 0];
this.targetPos = [
Math.round(this.pos[0] + 16),
Math.round(this.pos[1]),
];
break;
case "DOWN":
this.vel = [0, 1];
this.targetPos = [
Math.round(this.pos[0]),
Math.round(this.pos[1] + this.hitbox[3]),
];
break;
case "UP":
this.vel = [0, -1];
this.targetPos = [
Math.round(this.pos[0]),
Math.round(this.pos[1] - this.hitbox[3]),
];
break;
}
};
Player.prototype.pipe = function(direction, destination) { Player.prototype.flag = function () {
sounds.pipe.play(); this.noInput = true;
this.piping = true; this.flagging = true;
this.pipeLoc = destination; this.vel = [0, 2];
switch(direction) { this.acc = [0, 0];
case "LEFT": };
this.vel = [-1,0];
this.targetPos = [Math.round(this.pos[0]-16), Math.round(this.pos[1])]
break;
case "RIGHT":
this.vel = [1,0];
this.targetPos = [Math.round(this.pos[0]+16), Math.round(this.pos[1])]
break;
case "DOWN":
this.vel = [0,1];
this.targetPos = [Math.round(this.pos[0]), Math.round(this.pos[1]+this.hitbox[3])]
break;
case "UP":
this.vel = [0,-1];
this.targetPos = [Math.round(this.pos[0]), Math.round(this.pos[1]-this.hitbox[3])]
break;
}
}
Player.prototype.flag = function() { Player.prototype.exit = function () {
this.noInput = true; this.pos[0] += 16;
this.flagging = true; this.targetPos[0] = level.exit * 16;
this.vel = [0, 2]; this.left = true;
this.acc = [0, 0]; this.setAnimation();
} this.waiting = 1;
this.exiting = true;
Player.prototype.exit = function() { };
this.pos[0] += 16;
this.targetPos[0] = level.exit * 16;
this.left = true;
this.setAnimation();
this.waiting = 1;
this.exiting = true;
}
})(); })();

Some files were not shown because too many files have changed in this diff Show More