mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-22 19:53:00 -05:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b90ebdef5 | ||
|
|
15c19aa708 | ||
|
|
d0406be84c | ||
|
|
aab015b9b8 | ||
|
|
a1e5a05b49 | ||
|
|
f1a787e031 |
@@ -38,11 +38,11 @@ To try PyScript, import the appropriate pyscript files into the `<head>` tag of
|
|||||||
<head>
|
<head>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://pyscript.net/releases/2024.5.2/core.css"
|
href="https://pyscript.net/releases/2024.6.1/core.css"
|
||||||
/>
|
/>
|
||||||
<script
|
<script
|
||||||
type="module"
|
type="module"
|
||||||
src="https://pyscript.net/releases/2024.5.2/core.js"
|
src="https://pyscript.net/releases/2024.6.1/core.js"
|
||||||
></script>
|
></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
645
pyscript.core/package-lock.json
generated
645
pyscript.core/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pyscript/core",
|
"name": "@pyscript/core",
|
||||||
"version": "0.4.42",
|
"version": "0.4.50",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "PyScript",
|
"description": "PyScript",
|
||||||
"module": "./index.js",
|
"module": "./index.js",
|
||||||
@@ -20,8 +20,9 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"server": "npx static-handler --coi .",
|
"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: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:plugins": "node rollup/plugins.cjs",
|
||||||
"build:stdlib": "node rollup/stdlib.cjs",
|
"build:stdlib": "node rollup/stdlib.cjs",
|
||||||
"build:3rd-party": "node rollup/3rd-party.cjs",
|
"build:3rd-party": "node rollup/3rd-party.cjs",
|
||||||
@@ -43,7 +44,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ungap/with-resolvers": "^0.1.0",
|
"@ungap/with-resolvers": "^0.1.0",
|
||||||
"basic-devtools": "^0.1.6",
|
"basic-devtools": "^0.1.6",
|
||||||
"polyscript": "^0.12.14",
|
"polyscript": "^0.13.5",
|
||||||
"sticky-module": "^0.1.1",
|
"sticky-module": "^0.1.1",
|
||||||
"to-json-callback": "^0.1.1",
|
"to-json-callback": "^0.1.1",
|
||||||
"type-checked-collections": "^0.1.7"
|
"type-checked-collections": "^0.1.7"
|
||||||
@@ -53,18 +54,19 @@
|
|||||||
"@codemirror/lang-python": "^6.1.6",
|
"@codemirror/lang-python": "^6.1.6",
|
||||||
"@codemirror/language": "^6.10.2",
|
"@codemirror/language": "^6.10.2",
|
||||||
"@codemirror/state": "^6.4.1",
|
"@codemirror/state": "^6.4.1",
|
||||||
"@codemirror/view": "^6.27.0",
|
"@codemirror/view": "^6.28.1",
|
||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@rollup/plugin-commonjs": "^25.0.8",
|
"@rollup/plugin-commonjs": "^26.0.1",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@webreflection/toml-j0.4": "^1.1.3",
|
"@webreflection/toml-j0.4": "^1.1.3",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/addon-web-links": "^0.11.0",
|
"@xterm/addon-web-links": "^0.11.0",
|
||||||
"bun": "^1.1.12",
|
"bun": "^1.1.14",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"eslint": "^9.4.0",
|
"eslint": "^9.5.0",
|
||||||
|
"flatted": "^3.3.1",
|
||||||
"rollup": "^4.18.0",
|
"rollup": "^4.18.0",
|
||||||
"rollup-plugin-postcss": "^4.0.2",
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
"rollup-plugin-string": "^3.0.0",
|
"rollup-plugin-string": "^3.0.0",
|
||||||
|
|||||||
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)));
|
||||||
@@ -86,21 +86,24 @@ async function execute({ currentTarget }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeRunButton = (listener, type) => {
|
const makeRunButton = (handler, type) => {
|
||||||
const runButton = document.createElement("button");
|
const runButton = document.createElement("button");
|
||||||
runButton.className = `absolute ${type}-editor-run-button`;
|
runButton.className = `absolute ${type}-editor-run-button`;
|
||||||
runButton.innerHTML = RUN_BUTTON;
|
runButton.innerHTML = RUN_BUTTON;
|
||||||
runButton.setAttribute("aria-label", "Python Script Run Button");
|
runButton.setAttribute("aria-label", "Python Script Run Button");
|
||||||
runButton.addEventListener("click", listener);
|
runButton.addEventListener("click", async (event) => {
|
||||||
|
runButton.blur();
|
||||||
|
await handler.handleEvent(event);
|
||||||
|
});
|
||||||
return runButton;
|
return runButton;
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeEditorDiv = (listener, type) => {
|
const makeEditorDiv = (handler, type) => {
|
||||||
const editorDiv = document.createElement("div");
|
const editorDiv = document.createElement("div");
|
||||||
editorDiv.className = `${type}-editor-input`;
|
editorDiv.className = `${type}-editor-input`;
|
||||||
editorDiv.setAttribute("aria-label", "Python Script Area");
|
editorDiv.setAttribute("aria-label", "Python Script Area");
|
||||||
|
|
||||||
const runButton = makeRunButton(listener, type);
|
const runButton = makeRunButton(handler, type);
|
||||||
const editorShadowContainer = document.createElement("div");
|
const editorShadowContainer = document.createElement("div");
|
||||||
|
|
||||||
// avoid outer elements intercepting key events (reveal as example)
|
// avoid outer elements intercepting key events (reveal as example)
|
||||||
@@ -120,15 +123,15 @@ const makeOutDiv = (type) => {
|
|||||||
return outDiv;
|
return outDiv;
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeBoxDiv = (listener, type) => {
|
const makeBoxDiv = (handler, type) => {
|
||||||
const boxDiv = document.createElement("div");
|
const boxDiv = document.createElement("div");
|
||||||
boxDiv.className = `${type}-editor-box`;
|
boxDiv.className = `${type}-editor-box`;
|
||||||
|
|
||||||
const editorDiv = makeEditorDiv(listener, type);
|
const editorDiv = makeEditorDiv(handler, type);
|
||||||
const outDiv = makeOutDiv(type);
|
const outDiv = makeOutDiv(type);
|
||||||
boxDiv.append(editorDiv, outDiv);
|
boxDiv.append(editorDiv, outDiv);
|
||||||
|
|
||||||
return [boxDiv, outDiv];
|
return [boxDiv, outDiv, editorDiv.querySelector("button")];
|
||||||
};
|
};
|
||||||
|
|
||||||
const init = async (script, type, interpreter) => {
|
const init = async (script, type, interpreter) => {
|
||||||
@@ -138,7 +141,7 @@ const init = async (script, type, interpreter) => {
|
|||||||
{ python },
|
{ python },
|
||||||
{ indentUnit },
|
{ indentUnit },
|
||||||
{ keymap },
|
{ keymap },
|
||||||
{ defaultKeymap },
|
{ defaultKeymap, indentWithTab },
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
import(/* webpackIgnore: true */ "../3rd-party/codemirror.js"),
|
import(/* webpackIgnore: true */ "../3rd-party/codemirror.js"),
|
||||||
import(/* webpackIgnore: true */ "../3rd-party/codemirror_state.js"),
|
import(/* webpackIgnore: true */ "../3rd-party/codemirror_state.js"),
|
||||||
@@ -168,6 +171,8 @@ const init = async (script, type, interpreter) => {
|
|||||||
? await fetch(script.src).then((b) => b.text())
|
? await fetch(script.src).then((b) => b.text())
|
||||||
: script.textContent;
|
: script.textContent;
|
||||||
const context = {
|
const context = {
|
||||||
|
// allow the listener to be overridden at distance
|
||||||
|
handleEvent: execute,
|
||||||
interpreter,
|
interpreter,
|
||||||
env,
|
env,
|
||||||
config:
|
config:
|
||||||
@@ -184,6 +189,29 @@ const init = async (script, type, interpreter) => {
|
|||||||
let target;
|
let target;
|
||||||
defineProperties(script, {
|
defineProperties(script, {
|
||||||
target: { get: () => target },
|
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: {
|
code: {
|
||||||
get: () => context.pySrc,
|
get: () => context.pySrc,
|
||||||
set: (insert) => {
|
set: (insert) => {
|
||||||
@@ -214,8 +242,8 @@ const init = async (script, type, interpreter) => {
|
|||||||
isSetup = wasSetup;
|
isSetup = wasSetup;
|
||||||
source = wasSource;
|
source = wasSource;
|
||||||
};
|
};
|
||||||
return execute
|
return context
|
||||||
.call(context, { currentTarget: null })
|
.handleEvent({ currentTarget: null })
|
||||||
.then(restore, restore);
|
.then(restore, restore);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -227,7 +255,7 @@ const init = async (script, type, interpreter) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isSetup) {
|
if (isSetup) {
|
||||||
await execute.call(context, { currentTarget: null });
|
await context.handleEvent({ currentTarget: null });
|
||||||
notify();
|
notify();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -250,8 +278,7 @@ const init = async (script, type, interpreter) => {
|
|||||||
if (!target.hasAttribute("root")) target.setAttribute("root", target.id);
|
if (!target.hasAttribute("root")) target.setAttribute("root", target.id);
|
||||||
|
|
||||||
// @see https://github.com/JeffersGlass/mkdocs-pyscript/blob/main/mkdocs_pyscript/js/makeblocks.js
|
// @see https://github.com/JeffersGlass/mkdocs-pyscript/blob/main/mkdocs_pyscript/js/makeblocks.js
|
||||||
const listener = execute.bind(context);
|
const [boxDiv, outDiv, runButton] = makeBoxDiv(context, type);
|
||||||
const [boxDiv, outDiv] = makeBoxDiv(listener, type);
|
|
||||||
boxDiv.dataset.env = script.hasAttribute("env") ? env : interpreter;
|
boxDiv.dataset.env = script.hasAttribute("env") ? env : interpreter;
|
||||||
|
|
||||||
const inputChild = boxDiv.querySelector(`.${type}-editor-input > div`);
|
const inputChild = boxDiv.querySelector(`.${type}-editor-input > div`);
|
||||||
@@ -264,8 +291,9 @@ const init = async (script, type, interpreter) => {
|
|||||||
const doc = dedent(script.textContent).trim();
|
const doc = dedent(script.textContent).trim();
|
||||||
|
|
||||||
// preserve user indentation, if any
|
// 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({
|
const editor = new EditorView({
|
||||||
extensions: [
|
extensions: [
|
||||||
indentUnit.of(indentation),
|
indentUnit.of(indentation),
|
||||||
@@ -275,9 +303,13 @@ const init = async (script, type, interpreter) => {
|
|||||||
{ key: "Ctrl-Enter", run: listener, preventDefault: true },
|
{ key: "Ctrl-Enter", run: listener, preventDefault: true },
|
||||||
{ key: "Cmd-Enter", run: listener, preventDefault: true },
|
{ key: "Cmd-Enter", run: listener, preventDefault: true },
|
||||||
{ key: "Shift-Enter", run: listener, preventDefault: true },
|
{ key: "Shift-Enter", run: listener, preventDefault: true },
|
||||||
|
// @see https://codemirror.net/examples/tab/
|
||||||
|
indentWithTab,
|
||||||
]),
|
]),
|
||||||
basicSetup,
|
basicSetup,
|
||||||
],
|
],
|
||||||
|
foldGutter: true,
|
||||||
|
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||||
parent,
|
parent,
|
||||||
doc,
|
doc,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ from pyscript.magic_js import (
|
|||||||
sync,
|
sync,
|
||||||
window,
|
window,
|
||||||
)
|
)
|
||||||
|
from pyscript.storage import Storage, storage
|
||||||
from pyscript.websocket import WebSocket
|
from pyscript.websocket import WebSocket
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
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)
|
||||||
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}"))
|
||||||
5
pyscript.core/src/stdlib/pyscript/web/__init__.py
Normal file
5
pyscript.core/src/stdlib/pyscript/web/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from . import elements
|
||||||
|
|
||||||
|
# Ugly trick to hide the dom module in the web package since we want the module
|
||||||
|
# to allow querying right away.
|
||||||
|
from .dom import dom
|
||||||
21
pyscript.core/src/stdlib/pyscript/web/dom.py
Normal file
21
pyscript.core/src/stdlib/pyscript/web/dom.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from pyscript import document
|
||||||
|
from pyscript.web.elements import Element, ElementCollection
|
||||||
|
|
||||||
|
|
||||||
|
class PyDom:
|
||||||
|
# Add objects we want to expose to the DOM namespace since this class instance is being
|
||||||
|
# remapped as "the module" itself
|
||||||
|
ElementCollection = ElementCollection
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._js = document
|
||||||
|
|
||||||
|
self.body = Element(document.body)
|
||||||
|
self.head = Element(document.head)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
elements = self._js.querySelectorAll(key)
|
||||||
|
return ElementCollection([Element(el) for el in elements])
|
||||||
|
|
||||||
|
|
||||||
|
dom = PyDom()
|
||||||
1484
pyscript.core/src/stdlib/pyscript/web/elements.py
Normal file
1484
pyscript.core/src/stdlib/pyscript/web/elements.py
Normal file
File diff suppressed because it is too large
Load Diff
95
pyscript.core/src/stdlib/pyscript/web/media.py
Normal file
95
pyscript.core/src/stdlib/pyscript/web/media.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
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()
|
||||||
|
]
|
||||||
@@ -88,3 +88,8 @@ test('MicroPython + Pyodide ffi', async ({ page }) => {
|
|||||||
await page.goto('http://localhost:8080/test/ffi.html');
|
await page.goto('http://localhost:8080/test/ffi.html');
|
||||||
await page.waitForSelector('html.mpy.py');
|
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');
|
||||||
|
});
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
<body>
|
<body>
|
||||||
<script type="mpy" src="pydom.py"></script>
|
<script type="mpy" src="pydom.py"></script>
|
||||||
|
|
||||||
|
<div id="system-info"></div>
|
||||||
|
|
||||||
<button id="just-a-button">Click For Time</button>
|
<button id="just-a-button">Click For Time</button>
|
||||||
<button id="color-button">Click For Color</button>
|
<button id="color-button">Click For Color</button>
|
||||||
<button id="color-reset-button">Reset Color</button>
|
<button id="color-reset-button">Reset Color</button>
|
||||||
|
|||||||
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>
|
||||||
8
pyscript.core/types/stdlib/pyscript.d.ts
vendored
8
pyscript.core/types/stdlib/pyscript.d.ts
vendored
@@ -5,8 +5,16 @@ declare namespace _default {
|
|||||||
"event_handling.py": string;
|
"event_handling.py": string;
|
||||||
"fetch.py": string;
|
"fetch.py": string;
|
||||||
"ffi.py": string;
|
"ffi.py": string;
|
||||||
|
"flatted.py": string;
|
||||||
"magic_js.py": string;
|
"magic_js.py": string;
|
||||||
|
"storage.py": string;
|
||||||
"util.py": string;
|
"util.py": string;
|
||||||
|
web: {
|
||||||
|
"__init__.py": string;
|
||||||
|
"dom.py": string;
|
||||||
|
"elements.py": string;
|
||||||
|
"media.py": string;
|
||||||
|
};
|
||||||
"websocket.py": string;
|
"websocket.py": string;
|
||||||
};
|
};
|
||||||
let pyweb: {
|
let pyweb: {
|
||||||
|
|||||||
Reference in New Issue
Block a user