mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
999897df12 | ||
|
|
d47fb58ede | ||
|
|
f316341e73 | ||
|
|
8c46fcabf7 | ||
|
|
e4ff4d8fab | ||
|
|
f20a0003ed | ||
|
|
6c938dfe3b | ||
|
|
d884586a82 | ||
|
|
f8f7ba89c1 | ||
|
|
67d47511d5 | ||
|
|
6f49f18937 | ||
|
|
7b8ef7ebe2 | ||
|
|
461ae38763 | ||
|
|
4b90ebdef5 | ||
|
|
15c19aa708 | ||
|
|
d0406be84c | ||
|
|
aab015b9b8 | ||
|
|
a1e5a05b49 | ||
|
|
f1a787e031 |
@@ -38,11 +38,11 @@ To try PyScript, import the appropriate pyscript files into the `<head>` tag of
|
||||
<head>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://pyscript.net/releases/2024.5.2/core.css"
|
||||
href="https://pyscript.net/releases/2024.6.2/core.css"
|
||||
/>
|
||||
<script
|
||||
type="module"
|
||||
src="https://pyscript.net/releases/2024.5.2/core.js"
|
||||
src="https://pyscript.net/releases/2024.6.2/core.js"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
901
pyscript.core/package-lock.json
generated
901
pyscript.core/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.4.42",
|
||||
"version": "0.5.1",
|
||||
"type": "module",
|
||||
"description": "PyScript",
|
||||
"module": "./index.js",
|
||||
@@ -20,8 +20,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"server": "npx static-handler --coi .",
|
||||
"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": "export ESLINT_USE_FLAT_CONFIG=true;npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && if [ -z \"$NO_MIN\" ]; then eslint src/ && npm run ts && npm run test:mpy; fi",
|
||||
"build:core": "rm -rf dist && rollup --config rollup/core.config.js && cp src/3rd-party/*.css dist/",
|
||||
"build:flatted": "node rollup/flatted.cjs",
|
||||
"build:plugins": "node rollup/plugins.cjs",
|
||||
"build:stdlib": "node rollup/stdlib.cjs",
|
||||
"build:3rd-party": "node rollup/3rd-party.cjs",
|
||||
@@ -43,7 +44,7 @@
|
||||
"dependencies": {
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"polyscript": "^0.12.14",
|
||||
"polyscript": "^0.14.4",
|
||||
"sticky-module": "^0.1.1",
|
||||
"to-json-callback": "^0.1.1",
|
||||
"type-checked-collections": "^0.1.7"
|
||||
@@ -53,23 +54,24 @@
|
||||
"@codemirror/lang-python": "^6.1.6",
|
||||
"@codemirror/language": "^6.10.2",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/view": "^6.27.0",
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@rollup/plugin-commonjs": "^25.0.8",
|
||||
"@codemirror/view": "^6.29.1",
|
||||
"@playwright/test": "^1.45.3",
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@webreflection/toml-j0.4": "^1.1.3",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"bun": "^1.1.12",
|
||||
"bun": "^1.1.21",
|
||||
"chokidar": "^3.6.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"eslint": "^9.4.0",
|
||||
"rollup": "^4.18.0",
|
||||
"eslint": "^9.8.0",
|
||||
"flatted": "^3.3.1",
|
||||
"rollup": "^4.19.1",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
"static-handler": "^0.4.3",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript": "^5.5.4",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-readline": "^1.1.1"
|
||||
},
|
||||
|
||||
17
pyscript.core/rollup/flatted.cjs
Normal file
17
pyscript.core/rollup/flatted.cjs
Normal file
@@ -0,0 +1,17 @@
|
||||
const { writeFileSync, readFileSync } = require("node:fs");
|
||||
const { join } = require("node:path");
|
||||
|
||||
const flatted = "# https://www.npmjs.com/package/flatted\n\n";
|
||||
const source = join(
|
||||
__dirname,
|
||||
"..",
|
||||
"node_modules",
|
||||
"flatted",
|
||||
"python",
|
||||
"flatted.py",
|
||||
);
|
||||
const dest = join(__dirname, "..", "src", "stdlib", "pyscript", "flatted.py");
|
||||
|
||||
const clear = (str) => String(str).replace(/^#.*/gm, "").trimStart();
|
||||
|
||||
writeFileSync(dest, flatted + clear(readFileSync(source)));
|
||||
@@ -45,6 +45,8 @@ const configDetails = async (config, type) => {
|
||||
|
||||
const conflictError = (reason) => new Error(`(${CONFLICTING_CODE}): ${reason}`);
|
||||
|
||||
const relative_url = (url, base = location.href) => new URL(url, base).href;
|
||||
|
||||
const syntaxError = (type, url, { message }) => {
|
||||
let str = `(${BAD_CONFIG}): Invalid ${type}`;
|
||||
if (url) str += ` @ ${url}`;
|
||||
@@ -108,7 +110,7 @@ for (const [TYPE] of TYPES) {
|
||||
if (!error && config) {
|
||||
try {
|
||||
const { json, toml, text, url } = await configDetails(config, type);
|
||||
if (url) configURL = new URL(url, location.href).href;
|
||||
if (url) configURL = relative_url(url);
|
||||
config = text;
|
||||
if (json || type === "json") {
|
||||
try {
|
||||
@@ -153,4 +155,4 @@ for (const [TYPE] of TYPES) {
|
||||
configs.set(TYPE, { config: parsed, configURL, plugins, error });
|
||||
}
|
||||
|
||||
export default configs;
|
||||
export { configs, relative_url };
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
|
||||
import "./all-done.js";
|
||||
import TYPES from "./types.js";
|
||||
import configs from "./config.js";
|
||||
import { configs, relative_url } from "./config.js";
|
||||
import sync from "./sync.js";
|
||||
import bootstrapNodeAndPlugins from "./plugins-helper.js";
|
||||
import { ErrorCode } from "./exceptions.js";
|
||||
@@ -84,6 +84,7 @@ const [
|
||||
|
||||
export {
|
||||
TYPES,
|
||||
relative_url,
|
||||
exportedPyWorker as PyWorker,
|
||||
exportedMPWorker as MPWorker,
|
||||
exportedHooks as hooks,
|
||||
@@ -92,7 +93,7 @@ export {
|
||||
};
|
||||
|
||||
export const offline_interpreter = (config) =>
|
||||
config?.interpreter && new URL(config.interpreter, location.href).href;
|
||||
config?.interpreter && relative_url(config.interpreter);
|
||||
|
||||
const hooked = new Map();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// PyScript py-editor plugin
|
||||
import { Hook, XWorker, dedent, defineProperties } from "polyscript/exports";
|
||||
import { TYPES, offline_interpreter, stdlib } from "../core.js";
|
||||
import { TYPES, offline_interpreter, relative_url, 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>`;
|
||||
|
||||
@@ -34,14 +34,25 @@ async function execute({ currentTarget }) {
|
||||
|
||||
if (!envs.has(env)) {
|
||||
const srcLink = URL.createObjectURL(new Blob([""]));
|
||||
const details = { type: this.interpreter };
|
||||
const details = {
|
||||
type: this.interpreter,
|
||||
serviceWorker: this.serviceWorker,
|
||||
};
|
||||
const { config } = this;
|
||||
if (config) {
|
||||
details.configURL = config;
|
||||
const { parse } = config.endsWith(".toml")
|
||||
? await import(/* webpackIgnore: true */ "../3rd-party/toml.js")
|
||||
: JSON;
|
||||
details.config = parse(await fetch(config).then((r) => r.text()));
|
||||
details.configURL = relative_url(config);
|
||||
if (config.endsWith(".toml")) {
|
||||
const [{ parse }, toml] = await Promise.all([
|
||||
import(/* webpackIgnore: true */ "../3rd-party/toml.js"),
|
||||
fetch(config).then((r) => r.text()),
|
||||
]);
|
||||
details.config = parse(toml);
|
||||
} else if (config.endsWith(".json")) {
|
||||
details.config = await fetch(config).then((r) => r.json());
|
||||
} else {
|
||||
details.configURL = relative_url("./config.txt");
|
||||
details.config = JSON.parse(config);
|
||||
}
|
||||
details.version = offline_interpreter(details.config);
|
||||
} else {
|
||||
details.config = {};
|
||||
@@ -86,21 +97,24 @@ async function execute({ currentTarget }) {
|
||||
});
|
||||
}
|
||||
|
||||
const makeRunButton = (listener, type) => {
|
||||
const makeRunButton = (handler, type) => {
|
||||
const runButton = document.createElement("button");
|
||||
runButton.className = `absolute ${type}-editor-run-button`;
|
||||
runButton.innerHTML = RUN_BUTTON;
|
||||
runButton.setAttribute("aria-label", "Python Script Run Button");
|
||||
runButton.addEventListener("click", listener);
|
||||
runButton.addEventListener("click", async (event) => {
|
||||
runButton.blur();
|
||||
await handler.handleEvent(event);
|
||||
});
|
||||
return runButton;
|
||||
};
|
||||
|
||||
const makeEditorDiv = (listener, type) => {
|
||||
const makeEditorDiv = (handler, type) => {
|
||||
const editorDiv = document.createElement("div");
|
||||
editorDiv.className = `${type}-editor-input`;
|
||||
editorDiv.setAttribute("aria-label", "Python Script Area");
|
||||
|
||||
const runButton = makeRunButton(listener, type);
|
||||
const runButton = makeRunButton(handler, type);
|
||||
const editorShadowContainer = document.createElement("div");
|
||||
|
||||
// avoid outer elements intercepting key events (reveal as example)
|
||||
@@ -120,15 +134,15 @@ const makeOutDiv = (type) => {
|
||||
return outDiv;
|
||||
};
|
||||
|
||||
const makeBoxDiv = (listener, type) => {
|
||||
const makeBoxDiv = (handler, type) => {
|
||||
const boxDiv = document.createElement("div");
|
||||
boxDiv.className = `${type}-editor-box`;
|
||||
|
||||
const editorDiv = makeEditorDiv(listener, type);
|
||||
const editorDiv = makeEditorDiv(handler, type);
|
||||
const outDiv = makeOutDiv(type);
|
||||
boxDiv.append(editorDiv, outDiv);
|
||||
|
||||
return [boxDiv, outDiv];
|
||||
return [boxDiv, outDiv, editorDiv.querySelector("button")];
|
||||
};
|
||||
|
||||
const init = async (script, type, interpreter) => {
|
||||
@@ -138,7 +152,7 @@ const init = async (script, type, interpreter) => {
|
||||
{ python },
|
||||
{ indentUnit },
|
||||
{ keymap },
|
||||
{ defaultKeymap },
|
||||
{ defaultKeymap, indentWithTab },
|
||||
] = await Promise.all([
|
||||
import(/* webpackIgnore: true */ "../3rd-party/codemirror.js"),
|
||||
import(/* webpackIgnore: true */ "../3rd-party/codemirror_state.js"),
|
||||
@@ -152,8 +166,17 @@ const init = async (script, type, interpreter) => {
|
||||
|
||||
let isSetup = script.hasAttribute("setup");
|
||||
const hasConfig = script.hasAttribute("config");
|
||||
const serviceWorker = script.getAttribute("service-worker");
|
||||
const env = `${interpreter}-${script.getAttribute("env") || getID(type)}`;
|
||||
|
||||
// helps preventing too lazy ServiceWorker initialization on button run
|
||||
if (serviceWorker) {
|
||||
new XWorker("data:application/javascript,postMessage(0)", {
|
||||
type: "dummy",
|
||||
serviceWorker,
|
||||
}).onmessage = ({ target }) => target.terminate();
|
||||
}
|
||||
|
||||
if (hasConfig && configs.has(env)) {
|
||||
throw new SyntaxError(
|
||||
configs.get(env)
|
||||
@@ -168,11 +191,12 @@ const init = async (script, type, interpreter) => {
|
||||
? await fetch(script.src).then((b) => b.text())
|
||||
: script.textContent;
|
||||
const context = {
|
||||
// allow the listener to be overridden at distance
|
||||
handleEvent: execute,
|
||||
serviceWorker,
|
||||
interpreter,
|
||||
env,
|
||||
config:
|
||||
hasConfig &&
|
||||
new URL(script.getAttribute("config"), location.href).href,
|
||||
config: hasConfig && script.getAttribute("config"),
|
||||
get pySrc() {
|
||||
return isSetup ? source : editor.state.doc.toString();
|
||||
},
|
||||
@@ -184,6 +208,29 @@ const init = async (script, type, interpreter) => {
|
||||
let target;
|
||||
defineProperties(script, {
|
||||
target: { get: () => target },
|
||||
handleEvent: {
|
||||
get: () => context.handleEvent,
|
||||
set: (callback) => {
|
||||
// do not bother with logic if it was set back as its original handler
|
||||
if (callback === execute) context.handleEvent = execute;
|
||||
// in every other case be sure that if the listener override returned
|
||||
// `false` nothing happens, otherwise keep doing what it always did
|
||||
else {
|
||||
context.handleEvent = async (event) => {
|
||||
// trap the currentTarget ASAP (if any)
|
||||
// otherwise it gets lost asynchronously
|
||||
const { currentTarget } = event;
|
||||
// augment a code snapshot before invoking the override
|
||||
defineProperties(event, {
|
||||
code: { value: context.pySrc },
|
||||
});
|
||||
// avoid executing the default handler if the override returned `false`
|
||||
if ((await callback(event)) !== false)
|
||||
await execute.call(context, { currentTarget });
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
code: {
|
||||
get: () => context.pySrc,
|
||||
set: (insert) => {
|
||||
@@ -214,8 +261,8 @@ const init = async (script, type, interpreter) => {
|
||||
isSetup = wasSetup;
|
||||
source = wasSource;
|
||||
};
|
||||
return execute
|
||||
.call(context, { currentTarget: null })
|
||||
return context
|
||||
.handleEvent({ currentTarget: null })
|
||||
.then(restore, restore);
|
||||
},
|
||||
},
|
||||
@@ -227,7 +274,7 @@ const init = async (script, type, interpreter) => {
|
||||
};
|
||||
|
||||
if (isSetup) {
|
||||
await execute.call(context, { currentTarget: null });
|
||||
await context.handleEvent({ currentTarget: null });
|
||||
notify();
|
||||
return;
|
||||
}
|
||||
@@ -250,8 +297,7 @@ const init = async (script, type, interpreter) => {
|
||||
if (!target.hasAttribute("root")) target.setAttribute("root", target.id);
|
||||
|
||||
// @see https://github.com/JeffersGlass/mkdocs-pyscript/blob/main/mkdocs_pyscript/js/makeblocks.js
|
||||
const listener = execute.bind(context);
|
||||
const [boxDiv, outDiv] = makeBoxDiv(listener, type);
|
||||
const [boxDiv, outDiv, runButton] = makeBoxDiv(context, type);
|
||||
boxDiv.dataset.env = script.hasAttribute("env") ? env : interpreter;
|
||||
|
||||
const inputChild = boxDiv.querySelector(`.${type}-editor-input > div`);
|
||||
@@ -264,8 +310,9 @@ const init = async (script, type, interpreter) => {
|
||||
const doc = dedent(script.textContent).trim();
|
||||
|
||||
// preserve user indentation, if any
|
||||
const indentation = /^(\s+)/m.test(doc) ? RegExp.$1 : " ";
|
||||
const indentation = /^([ \t]+)/m.test(doc) ? RegExp.$1 : " ";
|
||||
|
||||
const listener = () => runButton.click();
|
||||
const editor = new EditorView({
|
||||
extensions: [
|
||||
indentUnit.of(indentation),
|
||||
@@ -275,9 +322,13 @@ const init = async (script, type, interpreter) => {
|
||||
{ key: "Ctrl-Enter", run: listener, preventDefault: true },
|
||||
{ key: "Cmd-Enter", run: listener, preventDefault: true },
|
||||
{ key: "Shift-Enter", run: listener, preventDefault: true },
|
||||
// @see https://codemirror.net/examples/tab/
|
||||
indentWithTab,
|
||||
]),
|
||||
basicSetup,
|
||||
],
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
parent,
|
||||
doc,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// PyScript py-terminal plugin
|
||||
import { TYPES } from "../core.js";
|
||||
import { TYPES, relative_url } from "../core.js";
|
||||
import { notify } from "./error.js";
|
||||
import { customObserver } from "polyscript/exports";
|
||||
|
||||
@@ -35,7 +35,7 @@ for (const type of TYPES.keys()) {
|
||||
document.head.append(
|
||||
Object.assign(document.createElement("link"), {
|
||||
rel: "stylesheet",
|
||||
href: new URL("./xterm.css", import.meta.url),
|
||||
href: relative_url("./xterm.css", import.meta.url),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,11 +49,12 @@ const workerReady = ({ interpreter, io, run, type }, { sync }) => {
|
||||
|
||||
const writer = encoder.writable.getWriter();
|
||||
sync.pyterminal_stream_write = (buffer) => writer.write(buffer);
|
||||
pyterminal_ready();
|
||||
|
||||
interpreter.replInit();
|
||||
},
|
||||
});
|
||||
|
||||
pyterminal_ready();
|
||||
};
|
||||
|
||||
export default async (element) => {
|
||||
@@ -163,13 +164,25 @@ export default async (element) => {
|
||||
};
|
||||
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 = "";
|
||||
// handle backspace on input
|
||||
if (buffer === "\x7f") {
|
||||
// avoid over-greedy backspace
|
||||
if (readChunks.length) {
|
||||
readChunks = readChunks.slice(0, -1);
|
||||
// override previous char position
|
||||
// put an empty space to clear the char
|
||||
// move back position again
|
||||
buffer = "\b \b";
|
||||
} else buffer = "";
|
||||
} else readChunks += buffer;
|
||||
if (buffer) {
|
||||
terminal.write(buffer);
|
||||
if (readChunks.endsWith("\r")) {
|
||||
terminal.write("\n");
|
||||
promisedChunks.resolve(readChunks.slice(0, -1));
|
||||
promisedChunks = null;
|
||||
readChunks = "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stream.write(buffer);
|
||||
|
||||
@@ -43,8 +43,12 @@ from pyscript.magic_js import (
|
||||
sync,
|
||||
window,
|
||||
)
|
||||
from pyscript.storage import Storage, storage
|
||||
from pyscript.websocket import WebSocket
|
||||
|
||||
if not RUNNING_IN_WORKER:
|
||||
from pyscript.workers import create_named_worker, workers
|
||||
|
||||
try:
|
||||
from pyscript.event_handling import when
|
||||
except:
|
||||
|
||||
@@ -19,22 +19,23 @@ def when(event_type=None, selector=None):
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
|
||||
from pyscript.web import Element, ElementCollection
|
||||
|
||||
if isinstance(selector, str):
|
||||
elements = document.querySelectorAll(selector)
|
||||
# TODO: This is a hack that will be removed when pyscript becomes a package
|
||||
# and we can better manage the imports without circular dependencies
|
||||
elif isinstance(selector, Element):
|
||||
elements = [selector._dom_element]
|
||||
elif isinstance(selector, ElementCollection):
|
||||
elements = [el._dom_element for el in selector]
|
||||
else:
|
||||
# TODO: This is a hack that will be removed when pyscript becomes a package
|
||||
# and we can better manage the imports without circular dependencies
|
||||
from pyweb import pydom
|
||||
|
||||
if isinstance(selector, pydom.Element):
|
||||
elements = [selector._js]
|
||||
elif isinstance(selector, pydom.ElementCollection):
|
||||
elements = [el._js for el in selector]
|
||||
if isinstance(selector, list):
|
||||
elements = selector
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid selector: {selector}. Selector must"
|
||||
" be a string, a pydom.Element or a pydom.ElementCollection."
|
||||
)
|
||||
elements = [selector]
|
||||
|
||||
try:
|
||||
sig = inspect.signature(func)
|
||||
# Function doesn't receive events
|
||||
|
||||
148
pyscript.core/src/stdlib/pyscript/flatted.py
Normal file
148
pyscript.core/src/stdlib/pyscript/flatted.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# https://www.npmjs.com/package/flatted
|
||||
|
||||
import json as _json
|
||||
|
||||
|
||||
class _Known:
|
||||
def __init__(self):
|
||||
self.key = []
|
||||
self.value = []
|
||||
|
||||
|
||||
class _String:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
|
||||
def _array_keys(value):
|
||||
keys = []
|
||||
i = 0
|
||||
for _ in value:
|
||||
keys.append(i)
|
||||
i += 1
|
||||
return keys
|
||||
|
||||
|
||||
def _object_keys(value):
|
||||
keys = []
|
||||
for key in value:
|
||||
keys.append(key)
|
||||
return keys
|
||||
|
||||
|
||||
def _is_array(value):
|
||||
return isinstance(value, list) or isinstance(value, tuple)
|
||||
|
||||
|
||||
def _is_object(value):
|
||||
return isinstance(value, dict)
|
||||
|
||||
|
||||
def _is_string(value):
|
||||
return isinstance(value, str)
|
||||
|
||||
|
||||
def _index(known, input, value):
|
||||
input.append(value)
|
||||
index = str(len(input) - 1)
|
||||
known.key.append(value)
|
||||
known.value.append(index)
|
||||
return index
|
||||
|
||||
|
||||
def _loop(keys, input, known, output):
|
||||
for key in keys:
|
||||
value = output[key]
|
||||
if isinstance(value, _String):
|
||||
_ref(key, input[int(value.value)], input, known, output)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def _ref(key, value, input, known, output):
|
||||
if _is_array(value) and not value in known:
|
||||
known.append(value)
|
||||
value = _loop(_array_keys(value), input, known, value)
|
||||
elif _is_object(value) and not value in known:
|
||||
known.append(value)
|
||||
value = _loop(_object_keys(value), input, known, value)
|
||||
|
||||
output[key] = value
|
||||
|
||||
|
||||
def _relate(known, input, value):
|
||||
if _is_string(value) or _is_array(value) or _is_object(value):
|
||||
try:
|
||||
return known.value[known.key.index(value)]
|
||||
except:
|
||||
return _index(known, input, value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _transform(known, input, value):
|
||||
if _is_array(value):
|
||||
output = []
|
||||
for val in value:
|
||||
output.append(_relate(known, input, val))
|
||||
return output
|
||||
|
||||
if _is_object(value):
|
||||
obj = {}
|
||||
for key in value:
|
||||
obj[key] = _relate(known, input, value[key])
|
||||
return obj
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _wrap(value):
|
||||
if _is_string(value):
|
||||
return _String(value)
|
||||
|
||||
if _is_array(value):
|
||||
i = 0
|
||||
for val in value:
|
||||
value[i] = _wrap(val)
|
||||
i += 1
|
||||
|
||||
elif _is_object(value):
|
||||
for key in value:
|
||||
value[key] = _wrap(value[key])
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def parse(value, *args, **kwargs):
|
||||
json = _json.loads(value, *args, **kwargs)
|
||||
wrapped = []
|
||||
for value in json:
|
||||
wrapped.append(_wrap(value))
|
||||
|
||||
input = []
|
||||
for value in wrapped:
|
||||
if isinstance(value, _String):
|
||||
input.append(value.value)
|
||||
else:
|
||||
input.append(value)
|
||||
|
||||
value = input[0]
|
||||
|
||||
if _is_array(value):
|
||||
return _loop(_array_keys(value), input, [value], value)
|
||||
|
||||
if _is_object(value):
|
||||
return _loop(_object_keys(value), input, [value], value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def stringify(value, *args, **kwargs):
|
||||
known = _Known()
|
||||
input = []
|
||||
output = []
|
||||
i = int(_index(known, input, value))
|
||||
while i < len(input):
|
||||
output.append(_transform(known, input, input[i]))
|
||||
i += 1
|
||||
return _json.dumps(output, *args, **kwargs)
|
||||
@@ -36,7 +36,6 @@ if RUNNING_IN_WORKER:
|
||||
)
|
||||
|
||||
try:
|
||||
globalThis.SharedArrayBuffer.new(4)
|
||||
import js
|
||||
|
||||
window = polyscript.xworker.window
|
||||
@@ -47,17 +46,11 @@ if RUNNING_IN_WORKER:
|
||||
"return (...urls) => Promise.all(urls.map((url) => import(url)))"
|
||||
)()
|
||||
except:
|
||||
globalThis.console.debug("SharedArrayBuffer is not available")
|
||||
# in this scenario none of the utilities would work
|
||||
# as expected so we better export these as NotSupported
|
||||
window = NotSupported(
|
||||
"pyscript.window",
|
||||
"pyscript.window in workers works only via SharedArrayBuffer",
|
||||
)
|
||||
document = NotSupported(
|
||||
"pyscript.document",
|
||||
"pyscript.document in workers works only via SharedArrayBuffer",
|
||||
)
|
||||
message = "Unable to use `window` or `document` -> https://docs.pyscript.net/latest/faq/#sharedarraybuffer"
|
||||
globalThis.console.warn(message)
|
||||
window = NotSupported("pyscript.window", message)
|
||||
document = NotSupported("pyscript.document", message)
|
||||
js_import = None
|
||||
|
||||
sync = polyscript.xworker.sync
|
||||
|
||||
|
||||
60
pyscript.core/src/stdlib/pyscript/storage.py
Normal file
60
pyscript.core/src/stdlib/pyscript/storage.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from polyscript import storage as _storage
|
||||
from pyscript.flatted import parse as _parse
|
||||
from pyscript.flatted import stringify as _stringify
|
||||
|
||||
|
||||
# convert a Python value into an IndexedDB compatible entry
|
||||
def _to_idb(value):
|
||||
if value is None:
|
||||
return _stringify(["null", 0])
|
||||
if isinstance(value, (bool, float, int, str, list, dict, tuple)):
|
||||
return _stringify(["generic", value])
|
||||
if isinstance(value, bytearray):
|
||||
return _stringify(["bytearray", [v for v in value]])
|
||||
if isinstance(value, memoryview):
|
||||
return _stringify(["memoryview", [v for v in value]])
|
||||
raise TypeError(f"Unexpected value: {value}")
|
||||
|
||||
|
||||
# convert an IndexedDB compatible entry into a Python value
|
||||
def _from_idb(value):
|
||||
(
|
||||
kind,
|
||||
result,
|
||||
) = _parse(value)
|
||||
if kind == "null":
|
||||
return None
|
||||
if kind == "generic":
|
||||
return result
|
||||
if kind == "bytearray":
|
||||
return bytearray(result)
|
||||
if kind == "memoryview":
|
||||
return memoryview(bytearray(result))
|
||||
return value
|
||||
|
||||
|
||||
class Storage(dict):
|
||||
def __init__(self, store):
|
||||
super().__init__({k: _from_idb(v) for k, v in store.entries()})
|
||||
self.__store__ = store
|
||||
|
||||
def __delitem__(self, attr):
|
||||
self.__store__.delete(attr)
|
||||
super().__delitem__(attr)
|
||||
|
||||
def __setitem__(self, attr, value):
|
||||
self.__store__.set(attr, _to_idb(value))
|
||||
super().__setitem__(attr, value)
|
||||
|
||||
def clear(self):
|
||||
self.__store__.clear()
|
||||
super().clear()
|
||||
|
||||
async def sync(self):
|
||||
await self.__store__.sync()
|
||||
|
||||
|
||||
async def storage(name="", storage_class=Storage):
|
||||
if not name:
|
||||
raise ValueError("The storage name must be defined")
|
||||
return storage_class(await _storage(f"@pyscript/{name}"))
|
||||
1176
pyscript.core/src/stdlib/pyscript/web.py
Normal file
1176
pyscript.core/src/stdlib/pyscript/web.py
Normal file
File diff suppressed because it is too large
Load Diff
43
pyscript.core/src/stdlib/pyscript/workers.py
Normal file
43
pyscript.core/src/stdlib/pyscript/workers.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import js as _js
|
||||
from polyscript import workers as _workers
|
||||
|
||||
_get = _js.Reflect.get
|
||||
|
||||
|
||||
def _set(script, name, value=""):
|
||||
script.setAttribute(name, value)
|
||||
|
||||
|
||||
# this solves an inconsistency between Pyodide and MicroPython
|
||||
# @see https://github.com/pyscript/pyscript/issues/2106
|
||||
class _ReadOnlyProxy:
|
||||
def __getitem__(self, name):
|
||||
return _get(_workers, name)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return _get(_workers, name)
|
||||
|
||||
|
||||
workers = _ReadOnlyProxy()
|
||||
|
||||
|
||||
async def create_named_worker(src="", name="", config=None, type="py"):
|
||||
from json import dumps
|
||||
|
||||
if not src:
|
||||
raise ValueError("Named workers require src")
|
||||
|
||||
if not name:
|
||||
raise ValueError("Named workers require a name")
|
||||
|
||||
s = _js.document.createElement("script")
|
||||
s.type = type
|
||||
s.src = src
|
||||
_set(s, "worker")
|
||||
_set(s, "name", name)
|
||||
|
||||
if config:
|
||||
_set(s, "config", isinstance(config, str) and config or dumps(config))
|
||||
|
||||
_js.document.body.append(s)
|
||||
return await workers[name]
|
||||
@@ -1,2 +0,0 @@
|
||||
from .pydom import JSProperty
|
||||
from .pydom import dom as pydom
|
||||
@@ -1,95 +0,0 @@
|
||||
from pyodide.ffi import to_js
|
||||
from pyscript import window
|
||||
|
||||
|
||||
class Device:
|
||||
"""Device represents a media input or output device, such as a microphone,
|
||||
camera, or headset.
|
||||
"""
|
||||
|
||||
def __init__(self, device):
|
||||
self._js = device
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._js.deviceId
|
||||
|
||||
@property
|
||||
def group(self):
|
||||
return self._js.groupId
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
return self._js.kind
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
return self._js.label
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
@classmethod
|
||||
async def load(cls, audio=False, video=True):
|
||||
"""Load the device stream."""
|
||||
options = window.Object.new()
|
||||
options.audio = audio
|
||||
if isinstance(video, bool):
|
||||
options.video = video
|
||||
else:
|
||||
# TODO: Think this can be simplified but need to check it on the pyodide side
|
||||
|
||||
# TODO: this is pyodide specific. shouldn't be!
|
||||
options.video = window.Object.new()
|
||||
for k in video:
|
||||
setattr(
|
||||
options.video,
|
||||
k,
|
||||
to_js(video[k], dict_converter=window.Object.fromEntries),
|
||||
)
|
||||
|
||||
stream = await window.navigator.mediaDevices.getUserMedia(options)
|
||||
return stream
|
||||
|
||||
async def get_stream(self):
|
||||
key = self.kind.replace("input", "").replace("output", "")
|
||||
options = {key: {"deviceId": {"exact": self.id}}}
|
||||
|
||||
return await self.load(**options)
|
||||
|
||||
|
||||
async def list_devices() -> list[dict]:
|
||||
"""
|
||||
Return the list of the currently available media input and output devices,
|
||||
such as microphones, cameras, headsets, and so forth.
|
||||
|
||||
Output:
|
||||
|
||||
list(dict) - list of dictionaries representing the available media devices.
|
||||
Each dictionary has the following keys:
|
||||
* deviceId: a string that is an identifier for the represented device
|
||||
that is persisted across sessions. It is un-guessable by other
|
||||
applications and unique to the origin of the calling application.
|
||||
It is reset when the user clears cookies (for Private Browsing, a
|
||||
different identifier is used that is not persisted across sessions).
|
||||
|
||||
* groupId: a string that is a group identifier. Two devices have the same
|
||||
group identifier if they belong to the same physical device — for
|
||||
example a monitor with both a built-in camera and a microphone.
|
||||
|
||||
* kind: an enumerated value that is either "videoinput", "audioinput"
|
||||
or "audiooutput".
|
||||
|
||||
* label: a string describing this device (for example "External USB
|
||||
Webcam").
|
||||
|
||||
Note: the returned list will omit any devices that are blocked by the document
|
||||
Permission Policy: microphone, camera, speaker-selection (for output devices),
|
||||
and so on. Access to particular non-default devices is also gated by the
|
||||
Permissions API, and the list will omit devices for which the user has not
|
||||
granted explicit permission.
|
||||
"""
|
||||
# https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices
|
||||
return [
|
||||
Device(obj) for obj in await window.navigator.mediaDevices.enumerateDevices()
|
||||
]
|
||||
@@ -1,569 +0,0 @@
|
||||
import inspect
|
||||
|
||||
try:
|
||||
from typing import Any
|
||||
except ImportError:
|
||||
Any = "Any"
|
||||
|
||||
try:
|
||||
import warnings
|
||||
except ImportError:
|
||||
# TODO: For now it probably means we are in MicroPython. We should figure
|
||||
# out the "right" way to handle this. For now we just ignore the warning
|
||||
# and logging to console
|
||||
class warnings:
|
||||
@staticmethod
|
||||
def warn(*args, **kwargs):
|
||||
print("WARNING: ", *args, **kwargs)
|
||||
|
||||
|
||||
try:
|
||||
from functools import cached_property
|
||||
except ImportError:
|
||||
# TODO: same comment about micropython as above
|
||||
cached_property = property
|
||||
|
||||
try:
|
||||
from pyodide.ffi import JsProxy
|
||||
except ImportError:
|
||||
# TODO: same comment about micropython as above
|
||||
def JsProxy(obj):
|
||||
return obj
|
||||
|
||||
|
||||
from pyscript import display, document, window
|
||||
|
||||
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:
|
||||
def __init__(self, js_element):
|
||||
self._js = js_element
|
||||
self._parent = None
|
||||
self.style = StyleProxy(self)
|
||||
self._proxies = {}
|
||||
|
||||
def __eq__(self, obj):
|
||||
"""Check if the element is the same as the other element by comparing
|
||||
the underlying JS element"""
|
||||
return isinstance(obj, BaseElement) and obj._js == self._js
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self._parent:
|
||||
return self._parent
|
||||
|
||||
if self._js.parentElement:
|
||||
self._parent = self.__class__(self._js.parentElement)
|
||||
|
||||
return self._parent
|
||||
|
||||
@property
|
||||
def __class(self):
|
||||
return self.__class__ if self.__class__ != PyDom else Element
|
||||
|
||||
def create(self, type_, is_child=True, classes=None, html=None, label=None):
|
||||
js_el = document.createElement(type_)
|
||||
element = self.__class(js_el)
|
||||
|
||||
if classes:
|
||||
for class_ in classes:
|
||||
element.add_class(class_)
|
||||
|
||||
if html is not None:
|
||||
element.html = html
|
||||
|
||||
if label is not None:
|
||||
element.label = label
|
||||
|
||||
if is_child:
|
||||
self.append(element)
|
||||
|
||||
return element
|
||||
|
||||
def find(self, selector):
|
||||
"""Return an ElementCollection representing all the child elements that
|
||||
match the specified selector.
|
||||
|
||||
Args:
|
||||
selector (str): A string containing a selector expression
|
||||
|
||||
Returns:
|
||||
ElementCollection: A collection of elements matching the selector
|
||||
"""
|
||||
elements = self._js.querySelectorAll(selector)
|
||||
if not elements:
|
||||
return None
|
||||
return ElementCollection([Element(el) for el in elements])
|
||||
|
||||
|
||||
class Element(BaseElement):
|
||||
@property
|
||||
def children(self):
|
||||
return [self.__class__(el) for el in self._js.children]
|
||||
|
||||
def append(self, child):
|
||||
# TODO: this is Pyodide specific for now!!!!!!
|
||||
# if we get passed a JSProxy Element directly we just map it to the
|
||||
# higher level Python element
|
||||
if inspect.isclass(JsProxy) and isinstance(child, JsProxy):
|
||||
return self.append(Element(child))
|
||||
|
||||
elif isinstance(child, Element):
|
||||
self._js.appendChild(child._js)
|
||||
|
||||
return child
|
||||
|
||||
elif isinstance(child, ElementCollection):
|
||||
for el in child:
|
||||
self.append(el)
|
||||
|
||||
# -------- Pythonic Interface to Element -------- #
|
||||
@property
|
||||
def html(self):
|
||||
return self._js.innerHTML
|
||||
|
||||
@html.setter
|
||||
def html(self, value):
|
||||
self._js.innerHTML = value
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._js.textContent
|
||||
|
||||
@text.setter
|
||||
def text(self, value):
|
||||
self._js.textContent = value
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
# TODO: This breaks with with standard template elements. Define how to best
|
||||
# handle this specifica use case. Just not support for now?
|
||||
if self._js.tagName == "TEMPLATE":
|
||||
warnings.warn(
|
||||
"Content attribute not supported for template elements.", stacklevel=2
|
||||
)
|
||||
return None
|
||||
return self._js.innerHTML
|
||||
|
||||
@content.setter
|
||||
def content(self, value):
|
||||
# TODO: (same comment as above)
|
||||
if self._js.tagName == "TEMPLATE":
|
||||
warnings.warn(
|
||||
"Content attribute not supported for template elements.", stacklevel=2
|
||||
)
|
||||
return
|
||||
|
||||
display(value, target=self.id)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._js.id
|
||||
|
||||
@id.setter
|
||||
def id(self, value):
|
||||
self._js.id = value
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
if "options" in self._proxies:
|
||||
return self._proxies["options"]
|
||||
|
||||
if not self._js.tagName.lower() in {"select", "datalist", "optgroup"}:
|
||||
raise AttributeError(
|
||||
f"Element {self._js.tagName} has no options attribute."
|
||||
)
|
||||
self._proxies["options"] = OptionsProxy(self)
|
||||
return self._proxies["options"]
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._js.value
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
# in order to avoid confusion to the user, we don't allow setting the
|
||||
# value of elements that don't have a value attribute
|
||||
if not hasattr(self._js, "value"):
|
||||
raise AttributeError(
|
||||
f"Element {self._js.tagName} has no value attribute. If you want to "
|
||||
"force a value attribute, set it directly using the `_js.value = <value>` "
|
||||
"javascript API attribute instead."
|
||||
)
|
||||
self._js.value = value
|
||||
|
||||
@property
|
||||
def selected(self):
|
||||
return self._js.selected
|
||||
|
||||
@selected.setter
|
||||
def selected(self, value):
|
||||
# in order to avoid confusion to the user, we don't allow setting the
|
||||
# value of elements that don't have a value attribute
|
||||
if not hasattr(self._js, "selected"):
|
||||
raise AttributeError(
|
||||
f"Element {self._js.tagName} has no value attribute. If you want to "
|
||||
"force a value attribute, set it directly using the `_js.value = <value>` "
|
||||
"javascript API attribute instead."
|
||||
)
|
||||
self._js.selected = value
|
||||
|
||||
def clone(self, new_id=None):
|
||||
clone = Element(self._js.cloneNode(True))
|
||||
clone.id = new_id
|
||||
|
||||
return clone
|
||||
|
||||
def remove_class(self, classname):
|
||||
classList = self._js.classList
|
||||
if isinstance(classname, list):
|
||||
classList.remove(*classname)
|
||||
else:
|
||||
classList.remove(classname)
|
||||
return self
|
||||
|
||||
def add_class(self, classname):
|
||||
classList = self._js.classList
|
||||
if isinstance(classname, list):
|
||||
classList.add(*classname)
|
||||
else:
|
||||
self._js.classList.add(classname)
|
||||
return self
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
classes = self._js.classList.values()
|
||||
return [x for x in classes]
|
||||
|
||||
def show_me(self):
|
||||
self._js.scrollIntoView()
|
||||
|
||||
def snap(
|
||||
self,
|
||||
to: BaseElement | str = None,
|
||||
width: int | None = None,
|
||||
height: int | None = None,
|
||||
):
|
||||
"""
|
||||
Captures a snapshot of a video element. (Only available for video elements)
|
||||
|
||||
Inputs:
|
||||
|
||||
* to: element where to save the snapshot of the video frame to
|
||||
* width: width of the image
|
||||
* height: height of the image
|
||||
|
||||
Output:
|
||||
(Element) canvas element where the video frame snapshot was drawn into
|
||||
"""
|
||||
if self._js.tagName != "VIDEO":
|
||||
raise AttributeError("Snap method is only available for video Elements")
|
||||
|
||||
if to is None:
|
||||
canvas = self.create("canvas")
|
||||
if width is None:
|
||||
width = self._js.width
|
||||
if height is None:
|
||||
height = self._js.height
|
||||
canvas._js.width = width
|
||||
canvas._js.height = height
|
||||
|
||||
elif isinstance(to, Element):
|
||||
if to._js.tagName != "CANVAS":
|
||||
raise TypeError("Element to snap to must a canvas.")
|
||||
canvas = to
|
||||
elif getattr(to, "tagName", "") == "CANVAS":
|
||||
canvas = Element(to)
|
||||
elif isinstance(to, str):
|
||||
canvas = pydom[to][0]
|
||||
if canvas._js.tagName != "CANVAS":
|
||||
raise TypeError("Element to snap to must a be canvas.")
|
||||
|
||||
canvas.draw(self, width, height)
|
||||
|
||||
return canvas
|
||||
|
||||
def download(self, filename: str = "snapped.png") -> None:
|
||||
"""Download the current element (only available for canvas elements) with the filename
|
||||
provided in input.
|
||||
|
||||
Inputs:
|
||||
* filename (str): name of the file being downloaded
|
||||
|
||||
Output:
|
||||
None
|
||||
"""
|
||||
if self._js.tagName != "CANVAS":
|
||||
raise AttributeError(
|
||||
"The download method is only available for canvas Elements"
|
||||
)
|
||||
|
||||
link = self.create("a")
|
||||
link._js.download = filename
|
||||
link._js.href = self._js.toDataURL()
|
||||
link._js.click()
|
||||
|
||||
def draw(self, what, width, height):
|
||||
"""Draw `what` on the current element (only available for canvas elements).
|
||||
|
||||
Inputs:
|
||||
|
||||
* what (canvas image source): An element to draw into the context. The specification permits any canvas
|
||||
image source, specifically, an HTMLImageElement, an SVGImageElement, an HTMLVideoElement,
|
||||
an HTMLCanvasElement, an ImageBitmap, an OffscreenCanvas, or a VideoFrame.
|
||||
"""
|
||||
if self._js.tagName != "CANVAS":
|
||||
raise AttributeError(
|
||||
"The draw method is only available for canvas Elements"
|
||||
)
|
||||
|
||||
if isinstance(what, Element):
|
||||
what = what._js
|
||||
|
||||
# https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
|
||||
self._js.getContext("2d").drawImage(what, 0, 0, width, height)
|
||||
|
||||
|
||||
class OptionsProxy:
|
||||
"""This class represents the options of a select element. It
|
||||
allows to access to add and remove options by using the `add` and `remove` methods.
|
||||
"""
|
||||
|
||||
def __init__(self, element: Element) -> None:
|
||||
self._element = element
|
||||
if self._element._js.tagName.lower() != "select":
|
||||
raise AttributeError(
|
||||
f"Element {self._element._js.tagName} has no options attribute."
|
||||
)
|
||||
|
||||
def add(
|
||||
self,
|
||||
value: Any = None,
|
||||
html: str = None,
|
||||
text: str = None,
|
||||
before: Element | int = None,
|
||||
**kws,
|
||||
) -> None:
|
||||
"""Add a new option to the select element"""
|
||||
# create the option element and set the attributes
|
||||
option = document.createElement("option")
|
||||
if value is not None:
|
||||
kws["value"] = value
|
||||
if html is not None:
|
||||
option.innerHTML = html
|
||||
if text is not None:
|
||||
kws["text"] = text
|
||||
|
||||
for key, value in kws.items():
|
||||
option.setAttribute(key, value)
|
||||
|
||||
if before:
|
||||
if isinstance(before, Element):
|
||||
before = before._js
|
||||
|
||||
self._element._js.add(option, before)
|
||||
|
||||
def remove(self, item: int) -> None:
|
||||
"""Remove the option at the specified index"""
|
||||
self._element._js.remove(item)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Remove all the options"""
|
||||
for i in range(len(self)):
|
||||
self.remove(0)
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
"""Return the list of options"""
|
||||
return [Element(opt) for opt in self._element._js.options]
|
||||
|
||||
@property
|
||||
def selected(self):
|
||||
"""Return the selected option"""
|
||||
return self.options[self._element._js.selectedIndex]
|
||||
|
||||
def __iter__(self):
|
||||
yield from self.options
|
||||
|
||||
def __len__(self):
|
||||
return len(self.options)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__} (length: {len(self)}) {self.options}"
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.options[key]
|
||||
|
||||
|
||||
class StyleProxy: # (dict):
|
||||
def __init__(self, element: Element) -> None:
|
||||
self._element = element
|
||||
|
||||
@cached_property
|
||||
def _style(self):
|
||||
return self._element._js.style
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._style.getPropertyValue(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._style.setProperty(key, value)
|
||||
|
||||
def remove(self, key):
|
||||
self._style.removeProperty(key)
|
||||
|
||||
def set(self, **kws):
|
||||
for k, v in kws.items():
|
||||
self._element._js.style.setProperty(k, v)
|
||||
|
||||
# CSS Properties
|
||||
# Reference: https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L3799C1-L5005C2
|
||||
# Following prperties automatically generated from the above reference using
|
||||
# tools/codegen_css_proxy.py
|
||||
@property
|
||||
def visible(self):
|
||||
return self._element._js.style.visibility
|
||||
|
||||
@visible.setter
|
||||
def visible(self, value):
|
||||
self._element._js.style.visibility = value
|
||||
|
||||
|
||||
class StyleCollection:
|
||||
def __init__(self, collection: "ElementCollection") -> None:
|
||||
self._collection = collection
|
||||
|
||||
def __get__(self, obj, objtype=None):
|
||||
return obj._get_attribute("style")
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._collection._get_attribute("style")[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
for element in self._collection._elements:
|
||||
element.style[key] = value
|
||||
|
||||
def remove(self, key):
|
||||
for element in self._collection._elements:
|
||||
element.style.remove(key)
|
||||
|
||||
|
||||
class ElementCollection:
|
||||
def __init__(self, elements: [Element]) -> None:
|
||||
self._elements = elements
|
||||
self.style = StyleCollection(self)
|
||||
|
||||
def __getitem__(self, key):
|
||||
# If it's an integer we use it to access the elements in the collection
|
||||
if isinstance(key, int):
|
||||
return self._elements[key]
|
||||
# If it's a slice we use it to support slice operations over the elements
|
||||
# in the collection
|
||||
elif isinstance(key, slice):
|
||||
return ElementCollection(self._elements[key])
|
||||
|
||||
# If it's anything else (basically a string) we use it as a selector
|
||||
# TODO: Write tests!
|
||||
elements = self._element.querySelectorAll(key)
|
||||
return ElementCollection([Element(el) for el in elements])
|
||||
|
||||
def __len__(self):
|
||||
return len(self._elements)
|
||||
|
||||
def __eq__(self, obj):
|
||||
"""Check if the element is the same as the other element by comparing
|
||||
the underlying JS element"""
|
||||
return isinstance(obj, ElementCollection) and obj._elements == self._elements
|
||||
|
||||
def _get_attribute(self, attr, index=None):
|
||||
if index is None:
|
||||
return [getattr(el, attr) for el in self._elements]
|
||||
|
||||
# As JQuery, when getting an attr, only return it for the first element
|
||||
return getattr(self._elements[index], attr)
|
||||
|
||||
def _set_attribute(self, attr, value):
|
||||
for el in self._elements:
|
||||
setattr(el, attr, value)
|
||||
|
||||
@property
|
||||
def html(self):
|
||||
return self._get_attribute("html")
|
||||
|
||||
@html.setter
|
||||
def html(self, value):
|
||||
self._set_attribute("html", value)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._get_attribute("value")
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self._set_attribute("value", value)
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
return self._elements
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._elements
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__} (length: {len(self._elements)}) {self._elements}"
|
||||
|
||||
|
||||
class DomScope:
|
||||
def __getattr__(self, __name: str):
|
||||
element = document[f"#{__name}"]
|
||||
if element:
|
||||
return element[0]
|
||||
|
||||
|
||||
class PyDom(BaseElement):
|
||||
# Add objects we want to expose to the DOM namespace since this class instance is being
|
||||
# remapped as "the module" itself
|
||||
BaseElement = BaseElement
|
||||
Element = Element
|
||||
ElementCollection = ElementCollection
|
||||
|
||||
def __init__(self):
|
||||
# PyDom is a special case of BaseElement where we don't want to create a new JS element
|
||||
# and it really doesn't have a need for styleproxy or parent to to call to __init__
|
||||
# (which actually fails in MP for some reason)
|
||||
self._js = document
|
||||
self._parent = None
|
||||
self._proxies = {}
|
||||
self.ids = DomScope()
|
||||
self.body = Element(document.body)
|
||||
self.head = Element(document.head)
|
||||
|
||||
def create(self, type_, classes=None, html=None):
|
||||
return super().create(type_, is_child=False, classes=classes, html=html)
|
||||
|
||||
def __getitem__(self, key):
|
||||
elements = self._js.querySelectorAll(key)
|
||||
if not elements:
|
||||
return None
|
||||
return ElementCollection([Element(el) for el in elements])
|
||||
|
||||
|
||||
dom = PyDom()
|
||||
@@ -1 +0,0 @@
|
||||
from . import elements
|
||||
@@ -1,947 +0,0 @@
|
||||
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
pyscript.core/test/issue-7015/config.toml
Normal file
4
pyscript.core/test/issue-7015/config.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages = [
|
||||
"https://cdn.holoviz.org/panel/wheels/bokeh-3.5.0-py3-none-any.whl",
|
||||
"https://cdn.holoviz.org/panel/1.5.0-b.2/dist/wheels/panel-1.5.0b2-py3-none-any.whl"
|
||||
]
|
||||
17
pyscript.core/test/issue-7015/index.html
Normal file
17
pyscript.core/test/issue-7015/index.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 src="https://cdn.bokeh.org/bokeh/release/bokeh-3.5.0.js"></script>
|
||||
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.5.0.min.js"></script>
|
||||
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.5.0.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@holoviz/panel@1.5.0-b.2/dist/panel.min.js"></script>
|
||||
<script type="module" src="../../dist/core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py" src="main.py" config="config.toml" worker></script>
|
||||
<div id="simple_app"></div>
|
||||
</body>
|
||||
</html>
|
||||
12
pyscript.core/test/issue-7015/main.py
Normal file
12
pyscript.core/test/issue-7015/main.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import panel as pn
|
||||
|
||||
pn.extension(sizing_mode="stretch_width")
|
||||
|
||||
slider = pn.widgets.FloatSlider(start=0, end=10, name="amplitude")
|
||||
|
||||
|
||||
def callback(new):
|
||||
return f"Amplitude is: {new}"
|
||||
|
||||
|
||||
pn.Row(slider, pn.bind(callback, slider)).servable(target="simple_app")
|
||||
@@ -88,3 +88,13 @@ test('MicroPython + Pyodide ffi', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/test/ffi.html');
|
||||
await page.waitForSelector('html.mpy.py');
|
||||
});
|
||||
|
||||
test('MicroPython + Storage', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/test/storage.html');
|
||||
await page.waitForSelector('html.ok');
|
||||
});
|
||||
|
||||
test('MicroPython + workers', async ({ page }) => {
|
||||
await page.goto('http://localhost:8080/test/workers/index.html');
|
||||
await page.waitForSelector('html.mpy.py');
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
a = 1
|
||||
</script>
|
||||
<!-- a share-nothing micropython editor -->
|
||||
<script type="mpy-editor" config="./config.toml">
|
||||
<script type="mpy-editor" config='{"js_modules":{"worker":{"https://cdn.jsdelivr.net/npm/html-escaper/+esm":"html_escaper"}}}'>
|
||||
from pyscript.js_modules.html_escaper import escape, unescape
|
||||
print(unescape(escape("<OK>")))
|
||||
b = 2
|
||||
|
||||
16
pyscript.core/test/py-editor/service-worker.html
Normal file
16
pyscript.core/test/py-editor/service-worker.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!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="mpy-editor" service-worker="./sw.js">
|
||||
from pyscript import document
|
||||
|
||||
document.body.append("OK")
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
pyscript.core/test/py-editor/sw.js
Normal file
1
pyscript.core/test/py-editor/sw.js
Normal file
@@ -0,0 +1 @@
|
||||
const{isArray:e}=Array,t=new Map,s=e=>{e.stopImmediatePropagation(),e.preventDefault()};var n=Object.freeze({__proto__:null,activate:e=>e.waitUntil(clients.claim()),fetch:e=>{const{request:n}=e;"POST"===n.method&&n.url===`${location.href}?sabayon`&&(s(e),e.respondWith(n.json().then((async e=>{const{promise:s,resolve:o}=Promise.withResolvers(),a=e.join(",");t.set(a,o);for(const t of await clients.matchAll())t.postMessage(e);return s.then((e=>new Response(`[${e.join(",")}]`,n.headers)))}))))},install:()=>skipWaiting(),message:n=>{const{data:o}=n;if(e(o)&&4===o.length){const[e,a,i,r]=o,l=[e,a,i].join(",");t.has(l)&&(s(n),t.get(l)(r),t.delete(l))}}});for(const e in n)addEventListener(e,n[e]);
|
||||
@@ -9,7 +9,7 @@
|
||||
<style>.xterm { padding: .5rem; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py" worker terminal>
|
||||
<script type="mpy" worker terminal>
|
||||
from pyscript import document
|
||||
document.documentElement.classList.add("first")
|
||||
|
||||
|
||||
18
pyscript.core/test/py-terminals/index.html
Normal file
18
pyscript.core/test/py-terminals/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="./no-repl.html">Prompt: NO REPL</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="./repl.html">Prompt: REPL</a>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
20
pyscript.core/test/py-terminals/no-repl.html
Normal file
20
pyscript.core/test/py-terminals/no-repl.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PyTerminal Prompt: NO REPL</title>
|
||||
<script type="module" src="../../dist/core.js"></script>
|
||||
<style>.xterm { padding: .5rem; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy" worker terminal>
|
||||
prompt = input("Say something: ")
|
||||
print("You said, ", prompt)
|
||||
</script>
|
||||
<script type="py" worker terminal>
|
||||
prompt = input("Say something: ")
|
||||
print("You said, ", prompt)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
28
pyscript.core/test/py-terminals/repl.html
Normal file
28
pyscript.core/test/py-terminals/repl.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PyTerminal Prompt: REPL</title>
|
||||
<script type="module" src="../../dist/core.js"></script>
|
||||
<style>.xterm { padding: .5rem; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy" worker terminal>
|
||||
import code
|
||||
code.interact()
|
||||
|
||||
prompt = input("Say something: ")
|
||||
print("You said, ", prompt)
|
||||
</script>
|
||||
<script type="py" worker terminal>
|
||||
import code
|
||||
code.interact()
|
||||
|
||||
# Pyodide won't execute this ... ever
|
||||
# this should be tested manually
|
||||
prompt = input("Say something: ")
|
||||
print("You said, ", prompt)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -10,6 +10,8 @@
|
||||
<body>
|
||||
<script type="mpy" src="pydom.py"></script>
|
||||
|
||||
<div id="system-info"></div>
|
||||
|
||||
<button id="just-a-button">Click For Time</button>
|
||||
<button id="color-button">Click For Color</button>
|
||||
<button id="color-reset-button">Reset Color</button>
|
||||
|
||||
@@ -4,7 +4,7 @@ import time
|
||||
from datetime import datetime as dt
|
||||
|
||||
from pyscript import display, when
|
||||
from pyweb import pydom
|
||||
from pyscript.web import dom
|
||||
|
||||
display(sys.version, target="system-info")
|
||||
|
||||
@@ -19,18 +19,15 @@ def on_click():
|
||||
tstr = "{:02d}/{:02d}/{:04d} {:02d}:{:02d}:{:02d}"
|
||||
timenow = tstr.format(tnow[2], tnow[1], tnow[0], *tnow[2:])
|
||||
|
||||
display(f"Hello from PyScript, time is: {timenow}", append=False, target="result")
|
||||
display(f"Hello from PyScript, time is: {timenow}", append=False, target="#result")
|
||||
|
||||
|
||||
@when("click", "#color-button")
|
||||
def on_color_click(event):
|
||||
btn = pydom["#result"]
|
||||
btn = dom["#result"]
|
||||
btn.style["background-color"] = f"#{random.randrange(0x1000000):06x}"
|
||||
|
||||
|
||||
@when("click", "#color-reset-button")
|
||||
def reset_color(*args, **kwargs):
|
||||
pydom["#result"].style["background-color"] = "white"
|
||||
|
||||
|
||||
# btn_reset = pydom["#color-reset-button"][0].when('click', reset_color)
|
||||
dom["#result"].style["background-color"] = "white"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py" src="./run_tests.py" config="./tests.toml"></script>
|
||||
<script type="py" src="/test/pyscript_dom/run_tests.py" config="/test/pyscript_dom/tests.toml"></script>
|
||||
|
||||
<h1>pyscript.dom Tests</h1>
|
||||
<p>You can pass test parameters to this test suite by passing them as query params on the url.
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from pyscript import document, when
|
||||
from pyweb import pydom
|
||||
from pyscript.web import Element, ElementCollection, div, p, page
|
||||
|
||||
|
||||
class TestDocument:
|
||||
def test__element(self):
|
||||
assert pydom._js == document
|
||||
|
||||
def test_no_parent(self):
|
||||
assert pydom.parent is None
|
||||
|
||||
def test_create_element(self):
|
||||
new_el = pydom.create("div")
|
||||
assert isinstance(new_el, pydom.BaseElement)
|
||||
assert new_el._js.tagName == "DIV"
|
||||
# EXPECT the new element to be associated with the document
|
||||
assert new_el.parent == None
|
||||
assert page.body._dom_element == document.body
|
||||
assert page.head._dom_element == document.head
|
||||
|
||||
|
||||
def test_getitem_by_id():
|
||||
@@ -26,14 +14,14 @@ def test_getitem_by_id():
|
||||
txt = "You found test_id_selector"
|
||||
selector = f"#{id_}"
|
||||
# EXPECT the element to be found by id
|
||||
result = pydom[selector]
|
||||
result = page.find(selector)
|
||||
div = result[0]
|
||||
# EXPECT the element text value to match what we expect and what
|
||||
# the JS document.querySelector API would return
|
||||
assert document.querySelector(selector).innerHTML == div.html == txt
|
||||
assert document.querySelector(selector).innerHTML == div.innerHTML == txt
|
||||
# EXPECT the results to be of the right types
|
||||
assert isinstance(div, pydom.BaseElement)
|
||||
assert isinstance(result, pydom.ElementCollection)
|
||||
assert isinstance(div, Element)
|
||||
assert isinstance(result, ElementCollection)
|
||||
|
||||
|
||||
def test_getitem_by_class():
|
||||
@@ -43,8 +31,7 @@ def test_getitem_by_class():
|
||||
"test_selector_w_children_child_1",
|
||||
]
|
||||
expected_class = "a-test-class"
|
||||
result = pydom[f".{expected_class}"]
|
||||
div = result[0]
|
||||
result = page.find(f".{expected_class}")
|
||||
|
||||
# EXPECT to find exact number of elements with the class in the page (== 3)
|
||||
assert len(result) == 3
|
||||
@@ -54,40 +41,40 @@ def test_getitem_by_class():
|
||||
|
||||
|
||||
def test_read_n_write_collection_elements():
|
||||
elements = pydom[".multi-elems"]
|
||||
elements = page.find(".multi-elems")
|
||||
|
||||
for element in elements:
|
||||
assert element.html == f"Content {element.id.replace('#', '')}"
|
||||
assert element.innerHTML == f"Content {element.id.replace('#', '')}"
|
||||
|
||||
new_content = "New Content"
|
||||
elements.html = new_content
|
||||
elements.innerHTML = new_content
|
||||
for element in elements:
|
||||
assert element.html == new_content
|
||||
assert element.innerHTML == new_content
|
||||
|
||||
|
||||
class TestElement:
|
||||
def test_query(self):
|
||||
# GIVEN an existing element on the page, with at least 1 child element
|
||||
id_ = "test_selector_w_children"
|
||||
parent_div = pydom[f"#{id_}"][0]
|
||||
parent_div = page.find(f"#{id_}")[0]
|
||||
|
||||
# EXPECT it to be able to query for the first child element
|
||||
div = parent_div.find("div")[0]
|
||||
|
||||
# EXPECT the new element to be associated with the parent
|
||||
assert div.parent == parent_div
|
||||
# EXPECT the new element to be a BaseElement
|
||||
assert isinstance(div, pydom.BaseElement)
|
||||
# EXPECT the new element to be an Element
|
||||
assert isinstance(div, Element)
|
||||
# EXPECT the div attributes to be == to how they are configured in the page
|
||||
assert div.html == "Child 1"
|
||||
assert div.innerHTML == "Child 1"
|
||||
assert div.id == "test_selector_w_children_child_1"
|
||||
|
||||
def test_equality(self):
|
||||
# GIVEN 2 different Elements pointing to the same underlying element
|
||||
id_ = "test_id_selector"
|
||||
selector = f"#{id_}"
|
||||
div = pydom[selector][0]
|
||||
div2 = pydom[selector][0]
|
||||
div = page.find(selector)[0]
|
||||
div2 = page.find(selector)[0]
|
||||
|
||||
# EXPECT them to be equal
|
||||
assert div == div2
|
||||
@@ -95,34 +82,34 @@ class TestElement:
|
||||
assert div is not div2
|
||||
|
||||
# EXPECT their value to always be equal
|
||||
assert div.html == div2.html
|
||||
div.html = "some value"
|
||||
assert div.innerHTML == div2.innerHTML
|
||||
div.innerHTML = "some value"
|
||||
|
||||
assert div.html == div2.html == "some value"
|
||||
assert div.innerHTML == div2.innerHTML == "some value"
|
||||
|
||||
def test_append_element(self):
|
||||
id_ = "element-append-tests"
|
||||
div = pydom[f"#{id_}"][0]
|
||||
div = page.find(f"#{id_}")[0]
|
||||
len_children_before = len(div.children)
|
||||
new_el = div.create("p")
|
||||
new_el = p("new element")
|
||||
div.append(new_el)
|
||||
assert len(div.children) == len_children_before + 1
|
||||
assert div.children[-1] == new_el
|
||||
|
||||
def test_append_js_element(self):
|
||||
def test_append_dom_element_element(self):
|
||||
id_ = "element-append-tests"
|
||||
div = pydom[f"#{id_}"][0]
|
||||
div = page.find(f"#{id_}")[0]
|
||||
len_children_before = len(div.children)
|
||||
new_el = div.create("p")
|
||||
div.append(new_el._js)
|
||||
new_el = p("new element")
|
||||
div.append(new_el._dom_element)
|
||||
assert len(div.children) == len_children_before + 1
|
||||
assert div.children[-1] == new_el
|
||||
|
||||
def test_append_collection(self):
|
||||
id_ = "element-append-tests"
|
||||
div = pydom[f"#{id_}"][0]
|
||||
div = page.find(f"#{id_}")[0]
|
||||
len_children_before = len(div.children)
|
||||
collection = pydom[".collection"]
|
||||
collection = page.find(".collection")
|
||||
div.append(collection)
|
||||
assert len(div.children) == len_children_before + len(collection)
|
||||
|
||||
@@ -132,24 +119,24 @@ class TestElement:
|
||||
def test_read_classes(self):
|
||||
id_ = "test_class_selector"
|
||||
expected_class = "a-test-class"
|
||||
div = pydom[f"#{id_}"][0]
|
||||
div = page.find(f"#{id_}")[0]
|
||||
assert div.classes == [expected_class]
|
||||
|
||||
def test_add_remove_class(self):
|
||||
id_ = "div-no-classes"
|
||||
classname = "tester-class"
|
||||
div = pydom[f"#{id_}"][0]
|
||||
div = page.find(f"#{id_}")[0]
|
||||
assert not div.classes
|
||||
div.add_class(classname)
|
||||
same_div = pydom[f"#{id_}"][0]
|
||||
div.classes.add(classname)
|
||||
same_div = page.find(f"#{id_}")[0]
|
||||
assert div.classes == [classname] == same_div.classes
|
||||
div.remove_class(classname)
|
||||
div.classes.remove(classname)
|
||||
assert div.classes == [] == same_div.classes
|
||||
|
||||
def test_when_decorator(self):
|
||||
called = False
|
||||
|
||||
just_a_button = pydom["#a-test-button"][0]
|
||||
just_a_button = page.find("#a-test-button")[0]
|
||||
|
||||
@when("click", just_a_button)
|
||||
def on_click(event):
|
||||
@@ -157,45 +144,49 @@ class TestElement:
|
||||
called = True
|
||||
|
||||
# Now let's simulate a click on the button (using the low level JS API)
|
||||
# so we don't risk pydom getting in the way
|
||||
# so we don't risk dom getting in the way
|
||||
assert not called
|
||||
just_a_button._js.click()
|
||||
just_a_button._dom_element.click()
|
||||
|
||||
assert called
|
||||
|
||||
def test_html_attribute(self):
|
||||
def test_inner_html_attribute(self):
|
||||
# GIVEN an existing element on the page with a known empty text content
|
||||
div = pydom["#element_attribute_tests"][0]
|
||||
div = page.find("#element_attribute_tests")[0]
|
||||
|
||||
# WHEN we set the html attribute
|
||||
div.html = "<b>New Content</b>"
|
||||
div.innerHTML = "<b>New Content</b>"
|
||||
|
||||
# EXPECT the element html and underlying JS Element innerHTML property
|
||||
# to match what we expect and what
|
||||
assert div.html == div._js.innerHTML == "<b>New Content</b>"
|
||||
assert div.text == div._js.textContent == "New Content"
|
||||
assert div.innerHTML == div._dom_element.innerHTML == "<b>New Content</b>"
|
||||
assert div.textContent == div._dom_element.textContent == "New Content"
|
||||
|
||||
def test_text_attribute(self):
|
||||
# GIVEN an existing element on the page with a known empty text content
|
||||
div = pydom["#element_attribute_tests"][0]
|
||||
div = page.find("#element_attribute_tests")[0]
|
||||
|
||||
# WHEN we set the html attribute
|
||||
div.text = "<b>New Content</b>"
|
||||
div.textContent = "<b>New Content</b>"
|
||||
|
||||
# EXPECT the element html and underlying JS Element innerHTML property
|
||||
# to match what we expect and what
|
||||
assert div.html == div._js.innerHTML == "<b>New Content</b>"
|
||||
assert div.text == div._js.textContent == "<b>New Content</b>"
|
||||
assert (
|
||||
div.innerHTML
|
||||
== div._dom_element.innerHTML
|
||||
== "<b>New Content</b>"
|
||||
)
|
||||
assert div.textContent == div._dom_element.textContent == "<b>New Content</b>"
|
||||
|
||||
|
||||
class TestCollection:
|
||||
def test_iter_eq_children(self):
|
||||
elements = pydom[".multi-elems"]
|
||||
assert [el for el in elements] == [el for el in elements.children]
|
||||
elements = page.find(".multi-elems")
|
||||
assert [el for el in elements] == [el for el in elements.elements]
|
||||
assert len(elements) == 3
|
||||
|
||||
def test_slices(self):
|
||||
elements = pydom[".multi-elems"]
|
||||
elements = page.find(".multi-elems")
|
||||
assert elements[0]
|
||||
_slice = elements[:2]
|
||||
assert len(_slice) == 2
|
||||
@@ -205,26 +196,26 @@ class TestCollection:
|
||||
|
||||
def test_style_rule(self):
|
||||
selector = ".multi-elems"
|
||||
elements = pydom[selector]
|
||||
elements = page.find(selector)
|
||||
for el in elements:
|
||||
assert el.style["background-color"] != "red"
|
||||
|
||||
elements.style["background-color"] = "red"
|
||||
|
||||
for i, el in enumerate(pydom[selector]):
|
||||
for i, el in enumerate(page.find(selector)):
|
||||
assert elements[i].style["background-color"] == "red"
|
||||
assert el.style["background-color"] == "red"
|
||||
|
||||
elements.style.remove("background-color")
|
||||
|
||||
for i, el in enumerate(pydom[selector]):
|
||||
for i, el in enumerate(page.find(selector)):
|
||||
assert el.style["background-color"] != "red"
|
||||
assert elements[i].style["background-color"] != "red"
|
||||
|
||||
def test_when_decorator(self):
|
||||
called = False
|
||||
|
||||
buttons_collection = pydom["button"]
|
||||
buttons_collection = page.find("button")
|
||||
|
||||
@when("click", buttons_collection)
|
||||
def on_click(event):
|
||||
@@ -232,42 +223,43 @@ class TestCollection:
|
||||
called = True
|
||||
|
||||
# Now let's simulate a click on the button (using the low level JS API)
|
||||
# so we don't risk pydom getting in the way
|
||||
# so we don't risk dom getting in the way
|
||||
assert not called
|
||||
for button in buttons_collection:
|
||||
button._js.click()
|
||||
button._dom_element.click()
|
||||
assert called
|
||||
called = False
|
||||
|
||||
|
||||
class TestCreation:
|
||||
def test_create_document_element(self):
|
||||
new_el = pydom.create("div")
|
||||
# TODO: This test should probably be removed since it's testing the elements
|
||||
# module.
|
||||
new_el = div("new element")
|
||||
new_el.id = "new_el_id"
|
||||
assert isinstance(new_el, pydom.BaseElement)
|
||||
assert new_el._js.tagName == "DIV"
|
||||
assert isinstance(new_el, Element)
|
||||
assert new_el._dom_element.tagName == "DIV"
|
||||
# EXPECT the new element to be associated with the document
|
||||
assert new_el.parent == None
|
||||
pydom.body.append(new_el)
|
||||
assert new_el.parent is None
|
||||
page.body.append(new_el)
|
||||
|
||||
assert pydom["#new_el_id"][0].parent == pydom.body
|
||||
assert page.find("#new_el_id")[0].parent == page.body
|
||||
|
||||
def test_create_element_child(self):
|
||||
selector = "#element-creation-test"
|
||||
parent_div = pydom[selector][0]
|
||||
parent_div = page.find(selector)[0]
|
||||
|
||||
# Creating an element from another element automatically creates that element
|
||||
# as a child of the original element
|
||||
new_el = parent_div.create(
|
||||
"p", classes=["code-description"], html="Ciao PyScripters!"
|
||||
)
|
||||
new_el = p("a div", classes=["code-description"], innerHTML="Ciao PyScripters!")
|
||||
parent_div.append(new_el)
|
||||
|
||||
assert isinstance(new_el, Element)
|
||||
assert new_el._dom_element.tagName == "P"
|
||||
|
||||
assert isinstance(new_el, pydom.BaseElement)
|
||||
assert new_el._js.tagName == "P"
|
||||
# EXPECT the new element to be associated with the document
|
||||
assert new_el.parent == parent_div
|
||||
|
||||
assert pydom[selector][0].children[0] == new_el
|
||||
assert page.find(selector)[0].children[0] == new_el
|
||||
|
||||
|
||||
class TestInput:
|
||||
@@ -281,10 +273,10 @@ class TestInput:
|
||||
def test_value(self):
|
||||
for id_ in self.input_ids:
|
||||
expected_type = id_.split("_")[-1]
|
||||
result = pydom[f"#{id_}"]
|
||||
result = page.find(f"#{id_}")
|
||||
input_el = result[0]
|
||||
assert input_el._js.type == expected_type
|
||||
assert input_el.value == f"Content {id_}" == input_el._js.value
|
||||
assert input_el._dom_element.type == expected_type
|
||||
assert input_el.value == f"Content {id_}" == input_el._dom_element.value
|
||||
|
||||
# Check that we can set the value
|
||||
new_value = f"New Value {expected_type}"
|
||||
@@ -299,7 +291,7 @@ class TestInput:
|
||||
|
||||
def test_set_value_collection(self):
|
||||
for id_ in self.input_ids:
|
||||
input_el = pydom[f"#{id_}"]
|
||||
input_el = page.find(f"#{id_}")
|
||||
|
||||
assert input_el.value[0] == f"Content {id_}" == input_el[0].value
|
||||
|
||||
@@ -307,36 +299,35 @@ class TestInput:
|
||||
input_el.value = new_value
|
||||
assert input_el.value[0] == new_value == input_el[0].value
|
||||
|
||||
def test_element_without_value(self):
|
||||
result = pydom[f"#tests-terminal"][0]
|
||||
with pytest.raises(AttributeError):
|
||||
result.value = "some value"
|
||||
|
||||
def test_element_without_collection(self):
|
||||
result = pydom[f"#tests-terminal"]
|
||||
with pytest.raises(AttributeError):
|
||||
result.value = "some value"
|
||||
|
||||
def test_element_without_collection(self):
|
||||
result = pydom[f"#tests-terminal"]
|
||||
with pytest.raises(AttributeError):
|
||||
result.value = "some value"
|
||||
# TODO: We only attach attributes to the classes that have them now which means we
|
||||
# would have to have some other way to help users if using attributes that aren't
|
||||
# actually on the class. Maybe a job for __setattr__?
|
||||
#
|
||||
# def test_element_without_value(self):
|
||||
# result = page.find(f"#tests-terminal"][0]
|
||||
# with pytest.raises(AttributeError):
|
||||
# result.value = "some value"
|
||||
#
|
||||
# def test_element_without_value_via_collection(self):
|
||||
# result = page.find(f"#tests-terminal"]
|
||||
# with pytest.raises(AttributeError):
|
||||
# result.value = "some value"
|
||||
|
||||
|
||||
class TestSelect:
|
||||
def test_select_options_iter(self):
|
||||
select = pydom[f"#test_select_element_w_options"][0]
|
||||
select = page.find(f"#test_select_element_w_options")[0]
|
||||
|
||||
for i, option in enumerate(select.options, 1):
|
||||
assert option.value == f"{i}"
|
||||
assert option.html == f"Option {i}"
|
||||
assert option.innerHTML == f"Option {i}"
|
||||
|
||||
def test_select_options_len(self):
|
||||
select = pydom[f"#test_select_element_w_options"][0]
|
||||
select = page.find(f"#test_select_element_w_options")[0]
|
||||
assert len(select.options) == 2
|
||||
|
||||
def test_select_options_clear(self):
|
||||
select = pydom[f"#test_select_element_to_clear"][0]
|
||||
select = page.find(f"#test_select_element_to_clear")[0]
|
||||
assert len(select.options) == 3
|
||||
|
||||
select.options.clear()
|
||||
@@ -345,7 +336,7 @@ class TestSelect:
|
||||
|
||||
def test_select_element_add(self):
|
||||
# GIVEN the existing select element with no options
|
||||
select = pydom[f"#test_select_element"][0]
|
||||
select = page.find(f"#test_select_element")[0]
|
||||
|
||||
# EXPECT the select element to have no options
|
||||
assert len(select.options) == 0
|
||||
@@ -357,7 +348,7 @@ class TestSelect:
|
||||
# we passed in
|
||||
assert len(select.options) == 1
|
||||
assert select.options[0].value == "1"
|
||||
assert select.options[0].html == "Option 1"
|
||||
assert select.options[0].innerHTML == "Option 1"
|
||||
|
||||
# WHEN we add another option (blank this time)
|
||||
select.options.add("")
|
||||
@@ -367,7 +358,7 @@ class TestSelect:
|
||||
|
||||
# EXPECT the last option to have an empty value and html
|
||||
assert select.options[1].value == ""
|
||||
assert select.options[1].html == ""
|
||||
assert select.options[1].innerHTML == ""
|
||||
|
||||
# WHEN we add another option (this time adding it in between the other 2
|
||||
# options by using an integer index)
|
||||
@@ -378,11 +369,11 @@ class TestSelect:
|
||||
|
||||
# EXPECT the middle option to have the value and html we passed in
|
||||
assert select.options[0].value == "1"
|
||||
assert select.options[0].html == "Option 1"
|
||||
assert select.options[0].innerHTML == "Option 1"
|
||||
assert select.options[1].value == "2"
|
||||
assert select.options[1].html == "Option 2"
|
||||
assert select.options[1].innerHTML == "Option 2"
|
||||
assert select.options[2].value == ""
|
||||
assert select.options[2].html == ""
|
||||
assert select.options[2].innerHTML == ""
|
||||
|
||||
# WHEN we add another option (this time adding it in between the other 2
|
||||
# options but using the option itself)
|
||||
@@ -395,38 +386,48 @@ class TestSelect:
|
||||
|
||||
# EXPECT the middle option to have the value and html we passed in
|
||||
assert select.options[0].value == "1"
|
||||
assert select.options[0].html == "Option 1"
|
||||
assert select.options[0].selected == select.options[0]._js.selected == False
|
||||
assert select.options[0].innerHTML == "Option 1"
|
||||
assert (
|
||||
select.options[0].selected
|
||||
== select.options[0]._dom_element.selected
|
||||
== False
|
||||
)
|
||||
assert select.options[1].value == "2"
|
||||
assert select.options[1].html == "Option 2"
|
||||
assert select.options[1].innerHTML == "Option 2"
|
||||
assert select.options[2].value == "3"
|
||||
assert select.options[2].html == "Option 3"
|
||||
assert select.options[2].selected == select.options[2]._js.selected == True
|
||||
assert select.options[2].innerHTML == "Option 3"
|
||||
assert (
|
||||
select.options[2].selected
|
||||
== select.options[2]._dom_element.selected
|
||||
== True
|
||||
)
|
||||
assert select.options[3].value == ""
|
||||
assert select.options[3].html == ""
|
||||
assert select.options[3].innerHTML == ""
|
||||
|
||||
# WHEN we add another option (this time adding it in between the other 2
|
||||
# options but using the JS element of the option itself)
|
||||
select.options.add(value="2a", html="Option 2a", before=select.options[2]._js)
|
||||
select.options.add(
|
||||
value="2a", html="Option 2a", before=select.options[2]._dom_element
|
||||
)
|
||||
|
||||
# EXPECT the select element to have 3 options
|
||||
assert len(select.options) == 5
|
||||
|
||||
# EXPECT the middle option to have the value and html we passed in
|
||||
assert select.options[0].value == "1"
|
||||
assert select.options[0].html == "Option 1"
|
||||
assert select.options[0].innerHTML == "Option 1"
|
||||
assert select.options[1].value == "2"
|
||||
assert select.options[1].html == "Option 2"
|
||||
assert select.options[1].innerHTML == "Option 2"
|
||||
assert select.options[2].value == "2a"
|
||||
assert select.options[2].html == "Option 2a"
|
||||
assert select.options[2].innerHTML == "Option 2a"
|
||||
assert select.options[3].value == "3"
|
||||
assert select.options[3].html == "Option 3"
|
||||
assert select.options[3].innerHTML == "Option 3"
|
||||
assert select.options[4].value == ""
|
||||
assert select.options[4].html == ""
|
||||
assert select.options[4].innerHTML == ""
|
||||
|
||||
def test_select_options_remove(self):
|
||||
# GIVEN the existing select element with 3 options
|
||||
select = pydom[f"#test_select_element_to_remove"][0]
|
||||
select = page.find(f"#test_select_element_to_remove")[0]
|
||||
|
||||
# EXPECT the select element to have 3 options
|
||||
assert len(select.options) == 4
|
||||
@@ -448,12 +449,12 @@ class TestSelect:
|
||||
|
||||
def test_select_get_selected_option(self):
|
||||
# GIVEN the existing select element with one selected option
|
||||
select = pydom[f"#test_select_element_w_options"][0]
|
||||
select = page.find(f"#test_select_element_w_options")[0]
|
||||
|
||||
# WHEN we get the selected option
|
||||
selected_option = select.options.selected
|
||||
|
||||
# EXPECT the selected option to be correct
|
||||
assert selected_option.value == "2"
|
||||
assert selected_option.html == "Option 2"
|
||||
assert selected_option.selected == selected_option._js.selected == True
|
||||
assert selected_option.innerHTML == "Option 2"
|
||||
assert selected_option.selected == selected_option._dom_element.selected == True
|
||||
|
||||
16
pyscript.core/test/service-worker/index.html
Normal file
16
pyscript.core/test/service-worker/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Service Worker</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>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy" service-worker="./sabayon.js" worker>
|
||||
from pyscript import document
|
||||
document.body.append('OK')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
28
pyscript.core/test/service-worker/mini-coi.js
Normal file
28
pyscript.core/test/service-worker/mini-coi.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
|
||||
/*! mini-coi - Andrea Giammarchi and contributors, licensed under MIT */
|
||||
(({ document: d, navigator: { serviceWorker: s } }) => {
|
||||
if (d) {
|
||||
const { currentScript: c } = d;
|
||||
s.register(c.src, { scope: c.getAttribute('scope') || '.' }).then(r => {
|
||||
r.addEventListener('updatefound', () => location.reload());
|
||||
if (r.active && !s.controller) location.reload();
|
||||
});
|
||||
}
|
||||
else {
|
||||
addEventListener('install', () => skipWaiting());
|
||||
addEventListener('activate', e => e.waitUntil(clients.claim()));
|
||||
addEventListener('fetch', e => {
|
||||
const { request: r } = e;
|
||||
if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return;
|
||||
e.respondWith(fetch(r).then(r => {
|
||||
const { body, status, statusText } = r;
|
||||
if (!status || status > 399) return r;
|
||||
const h = new Headers(r.headers);
|
||||
h.set('Cross-Origin-Opener-Policy', 'same-origin');
|
||||
h.set('Cross-Origin-Embedder-Policy', 'require-corp');
|
||||
h.set('Cross-Origin-Resource-Policy', 'cross-origin');
|
||||
return new Response(body, { status, statusText, headers: h });
|
||||
}));
|
||||
});
|
||||
}
|
||||
})(self);
|
||||
1
pyscript.core/test/service-worker/sabayon.js
Normal file
1
pyscript.core/test/service-worker/sabayon.js
Normal file
@@ -0,0 +1 @@
|
||||
const{isArray:e}=Array,t=new Map,s=e=>{e.stopImmediatePropagation(),e.preventDefault()};var n=Object.freeze({__proto__:null,activate:e=>e.waitUntil(clients.claim()),fetch:e=>{const{request:n}=e;"POST"===n.method&&n.url===`${location.href}?sabayon`&&(s(e),e.respondWith(n.json().then((async e=>{const{promise:s,resolve:o}=Promise.withResolvers(),a=e.join(",");t.set(a,o);for(const t of await clients.matchAll())t.postMessage(e);return s.then((e=>new Response(`[${e.join(",")}]`,n.headers)))}))))},install:()=>skipWaiting(),message:n=>{const{data:o}=n;if(e(o)&&4===o.length){const[e,a,i,r]=o,l=[e,a,i].join(",");t.has(l)&&(s(n),t.get(l)(r),t.delete(l))}}});for(const e in n)addEventListener(e,n[e]);
|
||||
46
pyscript.core/test/storage.html
Normal file
46
pyscript.core/test/storage.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@pyscript/core storage</title>
|
||||
<link rel="stylesheet" href="../dist/core.css">
|
||||
<script type="module" src="../dist/core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy" async>
|
||||
from random import random
|
||||
from pyscript import storage
|
||||
|
||||
store = await storage(name="test")
|
||||
|
||||
print("before", len(store))
|
||||
for k in store:
|
||||
if isinstance(store[k], memoryview):
|
||||
print(f" {k}: {store[k].hex()} as hex()")
|
||||
else:
|
||||
print(f" {k}: {store[k]}")
|
||||
|
||||
store["ba"] = bytearray([0, 1, 2, 3, 4])
|
||||
store["mv"] = memoryview(bytearray([5, 6, 7, 8, 9]))
|
||||
store["random"] = ("some", random(), True)
|
||||
store["key"] = "value"
|
||||
|
||||
print("now", len(store))
|
||||
for k in store:
|
||||
print(f" {k}: {store[k]}")
|
||||
|
||||
del store["key"]
|
||||
# store.clear()
|
||||
|
||||
print("after", len(store))
|
||||
for k in store:
|
||||
print(f" {k}: {store[k]}")
|
||||
|
||||
await store.sync()
|
||||
|
||||
import js
|
||||
js.document.documentElement.classList.add("ok")
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
2
pyscript.core/test/workers/config.toml
Normal file
2
pyscript.core/test/workers/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[files]
|
||||
"./test.py" = "./test.py"
|
||||
30
pyscript.core/test/workers/index.html
Normal file
30
pyscript.core/test/workers/index.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<script type="module" src="../../dist/core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy" async>
|
||||
from pyscript import create_named_worker
|
||||
|
||||
await create_named_worker("./worker.py", name="micropython_version", type="mpy")
|
||||
</script>
|
||||
<script type="mpy" config="./config.toml" async>
|
||||
from test import test
|
||||
await test("mpy")
|
||||
</script>
|
||||
<script type="py" config="./config.toml" async>
|
||||
from test import test
|
||||
await test("py")
|
||||
</script>
|
||||
<script type="py" name="pyodide_version" worker>
|
||||
def pyodide_version():
|
||||
import sys
|
||||
return sys.version
|
||||
|
||||
__export__ = ['pyodide_version']
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
29
pyscript.core/test/workers/named.html
Normal file
29
pyscript.core/test/workers/named.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>named workers</title>
|
||||
<script type="module" src="../../dist/core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy" async>
|
||||
from pyscript import workers
|
||||
|
||||
await (await workers["mpy"]).greetings()
|
||||
await (await workers["py"]).greetings()
|
||||
</script>
|
||||
<script type="mpy" worker name="mpy">
|
||||
def greetings():
|
||||
print("micropython")
|
||||
|
||||
__export__ = ['greetings']
|
||||
</script>
|
||||
<script type="py" worker name="py">
|
||||
def greetings():
|
||||
print("pyodide")
|
||||
|
||||
__export__ = ['greetings']
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
19
pyscript.core/test/workers/test.py
Normal file
19
pyscript.core/test/workers/test.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from pyscript import document, workers
|
||||
|
||||
|
||||
async def test(interpreter):
|
||||
# accessed as item
|
||||
named = await workers.micropython_version
|
||||
|
||||
version = await named.micropython_version()
|
||||
document.body.append(version)
|
||||
document.body.append(document.createElement("hr"))
|
||||
|
||||
# accessed as attribute
|
||||
named = await workers["pyodide_version"]
|
||||
|
||||
version = await named.pyodide_version()
|
||||
document.body.append(version)
|
||||
document.body.append(document.createElement("hr"))
|
||||
|
||||
document.documentElement.classList.add(interpreter)
|
||||
7
pyscript.core/test/workers/worker.py
Normal file
7
pyscript.core/test/workers/worker.py
Normal file
@@ -0,0 +1,7 @@
|
||||
def micropython_version():
|
||||
import sys
|
||||
|
||||
return sys.version
|
||||
|
||||
|
||||
__export__ = ["micropython_version"]
|
||||
@@ -101,10 +101,9 @@ class TestElements(PyScriptTest):
|
||||
code_ = f"""
|
||||
from pyscript import when
|
||||
<script type="{interpreter}">
|
||||
from pyweb import pydom
|
||||
from pyweb.ui.elements import {el_type}
|
||||
from pyscript.web import page, {el_type}
|
||||
el = {el_type}({attributes})
|
||||
pydom.body.append(el)
|
||||
page.body.append(el)
|
||||
</script>
|
||||
"""
|
||||
self.pyscript_run(code_)
|
||||
@@ -178,7 +177,7 @@ class TestElements(PyScriptTest):
|
||||
)
|
||||
|
||||
def test_b(self, interpreter):
|
||||
self._create_el_and_basic_asserts("aside", "some text", interpreter)
|
||||
self._create_el_and_basic_asserts("b", "some text", interpreter)
|
||||
|
||||
def test_blockquote(self, interpreter):
|
||||
self._create_el_and_basic_asserts("blockquote", "some text", interpreter)
|
||||
@@ -603,3 +602,203 @@ class TestElements(PyScriptTest):
|
||||
properties=properties,
|
||||
expected_errors=self.expected_missing_file_errors,
|
||||
)
|
||||
|
||||
def test_append_py_element(self, interpreter):
|
||||
# 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("div")
|
||||
assert not element.count()
|
||||
|
||||
div_text_content = "Luke, I am your father"
|
||||
p_text_content = "noooooooooo!"
|
||||
# Let's create the element
|
||||
code_ = f"""
|
||||
from pyscript import when
|
||||
<script type="{interpreter}">
|
||||
from pyscript.web import page, div, p
|
||||
|
||||
el = div("{div_text_content}")
|
||||
child = p('{p_text_content}')
|
||||
el.append(child)
|
||||
page.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
|
||||
el = self.page.locator("div")
|
||||
tag = el.evaluate("node => node.tagName")
|
||||
assert tag == "DIV"
|
||||
assert el.text_content() == f"{div_text_content}{p_text_content}"
|
||||
|
||||
assert (
|
||||
el.evaluate("node => node.children.length") == 1
|
||||
), "There should be only 1 child"
|
||||
assert el.evaluate("node => node.children[0].tagName") == "P"
|
||||
assert (
|
||||
el.evaluate("node => node.children[0].parentNode.textContent")
|
||||
== f"{div_text_content}{p_text_content}"
|
||||
)
|
||||
assert el.evaluate("node => node.children[0].textContent") == p_text_content
|
||||
|
||||
def test_append_proxy_element(self, interpreter):
|
||||
# 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("div")
|
||||
assert not element.count()
|
||||
|
||||
div_text_content = "Luke, I am your father"
|
||||
p_text_content = "noooooooooo!"
|
||||
# Let's create the element
|
||||
code_ = f"""
|
||||
from pyscript import when
|
||||
<script type="{interpreter}">
|
||||
from pyscript import document
|
||||
from pyscript.web import page, div, p
|
||||
|
||||
el = div("{div_text_content}")
|
||||
child = document.createElement('P')
|
||||
child.textContent = '{p_text_content}'
|
||||
el.append(child)
|
||||
page.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
|
||||
el = self.page.locator("div")
|
||||
tag = el.evaluate("node => node.tagName")
|
||||
assert tag == "DIV"
|
||||
assert el.text_content() == f"{div_text_content}{p_text_content}"
|
||||
|
||||
assert (
|
||||
el.evaluate("node => node.children.length") == 1
|
||||
), "There should be only 1 child"
|
||||
assert el.evaluate("node => node.children[0].tagName") == "P"
|
||||
assert (
|
||||
el.evaluate("node => node.children[0].parentNode.textContent")
|
||||
== f"{div_text_content}{p_text_content}"
|
||||
)
|
||||
assert el.evaluate("node => node.children[0].textContent") == p_text_content
|
||||
|
||||
def test_append_py_elementcollection(self, interpreter):
|
||||
# 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("div")
|
||||
assert not element.count()
|
||||
|
||||
div_text_content = "Luke, I am your father"
|
||||
p_text_content = "noooooooooo!"
|
||||
p2_text_content = "not me!"
|
||||
# Let's create the element
|
||||
code_ = f"""
|
||||
from pyscript import when
|
||||
<script type="{interpreter}">
|
||||
from pyscript.web import page, div, p, ElementCollection
|
||||
|
||||
el = div("{div_text_content}")
|
||||
child1 = p('{p_text_content}')
|
||||
child2 = p('{p2_text_content}', id='child2')
|
||||
collection = ElementCollection([child1, child2])
|
||||
el.append(collection)
|
||||
page.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
|
||||
el = self.page.locator("div")
|
||||
tag = el.evaluate("node => node.tagName")
|
||||
assert tag == "DIV"
|
||||
parent_full_content = f"{div_text_content}{p_text_content}{p2_text_content}"
|
||||
assert el.text_content() == parent_full_content
|
||||
|
||||
assert (
|
||||
el.evaluate("node => node.children.length") == 2
|
||||
), "There should be only 1 child"
|
||||
assert el.evaluate("node => node.children[0].tagName") == "P"
|
||||
assert (
|
||||
el.evaluate("node => node.children[0].parentNode.textContent")
|
||||
== parent_full_content
|
||||
)
|
||||
assert el.evaluate("node => node.children[0].textContent") == p_text_content
|
||||
|
||||
assert el.evaluate("node => node.children[1].tagName") == "P"
|
||||
assert el.evaluate("node => node.children[1].id") == "child2"
|
||||
assert (
|
||||
el.evaluate("node => node.children[1].parentNode.textContent")
|
||||
== parent_full_content
|
||||
)
|
||||
assert el.evaluate("node => node.children[1].textContent") == p2_text_content
|
||||
|
||||
def test_append_js_element_nodelist(self, interpreter):
|
||||
# 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("div")
|
||||
assert not element.count()
|
||||
|
||||
div_text_content = "Luke, I am your father"
|
||||
p_text_content = "noooooooooo!"
|
||||
p2_text_content = "not me!"
|
||||
# Let's create the element
|
||||
code_ = f"""
|
||||
from pyscript import when
|
||||
<script type="{interpreter}">
|
||||
from pyscript import document
|
||||
from pyscript.web import page, div, p, ElementCollection
|
||||
|
||||
el = div("{div_text_content}")
|
||||
child1 = p('{p_text_content}')
|
||||
child2 = p('{p2_text_content}', id='child2')
|
||||
|
||||
page.body.append(child1)
|
||||
page.body.append(child2)
|
||||
|
||||
nodes = document.querySelectorAll('p')
|
||||
el.append(nodes)
|
||||
|
||||
page.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
|
||||
el = self.page.locator("div")
|
||||
tag = el.evaluate("node => node.tagName")
|
||||
assert tag == "DIV"
|
||||
parent_full_content = f"{div_text_content}{p_text_content}{p2_text_content}"
|
||||
assert el.text_content() == parent_full_content
|
||||
|
||||
assert (
|
||||
el.evaluate("node => node.children.length") == 2
|
||||
), "There should be only 1 child"
|
||||
assert el.evaluate("node => node.children[0].tagName") == "P"
|
||||
assert (
|
||||
el.evaluate("node => node.children[0].parentNode.textContent")
|
||||
== parent_full_content
|
||||
)
|
||||
assert el.evaluate("node => node.children[0].textContent") == p_text_content
|
||||
|
||||
assert el.evaluate("node => node.children[1].tagName") == "P"
|
||||
assert el.evaluate("node => node.children[1].id") == "child2"
|
||||
assert (
|
||||
el.evaluate("node => node.children[1].parentNode.textContent")
|
||||
== parent_full_content
|
||||
)
|
||||
assert el.evaluate("node => node.children[1].textContent") == p2_text_content
|
||||
|
||||
4
pyscript.core/types/config.d.ts
vendored
4
pyscript.core/types/config.d.ts
vendored
@@ -1,2 +1,2 @@
|
||||
export default configs;
|
||||
declare const configs: Map<any, any>;
|
||||
export const configs: Map<any, any>;
|
||||
export function relative_url(url: any, base?: string): string;
|
||||
|
||||
5
pyscript.core/types/core.d.ts
vendored
5
pyscript.core/types/core.d.ts
vendored
@@ -3,6 +3,7 @@ import { stdlib } from "./stdlib.js";
|
||||
import { optional } from "./stdlib.js";
|
||||
import { inputFailure } from "./hooks.js";
|
||||
import TYPES from "./types.js";
|
||||
import { relative_url } from "./config.js";
|
||||
/**
|
||||
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
||||
* @param {string} file the python file to run ina worker.
|
||||
@@ -53,5 +54,5 @@ declare const exportedHooks: {
|
||||
};
|
||||
};
|
||||
declare const exportedConfig: {};
|
||||
declare const exportedWhenDefined: (type: string) => Promise<any>;
|
||||
export { stdlib, optional, inputFailure, TYPES, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };
|
||||
declare const exportedWhenDefined: (type: string) => Promise<object>;
|
||||
export { stdlib, optional, inputFailure, TYPES, relative_url, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };
|
||||
|
||||
17
pyscript.core/types/exceptions.d.ts
vendored
17
pyscript.core/types/exceptions.d.ts
vendored
@@ -53,19 +53,4 @@ export class InstallError extends UserError {
|
||||
/**
|
||||
* Keys of the ErrorCode object
|
||||
*/
|
||||
export type ErrorCodes = keyof {
|
||||
GENERIC: string;
|
||||
CONFLICTING_CODE: string;
|
||||
BAD_CONFIG: string;
|
||||
MICROPIP_INSTALL_ERROR: string;
|
||||
BAD_PLUGIN_FILE_EXTENSION: string;
|
||||
NO_DEFAULT_EXPORT: string;
|
||||
TOP_LEVEL_AWAIT: string;
|
||||
FETCH_ERROR: string;
|
||||
FETCH_NAME_ERROR: string;
|
||||
FETCH_UNAUTHORIZED_ERROR: string;
|
||||
FETCH_FORBIDDEN_ERROR: string;
|
||||
FETCH_NOT_FOUND_ERROR: string;
|
||||
FETCH_SERVER_ERROR: string;
|
||||
FETCH_UNAVAILABLE_ERROR: string;
|
||||
};
|
||||
export type ErrorCodes = "GENERIC" | "CONFLICTING_CODE" | "BAD_CONFIG" | "MICROPIP_INSTALL_ERROR" | "BAD_PLUGIN_FILE_EXTENSION" | "NO_DEFAULT_EXPORT" | "TOP_LEVEL_AWAIT" | "FETCH_ERROR" | "FETCH_NAME_ERROR" | "FETCH_UNAUTHORIZED_ERROR" | "FETCH_FORBIDDEN_ERROR" | "FETCH_NOT_FOUND_ERROR" | "FETCH_SERVER_ERROR" | "FETCH_UNAVAILABLE_ERROR";
|
||||
|
||||
13
pyscript.core/types/stdlib/pyscript.d.ts
vendored
13
pyscript.core/types/stdlib/pyscript.d.ts
vendored
@@ -5,18 +5,13 @@ declare namespace _default {
|
||||
"event_handling.py": string;
|
||||
"fetch.py": string;
|
||||
"ffi.py": string;
|
||||
"flatted.py": string;
|
||||
"magic_js.py": string;
|
||||
"storage.py": string;
|
||||
"util.py": string;
|
||||
"web.py": string;
|
||||
"websocket.py": string;
|
||||
};
|
||||
let pyweb: {
|
||||
"__init__.py": string;
|
||||
"media.py": string;
|
||||
"pydom.py": string;
|
||||
ui: {
|
||||
"__init__.py": string;
|
||||
"elements.py": string;
|
||||
};
|
||||
"workers.py": string;
|
||||
};
|
||||
}
|
||||
export default _default;
|
||||
|
||||
Reference in New Issue
Block a user