mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 10:47:35 -05:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b41cfb7b60 | ||
|
|
1c675307e1 | ||
|
|
ac56f82c6d | ||
|
|
2ac5ca79d7 | ||
|
|
cb9ee6f7e2 | ||
|
|
9abaef33bd | ||
|
|
320a537db2 | ||
|
|
9b775ce015 | ||
|
|
66f72eda1e | ||
|
|
39ca29749c | ||
|
|
85da548447 | ||
|
|
9985787e4b | ||
|
|
18ec6ce775 | ||
|
|
ed6d0136b8 | ||
|
|
e7216d26e7 | ||
|
|
d1a0d8ea98 | ||
|
|
04222b0d03 |
4
.github/workflows/publish-release.yml
vendored
4
.github/workflows/publish-release.yml
vendored
@@ -46,6 +46,10 @@ jobs:
|
|||||||
working-directory: .
|
working-directory: .
|
||||||
run: sed 's#_PATH_#https://pyscript.net/releases/${{ github.ref_name }}/#' ./public/index.html > ./pyscript.core/dist/index.html
|
run: sed 's#_PATH_#https://pyscript.net/releases/${{ github.ref_name }}/#' ./public/index.html > ./pyscript.core/dist/index.html
|
||||||
|
|
||||||
|
- name: Generate release.tar from snapshot and put it in dist/
|
||||||
|
working-directory: .
|
||||||
|
run: tar -cvf ../release.tar * && mv ../release.tar .
|
||||||
|
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v4
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -142,6 +142,7 @@ coverage/
|
|||||||
test_results
|
test_results
|
||||||
|
|
||||||
# @pyscript/core npm artifacts
|
# @pyscript/core npm artifacts
|
||||||
|
pyscript.core/test-results/*
|
||||||
pyscript.core/core.*
|
pyscript.core/core.*
|
||||||
pyscript.core/dist
|
pyscript.core/dist
|
||||||
pyscript.core/dist.zip
|
pyscript.core/dist.zip
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ repos:
|
|||||||
exclude: pyscript\.core/src/stdlib/pyscript/__init__\.py
|
exclude: pyscript\.core/src/stdlib/pyscript/__init__\.py
|
||||||
|
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
rev: v2.2.6
|
rev: v2.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: codespell # See 'pyproject.toml' for args
|
- id: codespell # See 'pyproject.toml' for args
|
||||||
exclude: \.js\.map$
|
exclude: \.js\.map$
|
||||||
|
|||||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
|||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
|
## 2024.05.21
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
- `py-editor` run buttons now display a spinner when disabled, which occurs when the editor is running code.
|
||||||
|
|
||||||
## 2023.05.01
|
## 2023.05.01
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ If you would like to contribute to PyScript, but you aren't sure where to begin,
|
|||||||
|
|
||||||
## 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://pyscript.github.io/docs/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/contributing/#set-up-your-development-environment) will help you get started.
|
||||||
|
|
||||||
You can also read about PyScript's [development process](https://pyscript.github.io/docs/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!
|
You can also read about PyScript's [development process](https://docs.pyscript.net/latest/developers/) 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
|
||||||
|
|
||||||
|
|||||||
27
README.md
27
README.md
@@ -38,11 +38,11 @@ To try PyScript, import the appropriate pyscript files into the `<head>` tag of
|
|||||||
<head>
|
<head>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://pyscript.net/releases/2023.11.2/core.css"
|
href="https://pyscript.net/releases/2024.5.2/core.css"
|
||||||
/>
|
/>
|
||||||
<script
|
<script
|
||||||
type="module"
|
type="module"
|
||||||
src="https://pyscript.net/releases/2023.11.2/core.js"
|
src="https://pyscript.net/releases/2024.5.2/core.js"
|
||||||
></script>
|
></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -67,10 +67,29 @@ Check out the [official docs](https://docs.pyscript.net/) for more detailed docu
|
|||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
|
|
||||||
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](https://docs.pyscript.net/latest/contributing/) to learn about our development process, reporting bugs and improvements, creating issues and asking questions.
|
||||||
|
|
||||||
Check out the [developing process](https://pyscript.github.io/docs/latest/contributing) documentation for more information on how to setup your development environment.
|
Check out the [developing process](https://docs.pyscript.net/latest/developers/) documentation for more information on how to setup your development environment.
|
||||||
|
|
||||||
## Governance
|
## Governance
|
||||||
|
|
||||||
The [PyScript organization governance](https://github.com/pyscript/governance) is documented in a separate repository.
|
The [PyScript organization governance](https://github.com/pyscript/governance) is documented in a separate repository.
|
||||||
|
|
||||||
|
## Release
|
||||||
|
|
||||||
|
To cut a new release of PyScript simply
|
||||||
|
[add a new release](https://github.com/pyscript/pyscript/releases) while
|
||||||
|
remembering to write a comprehensive changelog. A [GitHub action](https://github.com/pyscript/pyscript/blob/main/.github/workflows/publish-release.yml)
|
||||||
|
will kick in and ensure the release is described and deployed to a URL with the
|
||||||
|
pattern: https://pyscript.net/releases/YYYY.M.v/ (year/month/version - as per
|
||||||
|
our [CalVer](https://calver.org/) versioning scheme).
|
||||||
|
|
||||||
|
Then, the following three separate repositories need updating:
|
||||||
|
|
||||||
|
- [Documentation](https://github.com/pyscript/docs) - Change the `version.json`
|
||||||
|
file in the root of the directory and then `node version-update.js`.
|
||||||
|
- [Homepage](https://github.com/pyscript/pyscript.net) - Ensure the version
|
||||||
|
referenced in `index.html` is the latest version.
|
||||||
|
- [PSDC](https://pyscript.com) - Use discord or Anaconda Slack (if you work at
|
||||||
|
Anaconda) to let the PSDC team know there's a new version, so they can update
|
||||||
|
their project templates.
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es2021: true,
|
|
||||||
},
|
|
||||||
extends: "eslint:recommended",
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
files: [".eslintrc.{js,cjs}"],
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: "script",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
},
|
|
||||||
ignorePatterns: ["3rd-party"],
|
|
||||||
rules: {
|
|
||||||
"no-implicit-globals": ["error"],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
.eslintrc.cjs
|
.eslintrc.cjs
|
||||||
|
eslint.config.mjs
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
node_modules/
|
node_modules/
|
||||||
rollup/
|
rollup/
|
||||||
test/
|
test/
|
||||||
tests/
|
tests/
|
||||||
|
test-results/
|
||||||
src/stdlib/_pyscript
|
src/stdlib/_pyscript
|
||||||
src/stdlib/pyscript.py
|
src/stdlib/pyscript.py
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|||||||
22
pyscript.core/eslint.config.mjs
Normal file
22
pyscript.core/eslint.config.mjs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import globals from "globals";
|
||||||
|
import js from "@eslint/js";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
js.configs.recommended,
|
||||||
|
{
|
||||||
|
ignores: ["**/3rd-party/"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.es2021,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"no-implicit-globals": ["error"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
1054
pyscript.core/package-lock.json
generated
1054
pyscript.core/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pyscript/core",
|
"name": "@pyscript/core",
|
||||||
"version": "0.4.31",
|
"version": "0.4.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "PyScript",
|
"description": "PyScript",
|
||||||
"module": "./index.js",
|
"module": "./index.js",
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"server": "npx static-handler --coi .",
|
"server": "npx static-handler --coi .",
|
||||||
"build": "export ESLINT_USE_FLAT_CONFIG=false; npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && eslint src/ && npm run ts && npm run test:mpy",
|
"build": "export ESLINT_USE_FLAT_CONFIG=true;npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && eslint src/ && npm run ts && npm run test:mpy",
|
||||||
"build:core": "rm -rf dist && rollup --config rollup/core.config.js && cp src/3rd-party/*.css dist/",
|
"build:core": "rm -rf dist && rollup --config rollup/core.config.js && cp src/3rd-party/*.css dist/",
|
||||||
"build:plugins": "node rollup/plugins.cjs",
|
"build:plugins": "node rollup/plugins.cjs",
|
||||||
"build:stdlib": "node rollup/stdlib.cjs",
|
"build:stdlib": "node rollup/stdlib.cjs",
|
||||||
@@ -43,29 +43,29 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ungap/with-resolvers": "^0.1.0",
|
"@ungap/with-resolvers": "^0.1.0",
|
||||||
"basic-devtools": "^0.1.6",
|
"basic-devtools": "^0.1.6",
|
||||||
"polyscript": "^0.12.8",
|
"polyscript": "^0.12.14",
|
||||||
"sticky-module": "^0.1.1",
|
"sticky-module": "^0.1.1",
|
||||||
"to-json-callback": "^0.1.1",
|
"to-json-callback": "^0.1.1",
|
||||||
"type-checked-collections": "^0.1.7"
|
"type-checked-collections": "^0.1.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@codemirror/commands": "^6.5.0",
|
"@codemirror/commands": "^6.6.0",
|
||||||
"@codemirror/lang-python": "^6.1.6",
|
"@codemirror/lang-python": "^6.1.6",
|
||||||
"@codemirror/language": "^6.10.1",
|
"@codemirror/language": "^6.10.2",
|
||||||
"@codemirror/state": "^6.4.1",
|
"@codemirror/state": "^6.4.1",
|
||||||
"@codemirror/view": "^6.26.3",
|
"@codemirror/view": "^6.27.0",
|
||||||
"@playwright/test": "^1.44.0",
|
"@playwright/test": "^1.44.1",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.8",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@webreflection/toml-j0.4": "^1.1.3",
|
"@webreflection/toml-j0.4": "^1.1.3",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/addon-web-links": "^0.11.0",
|
"@xterm/addon-web-links": "^0.11.0",
|
||||||
"bun": "^1.1.7",
|
"bun": "^1.1.12",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"eslint": "^9.2.0",
|
"eslint": "^9.4.0",
|
||||||
"rollup": "^4.17.2",
|
"rollup": "^4.18.0",
|
||||||
"rollup-plugin-postcss": "^4.0.2",
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
"rollup-plugin-string": "^3.0.0",
|
"rollup-plugin-string": "^3.0.0",
|
||||||
"static-handler": "^0.4.3",
|
"static-handler": "^0.4.3",
|
||||||
|
|||||||
@@ -42,3 +42,34 @@ mpy-config {
|
|||||||
.mpy-editor-run-button:disabled {
|
.mpy-editor-run-button:disabled {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes spinner {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.py-editor-run-button:disabled > *,
|
||||||
|
.mpy-editor-run-button:disabled > * {
|
||||||
|
display: none; /* hide all the child elements of the run button when it is disabled */
|
||||||
|
}
|
||||||
|
.py-editor-run-button:disabled,
|
||||||
|
.mpy-editor-run-button:disabled {
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
.py-editor-run-button:disabled::before,
|
||||||
|
.mpy-editor-run-button:disabled::before {
|
||||||
|
content: "";
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 100%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-top: -23px; /* hardcoded value to center the spinner on the run button */
|
||||||
|
margin-left: -26px; /* hardcoded value to center the spinner on the run button */
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #aaa;
|
||||||
|
border-top-color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
animation: spinner 0.6s linear infinite;
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,10 +24,17 @@ import sync from "./sync.js";
|
|||||||
import bootstrapNodeAndPlugins from "./plugins-helper.js";
|
import bootstrapNodeAndPlugins from "./plugins-helper.js";
|
||||||
import { ErrorCode } from "./exceptions.js";
|
import { ErrorCode } from "./exceptions.js";
|
||||||
import { robustFetch as fetch, getText } from "./fetch.js";
|
import { robustFetch as fetch, getText } from "./fetch.js";
|
||||||
import { hooks, main, worker, codeFor, createFunction } from "./hooks.js";
|
import {
|
||||||
|
hooks,
|
||||||
|
main,
|
||||||
|
worker,
|
||||||
|
codeFor,
|
||||||
|
createFunction,
|
||||||
|
inputFailure,
|
||||||
|
} from "./hooks.js";
|
||||||
|
|
||||||
import { stdlib, optional } from "./stdlib.js";
|
import { stdlib, optional } from "./stdlib.js";
|
||||||
export { stdlib, optional };
|
export { stdlib, optional, inputFailure };
|
||||||
|
|
||||||
// generic helper to disambiguate between custom element and script
|
// generic helper to disambiguate between custom element and script
|
||||||
const isScript = ({ tagName }) => tagName === "SCRIPT";
|
const isScript = ({ tagName }) => tagName === "SCRIPT";
|
||||||
@@ -150,6 +157,7 @@ for (const [TYPE, interpreter] of TYPES) {
|
|||||||
// enrich the Python env with some JS utility for main
|
// enrich the Python env with some JS utility for main
|
||||||
interpreter.registerJsModule("_pyscript", {
|
interpreter.registerJsModule("_pyscript", {
|
||||||
PyWorker,
|
PyWorker,
|
||||||
|
js_import: (...urls) => Promise.all(urls.map((url) => import(url))),
|
||||||
get target() {
|
get target() {
|
||||||
return isScript(currentElement)
|
return isScript(currentElement)
|
||||||
? currentElement.target.id
|
? currentElement.target.id
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export const createFunction = (self, name) => {
|
|||||||
const SetFunction = typedSet({ typeof: "function" });
|
const SetFunction = typedSet({ typeof: "function" });
|
||||||
const SetString = typedSet({ typeof: "string" });
|
const SetString = typedSet({ typeof: "string" });
|
||||||
|
|
||||||
const inputFailure = `
|
export const inputFailure = `
|
||||||
import builtins
|
import builtins
|
||||||
def input(prompt=""):
|
def input(prompt=""):
|
||||||
raise Exception("\\n ".join([
|
raise Exception("\\n ".join([
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// PyScript py-editor plugin
|
// PyScript py-editor plugin
|
||||||
import { Hook, XWorker, dedent } from "polyscript/exports";
|
import { Hook, XWorker, dedent, defineProperties } from "polyscript/exports";
|
||||||
import { TYPES, offline_interpreter, stdlib } from "../core.js";
|
import { TYPES, offline_interpreter, stdlib } from "../core.js";
|
||||||
|
|
||||||
const RUN_BUTTON = `<svg style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>`;
|
const RUN_BUTTON = `<svg style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>`;
|
||||||
@@ -43,6 +43,8 @@ async function execute({ currentTarget }) {
|
|||||||
: JSON;
|
: JSON;
|
||||||
details.config = parse(await fetch(config).then((r) => r.text()));
|
details.config = parse(await fetch(config).then((r) => r.text()));
|
||||||
details.version = offline_interpreter(details.config);
|
details.version = offline_interpreter(details.config);
|
||||||
|
} else {
|
||||||
|
details.config = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const xworker = XWorker.call(new Hook(null, hooks), srcLink, details);
|
const xworker = XWorker.call(new Hook(null, hooks), srcLink, details);
|
||||||
@@ -58,7 +60,7 @@ async function execute({ currentTarget }) {
|
|||||||
|
|
||||||
// wait for the env then set the target div
|
// wait for the env then set the target div
|
||||||
// before executing the current code
|
// before executing the current code
|
||||||
envs.get(env).then((xworker) => {
|
return envs.get(env).then((xworker) => {
|
||||||
xworker.onerror = ({ error }) => {
|
xworker.onerror = ({ error }) => {
|
||||||
if (hasRunButton) {
|
if (hasRunButton) {
|
||||||
outDiv.innerHTML += `<span style='color:red'>${
|
outDiv.innerHTML += `<span style='color:red'>${
|
||||||
@@ -148,7 +150,7 @@ const init = async (script, type, interpreter) => {
|
|||||||
import(/* webpackIgnore: true */ "../3rd-party/codemirror_commands.js"),
|
import(/* webpackIgnore: true */ "../3rd-party/codemirror_commands.js"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const isSetup = script.hasAttribute("setup");
|
let isSetup = script.hasAttribute("setup");
|
||||||
const hasConfig = script.hasAttribute("config");
|
const hasConfig = script.hasAttribute("config");
|
||||||
const env = `${interpreter}-${script.getAttribute("env") || getID(type)}`;
|
const env = `${interpreter}-${script.getAttribute("env") || getID(type)}`;
|
||||||
|
|
||||||
@@ -162,7 +164,7 @@ const init = async (script, type, interpreter) => {
|
|||||||
|
|
||||||
configs.set(env, hasConfig);
|
configs.set(env, hasConfig);
|
||||||
|
|
||||||
const source = script.src
|
let source = script.src
|
||||||
? await fetch(script.src).then((b) => b.text())
|
? await fetch(script.src).then((b) => b.text())
|
||||||
: script.textContent;
|
: script.textContent;
|
||||||
const context = {
|
const context = {
|
||||||
@@ -179,14 +181,59 @@ const init = async (script, type, interpreter) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let target;
|
||||||
|
defineProperties(script, {
|
||||||
|
target: { get: () => target },
|
||||||
|
code: {
|
||||||
|
get: () => context.pySrc,
|
||||||
|
set: (insert) => {
|
||||||
|
if (isSetup) return;
|
||||||
|
editor.update([
|
||||||
|
editor.state.update({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: editor.state.doc.length,
|
||||||
|
insert,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
process: {
|
||||||
|
/**
|
||||||
|
* Simulate a setup node overriding the source to evaluate.
|
||||||
|
* @param {string} code the Python code to evaluate.
|
||||||
|
* @returns {Promise<...>} fulfill once code has been evaluated.
|
||||||
|
*/
|
||||||
|
value(code) {
|
||||||
|
const wasSetup = isSetup;
|
||||||
|
const wasSource = source;
|
||||||
|
isSetup = true;
|
||||||
|
source = code;
|
||||||
|
const restore = () => {
|
||||||
|
isSetup = wasSetup;
|
||||||
|
source = wasSource;
|
||||||
|
};
|
||||||
|
return execute
|
||||||
|
.call(context, { currentTarget: null })
|
||||||
|
.then(restore, restore);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const notify = () => {
|
||||||
|
const event = new Event(`${type}-editor`, { bubbles: true });
|
||||||
|
script.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
|
||||||
if (isSetup) {
|
if (isSetup) {
|
||||||
execute.call(context, { currentTarget: null });
|
await execute.call(context, { currentTarget: null });
|
||||||
|
notify();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selector = script.getAttribute("target");
|
const selector = script.getAttribute("target");
|
||||||
|
|
||||||
let target;
|
|
||||||
if (selector) {
|
if (selector) {
|
||||||
target =
|
target =
|
||||||
document.getElementById(selector) ||
|
document.getElementById(selector) ||
|
||||||
@@ -236,6 +283,7 @@ const init = async (script, type, interpreter) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
notify();
|
||||||
};
|
};
|
||||||
|
|
||||||
// avoid too greedy MutationObserver operations at distance
|
// avoid too greedy MutationObserver operations at distance
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
// PyScript py-terminal plugin
|
// PyScript py-terminal plugin
|
||||||
import { TYPES, hooks } from "../core.js";
|
import { TYPES } from "../core.js";
|
||||||
import { notify } from "./error.js";
|
import { notify } from "./error.js";
|
||||||
import { customObserver, defineProperties } from "polyscript/exports";
|
import { customObserver } from "polyscript/exports";
|
||||||
|
|
||||||
// will contain all valid selectors
|
// will contain all valid selectors
|
||||||
const SELECTORS = [];
|
const SELECTORS = [];
|
||||||
|
|
||||||
|
// avoid processing same elements twice
|
||||||
|
const processed = new WeakSet();
|
||||||
|
|
||||||
// show the error on main and
|
// show the error on main and
|
||||||
// stops the module from keep executing
|
// stops the module from keep executing
|
||||||
const notifyAndThrow = (message) => {
|
const notifyAndThrow = (message) => {
|
||||||
@@ -15,265 +18,10 @@ const notifyAndThrow = (message) => {
|
|||||||
|
|
||||||
const onceOnMain = ({ attributes: { worker } }) => !worker;
|
const onceOnMain = ({ attributes: { worker } }) => !worker;
|
||||||
|
|
||||||
const bootstrapped = new WeakSet();
|
|
||||||
|
|
||||||
let addStyle = true;
|
let addStyle = true;
|
||||||
|
|
||||||
// this callback will be serialized as string and it never needs
|
for (const type of TYPES.keys()) {
|
||||||
// to be invoked multiple times. Each xworker here is bootstrapped
|
const selector = `script[type="${type}"][terminal],${type}-script[terminal]`;
|
||||||
// only once thanks to the `sync.is_pyterminal()` check.
|
|
||||||
const workerReady = ({ interpreter, io, run, type }, { sync }) => {
|
|
||||||
if (!sync.is_pyterminal()) return;
|
|
||||||
|
|
||||||
// in workers it's always safe to grab the polyscript currentScript
|
|
||||||
// the ugly `_` dance is due MicroPython not able to import via:
|
|
||||||
// `from polyscript.currentScript import terminal as __terminal__`
|
|
||||||
run(
|
|
||||||
"from polyscript import currentScript as _; __terminal__ = _.terminal; del _",
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = "";
|
|
||||||
const { pyterminal_read, pyterminal_write } = sync;
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
const generic = {
|
|
||||||
isatty: false,
|
|
||||||
write(buffer) {
|
|
||||||
data = decoder.decode(buffer);
|
|
||||||
pyterminal_write(data);
|
|
||||||
return buffer.length;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// This part works already in both Pyodide and MicroPython
|
|
||||||
io.stderr = (error) => {
|
|
||||||
pyterminal_write(String(error.message || error));
|
|
||||||
};
|
|
||||||
|
|
||||||
// MicroPython has no code or code.interact()
|
|
||||||
// This part patches it in a way that simulates
|
|
||||||
// the code.interact() module in Pyodide.
|
|
||||||
if (type === "mpy") {
|
|
||||||
// monkey patch global input otherwise broken in MicroPython
|
|
||||||
interpreter.registerJsModule("_pyscript_input", {
|
|
||||||
input: pyterminal_read,
|
|
||||||
});
|
|
||||||
run("from _pyscript_input import input");
|
|
||||||
|
|
||||||
// this is needed to avoid truncated unicode in MicroPython
|
|
||||||
// the reason is that `linebuffer` false just send one byte
|
|
||||||
// per time and readline here doesn't like it much.
|
|
||||||
// MicroPython also has issues with code-points and
|
|
||||||
// replProcessChar(byte) but that function accepts only
|
|
||||||
// one byte per time so ... we have an issue!
|
|
||||||
// @see https://github.com/pyscript/pyscript/pull/2018
|
|
||||||
// @see https://github.com/WebReflection/buffer-points
|
|
||||||
const bufferPoints = (stdio) => {
|
|
||||||
const bytes = [];
|
|
||||||
let needed = 0;
|
|
||||||
return (buffer) => {
|
|
||||||
let written = 0;
|
|
||||||
for (const byte of buffer) {
|
|
||||||
bytes.push(byte);
|
|
||||||
// @see https://encoding.spec.whatwg.org/#utf-8-bytes-needed
|
|
||||||
if (needed) needed--;
|
|
||||||
else if (0xc2 <= byte && byte <= 0xdf) needed = 1;
|
|
||||||
else if (0xe0 <= byte && byte <= 0xef) needed = 2;
|
|
||||||
else if (0xf0 <= byte && byte <= 0xf4) needed = 3;
|
|
||||||
if (!needed) {
|
|
||||||
written += bytes.length;
|
|
||||||
stdio(new Uint8Array(bytes.splice(0)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return written;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
io.stdout = bufferPoints(generic.write);
|
|
||||||
|
|
||||||
// tiny shim of the code module with only interact
|
|
||||||
// to bootstrap a REPL like environment
|
|
||||||
interpreter.registerJsModule("code", {
|
|
||||||
interact() {
|
|
||||||
let input = "";
|
|
||||||
let length = 1;
|
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const acc = [];
|
|
||||||
const handlePoints = bufferPoints((buffer) => {
|
|
||||||
acc.push(...buffer);
|
|
||||||
pyterminal_write(decoder.decode(buffer));
|
|
||||||
});
|
|
||||||
|
|
||||||
// avoid duplicating the output produced by the input
|
|
||||||
io.stdout = (buffer) =>
|
|
||||||
length++ > input.length ? handlePoints(buffer) : 0;
|
|
||||||
|
|
||||||
interpreter.replInit();
|
|
||||||
|
|
||||||
// loop forever waiting for user inputs
|
|
||||||
(function repl() {
|
|
||||||
const out = decoder.decode(new Uint8Array(acc.splice(0)));
|
|
||||||
// print in current line only the last line produced by the REPL
|
|
||||||
const data = `${pyterminal_read(out.split("\n").at(-1))}\r`;
|
|
||||||
length = 0;
|
|
||||||
input = encoder.encode(data);
|
|
||||||
for (const c of input) interpreter.replProcessChar(c);
|
|
||||||
repl();
|
|
||||||
})();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
interpreter.setStdout(generic);
|
|
||||||
interpreter.setStderr(generic);
|
|
||||||
interpreter.setStdin({
|
|
||||||
isatty: false,
|
|
||||||
stdin: () => pyterminal_read(data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const pyTerminal = async (element) => {
|
|
||||||
// lazy load these only when a valid terminal is found
|
|
||||||
const [{ Terminal }, { Readline }, { FitAddon }, { WebLinksAddon }] =
|
|
||||||
await Promise.all([
|
|
||||||
import(/* webpackIgnore: true */ "../3rd-party/xterm.js"),
|
|
||||||
import(/* webpackIgnore: true */ "../3rd-party/xterm-readline.js"),
|
|
||||||
import(/* webpackIgnore: true */ "../3rd-party/xterm_addon-fit.js"),
|
|
||||||
import(
|
|
||||||
/* webpackIgnore: true */ "../3rd-party/xterm_addon-web-links.js"
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const readline = new Readline();
|
|
||||||
|
|
||||||
// common main thread initialization for both worker
|
|
||||||
// or main case, bootstrapping the terminal on its target
|
|
||||||
const init = (options) => {
|
|
||||||
let target = element;
|
|
||||||
const selector = element.getAttribute("target");
|
|
||||||
if (selector) {
|
|
||||||
target =
|
|
||||||
document.getElementById(selector) ||
|
|
||||||
document.querySelector(selector);
|
|
||||||
if (!target) throw new Error(`Unknown target ${selector}`);
|
|
||||||
} else {
|
|
||||||
target = document.createElement("py-terminal");
|
|
||||||
target.style.display = "block";
|
|
||||||
element.after(target);
|
|
||||||
}
|
|
||||||
const terminal = new Terminal({
|
|
||||||
theme: {
|
|
||||||
background: "#191A19",
|
|
||||||
foreground: "#F5F2E7",
|
|
||||||
},
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
const fitAddon = new FitAddon();
|
|
||||||
terminal.loadAddon(fitAddon);
|
|
||||||
terminal.loadAddon(readline);
|
|
||||||
terminal.loadAddon(new WebLinksAddon());
|
|
||||||
terminal.open(target);
|
|
||||||
fitAddon.fit();
|
|
||||||
terminal.focus();
|
|
||||||
defineProperties(element, {
|
|
||||||
terminal: { value: terminal },
|
|
||||||
process: {
|
|
||||||
value: async (code) => {
|
|
||||||
// this loop is the only way I could find to actually simulate
|
|
||||||
// the user input char after char in a way that works in both
|
|
||||||
// MicroPython and Pyodide
|
|
||||||
for (const line of code.split(/(?:\r|\n|\r\n)/)) {
|
|
||||||
terminal.paste(`${line}\n`);
|
|
||||||
do {
|
|
||||||
await new Promise((resolve) =>
|
|
||||||
setTimeout(resolve, 0),
|
|
||||||
);
|
|
||||||
} while (!readline.activeRead?.resolve);
|
|
||||||
readline.activeRead.resolve(line);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return terminal;
|
|
||||||
};
|
|
||||||
|
|
||||||
// branch logic for the worker
|
|
||||||
if (element.hasAttribute("worker")) {
|
|
||||||
// add a hook on the main thread to setup all sync helpers
|
|
||||||
// also bootstrapping the XTerm target on main *BUT* ...
|
|
||||||
hooks.main.onWorker.add(function worker(_, xworker) {
|
|
||||||
// ... as multiple workers will add multiple callbacks
|
|
||||||
// be sure no xworker is ever initialized twice!
|
|
||||||
if (bootstrapped.has(xworker)) return;
|
|
||||||
bootstrapped.add(xworker);
|
|
||||||
|
|
||||||
// still cleanup this callback for future scripts/workers
|
|
||||||
hooks.main.onWorker.delete(worker);
|
|
||||||
|
|
||||||
init({
|
|
||||||
disableStdin: false,
|
|
||||||
cursorBlink: true,
|
|
||||||
cursorStyle: "block",
|
|
||||||
});
|
|
||||||
|
|
||||||
xworker.sync.is_pyterminal = () => true;
|
|
||||||
xworker.sync.pyterminal_read = readline.read.bind(readline);
|
|
||||||
xworker.sync.pyterminal_write = readline.write.bind(readline);
|
|
||||||
});
|
|
||||||
|
|
||||||
// setup remote thread JS/Python code for whenever the
|
|
||||||
// worker is ready to become a terminal
|
|
||||||
hooks.worker.onReady.add(workerReady);
|
|
||||||
} else {
|
|
||||||
// in the main case, just bootstrap XTerm without
|
|
||||||
// allowing any input as that's not possible / awkward
|
|
||||||
hooks.main.onReady.add(function main({ interpreter, io, run, type }) {
|
|
||||||
console.warn("py-terminal is read only on main thread");
|
|
||||||
hooks.main.onReady.delete(main);
|
|
||||||
|
|
||||||
// on main, it's easy to trash and clean the current terminal
|
|
||||||
globalThis.__py_terminal__ = init({
|
|
||||||
disableStdin: true,
|
|
||||||
cursorBlink: false,
|
|
||||||
cursorStyle: "underline",
|
|
||||||
});
|
|
||||||
run("from js import __py_terminal__ as __terminal__");
|
|
||||||
delete globalThis.__py_terminal__;
|
|
||||||
|
|
||||||
io.stderr = (error) => {
|
|
||||||
readline.write(String(error.message || error));
|
|
||||||
};
|
|
||||||
|
|
||||||
if (type === "mpy") {
|
|
||||||
interpreter.setStdin = Object; // as no-op
|
|
||||||
interpreter.setStderr = Object; // as no-op
|
|
||||||
interpreter.setStdout = ({ write }) => {
|
|
||||||
io.stdout = write;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = "";
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
const generic = {
|
|
||||||
isatty: false,
|
|
||||||
write(buffer) {
|
|
||||||
data = decoder.decode(buffer);
|
|
||||||
readline.write(data);
|
|
||||||
return buffer.length;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
interpreter.setStdout(generic);
|
|
||||||
interpreter.setStderr(generic);
|
|
||||||
interpreter.setStdin({
|
|
||||||
isatty: false,
|
|
||||||
stdin: () => readline.read(data),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const key of TYPES.keys()) {
|
|
||||||
const selector = `script[type="${key}"][terminal],${key}-script[terminal]`;
|
|
||||||
SELECTORS.push(selector);
|
SELECTORS.push(selector);
|
||||||
customObserver.set(selector, async (element) => {
|
customObserver.set(selector, async (element) => {
|
||||||
// we currently support only one terminal on main as in "classic"
|
// we currently support only one terminal on main as in "classic"
|
||||||
@@ -292,6 +40,21 @@ for (const key of TYPES.keys()) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await pyTerminal(element);
|
if (processed.has(element)) return;
|
||||||
|
processed.add(element);
|
||||||
|
|
||||||
|
const bootstrap = (module) => module.default(element);
|
||||||
|
|
||||||
|
// we can't be smart with template literals for the dynamic import
|
||||||
|
// or bundlers are incapable of producing multiple files around
|
||||||
|
if (type === "mpy") {
|
||||||
|
await import(/* webpackIgnore: true */ "./py-terminal/mpy.js").then(
|
||||||
|
bootstrap,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await import(/* webpackIgnore: true */ "./py-terminal/py.js").then(
|
||||||
|
bootstrap,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
239
pyscript.core/src/plugins/py-terminal/mpy.js
Normal file
239
pyscript.core/src/plugins/py-terminal/mpy.js
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
// PyScript pyodide terminal plugin
|
||||||
|
import { hooks, inputFailure } from "../../core.js";
|
||||||
|
import { defineProperties } from "polyscript/exports";
|
||||||
|
|
||||||
|
const bootstrapped = new WeakSet();
|
||||||
|
|
||||||
|
// this callback will be serialized as string and it never needs
|
||||||
|
// to be invoked multiple times. Each xworker here is bootstrapped
|
||||||
|
// only once thanks to the `sync.is_pyterminal()` check.
|
||||||
|
const workerReady = ({ interpreter, io, run, type }, { sync }) => {
|
||||||
|
if (type !== "mpy" || !sync.is_pyterminal()) return;
|
||||||
|
|
||||||
|
const { pyterminal_ready, pyterminal_read, pyterminal_write } = sync;
|
||||||
|
|
||||||
|
interpreter.registerJsModule("_pyscript_input", {
|
||||||
|
input: pyterminal_read,
|
||||||
|
});
|
||||||
|
|
||||||
|
run(
|
||||||
|
[
|
||||||
|
"from _pyscript_input import input",
|
||||||
|
"from polyscript import currentScript as _",
|
||||||
|
"__terminal__ = _.terminal",
|
||||||
|
"del _",
|
||||||
|
].join(";"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const missingReturn = new Uint8Array([13]);
|
||||||
|
io.stdout = (buffer) => {
|
||||||
|
if (buffer[0] === 10) pyterminal_write(missingReturn);
|
||||||
|
pyterminal_write(buffer);
|
||||||
|
};
|
||||||
|
io.stderr = (error) => {
|
||||||
|
pyterminal_write(String(error.message || error));
|
||||||
|
};
|
||||||
|
|
||||||
|
// tiny shim of the code module with only interact
|
||||||
|
// to bootstrap a REPL like environment
|
||||||
|
interpreter.registerJsModule("code", {
|
||||||
|
interact() {
|
||||||
|
const encoder = new TextEncoderStream();
|
||||||
|
encoder.readable.pipeTo(
|
||||||
|
new WritableStream({
|
||||||
|
write(buffer) {
|
||||||
|
for (const c of buffer) interpreter.replProcessChar(c);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const writer = encoder.writable.getWriter();
|
||||||
|
sync.pyterminal_stream_write = (buffer) => writer.write(buffer);
|
||||||
|
pyterminal_ready();
|
||||||
|
|
||||||
|
interpreter.replInit();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async (element) => {
|
||||||
|
// lazy load these only when a valid terminal is found
|
||||||
|
const [{ Terminal }, { FitAddon }, { WebLinksAddon }] = await Promise.all([
|
||||||
|
import(/* webpackIgnore: true */ "../../3rd-party/xterm.js"),
|
||||||
|
import(/* webpackIgnore: true */ "../../3rd-party/xterm_addon-fit.js"),
|
||||||
|
import(
|
||||||
|
/* webpackIgnore: true */ "../../3rd-party/xterm_addon-web-links.js"
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const terminalOptions = {
|
||||||
|
disableStdin: false,
|
||||||
|
cursorBlink: true,
|
||||||
|
cursorStyle: "block",
|
||||||
|
};
|
||||||
|
|
||||||
|
let stream;
|
||||||
|
|
||||||
|
// common main thread initialization for both worker
|
||||||
|
// or main case, bootstrapping the terminal on its target
|
||||||
|
const init = () => {
|
||||||
|
let target = element;
|
||||||
|
const selector = element.getAttribute("target");
|
||||||
|
if (selector) {
|
||||||
|
target =
|
||||||
|
document.getElementById(selector) ||
|
||||||
|
document.querySelector(selector);
|
||||||
|
if (!target) throw new Error(`Unknown target ${selector}`);
|
||||||
|
} else {
|
||||||
|
target = document.createElement("py-terminal");
|
||||||
|
target.style.display = "block";
|
||||||
|
element.after(target);
|
||||||
|
}
|
||||||
|
const terminal = new Terminal({
|
||||||
|
theme: {
|
||||||
|
background: "#191A19",
|
||||||
|
foreground: "#F5F2E7",
|
||||||
|
},
|
||||||
|
...terminalOptions,
|
||||||
|
});
|
||||||
|
const fitAddon = new FitAddon();
|
||||||
|
terminal.loadAddon(fitAddon);
|
||||||
|
terminal.loadAddon(new WebLinksAddon());
|
||||||
|
terminal.open(target);
|
||||||
|
fitAddon.fit();
|
||||||
|
terminal.focus();
|
||||||
|
defineProperties(element, {
|
||||||
|
terminal: { value: terminal },
|
||||||
|
process: {
|
||||||
|
value: async (code) => {
|
||||||
|
for (const line of code.split(/(?:\r\n|\r|\n)/)) {
|
||||||
|
await stream.write(`${line}\r`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return terminal;
|
||||||
|
};
|
||||||
|
|
||||||
|
// branch logic for the worker
|
||||||
|
if (element.hasAttribute("worker")) {
|
||||||
|
// add a hook on the main thread to setup all sync helpers
|
||||||
|
// also bootstrapping the XTerm target on main *BUT* ...
|
||||||
|
hooks.main.onWorker.add(function worker(_, xworker) {
|
||||||
|
// ... as multiple workers will add multiple callbacks
|
||||||
|
// be sure no xworker is ever initialized twice!
|
||||||
|
if (bootstrapped.has(xworker)) return;
|
||||||
|
bootstrapped.add(xworker);
|
||||||
|
|
||||||
|
// still cleanup this callback for future scripts/workers
|
||||||
|
hooks.main.onWorker.delete(worker);
|
||||||
|
|
||||||
|
const terminal = init();
|
||||||
|
|
||||||
|
const { sync } = xworker;
|
||||||
|
|
||||||
|
// handle the read mode on input
|
||||||
|
let promisedChunks = null;
|
||||||
|
let readChunks = "";
|
||||||
|
|
||||||
|
sync.is_pyterminal = () => true;
|
||||||
|
|
||||||
|
// put the terminal in a read-only state
|
||||||
|
// frees the worker on \r
|
||||||
|
sync.pyterminal_read = (buffer) => {
|
||||||
|
terminal.write(buffer);
|
||||||
|
promisedChunks = Promise.withResolvers();
|
||||||
|
return promisedChunks.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
// write if not reading input
|
||||||
|
sync.pyterminal_write = (buffer) => {
|
||||||
|
if (!promisedChunks) terminal.write(buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
// add the onData terminal listener which forwards to the worker
|
||||||
|
// everything typed in a queued char-by-char way
|
||||||
|
sync.pyterminal_ready = () => {
|
||||||
|
let queue = Promise.resolve();
|
||||||
|
stream = {
|
||||||
|
write: (buffer) =>
|
||||||
|
(queue = queue.then(() =>
|
||||||
|
sync.pyterminal_stream_write(buffer),
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
terminal.onData((buffer) => {
|
||||||
|
if (promisedChunks) {
|
||||||
|
readChunks += buffer;
|
||||||
|
terminal.write(buffer);
|
||||||
|
if (readChunks.endsWith("\r")) {
|
||||||
|
terminal.write("\n");
|
||||||
|
promisedChunks.resolve(readChunks.slice(0, -1));
|
||||||
|
promisedChunks = null;
|
||||||
|
readChunks = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stream.write(buffer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// setup remote thread JS/Python code for whenever the
|
||||||
|
// worker is ready to become a terminal
|
||||||
|
hooks.worker.onReady.add(workerReady);
|
||||||
|
} else {
|
||||||
|
// ⚠️ In an ideal world the inputFailure should never be used on main.
|
||||||
|
// However, Pyodide still can't compete with MicroPython REPL mode
|
||||||
|
// so while it's OK to keep that entry on main as default, we need
|
||||||
|
// to remove it ASAP from `mpy` use cases, otherwise MicroPython would
|
||||||
|
// also throw whenever an `input(...)` is required / digited.
|
||||||
|
hooks.main.codeBeforeRun.delete(inputFailure);
|
||||||
|
|
||||||
|
// in the main case, just bootstrap XTerm without
|
||||||
|
// allowing any input as that's not possible / awkward
|
||||||
|
hooks.main.onReady.add(function main({ interpreter, io, run, type }) {
|
||||||
|
if (type !== "mpy") return;
|
||||||
|
|
||||||
|
hooks.main.onReady.delete(main);
|
||||||
|
|
||||||
|
const terminal = init();
|
||||||
|
|
||||||
|
const missingReturn = new Uint8Array([13]);
|
||||||
|
io.stdout = (buffer) => {
|
||||||
|
if (buffer[0] === 10) terminal.write(missingReturn);
|
||||||
|
terminal.write(buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
// expose the __terminal__ one-off reference
|
||||||
|
globalThis.__py_terminal__ = terminal;
|
||||||
|
run(
|
||||||
|
[
|
||||||
|
"from js import prompt as input",
|
||||||
|
"from js import __py_terminal__ as __terminal__",
|
||||||
|
].join(";"),
|
||||||
|
);
|
||||||
|
delete globalThis.__py_terminal__;
|
||||||
|
|
||||||
|
// NOTE: this is NOT the same as the one within
|
||||||
|
// the onWorkerReady callback!
|
||||||
|
interpreter.registerJsModule("code", {
|
||||||
|
interact() {
|
||||||
|
const encoder = new TextEncoderStream();
|
||||||
|
encoder.readable.pipeTo(
|
||||||
|
new WritableStream({
|
||||||
|
write(buffer) {
|
||||||
|
for (const c of buffer)
|
||||||
|
interpreter.replProcessChar(c);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
stream = encoder.writable.getWriter();
|
||||||
|
terminal.onData((buffer) => stream.write(buffer));
|
||||||
|
|
||||||
|
interpreter.replInit();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
179
pyscript.core/src/plugins/py-terminal/py.js
Normal file
179
pyscript.core/src/plugins/py-terminal/py.js
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
// PyScript py-terminal plugin
|
||||||
|
import { hooks } from "../../core.js";
|
||||||
|
import { defineProperties } from "polyscript/exports";
|
||||||
|
|
||||||
|
const bootstrapped = new WeakSet();
|
||||||
|
|
||||||
|
// this callback will be serialized as string and it never needs
|
||||||
|
// to be invoked multiple times. Each xworker here is bootstrapped
|
||||||
|
// only once thanks to the `sync.is_pyterminal()` check.
|
||||||
|
const workerReady = ({ interpreter, io, run, type }, { sync }) => {
|
||||||
|
if (type !== "py" || !sync.is_pyterminal()) return;
|
||||||
|
|
||||||
|
run(
|
||||||
|
[
|
||||||
|
"from polyscript import currentScript as _",
|
||||||
|
"__terminal__ = _.terminal",
|
||||||
|
"del _",
|
||||||
|
].join(";"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let data = "";
|
||||||
|
const { pyterminal_read, pyterminal_write } = sync;
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
const generic = {
|
||||||
|
isatty: false,
|
||||||
|
write(buffer) {
|
||||||
|
data = decoder.decode(buffer);
|
||||||
|
pyterminal_write(data);
|
||||||
|
return buffer.length;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
io.stderr = (error) => {
|
||||||
|
pyterminal_write(String(error.message || error));
|
||||||
|
};
|
||||||
|
|
||||||
|
interpreter.setStdout(generic);
|
||||||
|
interpreter.setStderr(generic);
|
||||||
|
interpreter.setStdin({
|
||||||
|
isatty: false,
|
||||||
|
stdin: () => pyterminal_read(data),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async (element) => {
|
||||||
|
// lazy load these only when a valid terminal is found
|
||||||
|
const [{ Terminal }, { Readline }, { FitAddon }, { WebLinksAddon }] =
|
||||||
|
await Promise.all([
|
||||||
|
import(/* webpackIgnore: true */ "../../3rd-party/xterm.js"),
|
||||||
|
import(
|
||||||
|
/* webpackIgnore: true */ "../../3rd-party/xterm-readline.js"
|
||||||
|
),
|
||||||
|
import(
|
||||||
|
/* webpackIgnore: true */ "../../3rd-party/xterm_addon-fit.js"
|
||||||
|
),
|
||||||
|
import(
|
||||||
|
/* webpackIgnore: true */ "../../3rd-party/xterm_addon-web-links.js"
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const readline = new Readline();
|
||||||
|
|
||||||
|
// common main thread initialization for both worker
|
||||||
|
// or main case, bootstrapping the terminal on its target
|
||||||
|
const init = (options) => {
|
||||||
|
let target = element;
|
||||||
|
const selector = element.getAttribute("target");
|
||||||
|
if (selector) {
|
||||||
|
target =
|
||||||
|
document.getElementById(selector) ||
|
||||||
|
document.querySelector(selector);
|
||||||
|
if (!target) throw new Error(`Unknown target ${selector}`);
|
||||||
|
} else {
|
||||||
|
target = document.createElement("py-terminal");
|
||||||
|
target.style.display = "block";
|
||||||
|
element.after(target);
|
||||||
|
}
|
||||||
|
const terminal = new Terminal({
|
||||||
|
theme: {
|
||||||
|
background: "#191A19",
|
||||||
|
foreground: "#F5F2E7",
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
const fitAddon = new FitAddon();
|
||||||
|
terminal.loadAddon(fitAddon);
|
||||||
|
terminal.loadAddon(readline);
|
||||||
|
terminal.loadAddon(new WebLinksAddon());
|
||||||
|
terminal.open(target);
|
||||||
|
fitAddon.fit();
|
||||||
|
terminal.focus();
|
||||||
|
defineProperties(element, {
|
||||||
|
terminal: { value: terminal },
|
||||||
|
process: {
|
||||||
|
value: async (code) => {
|
||||||
|
for (const line of code.split(/(?:\r\n|\r|\n)/)) {
|
||||||
|
terminal.paste(`${line}`);
|
||||||
|
terminal.write("\r\n");
|
||||||
|
do {
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
setTimeout(resolve, 0),
|
||||||
|
);
|
||||||
|
} while (!readline.activeRead?.resolve);
|
||||||
|
readline.activeRead.resolve(line);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return terminal;
|
||||||
|
};
|
||||||
|
|
||||||
|
// branch logic for the worker
|
||||||
|
if (element.hasAttribute("worker")) {
|
||||||
|
// add a hook on the main thread to setup all sync helpers
|
||||||
|
// also bootstrapping the XTerm target on main *BUT* ...
|
||||||
|
hooks.main.onWorker.add(function worker(_, xworker) {
|
||||||
|
// ... as multiple workers will add multiple callbacks
|
||||||
|
// be sure no xworker is ever initialized twice!
|
||||||
|
if (bootstrapped.has(xworker)) return;
|
||||||
|
bootstrapped.add(xworker);
|
||||||
|
|
||||||
|
// still cleanup this callback for future scripts/workers
|
||||||
|
hooks.main.onWorker.delete(worker);
|
||||||
|
|
||||||
|
init({
|
||||||
|
disableStdin: false,
|
||||||
|
cursorBlink: true,
|
||||||
|
cursorStyle: "block",
|
||||||
|
});
|
||||||
|
|
||||||
|
xworker.sync.is_pyterminal = () => true;
|
||||||
|
xworker.sync.pyterminal_read = readline.read.bind(readline);
|
||||||
|
xworker.sync.pyterminal_write = readline.write.bind(readline);
|
||||||
|
});
|
||||||
|
|
||||||
|
// setup remote thread JS/Python code for whenever the
|
||||||
|
// worker is ready to become a terminal
|
||||||
|
hooks.worker.onReady.add(workerReady);
|
||||||
|
} else {
|
||||||
|
// in the main case, just bootstrap XTerm without
|
||||||
|
// allowing any input as that's not possible / awkward
|
||||||
|
hooks.main.onReady.add(function main({ interpreter, io, run, type }) {
|
||||||
|
if (type !== "py") return;
|
||||||
|
|
||||||
|
console.warn("py-terminal is read only on main thread");
|
||||||
|
hooks.main.onReady.delete(main);
|
||||||
|
|
||||||
|
// on main, it's easy to trash and clean the current terminal
|
||||||
|
globalThis.__py_terminal__ = init({
|
||||||
|
disableStdin: true,
|
||||||
|
cursorBlink: false,
|
||||||
|
cursorStyle: "underline",
|
||||||
|
});
|
||||||
|
run("from js import __py_terminal__ as __terminal__");
|
||||||
|
delete globalThis.__py_terminal__;
|
||||||
|
|
||||||
|
io.stderr = (error) => {
|
||||||
|
readline.write(String(error.message || error));
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = "";
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
const generic = {
|
||||||
|
isatty: false,
|
||||||
|
write(buffer) {
|
||||||
|
data = decoder.decode(buffer);
|
||||||
|
readline.write(data);
|
||||||
|
return buffer.length;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
interpreter.setStdout(generic);
|
||||||
|
interpreter.setStderr(generic);
|
||||||
|
interpreter.setStdin({
|
||||||
|
isatty: false,
|
||||||
|
stdin: () => readline.read(data),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -37,7 +37,7 @@ const python = [
|
|||||||
"_path = None",
|
"_path = None",
|
||||||
];
|
];
|
||||||
|
|
||||||
const ignore = new Ignore(python, "./pyweb");
|
const ignore = new Ignore(python, "-");
|
||||||
|
|
||||||
const write = (base, literal) => {
|
const write = (base, literal) => {
|
||||||
for (const [key, value] of entries(literal)) {
|
for (const [key, value] of entries(literal)) {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
# pyscript.magic_js. This is the blessed way to access them from pyscript,
|
# pyscript.magic_js. This is the blessed way to access them from pyscript,
|
||||||
# as it works transparently in both the main thread and worker cases.
|
# as it works transparently in both the main thread and worker cases.
|
||||||
|
|
||||||
|
from polyscript import lazy_py_modules as py_import
|
||||||
from pyscript.display import HTML, display
|
from pyscript.display import HTML, display
|
||||||
from pyscript.fetch import fetch
|
from pyscript.fetch import fetch
|
||||||
from pyscript.magic_js import (
|
from pyscript.magic_js import (
|
||||||
@@ -37,6 +38,7 @@ from pyscript.magic_js import (
|
|||||||
config,
|
config,
|
||||||
current_target,
|
current_target,
|
||||||
document,
|
document,
|
||||||
|
js_import,
|
||||||
js_modules,
|
js_modules,
|
||||||
sync,
|
sync,
|
||||||
window,
|
window,
|
||||||
|
|||||||
@@ -47,13 +47,14 @@ def when(event_type=None, selector=None):
|
|||||||
wrapper = func
|
wrapper = func
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# TODO: this is currently an quick hack to get micropython working but we need
|
# TODO: this is very ugly hack to get micropython working because inspect.signature
|
||||||
# to actually properly replace inspect.signature with something else
|
# doesn't exist, but we need to actually properly replace inspect.signature.
|
||||||
|
# It may be actually better to not try any magic for now and raise the error
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
try:
|
try:
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
if "takes 0 positional arguments" in str(e):
|
if "takes" in str(e) and "positional arguments" in str(e):
|
||||||
return func()
|
return func()
|
||||||
|
|
||||||
raise
|
raise
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ if RUNNING_IN_WORKER:
|
|||||||
window = polyscript.xworker.window
|
window = polyscript.xworker.window
|
||||||
document = window.document
|
document = window.document
|
||||||
js.document = document
|
js.document = document
|
||||||
|
# this is the same as js_import on main and it lands modules on main
|
||||||
|
js_import = window.Function(
|
||||||
|
"return (...urls) => Promise.all(urls.map((url) => import(url)))"
|
||||||
|
)()
|
||||||
except:
|
except:
|
||||||
globalThis.console.debug("SharedArrayBuffer is not available")
|
globalThis.console.debug("SharedArrayBuffer is not available")
|
||||||
# in this scenario none of the utilities would work
|
# in this scenario none of the utilities would work
|
||||||
@@ -64,7 +68,7 @@ if RUNNING_IN_WORKER:
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
import _pyscript
|
import _pyscript
|
||||||
from _pyscript import PyWorker
|
from _pyscript import PyWorker, js_import
|
||||||
|
|
||||||
window = globalThis
|
window = globalThis
|
||||||
document = globalThis.document
|
document = globalThis.document
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
from .pydom import JSProperty
|
||||||
from .pydom import dom as pydom
|
from .pydom import dom as pydom
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import inspect
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from typing import Any
|
from typing import Any
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -34,6 +36,23 @@ from pyscript import display, document, window
|
|||||||
alert = window.alert
|
alert = window.alert
|
||||||
|
|
||||||
|
|
||||||
|
class JSProperty:
|
||||||
|
"""JS property descriptor that directly maps to the property with the same
|
||||||
|
name in the underlying JS component."""
|
||||||
|
|
||||||
|
def __init__(self, name: str, allow_nones: bool = False):
|
||||||
|
self.name = name
|
||||||
|
self.allow_nones = allow_nones
|
||||||
|
|
||||||
|
def __get__(self, obj, objtype=None):
|
||||||
|
return getattr(obj._js, self.name)
|
||||||
|
|
||||||
|
def __set__(self, obj, value):
|
||||||
|
if not self.allow_nones and value is None:
|
||||||
|
return
|
||||||
|
setattr(obj._js, self.name, value)
|
||||||
|
|
||||||
|
|
||||||
class BaseElement:
|
class BaseElement:
|
||||||
def __init__(self, js_element):
|
def __init__(self, js_element):
|
||||||
self._js = js_element
|
self._js = js_element
|
||||||
@@ -104,7 +123,7 @@ class Element(BaseElement):
|
|||||||
# TODO: this is Pyodide specific for now!!!!!!
|
# TODO: this is Pyodide specific for now!!!!!!
|
||||||
# if we get passed a JSProxy Element directly we just map it to the
|
# if we get passed a JSProxy Element directly we just map it to the
|
||||||
# higher level Python element
|
# higher level Python element
|
||||||
if isinstance(child, JsProxy):
|
if inspect.isclass(JsProxy) and isinstance(child, JsProxy):
|
||||||
return self.append(Element(child))
|
return self.append(Element(child))
|
||||||
|
|
||||||
elif isinstance(child, Element):
|
elif isinstance(child, Element):
|
||||||
@@ -267,7 +286,7 @@ class Element(BaseElement):
|
|||||||
canvas._js.width = width
|
canvas._js.width = width
|
||||||
canvas._js.height = height
|
canvas._js.height = height
|
||||||
|
|
||||||
elif isistance(to, Element):
|
elif isinstance(to, Element):
|
||||||
if to._js.tagName != "CANVAS":
|
if to._js.tagName != "CANVAS":
|
||||||
raise TypeError("Element to snap to must a canvas.")
|
raise TypeError("Element to snap to must a canvas.")
|
||||||
canvas = to
|
canvas = to
|
||||||
|
|||||||
1
pyscript.core/src/stdlib/pyweb/ui/__init__.py
Normal file
1
pyscript.core/src/stdlib/pyweb/ui/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import elements
|
||||||
947
pyscript.core/src/stdlib/pyweb/ui/elements.py
Normal file
947
pyscript.core/src/stdlib/pyweb/ui/elements.py
Normal file
@@ -0,0 +1,947 @@
|
|||||||
|
import inspect
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pyscript import document, when, window
|
||||||
|
from pyweb import JSProperty, pydom
|
||||||
|
|
||||||
|
#: A flag to show if MicroPython is the current Python interpreter.
|
||||||
|
is_micropython = "MicroPython" in sys.version
|
||||||
|
|
||||||
|
|
||||||
|
def getmembers_static(cls):
|
||||||
|
"""Cross-interpreter implementation of inspect.getmembers_static."""
|
||||||
|
|
||||||
|
if is_micropython: # pragma: no cover
|
||||||
|
return [(name, getattr(cls, name)) for name, _ in inspect.getmembers(cls)]
|
||||||
|
|
||||||
|
return inspect.getmembers_static(cls)
|
||||||
|
|
||||||
|
|
||||||
|
class ElementBase(pydom.Element):
|
||||||
|
tag = "div"
|
||||||
|
|
||||||
|
# GLOBAL ATTRIBUTES
|
||||||
|
# These are attribute that all elements have (this list is a subset of the official one)
|
||||||
|
# We are trying to capture the most used ones
|
||||||
|
accesskey = JSProperty("accesskey")
|
||||||
|
autofocus = JSProperty("autofocus")
|
||||||
|
autocapitalize = JSProperty("autocapitalize")
|
||||||
|
className = JSProperty("className")
|
||||||
|
contenteditable = JSProperty("contenteditable")
|
||||||
|
draggable = JSProperty("draggable")
|
||||||
|
enterkeyhint = JSProperty("enterkeyhint")
|
||||||
|
hidden = JSProperty("hidden")
|
||||||
|
id = JSProperty("id")
|
||||||
|
lang = JSProperty("lang")
|
||||||
|
nonce = JSProperty("nonce")
|
||||||
|
part = JSProperty("part")
|
||||||
|
popover = JSProperty("popover")
|
||||||
|
slot = JSProperty("slot")
|
||||||
|
spellcheck = JSProperty("spellcheck")
|
||||||
|
tabindex = JSProperty("tabindex")
|
||||||
|
title = JSProperty("title")
|
||||||
|
translate = JSProperty("translate")
|
||||||
|
virtualkeyboardpolicy = JSProperty("virtualkeyboardpolicy")
|
||||||
|
|
||||||
|
def __init__(self, style=None, **kwargs):
|
||||||
|
super().__init__(document.createElement(self.tag))
|
||||||
|
|
||||||
|
# set all the style properties provided in input
|
||||||
|
if isinstance(style, dict):
|
||||||
|
for key, value in style.items():
|
||||||
|
self.style[key] = value
|
||||||
|
elif style is None:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Style should be a dictionary, received {style} (type {type(style)}) instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
# IMPORTANT!!! This is used to auto-harvest all input arguments and set them as properties
|
||||||
|
self._init_properties(**kwargs)
|
||||||
|
|
||||||
|
def _init_properties(self, **kwargs):
|
||||||
|
"""Set all the properties (of type JSProperties) provided in input as properties
|
||||||
|
of the class instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs: The properties to set
|
||||||
|
"""
|
||||||
|
# Look at all the properties of the class and see if they were provided in kwargs
|
||||||
|
for attr_name, attr in getmembers_static(self.__class__):
|
||||||
|
# For each one, actually check if it is a property of the class and set it
|
||||||
|
if isinstance(attr, JSProperty) and attr_name in kwargs:
|
||||||
|
try:
|
||||||
|
setattr(self, attr_name, kwargs[attr_name])
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error setting {attr_name} to {kwargs[attr_name]}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class TextElementBase(ElementBase):
|
||||||
|
def __init__(self, content=None, style=None, **kwargs):
|
||||||
|
super().__init__(style=style, **kwargs)
|
||||||
|
|
||||||
|
# If it's an element, append the element
|
||||||
|
if isinstance(content, pydom.Element):
|
||||||
|
self.append(content)
|
||||||
|
# If it's a list of elements
|
||||||
|
elif isinstance(content, list):
|
||||||
|
for item in content:
|
||||||
|
self.append(item)
|
||||||
|
# If the content wasn't set just ignore
|
||||||
|
elif content is None:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Otherwise, set content as the html of the element
|
||||||
|
self.html = content
|
||||||
|
|
||||||
|
|
||||||
|
# IMPORTANT: For all HTML components defined below, we are not mapping all
|
||||||
|
# available attributes, just the global and the most common ones.
|
||||||
|
# If you need to access a specific attribute, you can always use the `_js.<attribute>`
|
||||||
|
class a(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a"""
|
||||||
|
|
||||||
|
tag = "a"
|
||||||
|
|
||||||
|
download = JSProperty("download")
|
||||||
|
href = JSProperty("href")
|
||||||
|
referrerpolicy = JSProperty("referrerpolicy")
|
||||||
|
rel = JSProperty("rel")
|
||||||
|
target = JSProperty("target")
|
||||||
|
type = JSProperty("type")
|
||||||
|
|
||||||
|
|
||||||
|
class abbr(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr"""
|
||||||
|
|
||||||
|
tag = "abbr"
|
||||||
|
|
||||||
|
|
||||||
|
class address(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address"""
|
||||||
|
|
||||||
|
tag = "address"
|
||||||
|
|
||||||
|
|
||||||
|
class area(ElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area"""
|
||||||
|
|
||||||
|
tag = "area"
|
||||||
|
|
||||||
|
alt = JSProperty("alt")
|
||||||
|
coords = JSProperty("coords")
|
||||||
|
download = JSProperty("download")
|
||||||
|
href = JSProperty("href")
|
||||||
|
ping = JSProperty("ping")
|
||||||
|
referrerpolicy = JSProperty("referrerpolicy")
|
||||||
|
rel = JSProperty("rel")
|
||||||
|
shape = JSProperty("shape")
|
||||||
|
target = JSProperty("target")
|
||||||
|
|
||||||
|
|
||||||
|
class article(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article"""
|
||||||
|
|
||||||
|
tag = "article"
|
||||||
|
|
||||||
|
|
||||||
|
class aside(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside"""
|
||||||
|
|
||||||
|
tag = "aside"
|
||||||
|
|
||||||
|
|
||||||
|
class audio(ElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio"""
|
||||||
|
|
||||||
|
tag = "audio"
|
||||||
|
|
||||||
|
autoplay = JSProperty("autoplay")
|
||||||
|
controls = JSProperty("controls")
|
||||||
|
controlslist = JSProperty("controlslist")
|
||||||
|
crossorigin = JSProperty("crossorigin")
|
||||||
|
disableremoteplayback = JSProperty("disableremoteplayback")
|
||||||
|
loop = JSProperty("loop")
|
||||||
|
muted = JSProperty("muted")
|
||||||
|
preload = JSProperty("preload")
|
||||||
|
src = JSProperty("src")
|
||||||
|
|
||||||
|
|
||||||
|
class b(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b"""
|
||||||
|
|
||||||
|
tag = "b"
|
||||||
|
|
||||||
|
|
||||||
|
class blockquote(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"""
|
||||||
|
|
||||||
|
tag = "blockquote"
|
||||||
|
|
||||||
|
cite = JSProperty("cite")
|
||||||
|
|
||||||
|
|
||||||
|
class br(ElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br"""
|
||||||
|
|
||||||
|
tag = "br"
|
||||||
|
|
||||||
|
|
||||||
|
class button(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button"""
|
||||||
|
|
||||||
|
tag = "button"
|
||||||
|
|
||||||
|
autofocus = JSProperty("autofocus")
|
||||||
|
disabled = JSProperty("disabled")
|
||||||
|
form = JSProperty("form")
|
||||||
|
formaction = JSProperty("formaction")
|
||||||
|
formenctype = JSProperty("formenctype")
|
||||||
|
formmethod = JSProperty("formmethod")
|
||||||
|
formnovalidate = JSProperty("formnovalidate")
|
||||||
|
formtarget = JSProperty("formtarget")
|
||||||
|
name = JSProperty("name")
|
||||||
|
type = JSProperty("type")
|
||||||
|
value = JSProperty("value")
|
||||||
|
|
||||||
|
|
||||||
|
class canvas(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas"""
|
||||||
|
|
||||||
|
tag = "canvas"
|
||||||
|
|
||||||
|
height = JSProperty("height")
|
||||||
|
width = JSProperty("width")
|
||||||
|
|
||||||
|
|
||||||
|
class caption(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption"""
|
||||||
|
|
||||||
|
tag = "caption"
|
||||||
|
|
||||||
|
|
||||||
|
class cite(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite"""
|
||||||
|
|
||||||
|
tag = "cite"
|
||||||
|
|
||||||
|
|
||||||
|
class code(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code"""
|
||||||
|
|
||||||
|
tag = "code"
|
||||||
|
|
||||||
|
|
||||||
|
class data(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data"""
|
||||||
|
|
||||||
|
tag = "data"
|
||||||
|
|
||||||
|
value = JSProperty("value")
|
||||||
|
|
||||||
|
|
||||||
|
class datalist(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist"""
|
||||||
|
|
||||||
|
tag = "datalist"
|
||||||
|
|
||||||
|
|
||||||
|
class dd(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd"""
|
||||||
|
|
||||||
|
tag = "dd"
|
||||||
|
|
||||||
|
|
||||||
|
class del_(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del"""
|
||||||
|
|
||||||
|
tag = "del"
|
||||||
|
|
||||||
|
cite = JSProperty("cite")
|
||||||
|
datetime = JSProperty("datetime")
|
||||||
|
|
||||||
|
|
||||||
|
class details(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details"""
|
||||||
|
|
||||||
|
tag = "details"
|
||||||
|
|
||||||
|
open = JSProperty("open")
|
||||||
|
|
||||||
|
|
||||||
|
class dialog(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"""
|
||||||
|
|
||||||
|
tag = "dialog"
|
||||||
|
|
||||||
|
open = JSProperty("open")
|
||||||
|
|
||||||
|
|
||||||
|
class div(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div"""
|
||||||
|
|
||||||
|
tag = "div"
|
||||||
|
|
||||||
|
|
||||||
|
class dl(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl"""
|
||||||
|
|
||||||
|
tag = "dl"
|
||||||
|
|
||||||
|
value = JSProperty("value")
|
||||||
|
|
||||||
|
|
||||||
|
class dt(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt"""
|
||||||
|
|
||||||
|
tag = "dt"
|
||||||
|
|
||||||
|
|
||||||
|
class em(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em"""
|
||||||
|
|
||||||
|
tag = "em"
|
||||||
|
|
||||||
|
|
||||||
|
class embed(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed"""
|
||||||
|
|
||||||
|
tag = "embed"
|
||||||
|
|
||||||
|
height = JSProperty("height")
|
||||||
|
src = JSProperty("src")
|
||||||
|
type = JSProperty("type")
|
||||||
|
width = JSProperty("width")
|
||||||
|
|
||||||
|
|
||||||
|
class fieldset(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset"""
|
||||||
|
|
||||||
|
tag = "fieldset"
|
||||||
|
|
||||||
|
disabled = JSProperty("disabled")
|
||||||
|
form = JSProperty("form")
|
||||||
|
name = JSProperty("name")
|
||||||
|
|
||||||
|
|
||||||
|
class figcaption(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption"""
|
||||||
|
|
||||||
|
tag = "figcaption"
|
||||||
|
|
||||||
|
|
||||||
|
class figure(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure"""
|
||||||
|
|
||||||
|
tag = "figure"
|
||||||
|
|
||||||
|
|
||||||
|
class footer(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer"""
|
||||||
|
|
||||||
|
tag = "footer"
|
||||||
|
|
||||||
|
|
||||||
|
class form(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form"""
|
||||||
|
|
||||||
|
tag = "form"
|
||||||
|
|
||||||
|
accept_charset = JSProperty("accept-charset")
|
||||||
|
action = JSProperty("action")
|
||||||
|
autocapitalize = JSProperty("autocapitalize")
|
||||||
|
autocomplete = JSProperty("autocomplete")
|
||||||
|
enctype = JSProperty("enctype")
|
||||||
|
name = JSProperty("name")
|
||||||
|
method = JSProperty("method")
|
||||||
|
nonvalidate = JSProperty("nonvalidate")
|
||||||
|
rel = JSProperty("rel")
|
||||||
|
target = JSProperty("target")
|
||||||
|
|
||||||
|
|
||||||
|
class h1(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1"""
|
||||||
|
|
||||||
|
tag = "h1"
|
||||||
|
|
||||||
|
|
||||||
|
class h2(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2"""
|
||||||
|
|
||||||
|
tag = "h2"
|
||||||
|
|
||||||
|
|
||||||
|
class h3(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3"""
|
||||||
|
|
||||||
|
tag = "h3"
|
||||||
|
|
||||||
|
|
||||||
|
class h4(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4"""
|
||||||
|
|
||||||
|
tag = "h4"
|
||||||
|
|
||||||
|
|
||||||
|
class h5(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5"""
|
||||||
|
|
||||||
|
tag = "h5"
|
||||||
|
|
||||||
|
|
||||||
|
class h6(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6"""
|
||||||
|
|
||||||
|
tag = "h6"
|
||||||
|
|
||||||
|
|
||||||
|
class header(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header"""
|
||||||
|
|
||||||
|
tag = "header"
|
||||||
|
|
||||||
|
|
||||||
|
class hgroup(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup"""
|
||||||
|
|
||||||
|
tag = "hgroup"
|
||||||
|
|
||||||
|
|
||||||
|
class hr(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr"""
|
||||||
|
|
||||||
|
tag = "hr"
|
||||||
|
|
||||||
|
|
||||||
|
class i(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i"""
|
||||||
|
|
||||||
|
tag = "i"
|
||||||
|
|
||||||
|
|
||||||
|
class iframe(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe"""
|
||||||
|
|
||||||
|
tag = "iframe"
|
||||||
|
|
||||||
|
allow = JSProperty("allow")
|
||||||
|
allowfullscreen = JSProperty("allowfullscreen")
|
||||||
|
height = JSProperty("height")
|
||||||
|
loading = JSProperty("loading")
|
||||||
|
name = JSProperty("name")
|
||||||
|
referrerpolicy = JSProperty("referrerpolicy")
|
||||||
|
sandbox = JSProperty("sandbox")
|
||||||
|
src = JSProperty("src")
|
||||||
|
srcdoc = JSProperty("srcdoc")
|
||||||
|
width = JSProperty("width")
|
||||||
|
|
||||||
|
|
||||||
|
class img(ElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img"""
|
||||||
|
|
||||||
|
tag = "img"
|
||||||
|
|
||||||
|
alt = JSProperty("alt")
|
||||||
|
crossorigin = JSProperty("crossorigin")
|
||||||
|
decoding = JSProperty("decoding")
|
||||||
|
fetchpriority = JSProperty("fetchpriority")
|
||||||
|
height = JSProperty("height")
|
||||||
|
ismap = JSProperty("ismap")
|
||||||
|
loading = JSProperty("loading")
|
||||||
|
referrerpolicy = JSProperty("referrerpolicy")
|
||||||
|
sizes = JSProperty("sizes")
|
||||||
|
src = JSProperty("src")
|
||||||
|
width = JSProperty("width")
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: Input is a reserved keyword in Python, so we use input_ instead
|
||||||
|
class input_(ElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input"""
|
||||||
|
|
||||||
|
tag = "input"
|
||||||
|
|
||||||
|
accept = JSProperty("accept")
|
||||||
|
alt = JSProperty("alt")
|
||||||
|
autofocus = JSProperty("autofocus")
|
||||||
|
capture = JSProperty("capture")
|
||||||
|
checked = JSProperty("checked")
|
||||||
|
dirname = JSProperty("dirname")
|
||||||
|
disabled = JSProperty("disabled")
|
||||||
|
form = JSProperty("form")
|
||||||
|
formaction = JSProperty("formaction")
|
||||||
|
formenctype = JSProperty("formenctype")
|
||||||
|
formmethod = JSProperty("formmethod")
|
||||||
|
formnovalidate = JSProperty("formnovalidate")
|
||||||
|
formtarget = JSProperty("formtarget")
|
||||||
|
height = JSProperty("height")
|
||||||
|
list = JSProperty("list")
|
||||||
|
max = JSProperty("max")
|
||||||
|
maxlength = JSProperty("maxlength")
|
||||||
|
min = JSProperty("min")
|
||||||
|
minlength = JSProperty("minlength")
|
||||||
|
multiple = JSProperty("multiple")
|
||||||
|
name = JSProperty("name")
|
||||||
|
pattern = JSProperty("pattern")
|
||||||
|
placeholder = JSProperty("placeholder")
|
||||||
|
popovertarget = JSProperty("popovertarget")
|
||||||
|
popovertargetaction = JSProperty("popovertargetaction")
|
||||||
|
readonly = JSProperty("readonly")
|
||||||
|
required = JSProperty("required")
|
||||||
|
size = JSProperty("size")
|
||||||
|
src = JSProperty("src")
|
||||||
|
step = JSProperty("step")
|
||||||
|
type = JSProperty("type")
|
||||||
|
value = JSProperty("value")
|
||||||
|
width = JSProperty("width")
|
||||||
|
|
||||||
|
|
||||||
|
class ins(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins"""
|
||||||
|
|
||||||
|
tag = "ins"
|
||||||
|
|
||||||
|
cite = JSProperty("cite")
|
||||||
|
datetime = JSProperty("datetime")
|
||||||
|
|
||||||
|
|
||||||
|
class kbd(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd"""
|
||||||
|
|
||||||
|
tag = "kbd"
|
||||||
|
|
||||||
|
|
||||||
|
class label(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label"""
|
||||||
|
|
||||||
|
tag = "label"
|
||||||
|
|
||||||
|
for_ = JSProperty("for")
|
||||||
|
|
||||||
|
|
||||||
|
class legend(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend"""
|
||||||
|
|
||||||
|
tag = "legend"
|
||||||
|
|
||||||
|
|
||||||
|
class li(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li"""
|
||||||
|
|
||||||
|
tag = "li"
|
||||||
|
|
||||||
|
value = JSProperty("value")
|
||||||
|
|
||||||
|
|
||||||
|
class link(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link"""
|
||||||
|
|
||||||
|
tag = "link"
|
||||||
|
|
||||||
|
as_ = JSProperty("as")
|
||||||
|
crossorigin = JSProperty("crossorigin")
|
||||||
|
disabled = JSProperty("disabled")
|
||||||
|
fetchpriority = JSProperty("fetchpriority")
|
||||||
|
href = JSProperty("href")
|
||||||
|
imagesizes = JSProperty("imagesizes")
|
||||||
|
imagesrcset = JSProperty("imagesrcset")
|
||||||
|
integrity = JSProperty("integrity")
|
||||||
|
media = JSProperty("media")
|
||||||
|
rel = JSProperty("rel")
|
||||||
|
referrerpolicy = JSProperty("referrerpolicy")
|
||||||
|
sizes = JSProperty("sizes")
|
||||||
|
title = JSProperty("title")
|
||||||
|
type = JSProperty("type")
|
||||||
|
|
||||||
|
|
||||||
|
class main(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main"""
|
||||||
|
|
||||||
|
tag = "main"
|
||||||
|
|
||||||
|
|
||||||
|
class map_(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map"""
|
||||||
|
|
||||||
|
tag = "map"
|
||||||
|
|
||||||
|
name = JSProperty("name")
|
||||||
|
|
||||||
|
|
||||||
|
class mark(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark"""
|
||||||
|
|
||||||
|
tag = "mark"
|
||||||
|
|
||||||
|
|
||||||
|
class menu(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu"""
|
||||||
|
|
||||||
|
tag = "menu"
|
||||||
|
|
||||||
|
|
||||||
|
class meter(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter"""
|
||||||
|
|
||||||
|
tag = "meter"
|
||||||
|
|
||||||
|
form = JSProperty("form")
|
||||||
|
high = JSProperty("high")
|
||||||
|
low = JSProperty("low")
|
||||||
|
max = JSProperty("max")
|
||||||
|
min = JSProperty("min")
|
||||||
|
optimum = JSProperty("optimum")
|
||||||
|
value = JSProperty("value")
|
||||||
|
|
||||||
|
|
||||||
|
class nav(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav"""
|
||||||
|
|
||||||
|
tag = "nav"
|
||||||
|
|
||||||
|
|
||||||
|
class object_(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object"""
|
||||||
|
|
||||||
|
tag = "object"
|
||||||
|
|
||||||
|
data = JSProperty("data")
|
||||||
|
form = JSProperty("form")
|
||||||
|
height = JSProperty("height")
|
||||||
|
name = JSProperty("name")
|
||||||
|
type = JSProperty("type")
|
||||||
|
usemap = JSProperty("usemap")
|
||||||
|
width = JSProperty("width")
|
||||||
|
|
||||||
|
|
||||||
|
class ol(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol"""
|
||||||
|
|
||||||
|
tag = "ol"
|
||||||
|
|
||||||
|
reversed = JSProperty("reversed")
|
||||||
|
start = JSProperty("start")
|
||||||
|
type = JSProperty("type")
|
||||||
|
|
||||||
|
|
||||||
|
class optgroup(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup"""
|
||||||
|
|
||||||
|
tag = "optgroup"
|
||||||
|
|
||||||
|
disabled = JSProperty("disabled")
|
||||||
|
label = JSProperty("label")
|
||||||
|
|
||||||
|
|
||||||
|
class option(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option"""
|
||||||
|
|
||||||
|
tag = "option"
|
||||||
|
|
||||||
|
disabled = JSProperty("value")
|
||||||
|
label = JSProperty("label")
|
||||||
|
selected = JSProperty("selected")
|
||||||
|
value = JSProperty("value")
|
||||||
|
|
||||||
|
|
||||||
|
class output(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output"""
|
||||||
|
|
||||||
|
tag = "output"
|
||||||
|
|
||||||
|
for_ = JSProperty("for")
|
||||||
|
form = JSProperty("form")
|
||||||
|
name = JSProperty("name")
|
||||||
|
|
||||||
|
|
||||||
|
class p(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p"""
|
||||||
|
|
||||||
|
tag = "p"
|
||||||
|
|
||||||
|
|
||||||
|
class picture(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture"""
|
||||||
|
|
||||||
|
tag = "picture"
|
||||||
|
|
||||||
|
|
||||||
|
class pre(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre"""
|
||||||
|
|
||||||
|
tag = "pre"
|
||||||
|
|
||||||
|
|
||||||
|
class progress(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress"""
|
||||||
|
|
||||||
|
tag = "progress"
|
||||||
|
|
||||||
|
max = JSProperty("max")
|
||||||
|
value = JSProperty("value")
|
||||||
|
|
||||||
|
|
||||||
|
class q(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q"""
|
||||||
|
|
||||||
|
tag = "q"
|
||||||
|
|
||||||
|
cite = JSProperty("cite")
|
||||||
|
|
||||||
|
|
||||||
|
class s(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s"""
|
||||||
|
|
||||||
|
tag = "s"
|
||||||
|
|
||||||
|
|
||||||
|
class script(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script"""
|
||||||
|
|
||||||
|
tag = "script"
|
||||||
|
|
||||||
|
# Let's add async manually since it's a reserved keyword in Python
|
||||||
|
async_ = JSProperty("async")
|
||||||
|
blocking = JSProperty("blocking")
|
||||||
|
crossorigin = JSProperty("crossorigin")
|
||||||
|
defer = JSProperty("defer")
|
||||||
|
fetchpriority = JSProperty("fetchpriority")
|
||||||
|
integrity = JSProperty("integrity")
|
||||||
|
nomodule = JSProperty("nomodule")
|
||||||
|
nonce = JSProperty("nonce")
|
||||||
|
referrerpolicy = JSProperty("referrerpolicy")
|
||||||
|
src = JSProperty("src")
|
||||||
|
type = JSProperty("type")
|
||||||
|
|
||||||
|
|
||||||
|
class section(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section"""
|
||||||
|
|
||||||
|
tag = "section"
|
||||||
|
|
||||||
|
|
||||||
|
class select(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select"""
|
||||||
|
|
||||||
|
tag = "select"
|
||||||
|
|
||||||
|
|
||||||
|
class small(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small"""
|
||||||
|
|
||||||
|
tag = "small"
|
||||||
|
|
||||||
|
|
||||||
|
class source(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source"""
|
||||||
|
|
||||||
|
tag = "source"
|
||||||
|
|
||||||
|
media = JSProperty("media")
|
||||||
|
sizes = JSProperty("sizes")
|
||||||
|
src = JSProperty("src")
|
||||||
|
srcset = JSProperty("srcset")
|
||||||
|
type = JSProperty("type")
|
||||||
|
|
||||||
|
|
||||||
|
class span(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span"""
|
||||||
|
|
||||||
|
tag = "span"
|
||||||
|
|
||||||
|
|
||||||
|
class strong(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong"""
|
||||||
|
|
||||||
|
tag = "strong"
|
||||||
|
|
||||||
|
|
||||||
|
class style(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style"""
|
||||||
|
|
||||||
|
tag = "style"
|
||||||
|
|
||||||
|
blocking = JSProperty("blocking")
|
||||||
|
media = JSProperty("media")
|
||||||
|
nonce = JSProperty("nonce")
|
||||||
|
title = JSProperty("title")
|
||||||
|
|
||||||
|
|
||||||
|
class sub(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub"""
|
||||||
|
|
||||||
|
tag = "sub"
|
||||||
|
|
||||||
|
|
||||||
|
class summary(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary"""
|
||||||
|
|
||||||
|
tag = "summary"
|
||||||
|
|
||||||
|
|
||||||
|
class sup(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup"""
|
||||||
|
|
||||||
|
tag = "sup"
|
||||||
|
|
||||||
|
|
||||||
|
class table(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table"""
|
||||||
|
|
||||||
|
tag = "table"
|
||||||
|
|
||||||
|
|
||||||
|
class tbody(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody"""
|
||||||
|
|
||||||
|
tag = "tbody"
|
||||||
|
|
||||||
|
|
||||||
|
class td(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td"""
|
||||||
|
|
||||||
|
tag = "td"
|
||||||
|
|
||||||
|
colspan = JSProperty("colspan")
|
||||||
|
headers = JSProperty("headers")
|
||||||
|
rowspan = JSProperty("rowspan")
|
||||||
|
|
||||||
|
|
||||||
|
class template(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template"""
|
||||||
|
|
||||||
|
tag = "template"
|
||||||
|
|
||||||
|
shadowrootmode = JSProperty("shadowrootmode")
|
||||||
|
|
||||||
|
|
||||||
|
class textarea(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea"""
|
||||||
|
|
||||||
|
tag = "textarea"
|
||||||
|
|
||||||
|
autocapitalize = JSProperty("autocapitalize")
|
||||||
|
autocomplete = JSProperty("autocomplete")
|
||||||
|
autofocus = JSProperty("autofocus")
|
||||||
|
cols = JSProperty("cols")
|
||||||
|
dirname = JSProperty("dirname")
|
||||||
|
disabled = JSProperty("disabled")
|
||||||
|
form = JSProperty("form")
|
||||||
|
maxlength = JSProperty("maxlength")
|
||||||
|
minlength = JSProperty("minlength")
|
||||||
|
name = JSProperty("name")
|
||||||
|
placeholder = JSProperty("placeholder")
|
||||||
|
readonly = JSProperty("readonly")
|
||||||
|
required = JSProperty("required")
|
||||||
|
rows = JSProperty("rows")
|
||||||
|
spellcheck = JSProperty("spellcheck")
|
||||||
|
wrap = JSProperty("wrap")
|
||||||
|
|
||||||
|
|
||||||
|
class tfoot(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot"""
|
||||||
|
|
||||||
|
tag = "tfoot"
|
||||||
|
|
||||||
|
|
||||||
|
class th(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th"""
|
||||||
|
|
||||||
|
tag = "th"
|
||||||
|
|
||||||
|
|
||||||
|
class thead(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead"""
|
||||||
|
|
||||||
|
tag = "thead"
|
||||||
|
|
||||||
|
|
||||||
|
class time(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time"""
|
||||||
|
|
||||||
|
tag = "time"
|
||||||
|
|
||||||
|
datetime = JSProperty("datetime")
|
||||||
|
|
||||||
|
|
||||||
|
class title(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title"""
|
||||||
|
|
||||||
|
tag = "title"
|
||||||
|
|
||||||
|
|
||||||
|
class tr(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr"""
|
||||||
|
|
||||||
|
tag = "tr"
|
||||||
|
|
||||||
|
abbr = JSProperty("abbr")
|
||||||
|
colspan = JSProperty("colspan")
|
||||||
|
headers = JSProperty("headers")
|
||||||
|
rowspan = JSProperty("rowspan")
|
||||||
|
scope = JSProperty("scope")
|
||||||
|
|
||||||
|
|
||||||
|
class track(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track"""
|
||||||
|
|
||||||
|
tag = "track"
|
||||||
|
|
||||||
|
default = JSProperty("default")
|
||||||
|
kind = JSProperty("kind")
|
||||||
|
label = JSProperty("label")
|
||||||
|
src = JSProperty("src")
|
||||||
|
srclang = JSProperty("srclang")
|
||||||
|
|
||||||
|
|
||||||
|
class u(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u"""
|
||||||
|
|
||||||
|
tag = "u"
|
||||||
|
|
||||||
|
|
||||||
|
class ul(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul"""
|
||||||
|
|
||||||
|
tag = "ul"
|
||||||
|
|
||||||
|
|
||||||
|
class var(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var"""
|
||||||
|
|
||||||
|
tag = "var"
|
||||||
|
|
||||||
|
|
||||||
|
class video(TextElementBase):
|
||||||
|
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video"""
|
||||||
|
|
||||||
|
tag = "video"
|
||||||
|
|
||||||
|
autoplay = JSProperty("autoplay")
|
||||||
|
controls = JSProperty("controls")
|
||||||
|
crossorigin = JSProperty("crossorigin")
|
||||||
|
disablepictureinpicture = JSProperty("disablepictureinpicture")
|
||||||
|
disableremoteplayback = JSProperty("disableremoteplayback")
|
||||||
|
height = JSProperty("height")
|
||||||
|
loop = JSProperty("loop")
|
||||||
|
muted = JSProperty("muted")
|
||||||
|
playsinline = JSProperty("playsinline")
|
||||||
|
poster = JSProperty("poster")
|
||||||
|
preload = JSProperty("preload")
|
||||||
|
src = JSProperty("src")
|
||||||
|
width = JSProperty("width")
|
||||||
|
|
||||||
|
|
||||||
|
# Custom Elements
|
||||||
|
class grid(TextElementBase):
|
||||||
|
tag = "div"
|
||||||
|
|
||||||
|
def __init__(self, layout, content=None, gap=None, **kwargs):
|
||||||
|
super().__init__(content, **kwargs)
|
||||||
|
self.style["display"] = "grid"
|
||||||
|
self.style["grid-template-columns"] = layout
|
||||||
|
|
||||||
|
# TODO: This should be a property
|
||||||
|
if not gap is None:
|
||||||
|
self.style["gap"] = gap
|
||||||
@@ -4,7 +4,21 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="../../dist/core.css">
|
<link rel="stylesheet" href="../../dist/core.css">
|
||||||
<script type="module" src="../../dist/core.js"></script>
|
<script type="module">
|
||||||
|
import '../../dist/core.js';
|
||||||
|
|
||||||
|
addEventListener('mpy-editor', async ({ target }) => {
|
||||||
|
if (target.hasAttribute('setup')) {
|
||||||
|
await target.process([
|
||||||
|
'from pyscript import document',
|
||||||
|
// adds class="a-1" to the <html> element
|
||||||
|
'document.documentElement.classList.add(f"a-{a}")',
|
||||||
|
'from js import console',
|
||||||
|
'console.log("Hello JS")',
|
||||||
|
].join('\n'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- a setup node with a config for an env -->
|
<!-- a setup node with a config for an env -->
|
||||||
|
|||||||
17
pyscript.core/test/py-editor/issue-2056.html
Normal file
17
pyscript.core/test/py-editor/issue-2056.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="../../dist/core.css">
|
||||||
|
<script type="module" src="../../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py-editor">
|
||||||
|
print("Hello!")
|
||||||
|
</script>
|
||||||
|
<script type="mpy-editor">
|
||||||
|
print("Hello!")
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -10,6 +10,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script type="py" src="terminal.py" worker terminal></script>
|
<script type="py" src="terminal.py" worker terminal></script>
|
||||||
<script type="py" src="terminal.py" worker terminal></script>
|
<script type="mpy" src="terminal.py" worker terminal></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
27
pyscript.core/test/py_modules.html
Normal file
27
pyscript.core/test/py_modules.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py" async>
|
||||||
|
from pyscript import py_import, js_import, window
|
||||||
|
|
||||||
|
window.console.time("first")
|
||||||
|
matplotlib, regex, = await py_import("matplotlib", "regex")
|
||||||
|
window.console.timeEnd("first")
|
||||||
|
|
||||||
|
window.console.time("second")
|
||||||
|
matplotlib, regex, = await py_import("matplotlib", "regex")
|
||||||
|
window.console.timeEnd("second")
|
||||||
|
|
||||||
|
print(matplotlib, regex)
|
||||||
|
|
||||||
|
escaper, = await js_import("https://esm.run/html-escaper")
|
||||||
|
window.console.log(escaper)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<script type="module" src="../dist/core.js"></script>
|
<script type="module" src="../dist/core.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script type="py" src="pydom.py"></script>
|
<script type="mpy" src="pydom.py"></script>
|
||||||
|
|
||||||
<button id="just-a-button">Click For Time</button>
|
<button id="just-a-button">Click For Time</button>
|
||||||
<button id="color-button">Click For Color</button>
|
<button id="color-button">Click For Color</button>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import random
|
import random
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
|
|
||||||
from pyscript import display, when
|
from pyscript import display, when
|
||||||
from pyweb import pydom
|
from pyweb import pydom
|
||||||
|
|
||||||
|
display(sys.version, target="system-info")
|
||||||
|
|
||||||
|
|
||||||
@when("click", "#just-a-button")
|
@when("click", "#just-a-button")
|
||||||
def on_click():
|
def on_click():
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
<body>
|
<body>
|
||||||
<script type="mpy" src="pydom.py"></script>
|
<script type="mpy" src="pydom.py"></script>
|
||||||
|
|
||||||
|
<div id="system-info"></div>
|
||||||
|
|
||||||
<button id="just-a-button">Click For Time</button>
|
<button id="just-a-button">Click For Time</button>
|
||||||
<button id="color-button">Click For Color</button>
|
<button id="color-button">Click For Color</button>
|
||||||
<button id="color-reset-button">Reset Color</button>
|
<button id="color-reset-button">Reset Color</button>
|
||||||
|
|||||||
251
pyscript.core/test/ui/demo.py
Normal file
251
pyscript.core/test/ui/demo.py
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
try:
|
||||||
|
from textwrap import dedent
|
||||||
|
except ImportError:
|
||||||
|
dedent = lambda x: x
|
||||||
|
|
||||||
|
import examples
|
||||||
|
import shoelace
|
||||||
|
import styles
|
||||||
|
from markdown import markdown
|
||||||
|
from pyscript import when, window
|
||||||
|
from pyweb import pydom
|
||||||
|
from pyweb.ui import elements as el
|
||||||
|
from pyweb.ui.elements import a, button, div, grid, h1, h2, h3
|
||||||
|
|
||||||
|
MAIN_PAGE_MARKDOWN = dedent(
|
||||||
|
"""
|
||||||
|
## What is pyweb.ui?
|
||||||
|
Pyweb UI is a totally immagnary exercise atm but..... imagine it is a Python library that allows you to create
|
||||||
|
web applications using Python only.
|
||||||
|
|
||||||
|
It is based on base HTML/JS components but is extensible, for instance, it can have a [Shoelace](https://shoelace.style/) backend...
|
||||||
|
|
||||||
|
PyWeb is a Python library that allows you to create web applications using Python only.
|
||||||
|
|
||||||
|
## What can I do with Pyweb.ui?
|
||||||
|
|
||||||
|
You can create web applications using Python only.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# First thing we do is to load all the external resources we need
|
||||||
|
shoelace.load_resources()
|
||||||
|
|
||||||
|
|
||||||
|
# Let's define some convenience functions first
|
||||||
|
def create_component_details(component_label, component):
|
||||||
|
"""Create a component details card.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component (str): The name of the component to create.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
the component created
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get the example from the examples catalog
|
||||||
|
example = component["instance"]
|
||||||
|
details = (
|
||||||
|
getattr(example, "__doc__", "")
|
||||||
|
or f"Details missing for component {component_label}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return div(
|
||||||
|
[
|
||||||
|
# Title and description (description is picked from the class docstring)
|
||||||
|
h1(component_label),
|
||||||
|
markdown(details),
|
||||||
|
# Example section
|
||||||
|
h2("Example:"),
|
||||||
|
create_component_example(component["instance"], component["code"]),
|
||||||
|
],
|
||||||
|
style={"margin": "20px"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_component_section(component_label, component, parent_div):
|
||||||
|
"""Create a link to a component and add it to the left panel.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component (str): The name of the component to add.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
the component created
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Create the component link element
|
||||||
|
div_ = div(
|
||||||
|
a(component_label, href="#"),
|
||||||
|
style={"display": "block", "text-align": "center", "margin": "auto"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a handler that opens the component details when the link is clicked
|
||||||
|
@when("click", div_)
|
||||||
|
def _change():
|
||||||
|
new_main = create_component_details(component_label, component)
|
||||||
|
main_area.html = ""
|
||||||
|
main_area.append(new_main)
|
||||||
|
|
||||||
|
# Add the new link element to the parent div (left panel)
|
||||||
|
parent_div.append(div_)
|
||||||
|
return div_
|
||||||
|
|
||||||
|
|
||||||
|
def create_component_example(widget, code):
|
||||||
|
"""Create a grid div with the widget on the left side and the relate code
|
||||||
|
on the right side.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
widget (ElementBase): The widget to add to the grid.
|
||||||
|
code (str): The code to add to the grid.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
the grid created
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Create the grid that splits the window in two columns (25% and 75%)
|
||||||
|
grid_ = grid("29% 2% 74%")
|
||||||
|
|
||||||
|
# Add the widget
|
||||||
|
grid_.append(div(widget, style=styles.STYLE_EXAMPLE_INSTANCE))
|
||||||
|
|
||||||
|
# Add the code div
|
||||||
|
widget_code = markdown(dedent(f"""```python\n{code}\n```"""))
|
||||||
|
grid_.append(shoelace.Divider(vertical=True))
|
||||||
|
grid_.append(div(widget_code, style=styles.STYLE_CODE_BLOCK))
|
||||||
|
|
||||||
|
return grid_
|
||||||
|
|
||||||
|
|
||||||
|
def create_main_area():
|
||||||
|
"""Create the main area of the right side of page, with the description of the
|
||||||
|
demo itself and how to use it.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
the main area
|
||||||
|
|
||||||
|
"""
|
||||||
|
div_ = div(
|
||||||
|
[
|
||||||
|
h1("Welcome to PyWeb UI!", style={"text-align": "center"}),
|
||||||
|
markdown(MAIN_PAGE_MARKDOWN),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
main = el.main(
|
||||||
|
style={
|
||||||
|
"padding-top": "4rem",
|
||||||
|
"padding-bottom": "7rem",
|
||||||
|
"max-width": "52rem",
|
||||||
|
"margin-left": "auto",
|
||||||
|
"margin-right": "auto",
|
||||||
|
"padding-left": "1.5rem",
|
||||||
|
"padding-right": "1.5rem",
|
||||||
|
"width": "100%",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
main.append(div_)
|
||||||
|
|
||||||
|
return main
|
||||||
|
|
||||||
|
|
||||||
|
def create_basic_components_page(label, kit_name):
|
||||||
|
"""Create the basic components page.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
the main area
|
||||||
|
|
||||||
|
"""
|
||||||
|
div_ = div(h2(label))
|
||||||
|
|
||||||
|
for component_label, component in examples.kits[kit_name].items():
|
||||||
|
div_.append(h3(component_label))
|
||||||
|
div_.append(create_component_example(component["instance"], component["code"]))
|
||||||
|
|
||||||
|
return div_
|
||||||
|
|
||||||
|
|
||||||
|
# ********** CREATE ALL THE LAYOUT **********
|
||||||
|
|
||||||
|
main_grid = grid("140px 20px auto", style={"min-height": "100%"})
|
||||||
|
|
||||||
|
# ********** MAIN PANEL **********
|
||||||
|
main_area = create_main_area()
|
||||||
|
|
||||||
|
|
||||||
|
def write_to_main(content):
|
||||||
|
main_area.html = ""
|
||||||
|
main_area.append(content)
|
||||||
|
|
||||||
|
|
||||||
|
def restore_home():
|
||||||
|
write_to_main(create_main_area())
|
||||||
|
|
||||||
|
|
||||||
|
def basic_components():
|
||||||
|
write_to_main(
|
||||||
|
create_basic_components_page(label="Basic Components", kit_name="elements")
|
||||||
|
)
|
||||||
|
# Make sure we highlight the code
|
||||||
|
window.hljs.highlightAll()
|
||||||
|
|
||||||
|
|
||||||
|
def markdown_components():
|
||||||
|
write_to_main(create_basic_components_page(label="", kit_name="markdown"))
|
||||||
|
|
||||||
|
|
||||||
|
def create_new_section(title, parent_div):
|
||||||
|
basic_components_text = h3(
|
||||||
|
title, style={"text-align": "left", "margin": "20px auto 0"}
|
||||||
|
)
|
||||||
|
parent_div.append(basic_components_text)
|
||||||
|
parent_div.append(
|
||||||
|
shoelace.Divider(style={"margin-top": "5px", "margin-bottom": "30px"})
|
||||||
|
)
|
||||||
|
return basic_components_text
|
||||||
|
|
||||||
|
|
||||||
|
# ********** LEFT PANEL **********
|
||||||
|
left_div = div()
|
||||||
|
left_panel_title = h1(
|
||||||
|
"PyWeb.UI", style={"text-align": "center", "margin": "20px auto 30px"}
|
||||||
|
)
|
||||||
|
left_div.append(left_panel_title)
|
||||||
|
left_div.append(shoelace.Divider(style={"margin-bottom": "30px"}))
|
||||||
|
# Let's map the creation of the main area to when the user clocks on "Components"
|
||||||
|
when("click", left_panel_title)(restore_home)
|
||||||
|
|
||||||
|
# BASIC COMPONENTS
|
||||||
|
basic_components_text = h3(
|
||||||
|
"Basic Components",
|
||||||
|
style={"text-align": "left", "margin": "20px auto 0", "cursor": "pointer"},
|
||||||
|
)
|
||||||
|
left_div.append(basic_components_text)
|
||||||
|
left_div.append(shoelace.Divider(style={"margin-top": "5px", "margin-bottom": "30px"}))
|
||||||
|
# Let's map the creation of the main area to when the user clocks on "Components"
|
||||||
|
when("click", basic_components_text)(basic_components)
|
||||||
|
|
||||||
|
# MARKDOWN COMPONENTS
|
||||||
|
markdown_title = create_new_section("Markdown", left_div)
|
||||||
|
when("click", markdown_title)(markdown_components)
|
||||||
|
|
||||||
|
|
||||||
|
# SHOELACE COMPONENTS
|
||||||
|
shoe_components_text = h3(
|
||||||
|
"Shoe Components", style={"text-align": "left", "margin": "20px auto 0"}
|
||||||
|
)
|
||||||
|
left_div.append(shoe_components_text)
|
||||||
|
left_div.append(shoelace.Divider(style={"margin-top": "5px", "margin-bottom": "30px"}))
|
||||||
|
|
||||||
|
# Create the links to the components on th left panel
|
||||||
|
print("SHOELACE EXAMPLES", examples.kits["shoelace"])
|
||||||
|
for component_label, component in examples.kits["shoelace"].items():
|
||||||
|
add_component_section(component_label, component, left_div)
|
||||||
|
|
||||||
|
left_div.append(shoelace.Divider(style={"margin-top": "5px", "margin-bottom": "30px"}))
|
||||||
|
left_div.append(a("Gallery", href="gallery.html", style={"text-align": "left"}))
|
||||||
|
# ********** ADD LEFT AND MAIN PANEL TO MAIN **********
|
||||||
|
main_grid.append(left_div)
|
||||||
|
main_grid.append(shoelace.Divider(vertical=True))
|
||||||
|
main_grid.append(main_area)
|
||||||
|
pydom.body.append(main_grid)
|
||||||
300
pyscript.core/test/ui/examples.py
Normal file
300
pyscript.core/test/ui/examples.py
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
from markdown import markdown
|
||||||
|
from pyscript import when, window
|
||||||
|
from pyweb import pydom
|
||||||
|
from pyweb.ui.elements import (
|
||||||
|
a,
|
||||||
|
br,
|
||||||
|
button,
|
||||||
|
code,
|
||||||
|
div,
|
||||||
|
grid,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
img,
|
||||||
|
input_,
|
||||||
|
p,
|
||||||
|
small,
|
||||||
|
strong,
|
||||||
|
)
|
||||||
|
from shoelace import (
|
||||||
|
Alert,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CopyButton,
|
||||||
|
Details,
|
||||||
|
Dialog,
|
||||||
|
Divider,
|
||||||
|
Icon,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
Range,
|
||||||
|
Rating,
|
||||||
|
RelativeTime,
|
||||||
|
Skeleton,
|
||||||
|
Spinner,
|
||||||
|
Switch,
|
||||||
|
Tag,
|
||||||
|
Textarea,
|
||||||
|
)
|
||||||
|
|
||||||
|
LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||||
|
details_code = """
|
||||||
|
LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||||
|
Details(LOREM_IPSUM, summary="Try me")
|
||||||
|
"""
|
||||||
|
example_dialog_close_btn = Button("Close")
|
||||||
|
example_dialog = Dialog(div([p(LOREM_IPSUM), example_dialog_close_btn]), label="Try me")
|
||||||
|
example_dialog_btn = Button("Open Dialog")
|
||||||
|
|
||||||
|
|
||||||
|
def toggle_dialog():
|
||||||
|
example_dialog.open = not (example_dialog.open)
|
||||||
|
|
||||||
|
|
||||||
|
when("click", example_dialog_btn)(toggle_dialog)
|
||||||
|
when("click", example_dialog_close_btn)(toggle_dialog)
|
||||||
|
|
||||||
|
pydom.body.append(example_dialog)
|
||||||
|
|
||||||
|
|
||||||
|
# ELEMENTS
|
||||||
|
|
||||||
|
# Button
|
||||||
|
btn = button("Click me!")
|
||||||
|
when("click", btn)(lambda: window.alert("Clicked!"))
|
||||||
|
|
||||||
|
# Inputs
|
||||||
|
inputs_div = div()
|
||||||
|
inputs_code = []
|
||||||
|
for input_type in [
|
||||||
|
"text",
|
||||||
|
"password",
|
||||||
|
"email",
|
||||||
|
"number",
|
||||||
|
"date",
|
||||||
|
"time",
|
||||||
|
"color",
|
||||||
|
"range",
|
||||||
|
]:
|
||||||
|
inputs_div.append(input_(type=input_type, style={"display": "block"}))
|
||||||
|
inputs_code.append(f"input_(type='{input_type}')")
|
||||||
|
|
||||||
|
|
||||||
|
headers_div = div()
|
||||||
|
headers_code = []
|
||||||
|
for header in [h1, h2, h3, h4, h5, h6]:
|
||||||
|
headers_div.append(header(f"{header.tag.upper()} header"))
|
||||||
|
headers_code.append(f'{header.tag}("{header.tag.upper()} header")')
|
||||||
|
headers_code = "\n".join(headers_code)
|
||||||
|
|
||||||
|
rich_input = input_(
|
||||||
|
type="text",
|
||||||
|
name="some name",
|
||||||
|
autofocus=True,
|
||||||
|
pattern="\w{3,16}",
|
||||||
|
placeholder="add text with > 3 chars",
|
||||||
|
required=True,
|
||||||
|
size="20",
|
||||||
|
)
|
||||||
|
inputs_div.append(rich_input)
|
||||||
|
inputs_code.append("# You can create inputs with more options like")
|
||||||
|
inputs_code.append("# this by passing properties as kwargs")
|
||||||
|
inputs_code.append(
|
||||||
|
"input_(type='text', name='some name', autofocus=True, pattern='\\w{3,16}', placeholder='add text with > 3 chars', required=True, size='20')"
|
||||||
|
)
|
||||||
|
inputs_code = "\n".join(inputs_code)
|
||||||
|
|
||||||
|
MARKDOWN_EXAMPLE = """# This is a header
|
||||||
|
|
||||||
|
This is a ~~paragraph~~ text with **bold** and *italic* text in it!
|
||||||
|
"""
|
||||||
|
|
||||||
|
kits = {
|
||||||
|
"shoelace": {
|
||||||
|
"Alert": {
|
||||||
|
"instance": Alert(
|
||||||
|
"This is a standard alert. You can customize its content and even the icon."
|
||||||
|
),
|
||||||
|
"code": "Alert('This is a standard alert. You can customize its content and even the icon.'",
|
||||||
|
},
|
||||||
|
"Icon": {
|
||||||
|
"instance": Icon(name="heart"),
|
||||||
|
"code": 'Icon(name="heart")',
|
||||||
|
},
|
||||||
|
"Button": {
|
||||||
|
"instance": Button("Try me"),
|
||||||
|
"code": 'Button("Try me")',
|
||||||
|
},
|
||||||
|
"Card": {
|
||||||
|
"instance": Card(
|
||||||
|
p("This is a cool card!"),
|
||||||
|
image="https://pyscript.net/assets/images/pyscript-sticker-black.svg",
|
||||||
|
footer=div([Button("More Info"), Rating()]),
|
||||||
|
),
|
||||||
|
"code": """
|
||||||
|
Card(p("This is a cool card!"), image="https://pyscript.net/assets/images/pyscript-sticker-black.svg", footer=div([Button("More Info"), Rating()]))
|
||||||
|
""",
|
||||||
|
},
|
||||||
|
"Details": {
|
||||||
|
"instance": Details(LOREM_IPSUM, summary="Try me"),
|
||||||
|
"code": 'Details(LOREM_IPSUM, summary="Try me")',
|
||||||
|
},
|
||||||
|
"Dialog": {
|
||||||
|
"instance": example_dialog_btn,
|
||||||
|
"code": 'Dialog(div([p(LOREM_IPSUM), Button("Close")]), summary="Try me")',
|
||||||
|
},
|
||||||
|
"Divider": {
|
||||||
|
"instance": Divider(),
|
||||||
|
"code": "Divider()",
|
||||||
|
},
|
||||||
|
"Rating": {
|
||||||
|
"instance": Rating(),
|
||||||
|
"code": "Rating()",
|
||||||
|
},
|
||||||
|
"Radio": {
|
||||||
|
"instance": Radio("Option 42"),
|
||||||
|
"code": code('Radio("Option 42")'),
|
||||||
|
},
|
||||||
|
"Radio Group": {
|
||||||
|
"instance": RadioGroup(
|
||||||
|
[
|
||||||
|
Radio("radio 1", name="radio 1", value=1, style={"margin": "20px"}),
|
||||||
|
Radio("radio 2", name="radio 2", value=2, style={"margin": "20px"}),
|
||||||
|
Radio("radio 3", name="radio 3", value=3, style={"margin": "20px"}),
|
||||||
|
],
|
||||||
|
label="Select an option",
|
||||||
|
),
|
||||||
|
"code": code(
|
||||||
|
"""
|
||||||
|
RadioGroup([Radio("radio 1", name="radio 1", value=1, style={"margin": "20px"}),
|
||||||
|
Radio("radio 2", name="radio 2", value=2, style={"margin": "20px"}),
|
||||||
|
Radio("radio 3", name="radio 3", value=3, style={"margin": "20px"})],
|
||||||
|
label="Select an option"),"""
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"CopyButton": {
|
||||||
|
"instance": CopyButton(
|
||||||
|
value="PyShoes!",
|
||||||
|
copy_label="Copy me!",
|
||||||
|
sucess_label="Copied, check your clipboard!",
|
||||||
|
error_label="Oops, something went wrong!",
|
||||||
|
feedback_timeout=2000,
|
||||||
|
tooltip_placement="top",
|
||||||
|
),
|
||||||
|
"code": 'CopyButton(value="PyShoes!", copy_label="Copy me!", sucess_label="Copied, check your clipboard!", error_label="Oops, something went wrong!", feedback_timeout=2000, tooltip_placement="top")',
|
||||||
|
},
|
||||||
|
"Skeleton": {
|
||||||
|
"instance": Skeleton(effect="pulse"),
|
||||||
|
"code": "Skeleton(effect='pulse')",
|
||||||
|
},
|
||||||
|
"Spinner": {
|
||||||
|
"instance": Spinner(),
|
||||||
|
"code": "Spinner()",
|
||||||
|
},
|
||||||
|
"Switch": {
|
||||||
|
"instance": Switch(name="switch", size="large"),
|
||||||
|
"code": 'Switch(name="switch", size="large")',
|
||||||
|
},
|
||||||
|
"Textarea": {
|
||||||
|
"instance": Textarea(
|
||||||
|
name="textarea",
|
||||||
|
label="Textarea",
|
||||||
|
size="medium",
|
||||||
|
help_text="This is a textarea",
|
||||||
|
resize="auto",
|
||||||
|
),
|
||||||
|
"code": 'Textarea(name="textarea", label="Textarea", size="medium", help_text="This is a textarea", resize="auto")',
|
||||||
|
},
|
||||||
|
"Tag": {
|
||||||
|
"instance": Tag("Tag", variant="primary", size="medium"),
|
||||||
|
"code": 'Tag("Tag", variant="primary", size="medium")',
|
||||||
|
},
|
||||||
|
"Range": {
|
||||||
|
"instance": Range(min=0, max=100, value=50),
|
||||||
|
"code": "Range(min=0, max=100, value=50)",
|
||||||
|
},
|
||||||
|
"RelativeTime": {
|
||||||
|
"instance": RelativeTime(date="2021-01-01T00:00:00Z"),
|
||||||
|
"code": 'RelativeTime(date="2021-01-01T00:00:00Z")',
|
||||||
|
},
|
||||||
|
# "SplitPanel": {
|
||||||
|
# "instance": SplitPanel(
|
||||||
|
# div("First panel"), div("Second panel"), orientation="vertical"
|
||||||
|
# ),
|
||||||
|
# "code": code(
|
||||||
|
# 'SplitPanel(div("First panel"), div("Second panel"), orientation="vertical")'
|
||||||
|
# ),
|
||||||
|
# },
|
||||||
|
},
|
||||||
|
"elements": {
|
||||||
|
"button": {
|
||||||
|
"instance": btn,
|
||||||
|
"code": """btn = button("Click me!")
|
||||||
|
when('click', btn)(lambda: window.alert("Clicked!"))
|
||||||
|
parentdiv.append(btn)
|
||||||
|
""",
|
||||||
|
},
|
||||||
|
"div": {
|
||||||
|
"instance": div(
|
||||||
|
"This is a div",
|
||||||
|
style={
|
||||||
|
"text-align": "center",
|
||||||
|
"margin": "0 auto",
|
||||||
|
"background-color": "cornsilk",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"code": 'div("This is a div", style={"text-align": "center", "margin": "0 auto", "background-color": "cornsilk"})',
|
||||||
|
},
|
||||||
|
"input": {"instance": inputs_div, "code": inputs_code},
|
||||||
|
"grid": {
|
||||||
|
"instance": grid(
|
||||||
|
"30% 70%",
|
||||||
|
[
|
||||||
|
div("This is a grid", style={"background-color": "lightblue"}),
|
||||||
|
p("with 2 elements", style={"background-color": "lightyellow"}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"code": 'grid([div("This is a grid")])',
|
||||||
|
},
|
||||||
|
"headers": {"instance": headers_div, "code": headers_code},
|
||||||
|
"a": {
|
||||||
|
"instance": a(
|
||||||
|
"Click here for something awesome",
|
||||||
|
href="https://pyscript.net",
|
||||||
|
target="_blank",
|
||||||
|
),
|
||||||
|
"code": 'a("Click here for something awesome", href="https://pyscript.net", target="_blank")',
|
||||||
|
},
|
||||||
|
"br": {
|
||||||
|
"instance": div([p("This is a paragraph"), br(), p("with a line break")]),
|
||||||
|
"code": 'div([p("This is a paragraph"), br(), p("with a line break")])',
|
||||||
|
},
|
||||||
|
"img": {
|
||||||
|
"instance": img(src="./giphy_winner.gif", style={"max-width": "200px"}),
|
||||||
|
"code": 'img(src="./giphy_winner.gif", style={"max-width": "200px"})',
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"instance": code("print('Hello, World!')"),
|
||||||
|
"code": "code(\"print('Hello, World!')\")",
|
||||||
|
},
|
||||||
|
"p": {"instance": p("This is a paragraph"), "code": 'p("This is a paragraph")'},
|
||||||
|
"small": {
|
||||||
|
"instance": small("This is a small text"),
|
||||||
|
"code": 'small("This is a small text")',
|
||||||
|
},
|
||||||
|
"strong": {
|
||||||
|
"instance": strong("This is a strong text"),
|
||||||
|
"code": 'strong("This is a strong text")',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"markdown": {
|
||||||
|
"markdown": {
|
||||||
|
"instance": markdown(MARKDOWN_EXAMPLE),
|
||||||
|
"code": f'markdown("""{MARKDOWN_EXAMPLE}""")',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
31
pyscript.core/test/ui/gallery.html
Normal file
31
pyscript.core/test/ui/gallery.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>PyDom UI</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../../dist/core.css">
|
||||||
|
<script type="module" src="../../dist/core.js"></script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked@11.1.1/lib/marked.umd.min.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
|
|
||||||
|
<!-- and it's easy to individually load additional languages -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js"></script>
|
||||||
|
|
||||||
|
<script>hljs.highlightAll();</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, "system-ui", "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script type="mpy" src="./gallery.py" config="./pyscript.toml"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
180
pyscript.core/test/ui/gallery.py
Normal file
180
pyscript.core/test/ui/gallery.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
try:
|
||||||
|
from textwrap import dedent
|
||||||
|
except ImportError:
|
||||||
|
dedent = lambda x: x
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
import shoelace
|
||||||
|
import styles
|
||||||
|
import tictactoe
|
||||||
|
from markdown import markdown
|
||||||
|
from pyscript import when, window
|
||||||
|
from pyweb import pydom
|
||||||
|
from pyweb.ui import elements as el
|
||||||
|
|
||||||
|
MAIN_PAGE_MARKDOWN = dedent(
|
||||||
|
"""
|
||||||
|
This gallery is a collection of demos using the PyWeb.UI library. There are meant
|
||||||
|
to be examples of how to use the library to create GUI applications using Python
|
||||||
|
only.
|
||||||
|
|
||||||
|
## How to use the gallery
|
||||||
|
|
||||||
|
Simply click on the demo you want to see and the details will appear on the right
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# First thing we do is to load all the external resources we need
|
||||||
|
shoelace.load_resources()
|
||||||
|
|
||||||
|
|
||||||
|
def add_demo(demo_name, demo_creator_cb, parent_div, source=None):
|
||||||
|
"""Create a link to a component and add it to the left panel.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component (str): The name of the component to add.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
the component created
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Create the component link element
|
||||||
|
div = el.div(el.a(demo_name, href="#"), style=styles.STYLE_LEFT_PANEL_LINKS)
|
||||||
|
|
||||||
|
# Create a handler that opens the component details when the link is clicked
|
||||||
|
@when("click", div)
|
||||||
|
def _change():
|
||||||
|
if source:
|
||||||
|
demo_div = el.grid("50% 50%")
|
||||||
|
demo_div.append(demo_creator_cb())
|
||||||
|
widget_code = markdown(dedent(f"""```python\n{source}\n```"""))
|
||||||
|
demo_div.append(el.div(widget_code, style=styles.STYLE_CODE_BLOCK))
|
||||||
|
else:
|
||||||
|
demo_div = demo_creator_cb()
|
||||||
|
demo_div.style["margin"] = "20px"
|
||||||
|
write_to_main(demo_div)
|
||||||
|
window.hljs.highlightAll()
|
||||||
|
|
||||||
|
# Add the new link element to the parent div (left panel)
|
||||||
|
parent_div.append(div)
|
||||||
|
return div
|
||||||
|
|
||||||
|
|
||||||
|
def create_main_area():
|
||||||
|
"""Create the main area of the right side of page, with the description of the
|
||||||
|
demo itself and how to use it.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
the main area
|
||||||
|
|
||||||
|
"""
|
||||||
|
return el.div(
|
||||||
|
[
|
||||||
|
el.h1("PyWeb UI Gallery", style={"text-align": "center"}),
|
||||||
|
markdown(MAIN_PAGE_MARKDOWN),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_markdown_app():
|
||||||
|
"""Create the basic components page.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
the main area
|
||||||
|
|
||||||
|
"""
|
||||||
|
translate_button = shoelace.Button("Convert", variant="primary")
|
||||||
|
markdown_txt_area = shoelace.TextArea(label="Use this to write your Markdown")
|
||||||
|
result_div = el.div(style=styles.STYLE_MARKDOWN_RESULT)
|
||||||
|
|
||||||
|
@when("click", translate_button)
|
||||||
|
def translate_markdown():
|
||||||
|
result_div.html = markdown(markdown_txt_area.value).html
|
||||||
|
|
||||||
|
return el.div(
|
||||||
|
[
|
||||||
|
el.h2("Markdown"),
|
||||||
|
markdown_txt_area,
|
||||||
|
translate_button,
|
||||||
|
result_div,
|
||||||
|
],
|
||||||
|
style={"margin": "20px"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ********** MAIN PANEL **********
|
||||||
|
main_area = create_main_area()
|
||||||
|
|
||||||
|
|
||||||
|
def write_to_main(content):
|
||||||
|
main_area.html = ""
|
||||||
|
main_area.append(content)
|
||||||
|
|
||||||
|
|
||||||
|
def restore_home():
|
||||||
|
write_to_main(create_main_area())
|
||||||
|
|
||||||
|
|
||||||
|
def create_new_section(title, parent_div):
|
||||||
|
basic_components_text = el.h3(
|
||||||
|
title, style={"text-align": "left", "margin": "20px auto 0"}
|
||||||
|
)
|
||||||
|
parent_div.append(basic_components_text)
|
||||||
|
parent_div.append(
|
||||||
|
shoelace.Divider(style={"margin-top": "5px", "margin-bottom": "30px"})
|
||||||
|
)
|
||||||
|
return basic_components_text
|
||||||
|
|
||||||
|
|
||||||
|
# ********** LEFT PANEL **********
|
||||||
|
left_panel_title = el.h1("PyWeb.UI", style=styles.STYLE_LEFT_PANEL_TITLE)
|
||||||
|
left_div = el.div(
|
||||||
|
[
|
||||||
|
left_panel_title,
|
||||||
|
shoelace.Divider(style={"margin-bottom": "30px"}),
|
||||||
|
el.h3("Demos", style=styles.STYLE_LEFT_PANEL_TITLE),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Let's map the creation of the main area to when the user clocks on "Components"
|
||||||
|
when("click", left_panel_title)(restore_home)
|
||||||
|
|
||||||
|
# ------ ADD DEMOS ------
|
||||||
|
markdown_source = """
|
||||||
|
translate_button = shoelace.Button("Convert", variant="primary")
|
||||||
|
markdown_txt_area = shoelace.TextArea(label="Markdown",
|
||||||
|
help_text="Write your Mardown here and press convert to see the result",
|
||||||
|
)
|
||||||
|
result_div = el.div(style=styles.STYLE_MARKDOWN_RESULT)
|
||||||
|
@when("click", translate_button)
|
||||||
|
def translate_markdown():
|
||||||
|
result_div.html = markdown(markdown_txt_area.value).html
|
||||||
|
|
||||||
|
el.div([
|
||||||
|
el.h2("Markdown"),
|
||||||
|
markdown_txt_area,
|
||||||
|
translate_button,
|
||||||
|
result_div,
|
||||||
|
])
|
||||||
|
"""
|
||||||
|
add_demo("Markdown", create_markdown_app, left_div, source=markdown_source)
|
||||||
|
add_demo(
|
||||||
|
"Tic Tac Toe",
|
||||||
|
tictactoe.create_tic_tac_toe,
|
||||||
|
left_div,
|
||||||
|
source=inspect.getsource(tictactoe),
|
||||||
|
)
|
||||||
|
|
||||||
|
left_div.append(shoelace.Divider(style={"margin-top": "5px", "margin-bottom": "30px"}))
|
||||||
|
left_div.append(el.a("Examples", href="/test/ui/", style={"text-align": "left"}))
|
||||||
|
|
||||||
|
# ********** CREATE ALL THE LAYOUT **********
|
||||||
|
grid = el.grid("minmax(100px, 200px) 20px auto", style={"min-height": "100%"})
|
||||||
|
grid.append(left_div)
|
||||||
|
grid.append(shoelace.Divider(vertical=True))
|
||||||
|
grid.append(main_area)
|
||||||
|
|
||||||
|
pydom.body.append(grid)
|
||||||
|
pydom.body.append(el.a("Back to the main page", href="/test/ui/", target="_blank"))
|
||||||
|
pydom.body.append(el.a("Hidden!!!", href="/test/ui/", target="_blank", hidden=True))
|
||||||
39
pyscript.core/test/ui/index.html
Normal file
39
pyscript.core/test/ui/index.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>PyDom UI</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../../dist/core.css">
|
||||||
|
<script type="module" src="../../dist/core.js"></script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked@11.1.1/lib/marked.umd.min.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
|
|
||||||
|
<!-- and it's easy to individually load additional languages -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js"></script>
|
||||||
|
<!-- SHOWLACE CUSTOM CSS -->
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, "system-ui", "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"
|
||||||
|
}
|
||||||
|
|
||||||
|
input:invalid {
|
||||||
|
background-color: lightpink;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script type="mpy" src="./demo.py" config="./pyscript.toml"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
pyscript.core/test/ui/pyscript.toml
Normal file
8
pyscript.core/test/ui/pyscript.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
packages = []
|
||||||
|
|
||||||
|
[files]
|
||||||
|
"./examples.py" = "./examples.py"
|
||||||
|
"./tictactoe.py" = "./tictactoe.py"
|
||||||
|
"./styles.py" = "./styles.py"
|
||||||
|
"./shoelace.py" = "./shoelace.py"
|
||||||
|
"./markdown.py" = "./markdown.py"
|
||||||
@@ -186,12 +186,12 @@ class TestSupport(PyScriptTest):
|
|||||||
#
|
#
|
||||||
msg = str(exc.value)
|
msg = str(exc.value)
|
||||||
expected = textwrap.dedent(
|
expected = textwrap.dedent(
|
||||||
"""
|
f"""
|
||||||
JS errors found: 2
|
JS errors found: 2
|
||||||
Error: error 1
|
Error: error 1
|
||||||
at https://fake_server/mytest.html:.*
|
at {self.http_server_addr}/mytest.html:.*
|
||||||
Error: error 2
|
Error: error 2
|
||||||
at https://fake_server/mytest.html:.*
|
at {self.http_server_addr}/mytest.html:.*
|
||||||
"""
|
"""
|
||||||
).strip()
|
).strip()
|
||||||
assert re.search(expected, msg)
|
assert re.search(expected, msg)
|
||||||
@@ -217,12 +217,12 @@ class TestSupport(PyScriptTest):
|
|||||||
#
|
#
|
||||||
msg = str(exc.value)
|
msg = str(exc.value)
|
||||||
expected = textwrap.dedent(
|
expected = textwrap.dedent(
|
||||||
"""
|
f"""
|
||||||
JS errors found: 2
|
JS errors found: 2
|
||||||
Error: NOT expected 2
|
Error: NOT expected 2
|
||||||
at https://fake_server/mytest.html:.*
|
at {self.http_server_addr}/mytest.html:.*
|
||||||
Error: NOT expected 4
|
Error: NOT expected 4
|
||||||
at https://fake_server/mytest.html:.*
|
at {self.http_server_addr}/mytest.html:.*
|
||||||
"""
|
"""
|
||||||
).strip()
|
).strip()
|
||||||
assert re.search(expected, msg)
|
assert re.search(expected, msg)
|
||||||
@@ -243,15 +243,15 @@ class TestSupport(PyScriptTest):
|
|||||||
#
|
#
|
||||||
msg = str(exc.value)
|
msg = str(exc.value)
|
||||||
expected = textwrap.dedent(
|
expected = textwrap.dedent(
|
||||||
"""
|
f"""
|
||||||
The following JS errors were expected but could not be found:
|
The following JS errors were expected but could not be found:
|
||||||
- this is not going to be found
|
- this is not going to be found
|
||||||
---
|
---
|
||||||
The following JS errors were raised but not expected:
|
The following JS errors were raised but not expected:
|
||||||
Error: error 1
|
Error: error 1
|
||||||
at https://fake_server/mytest.html:.*
|
at {self.http_server_addr}/mytest.html:.*
|
||||||
Error: error 2
|
Error: error 2
|
||||||
at https://fake_server/mytest.html:.*
|
at {self.http_server_addr}/mytest.html:.*
|
||||||
"""
|
"""
|
||||||
).strip()
|
).strip()
|
||||||
assert re.search(expected, msg)
|
assert re.search(expected, msg)
|
||||||
@@ -471,6 +471,8 @@ class TestSupport(PyScriptTest):
|
|||||||
Test that we capture a 404 in loading a page that does not exist.
|
Test that we capture a 404 in loading a page that does not exist.
|
||||||
"""
|
"""
|
||||||
self.goto("this_url_does_not_exist.html")
|
self.goto("this_url_does_not_exist.html")
|
||||||
assert [
|
if self.dev_server:
|
||||||
"Failed to load resource: the server responded with a status of 404 (Not Found)"
|
error = "Failed to load resource: the server responded with a status of 404 (File not found)"
|
||||||
] == self.console.all.lines
|
else:
|
||||||
|
error = "Failed to load resource: the server responded with a status of 404 (Not Found)"
|
||||||
|
assert [error] == self.console.all.lines
|
||||||
|
|||||||
605
pyscript.core/tests/integration/test_pyweb.py
Normal file
605
pyscript.core/tests/integration/test_pyweb.py
Normal file
@@ -0,0 +1,605 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .support import PyScriptTest, only_main, skip_worker
|
||||||
|
|
||||||
|
DEFAULT_ELEMENT_ATTRIBUTES = {
|
||||||
|
"accesskey": "s",
|
||||||
|
"autocapitalize": "off",
|
||||||
|
"autofocus": True,
|
||||||
|
"contenteditable": True,
|
||||||
|
"draggable": True,
|
||||||
|
"enterkeyhint": "go",
|
||||||
|
"hidden": False,
|
||||||
|
"id": "whateverid",
|
||||||
|
"lang": "br",
|
||||||
|
"nonce": "123",
|
||||||
|
"part": "part1:exposed1",
|
||||||
|
"popover": True,
|
||||||
|
"slot": "slot1",
|
||||||
|
"spellcheck": False,
|
||||||
|
"tabindex": 3,
|
||||||
|
"title": "whatevertitle",
|
||||||
|
"translate": "no",
|
||||||
|
"virtualkeyboardpolicy": "manual",
|
||||||
|
}
|
||||||
|
|
||||||
|
INTERPRETERS = ["py", "mpy"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(params=INTERPRETERS)
|
||||||
|
def interpreter(request):
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
|
||||||
|
class TestElements(PyScriptTest):
|
||||||
|
"""Test all elements in the pyweb.ui.elements module.
|
||||||
|
|
||||||
|
This class tests all elements in the pyweb.ui.elements module. It creates
|
||||||
|
an element of each type, both executing in the main thread and in a worker.
|
||||||
|
It runs each test for each interpreter defined in `INTERPRETERS`
|
||||||
|
|
||||||
|
Each individual element test looks for the element properties, sets a value
|
||||||
|
on each the supported properties and checks if the element was created correctly
|
||||||
|
and all it's properties were set correctly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expected_missing_file_errors(self):
|
||||||
|
# In fake server conditions this test will not throw an error due to missing files.
|
||||||
|
# If we want to skip the test, use:
|
||||||
|
# pytest.skip("Skipping: fake server doesn't throw 404 errors on missing local files.")
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
"Failed to load resource: the server responded with a status of 404 (File not found)"
|
||||||
|
]
|
||||||
|
if self.dev_server
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
|
||||||
|
def _create_el_and_basic_asserts(
|
||||||
|
self,
|
||||||
|
el_type,
|
||||||
|
el_text=None,
|
||||||
|
interpreter="py",
|
||||||
|
properties=None,
|
||||||
|
expected_errors=None,
|
||||||
|
additional_selector_rules=None,
|
||||||
|
):
|
||||||
|
"""Create an element with all its properties set, by running <script type=<interpreter> ... >
|
||||||
|
, and check if the element was created correctly and all its properties were set correctly.
|
||||||
|
"""
|
||||||
|
expected_errors = expected_errors or []
|
||||||
|
if not properties:
|
||||||
|
properties = {}
|
||||||
|
|
||||||
|
def parse_value(v):
|
||||||
|
if isinstance(v, bool):
|
||||||
|
return str(v)
|
||||||
|
|
||||||
|
return f"'{v}'"
|
||||||
|
|
||||||
|
attributes = ""
|
||||||
|
if el_text:
|
||||||
|
attributes += f'"{el_text}",'
|
||||||
|
|
||||||
|
if properties:
|
||||||
|
attributes += ", ".join(
|
||||||
|
[f"{k}={parse_value(v)}" for k, v in properties.items()]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Let's make sure the body of the page is clean first
|
||||||
|
body = self.page.locator("body")
|
||||||
|
assert body.inner_html() == ""
|
||||||
|
|
||||||
|
# Let's make sure the element is not in the page
|
||||||
|
element = self.page.locator(el_type)
|
||||||
|
assert not element.count()
|
||||||
|
|
||||||
|
# Let's create the element
|
||||||
|
code_ = f"""
|
||||||
|
from pyscript import when
|
||||||
|
<script type="{interpreter}">
|
||||||
|
from pyweb import pydom
|
||||||
|
from pyweb.ui.elements import {el_type}
|
||||||
|
el = {el_type}({attributes})
|
||||||
|
pydom.body.append(el)
|
||||||
|
</script>
|
||||||
|
"""
|
||||||
|
self.pyscript_run(code_)
|
||||||
|
|
||||||
|
# Let's keep the tag in 2 variables, one for the selector and another to
|
||||||
|
# check the return tag from the selector
|
||||||
|
locator_type = el_tag = el_type[:-1] if el_type.endswith("_") else el_type
|
||||||
|
if additional_selector_rules:
|
||||||
|
locator_type += f"{additional_selector_rules}"
|
||||||
|
|
||||||
|
el = self.page.locator(locator_type)
|
||||||
|
tag = el.evaluate("node => node.tagName")
|
||||||
|
assert tag == el_tag.upper()
|
||||||
|
if el_text:
|
||||||
|
assert el.inner_html() == el_text
|
||||||
|
assert el.text_content() == el_text
|
||||||
|
|
||||||
|
# if we expect specific errors, check that they are in the console
|
||||||
|
if expected_errors:
|
||||||
|
for error in expected_errors:
|
||||||
|
assert error in self.console.error.lines
|
||||||
|
else:
|
||||||
|
# if we don't expect errors, check that there are no errors
|
||||||
|
assert self.console.error.lines == []
|
||||||
|
|
||||||
|
if properties:
|
||||||
|
for k, v in properties.items():
|
||||||
|
actual_val = el.evaluate(f"node => node.{k}")
|
||||||
|
assert actual_val == v
|
||||||
|
return el
|
||||||
|
|
||||||
|
def test_a(self, interpreter):
|
||||||
|
a = self._create_el_and_basic_asserts("a", "click me", interpreter)
|
||||||
|
assert a.text_content() == "click me"
|
||||||
|
|
||||||
|
def test_abbr(self, interpreter):
|
||||||
|
abbr = self._create_el_and_basic_asserts(
|
||||||
|
"abbr", "some text", interpreter=interpreter
|
||||||
|
)
|
||||||
|
assert abbr.text_content() == "some text"
|
||||||
|
|
||||||
|
def test_address(self, interpreter):
|
||||||
|
address = self._create_el_and_basic_asserts("address", "some text", interpreter)
|
||||||
|
assert address.text_content() == "some text"
|
||||||
|
|
||||||
|
def test_area(self, interpreter):
|
||||||
|
properties = {
|
||||||
|
"shape": "poly",
|
||||||
|
"coords": "129,0,260,95,129,138",
|
||||||
|
"href": "https://developer.mozilla.org/docs/Web/HTTP",
|
||||||
|
"target": "_blank",
|
||||||
|
"alt": "HTTP",
|
||||||
|
}
|
||||||
|
# TODO: Check why click times out
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"area", interpreter=interpreter, properties=properties
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_article(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("article", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_aside(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("aside", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_audio(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"audio",
|
||||||
|
interpreter=interpreter,
|
||||||
|
properties={"src": "http://localhost:8080/somefile.ogg", "controls": True},
|
||||||
|
expected_errors=self.expected_missing_file_errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_b(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("aside", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_blockquote(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("blockquote", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_br(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("br", interpreter=interpreter)
|
||||||
|
|
||||||
|
def test_element_button(self, interpreter):
|
||||||
|
button = self._create_el_and_basic_asserts("button", "click me", interpreter)
|
||||||
|
assert button.inner_html() == "click me"
|
||||||
|
|
||||||
|
def test_element_button_attributes(self, interpreter):
|
||||||
|
button = self._create_el_and_basic_asserts(
|
||||||
|
"button", "click me", interpreter, None
|
||||||
|
)
|
||||||
|
assert button.inner_html() == "click me"
|
||||||
|
|
||||||
|
def test_canvas(self, interpreter):
|
||||||
|
properties = {
|
||||||
|
"height": 100,
|
||||||
|
"width": 120,
|
||||||
|
}
|
||||||
|
# TODO: Check why click times out
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"canvas", "alt text for canvas", interpreter, properties=properties
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_caption(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("caption", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_cite(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("cite", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_code(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("code", "import pyweb", interpreter)
|
||||||
|
|
||||||
|
def test_data(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"data", "some text", interpreter, properties={"value": "123"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_datalist(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("datalist", "some items", interpreter)
|
||||||
|
|
||||||
|
def test_dd(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("dd", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_del_(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"del_", "some text", interpreter, properties={"cite": "http://example.com/"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_details(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"details", "some text", interpreter, properties={"open": True}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_dialog(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"dialog", "some text", interpreter, properties={"open": True}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_div(self, interpreter):
|
||||||
|
div = self._create_el_and_basic_asserts("div", "click me", interpreter)
|
||||||
|
assert div.inner_html() == "click me"
|
||||||
|
|
||||||
|
def test_dl(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("dl", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_dt(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("dt", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_em(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("em", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_embed(self, interpreter):
|
||||||
|
# NOTE: Types actually matter and embed expects a string for height and width
|
||||||
|
# while other elements expect an int
|
||||||
|
|
||||||
|
# TODO: It's important that we add typing soon to help with the user experience
|
||||||
|
properties = {
|
||||||
|
"src": "http://localhost:8080/somefile.ogg",
|
||||||
|
"type": "video/ogg",
|
||||||
|
"width": "250",
|
||||||
|
"height": "200",
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"embed",
|
||||||
|
interpreter=interpreter,
|
||||||
|
properties=properties,
|
||||||
|
expected_errors=self.expected_missing_file_errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fieldset(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"fieldset", "some text", interpreter, properties={"name": "some name"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_figcaption(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("figcaption", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_figure(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("figure", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_footer(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("footer", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_form(self, interpreter):
|
||||||
|
properties = {
|
||||||
|
"action": "https://example.com/submit",
|
||||||
|
"method": "post",
|
||||||
|
"name": "some name",
|
||||||
|
"autocomplete": "on",
|
||||||
|
"rel": "external",
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"form", "some text", interpreter, properties=properties
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_h1(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("h1", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_h2(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("h2", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_h3(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("h3", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_h4(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("h4", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_h5(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("h5", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_h6(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("h6", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_header(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("header", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_hgroup(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("hgroup", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_hr(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("hr", interpreter=interpreter)
|
||||||
|
|
||||||
|
def test_i(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("i", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_iframe(self, interpreter):
|
||||||
|
# TODO: same comment about defining the right types
|
||||||
|
properties = {
|
||||||
|
"src": "http://localhost:8080/somefile.html",
|
||||||
|
"width": "250",
|
||||||
|
"height": "200",
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"iframe",
|
||||||
|
interpreter,
|
||||||
|
properties=properties,
|
||||||
|
expected_errors=self.expected_missing_file_errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_img(self, interpreter):
|
||||||
|
properties = {
|
||||||
|
"src": "http://localhost:8080/somefile.png",
|
||||||
|
"alt": "some image",
|
||||||
|
"width": 250,
|
||||||
|
"height": 200,
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"img",
|
||||||
|
interpreter=interpreter,
|
||||||
|
properties=properties,
|
||||||
|
expected_errors=self.expected_missing_file_errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_input(self, interpreter):
|
||||||
|
# TODO: we need multiple input tests
|
||||||
|
properties = {
|
||||||
|
"type": "text",
|
||||||
|
"value": "some value",
|
||||||
|
"name": "some name",
|
||||||
|
"autofocus": True,
|
||||||
|
"pattern": "[A-Za-z]{3}",
|
||||||
|
"placeholder": "some placeholder",
|
||||||
|
"required": True,
|
||||||
|
"size": 20,
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"input_", interpreter=interpreter, properties=properties
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ins(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"ins", "some text", interpreter, properties={"cite": "http://example.com/"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_kbd(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("kbd", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_label(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("label", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_legend(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("legend", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_li(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("li", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_link(self, interpreter):
|
||||||
|
properties = {
|
||||||
|
"href": "http://localhost:8080/somefile.css",
|
||||||
|
"rel": "stylesheet",
|
||||||
|
"type": "text/css",
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"link",
|
||||||
|
interpreter=interpreter,
|
||||||
|
properties=properties,
|
||||||
|
expected_errors=self.expected_missing_file_errors,
|
||||||
|
additional_selector_rules="[href='http://localhost:8080/somefile.css']",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_main(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("main", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_map(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"map_", "some text", interpreter, properties={"name": "somemap"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_mark(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("mark", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_menu(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("menu", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_meter(self, interpreter):
|
||||||
|
properties = {
|
||||||
|
"value": 50,
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"low": 30,
|
||||||
|
"high": 80,
|
||||||
|
"optimum": 50,
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"meter", "some text", interpreter, properties=properties
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_nav(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("nav", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_object(self, interpreter):
|
||||||
|
properties = {
|
||||||
|
"data": "http://localhost:8080/somefile.swf",
|
||||||
|
"type": "application/x-shockwave-flash",
|
||||||
|
"width": "250",
|
||||||
|
"height": "200",
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"object_",
|
||||||
|
interpreter=interpreter,
|
||||||
|
properties=properties,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ol(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("ol", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_optgroup(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"optgroup", "some text", interpreter, properties={"label": "some label"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_option(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"option", "some text", interpreter, properties={"value": "some value"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_output(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("output", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_p(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("p", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_picture(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("picture", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_pre(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("pre", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_progress(self, interpreter):
|
||||||
|
properties = {
|
||||||
|
"value": 50,
|
||||||
|
"max": 100,
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"progress", "some text", interpreter, properties=properties
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_q(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"q", "some text", interpreter, properties={"cite": "http://example.com/"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_s(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("s", "some text", interpreter)
|
||||||
|
|
||||||
|
# def test_script(self):
|
||||||
|
# self._create_el_and_basic_asserts("script", "some text")
|
||||||
|
|
||||||
|
def test_section(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("section", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_select(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("select", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_small(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("small", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_source(self, interpreter):
|
||||||
|
properties = {
|
||||||
|
"src": "http://localhost:8080/somefile.ogg",
|
||||||
|
"type": "audio/ogg",
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"source",
|
||||||
|
interpreter=interpreter,
|
||||||
|
properties=properties,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_span(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("span", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_strong(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("strong", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_style(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"style",
|
||||||
|
"body {background-color: red;}",
|
||||||
|
interpreter,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sub(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("sub", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_summary(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("summary", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_sup(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("sup", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_table(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("table", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_tbody(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("tbody", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_td(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("td", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_template(self, interpreter):
|
||||||
|
# We are not checking the content of template since it's sort of
|
||||||
|
# special element
|
||||||
|
self._create_el_and_basic_asserts("template", interpreter=interpreter)
|
||||||
|
|
||||||
|
def test_textarea(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("textarea", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_tfoot(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("tfoot", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_th(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("th", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_thead(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("thead", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_time(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("time", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_title(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("title", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_tr(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("tr", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_track(self, interpreter):
|
||||||
|
properties = {
|
||||||
|
"src": "http://localhost:8080/somefile.vtt",
|
||||||
|
"kind": "subtitles",
|
||||||
|
"srclang": "en",
|
||||||
|
"label": "English",
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"track",
|
||||||
|
interpreter=interpreter,
|
||||||
|
properties=properties,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_u(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("u", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_ul(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("ul", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_var(self, interpreter):
|
||||||
|
self._create_el_and_basic_asserts("var", "some text", interpreter)
|
||||||
|
|
||||||
|
def test_video(self, interpreter):
|
||||||
|
properties = {
|
||||||
|
"src": "http://localhost:8080/somefile.ogg",
|
||||||
|
"controls": True,
|
||||||
|
"width": 250,
|
||||||
|
"height": 200,
|
||||||
|
}
|
||||||
|
self._create_el_and_basic_asserts(
|
||||||
|
"video",
|
||||||
|
interpreter=interpreter,
|
||||||
|
properties=properties,
|
||||||
|
expected_errors=self.expected_missing_file_errors,
|
||||||
|
)
|
||||||
3
pyscript.core/types/core.d.ts
vendored
3
pyscript.core/types/core.d.ts
vendored
@@ -1,6 +1,7 @@
|
|||||||
export function offline_interpreter(config: any): string;
|
export function offline_interpreter(config: any): string;
|
||||||
import { stdlib } from "./stdlib.js";
|
import { stdlib } from "./stdlib.js";
|
||||||
import { optional } from "./stdlib.js";
|
import { optional } from "./stdlib.js";
|
||||||
|
import { inputFailure } from "./hooks.js";
|
||||||
import TYPES from "./types.js";
|
import TYPES from "./types.js";
|
||||||
/**
|
/**
|
||||||
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
||||||
@@ -53,4 +54,4 @@ declare const exportedHooks: {
|
|||||||
};
|
};
|
||||||
declare const exportedConfig: {};
|
declare const exportedConfig: {};
|
||||||
declare const exportedWhenDefined: (type: string) => Promise<any>;
|
declare const exportedWhenDefined: (type: string) => Promise<any>;
|
||||||
export { stdlib, optional, TYPES, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };
|
export { stdlib, optional, inputFailure, TYPES, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };
|
||||||
|
|||||||
1
pyscript.core/types/hooks.d.ts
vendored
1
pyscript.core/types/hooks.d.ts
vendored
@@ -2,6 +2,7 @@ export function main(name: any): any;
|
|||||||
export function worker(name: any): any;
|
export function worker(name: any): any;
|
||||||
export function codeFor(branch: any, type: any): {};
|
export function codeFor(branch: any, type: any): {};
|
||||||
export function createFunction(self: any, name: any): any;
|
export function createFunction(self: any, name: any): any;
|
||||||
|
export const inputFailure: "\n import builtins\n def input(prompt=\"\"):\n raise Exception(\"\\n \".join([\n \"input() doesn't work when PyScript runs in the main thread.\",\n \"Consider using the worker attribute: https://pyscript.github.io/docs/2023.11.2/user-guide/workers/\"\n ]))\n\n builtins.input = input\n del builtins\n del input\n";
|
||||||
export namespace hooks {
|
export namespace hooks {
|
||||||
namespace main {
|
namespace main {
|
||||||
let onWorker: Set<Function>;
|
let onWorker: Set<Function>;
|
||||||
|
|||||||
2
pyscript.core/types/plugins/py-terminal/mpy.d.ts
vendored
Normal file
2
pyscript.core/types/plugins/py-terminal/mpy.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
declare function _default(element: any): Promise<void>;
|
||||||
|
export default _default;
|
||||||
2
pyscript.core/types/plugins/py-terminal/py.d.ts
vendored
Normal file
2
pyscript.core/types/plugins/py-terminal/py.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
declare function _default(element: any): Promise<void>;
|
||||||
|
export default _default;
|
||||||
4
pyscript.core/types/stdlib/pyscript.d.ts
vendored
4
pyscript.core/types/stdlib/pyscript.d.ts
vendored
@@ -13,6 +13,10 @@ declare namespace _default {
|
|||||||
"__init__.py": string;
|
"__init__.py": string;
|
||||||
"media.py": string;
|
"media.py": string;
|
||||||
"pydom.py": string;
|
"pydom.py": string;
|
||||||
|
ui: {
|
||||||
|
"__init__.py": string;
|
||||||
|
"elements.py": string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export default _default;
|
export default _default;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
black==23.11.0
|
black==24.4.2
|
||||||
isort==5.12.0
|
isort==5.13.2
|
||||||
pytest==7.1.2
|
pytest==8.2.1
|
||||||
pre-commit==3.5.0
|
pre-commit==3.7.1
|
||||||
playwright==1.33.0
|
playwright==1.44.0
|
||||||
pytest-playwright==0.3.3
|
pytest-playwright==0.5.0
|
||||||
pytest-xdist==3.3.0
|
pytest-xdist==3.6.1
|
||||||
pexpect==4.9.0
|
pexpect==4.9.0
|
||||||
pyodide_py==0.24.1
|
pyodide_py==0.24.1
|
||||||
micropip==0.5.0
|
micropip==0.5.0
|
||||||
toml==0.10.2
|
toml==0.10.2
|
||||||
numpy==1.26.2
|
numpy==1.26.4
|
||||||
pillow==10.1.0
|
pillow==10.3.0
|
||||||
|
|||||||
Reference in New Issue
Block a user