1
0
mirror of https://github.com/pyscript/pyscript.git synced 2022-05-01 19:47:48 +03:00

[PYS-12] Format and lint

This commit is contained in:
Ross Bermudez
2022-04-25 14:57:00 +02:00
parent 6f8f978ef7
commit 27deee3f86
17 changed files with 874 additions and 863 deletions

View File

@@ -3,31 +3,44 @@ module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking'
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
extraFileExtensions: ['.svelte']
extraFileExtensions: ['.svelte'],
},
env: {
es6: true,
browser: true
browser: true,
},
overrides: [
{
files: ['*.svelte'],
processor: 'svelte3/svelte3'
}
processor: 'svelte3/svelte3',
},
],
settings: {
'svelte3/typescript': require('typescript'),
// ignore style tags in Svelte because of Tailwind CSS
// See https://github.com/sveltejs/eslint-plugin-svelte3/issues/70
'svelte3/ignore-styles': () => true
'svelte3/ignore-styles': () => true,
},
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['node_modules'],
}
rules: {
'no-prototype-builtins': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/restrict-plus-operands': 'warn',
'@typescript-eslint/no-empty-function': 'warn',
},
};

View File

@@ -4,10 +4,10 @@ module.exports = {
singleQuote: true,
printWidth: 120,
plugins: ['prettier-plugin-svelte'],
semi: false,
semi: true,
svelteSortOrder: 'options-styles-scripts-markup',
svelteStrictMode: false,
svelteIndentScriptAndStyle: true,
tabWidth: 2,
tabWidth: 4,
trailingComma: 'all',
}

View File

@@ -1,81 +1,84 @@
<script lang="ts">
import Tailwind from "./Tailwind.svelte";
import { loadInterpreter } from './interpreter';
import { pyodideLoaded, loadedEnvironments, navBarOpen, componentsNavOpen, mode, scriptsQueue, initializers, postInitializers } from './stores';
let iconSize = 2;
let pyodideReadyPromise
function bumpSize(evt){
iconSize = 4;
}
function downSize(evt){
iconSize = 2;
}
const initializePyodide = async () =>{
// @ts-ignore
pyodideReadyPromise = loadInterpreter();
// @ts-ignore
let newEnv = {
'id': 'a',
'promise': pyodideReadyPromise,
'state': 'loading',
}
pyodideLoaded.set(pyodideReadyPromise);
loadedEnvironments.update((value: any): any => {
value[newEnv['id']] = newEnv;
});
let showNavBar = false;
let main = document.querySelector("#main");
navBarOpen.subscribe(value => {
showNavBar = value;
});
// now we call all initializers before we actually executed all page scripts
for (let initializer of $initializers){
await initializer();
}
// now we can actually execute the page scripts if we are in play mode
if ($mode == "play"){
for (let script of $scriptsQueue) {
script.evaluate();
}
scriptsQueue.set([]);
}
// now we call all post initializers AFTER we actually executed all page scripts
setTimeout(() => {
for (let initializer of $postInitializers){
initializer();
}
}, 3000);
}
function toggleComponentsNavBar(evt){
componentsNavOpen.set(!$componentsNavOpen);
}
</script>
<style>
:global(div.buttons-box) {
margin-top: -25px;
}
:global(.parentBox:hover .buttons-box) {
visibility: visible;
}
:global(div.buttons-box) {
margin-top: -25px;
}
:global(.parentBox:hover .buttons-box) {
visibility: visible;
}
</style>
<script lang="ts">
import Tailwind from './Tailwind.svelte';
import { loadInterpreter } from './interpreter';
import {
componentsNavOpen,
initializers,
loadedEnvironments,
mode,
navBarOpen,
postInitializers,
pyodideLoaded,
scriptsQueue,
} from './stores';
let iconSize = 2;
let pyodideReadyPromise;
function bumpSize(evt) {
iconSize = 4;
}
function downSize(evt) {
iconSize = 2;
}
const initializePyodide = async () => {
pyodideReadyPromise = loadInterpreter();
let newEnv = {
id: 'a',
promise: pyodideReadyPromise,
state: 'loading',
};
pyodideLoaded.set(pyodideReadyPromise);
loadedEnvironments.update((value: any): any => {
value[newEnv['id']] = newEnv;
});
let showNavBar = false;
let main = document.querySelector('#main');
navBarOpen.subscribe(value => {
showNavBar = value;
});
// now we call all initializers before we actually executed all page scripts
for (let initializer of $initializers) {
await initializer();
}
// now we can actually execute the page scripts if we are in play mode
if ($mode == 'play') {
for (let script of $scriptsQueue) {
script.evaluate();
}
scriptsQueue.set([]);
}
// now we call all post initializers AFTER we actually executed all page scripts
setTimeout(() => {
for (let initializer of $postInitializers) {
initializer();
}
}, 3000);
};
function toggleComponentsNavBar(evt) {
componentsNavOpen.set(!$componentsNavOpen);
}
</script>
<svelte:head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js" on:load={initializePyodide}></script>
<script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js" on:load={initializePyodide}></script>
</svelte:head>
<Tailwind />

View File

@@ -2,4 +2,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>
</style>

View File

@@ -1,4 +1,4 @@
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, mode } from '../stores';
import { componentDetailsNavOpen, loadedEnvironments, mode, pyodideLoaded } from '../stores';
import { guidGenerator } from '../utils';
// Premise used to connect to the first available pyodide interpreter
let pyodideReadyPromise;
@@ -7,7 +7,7 @@ let currentMode;
let Element;
pyodideLoaded.subscribe(value => {
pyodideReadyPromise = value;
pyodideReadyPromise = value;
});
loadedEnvironments.subscribe(value => {
environments = value;
@@ -15,17 +15,17 @@ loadedEnvironments.subscribe(value => {
let propertiesNavOpen;
componentDetailsNavOpen.subscribe(value => {
propertiesNavOpen = value;
propertiesNavOpen = value;
});
mode.subscribe(value => {
currentMode = value;
currentMode = value;
});
// TODO: use type declaractions
type PyodideInterface = {
registerJsModule(name: string, module: object): void
}
registerJsModule(name: string, module: object): void;
};
export class BaseEvalElement extends HTMLElement {
shadow: ShadowRoot;
@@ -37,142 +37,136 @@ export class BaseEvalElement extends HTMLElement {
outputElement: HTMLElement;
errorElement: HTMLElement;
theme: string;
constructor() {
super();
// attach shadow so we can preserve the element original innerHtml content
this.shadow = this.attachShadow({ mode: 'open'});
this.shadow = this.attachShadow({ mode: 'open' });
this.wrapper = document.createElement('slot');
this.shadow.appendChild(this.wrapper);
}
addToOutput(s: string) {
this.outputElement.innerHTML += "<div>"+s+"</div>";
this.outputElement.innerHTML += '<div>' + s + '</div>';
this.outputElement.hidden = false;
}
postEvaluate(){
}
checkId(){
if (!this.id)
this.id = this.constructor.name+"-"+guidGenerator();
postEvaluate() {}
checkId() {
if (!this.id) this.id = this.constructor.name + '-' + guidGenerator();
}
getSourceFromElement(): string{
return "";
getSourceFromElement(): string {
return '';
}
async getSourceFromFile(s: string): Promise<string>{
let pyodide = await pyodideReadyPromise;
let response = await fetch(s);
async getSourceFromFile(s: string): Promise<string> {
const pyodide = await pyodideReadyPromise;
const response = await fetch(s);
this.code = await response.text();
return this.code;
}
}
protected async _register_esm(pyodide: PyodideInterface): Promise<void> {
const imports: {[key: string]: unknown} = {}
const imports: { [key: string]: unknown } = {};
for (const node of document.querySelectorAll("script[type='importmap']")) {
const importmap = (() => {
try {
return JSON.parse(node.textContent)
} catch {
return null
const importmap = (() => {
try {
return JSON.parse(node.textContent);
} catch {
return null;
}
})();
if (importmap?.imports == null) continue;
for (const [name, url] of Object.entries(importmap.imports)) {
if (typeof name != 'string' || typeof url != 'string') continue;
try {
// XXX: pyodide doesn't like Module(), failing with
// "can't read 'name' of undefined" at import time
imports[name] = { ...(await import(url)) };
} catch {
console.error(`failed to fetch '${url}' for '${name}'`);
}
}
})()
if (importmap?.imports == null)
continue
for (const [name, url] of Object.entries(importmap.imports)) {
if (typeof name != "string" || typeof url != "string")
continue
try {
// XXX: pyodide doesn't like Module(), failing with
// "can't read 'name' of undefined" at import time
imports[name] = {...await import(url)}
} catch {
console.error(`failed to fetch '${url}' for '${name}'`)
}
}
}
pyodide.registerJsModule("esm", imports)
pyodide.registerJsModule('esm', imports);
}
async evaluate(): Promise<void> {
console.log('evaluate');
let pyodide = await pyodideReadyPromise;
const pyodide = await pyodideReadyPromise;
let source: string;
let output;
try {
// @ts-ignore
if (this.source){
if (this.source) {
source = await this.getSourceFromFile(this.source);
}else{
} else {
source = this.getSourceFromElement();
}
await this._register_esm(pyodide);
if (source.includes("asyncio")){
await pyodide.runPythonAsync(`output_manager.change("`+this.outputElement.id+`", "`+this.errorElement.id+`")`);
if (source.includes('asyncio')) {
await pyodide.runPythonAsync(
`output_manager.change("` + this.outputElement.id + `", "` + this.errorElement.id + `")`,
);
output = await pyodide.runPythonAsync(source);
await pyodide.runPythonAsync(`output_manager.revert()`)
}else{
output = pyodide.runPython(`output_manager.change("`+this.outputElement.id+`", "`+this.errorElement.id+`")`);
await pyodide.runPythonAsync(`output_manager.revert()`);
} else {
output = pyodide.runPython(
`output_manager.change("` + this.outputElement.id + `", "` + this.errorElement.id + `")`,
);
output = pyodide.runPython(source);
pyodide.runPython(`output_manager.revert()`)
pyodide.runPython(`output_manager.revert()`);
}
if (output !== undefined){
if (Element === undefined){
if (output !== undefined) {
if (Element === undefined) {
Element = pyodide.globals.get('Element');
}
const out = Element(this.outputElement.id);
// @ts-ignore
out.write.callKwargs(output, { append : true});
out.write.callKwargs(output, { append: true });
this.outputElement.hidden = false;
this.outputElement.hidden = false;
this.outputElement.style.display = 'block';
}
this.postEvaluate()
this.postEvaluate();
} catch (err) {
if (Element === undefined){
if (Element === undefined) {
Element = pyodide.globals.get('Element');
}
const out = Element(this.errorElement.id);
// @ts-ignore
out.write.callKwargs(err, { append : true});
out.write.callKwargs(err, { append: true });
this.errorElement.hidden = false;
this.errorElement.style.display = 'block';
}
} // end evaluate
}
} // end evaluate
async eval(source: string): Promise<void> {
async eval(source: string): Promise<void> {
let output;
let pyodide = await pyodideReadyPromise;
try{
const pyodide = await pyodideReadyPromise;
try {
output = await pyodide.runPythonAsync(source);
if (output !== undefined){ console.log(output); }
if (output !== undefined) {
console.log(output);
}
} catch (err) {
console.log(err);
}
} // end eval
}
}
function createWidget(name: string, code: string, klass: string){
class CustomWidget extends HTMLElement{
function createWidget(name: string, code: string, klass: string) {
class CustomWidget extends HTMLElement {
shadow: ShadowRoot;
wrapper: HTMLElement;
@@ -183,13 +177,13 @@ export class BaseEvalElement extends HTMLElement {
proxyClass: any;
constructor() {
super();
// attach shadow so we can preserve the element original innerHtml content
this.shadow = this.attachShadow({ mode: 'open'});
this.wrapper = document.createElement('slot');
this.shadow.appendChild(this.wrapper);
super();
// attach shadow so we can preserve the element original innerHtml content
this.shadow = this.attachShadow({ mode: 'open' });
this.wrapper = document.createElement('slot');
this.shadow.appendChild(this.wrapper);
}
connectedCallback() {
@@ -207,19 +201,19 @@ export class BaseEvalElement extends HTMLElement {
}, 2000);
}
async registerWidget(){
let pyodide = await pyodideReadyPromise;
async registerWidget() {
const pyodide = await pyodideReadyPromise;
console.log('new widget registered:', this.name);
pyodide.globals.set(this.id, this.proxy);
}
async eval(source: string): Promise<void> {
let output;
let pyodide = await pyodideReadyPromise;
try{
const pyodide = await pyodideReadyPromise;
try {
output = await pyodide.runPythonAsync(source);
this.proxyClass = pyodide.globals.get(this.klass);
if (output !== undefined){
if (output !== undefined) {
console.log(output);
}
} catch (err) {
@@ -227,10 +221,10 @@ export class BaseEvalElement extends HTMLElement {
}
}
}
let xPyWidget = customElements.define(name, CustomWidget);
}
const xPyWidget = customElements.define(name, CustomWidget);
}
export class PyWidget extends HTMLElement {
export class PyWidget extends HTMLElement {
shadow: ShadowRoot;
name: string;
klass: string;
@@ -240,43 +234,45 @@ export class BaseEvalElement extends HTMLElement {
theme: string;
source: string;
code: string;
constructor() {
super();
// attach shadow so we can preserve the element original innerHtml content
this.shadow = this.attachShadow({ mode: 'open'});
this.shadow = this.attachShadow({ mode: 'open' });
this.wrapper = document.createElement('slot');
this.shadow.appendChild(this.wrapper);
if (this.hasAttribute('src')) {
this.source = this.getAttribute('src');
this.source = this.getAttribute('src');
}
if (this.hasAttribute('name')) {
this.name = this.getAttribute('name');
this.name = this.getAttribute('name');
}
if (this.hasAttribute('klass')) {
this.klass = this.getAttribute('klass');
this.klass = this.getAttribute('klass');
}
}
}
connectedCallback() {
if (this.id === undefined){
throw new ReferenceError(`No id specified for component. Components must have an explicit id. Please use id="" to specify your component id.`)
return;
}
if (this.id === undefined) {
throw new ReferenceError(
`No id specified for component. Components must have an explicit id. Please use id="" to specify your component id.`,
);
return;
}
let mainDiv = document.createElement('div');
mainDiv.id = this.id + '-main';
this.appendChild(mainDiv);
console.log('reading source')
this.getSourceFromFile(this.source).then((code:string) => {
this.code = code;
createWidget(this.name, code, this.klass);
});
const mainDiv = document.createElement('div');
mainDiv.id = this.id + '-main';
this.appendChild(mainDiv);
console.log('reading source');
this.getSourceFromFile(this.source).then((code: string) => {
this.code = code;
createWidget(this.name, code, this.klass);
});
}
initOutErr(): void {
@@ -287,42 +283,42 @@ export class BaseEvalElement extends HTMLElement {
if (!this.hasAttribute('output-mode')) {
this.setAttribute('output-mode', 'append');
}
}else{
if (this.hasAttribute('std-out')){
} else {
if (this.hasAttribute('std-out')) {
this.outputElement = document.getElementById(this.getAttribute('std-out'));
}else{
} else {
// In this case neither output or std-out have been provided so we need
// to create a new output div to output to
this.outputElement = document.createElement('div');
this.outputElement.classList.add("output");
this.outputElement.classList.add('output');
this.outputElement.hidden = true;
this.outputElement.id = this.id + "-" + this.getAttribute("exec-id");
this.outputElement.id = this.id + '-' + this.getAttribute('exec-id');
}
if (this.hasAttribute('std-err')){
if (this.hasAttribute('std-err')) {
this.outputElement = document.getElementById(this.getAttribute('std-err'));
}else{
} else {
this.errorElement = this.outputElement;
}
}
}
async getSourceFromFile(s: string): Promise<string>{
let pyodide = await pyodideReadyPromise;
let response = await fetch(s);
async getSourceFromFile(s: string): Promise<string> {
const pyodide = await pyodideReadyPromise;
const response = await fetch(s);
return await response.text();
}
async eval(source: string): Promise<void> {
let output;
let pyodide = await pyodideReadyPromise;
try{
const pyodide = await pyodideReadyPromise;
try {
output = await pyodide.runPythonAsync(source);
if (output !== undefined){
if (output !== undefined) {
console.log(output);
}
} catch (err) {
console.log(err);
}
}
}
}

View File

@@ -5,65 +5,62 @@ export class PyBox extends HTMLElement {
wrapper: HTMLElement;
theme: string;
widths: Array<string>;
constructor() {
super();
// attach shadow so we can preserve the element original innerHtml content
this.shadow = this.attachShadow({ mode: 'open'});
this.shadow = this.attachShadow({ mode: 'open' });
this.wrapper = document.createElement('slot');
this.shadow.appendChild(this.wrapper);
}
}
connectedCallback() {
let mainDiv = document.createElement('div');
addClasses(mainDiv, ["flex", "mx-8"])
// Hack: for some reason when moving children, the editor box duplicates children
// meaning that we end up with 2 editors, if there's a <py-repl> inside the <py-box>
// so, if we have more than 2 children with the cm-editor class, we remove one of them
while (this.childNodes.length > 0) {
console.log(this.firstChild);
if ( this.firstChild.nodeName == "PY-REPL" ){
// in this case we need to remove the child and craete a new one from scratch
let replDiv = document.createElement('div');
// we need to put the new repl inside a div so that if the repl has auto-generate true
// it can replicate itself inside that constrained div
replDiv.appendChild(this.firstChild.cloneNode());
mainDiv.appendChild(replDiv);
this.firstChild.remove();
}
else{
if ( this.firstChild.nodeName != "#text" ){
mainDiv.appendChild(this.firstChild);
}else{
this.firstChild.remove()
}
}
}
const mainDiv = document.createElement('div');
addClasses(mainDiv, ['flex', 'mx-8']);
// now we need to set widths
this.widths = []
if (this.hasAttribute('widths')) {
for (let w of this.getAttribute('widths').split(";")) {
this.widths.push(`w-${w}`);
// Hack: for some reason when moving children, the editor box duplicates children
// meaning that we end up with 2 editors, if there's a <py-repl> inside the <py-box>
// so, if we have more than 2 children with the cm-editor class, we remove one of them
while (this.childNodes.length > 0) {
console.log(this.firstChild);
if (this.firstChild.nodeName == 'PY-REPL') {
// in this case we need to remove the child and craete a new one from scratch
const replDiv = document.createElement('div');
// we need to put the new repl inside a div so that if the repl has auto-generate true
// it can replicate itself inside that constrained div
replDiv.appendChild(this.firstChild.cloneNode());
mainDiv.appendChild(replDiv);
this.firstChild.remove();
} else {
if (this.firstChild.nodeName != '#text') {
mainDiv.appendChild(this.firstChild);
} else {
this.firstChild.remove();
}
}
}
}else{
for (let el of mainDiv.childNodes) {
this.widths.push(`w-1/${mainDiv.childNodes.length}`);
// now we need to set widths
this.widths = [];
if (this.hasAttribute('widths')) {
for (const w of this.getAttribute('widths').split(';')) {
this.widths.push(`w-${w}`);
}
} else {
for (const el of mainDiv.childNodes) {
this.widths.push(`w-1/${mainDiv.childNodes.length}`);
}
}
}
for (let i in this.widths) {
// @ts-ignore
addClasses(mainDiv.childNodes[parseInt(i)], [this.widths[i], 'mx-4']);
}
this.widths.forEach((width, index)=>{
const node: ChildNode = mainDiv.childNodes[index];
addClasses(node, [width, 'mx-4'])
this.appendChild(mainDiv);
console.log('py-box connected');
})
this.appendChild(mainDiv);
console.log('py-box connected');
}
}
}

View File

@@ -10,47 +10,47 @@ export class PyButton extends BaseEvalElement {
mount_name: string;
constructor() {
super();
if (this.hasAttribute('label')) {
this.label = this.getAttribute('label');
this.label = this.getAttribute('label');
}
}
}
connectedCallback() {
this.code = htmlDecode(this.innerHTML);
this.mount_name = this.id.split("-").join("_");
this.innerHTML = '';
let mainDiv = document.createElement('button');
mainDiv.innerHTML = this.label;
addClasses(mainDiv, ["p-2", "text-white", "bg-blue-600", "border", "border-blue-600", "rounded"]);
mainDiv.id = this.id;
this.id = `${this.id}-container`;
this.code = htmlDecode(this.innerHTML);
this.mount_name = this.id.split('-').join('_');
this.innerHTML = '';
this.appendChild(mainDiv);
this.code = this.code.split("self").join(this.mount_name);
let registrationCode = `${this.mount_name} = Element("${ mainDiv.id }")`;
if (this.code.includes("def on_focus")){
this.code = this.code.replace("def on_focus", `def on_focus_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.onfocus = on_focus_${this.mount_name}`
}
const mainDiv = document.createElement('button');
mainDiv.innerHTML = this.label;
addClasses(mainDiv, ['p-2', 'text-white', 'bg-blue-600', 'border', 'border-blue-600', 'rounded']);
if (this.code.includes("def on_click")){
this.code = this.code.replace("def on_click", `def on_click_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.onclick = on_click_${this.mount_name}`
}
// now that we appended and the element is attached, lets connect with the event handlers
// defined for this widget
setTimeout(() => {
this.eval(this.code).then(() => {
this.eval(registrationCode).then(() => {
console.log('registered handlers');
});
});
}, 4000);
console.log('py-button connected');
mainDiv.id = this.id;
this.id = `${this.id}-container`;
this.appendChild(mainDiv);
this.code = this.code.split('self').join(this.mount_name);
let registrationCode = `${this.mount_name} = Element("${mainDiv.id}")`;
if (this.code.includes('def on_focus')) {
this.code = this.code.replace('def on_focus', `def on_focus_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.onfocus = on_focus_${this.mount_name}`;
}
if (this.code.includes('def on_click')) {
this.code = this.code.replace('def on_click', `def on_click_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.onclick = on_click_${this.mount_name}`;
}
// now that we appended and the element is attached, lets connect with the event handlers
// defined for this widget
setTimeout(() => {
this.eval(this.code).then(() => {
this.eval(registrationCode).then(() => {
console.log('registered handlers');
});
});
}, 4000);
console.log('py-button connected');
}
}
}

View File

@@ -9,68 +9,65 @@ let environments;
let currentMode;
pyodideLoaded.subscribe(value => {
pyodideReadyPromise = value;
pyodideReadyPromise = value;
});
loadedEnvironments.subscribe(value => {
environments = value;
environments = value;
});
mode.subscribe(value => {
currentMode = value;
currentMode = value;
});
export class PyEnv extends HTMLElement {
shadow: ShadowRoot;
wrapper: HTMLElement;
code: string;
environment: any;
shadow: ShadowRoot;
wrapper: HTMLElement;
code: string;
environment: any;
constructor() {
super();
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open'});
this.wrapper = document.createElement('slot');
}
this.shadow = this.attachShadow({ mode: 'open' });
this.wrapper = document.createElement('slot');
}
connectedCallback() {
this.code = this.innerHTML;
this.innerHTML = '';
connectedCallback() {
this.code = this.innerHTML;
this.innerHTML = '';
let env = [];
let paths = [];
const env = [];
const paths = [];
this.environment = jsyaml.load(this.code);
if (this.environment === undefined)
return
this.environment = jsyaml.load(this.code);
if (this.environment === undefined) return;
for (let entry of this.environment) {
if (typeof entry == "string" ){
env.push(entry);
}
else if (entry.hasOwnProperty('paths')){
for (let path of entry.paths) {
paths.push(path);
for (const entry of this.environment) {
if (typeof entry == 'string') {
env.push(entry);
} else if (entry.hasOwnProperty('paths')) {
for (const path of entry.paths) {
paths.push(path);
}
}
}
}
}
async function loadEnv() {
let pyodide = await pyodideReadyPromise;
await loadPackage(env, pyodide);
console.log("enviroment loaded")
}
async function loadEnv() {
const pyodide = await pyodideReadyPromise;
await loadPackage(env, pyodide);
console.log('enviroment loaded');
}
async function loadPaths() {
let pyodide = await pyodideReadyPromise;
for (let singleFile of paths) {
await loadFromFile(singleFile, pyodide);
}
console.log("paths loaded")
async function loadPaths() {
const pyodide = await pyodideReadyPromise;
for (const singleFile of paths) {
await loadFromFile(singleFile, pyodide);
}
console.log('paths loaded');
}
addInitializer(loadEnv);
addInitializer(loadPaths);
console.log('enviroment loading...', env);
}
addInitializer(loadEnv);
addInitializer(loadPaths);
console.log("enviroment loading...", env)
}
}

View File

@@ -9,46 +9,44 @@ export class PyInputBox extends BaseEvalElement {
label: string;
mount_name: string;
constructor() {
super();
super();
if (this.hasAttribute('label')) {
this.label = this.getAttribute('label');
}
if (this.hasAttribute('label')) {
this.label = this.getAttribute('label');
}
}
connectedCallback() {
this.code = htmlDecode(this.innerHTML);
this.mount_name = this.id.split("-").join("_");
this.innerHTML = '';
let mainDiv = document.createElement('input');
mainDiv.type = "text";
addClasses(mainDiv, ["border", "flex-1", "w-full", "mr-3", "border-gray-300", "p-2", "rounded"]);
this.code = htmlDecode(this.innerHTML);
this.mount_name = this.id.split('-').join('_');
this.innerHTML = '';
mainDiv.id = this.id;
this.id = `${this.id}-container`;
this.appendChild(mainDiv);
const mainDiv = document.createElement('input');
mainDiv.type = 'text';
addClasses(mainDiv, ['border', 'flex-1', 'w-full', 'mr-3', 'border-gray-300', 'p-2', 'rounded']);
// now that we appended and the element is attached, lets connect with the event handlers
// defined for this widget
this.appendChild(mainDiv);
this.code = this.code.split("self").join(this.mount_name);
let registrationCode = `${this.mount_name} = Element("${ mainDiv.id }")`;
if (this.code.includes("def on_keypress")){
this.code = this.code.replace("def on_keypress", `def on_keypress_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.onkeypress = on_keypress_${this.mount_name}`
}
mainDiv.id = this.id;
this.id = `${this.id}-container`;
this.appendChild(mainDiv);
// TODO: For now we delay execution to allow pyodide to load but in the future this
// should really wait for it to load..
setTimeout(() => {
this.eval(this.code).then(() => {
this.eval(registrationCode).then(() => {
console.log('registered handlers');
});
});
// now that we appended and the element is attached, lets connect with the event handlers
// defined for this widget
this.appendChild(mainDiv);
this.code = this.code.split('self').join(this.mount_name);
let registrationCode = `${this.mount_name} = Element("${mainDiv.id}")`;
if (this.code.includes('def on_keypress')) {
this.code = this.code.replace('def on_keypress', `def on_keypress_${this.mount_name}`);
registrationCode += `\n${this.mount_name}.element.onkeypress = on_keypress_${this.mount_name}`;
}
// TODO: For now we delay execution to allow pyodide to load but in the future this
// should really wait for it to load..
setTimeout(() => {
this.eval(this.code).then(() => {
this.eval(registrationCode).then(() => {
console.log('registered handlers');
});
});
}, 4000);
}
}
}

View File

@@ -1,12 +1,11 @@
import {EditorState, EditorView, basicSetup} from "@codemirror/basic-setup"
import { python } from "@codemirror/lang-python"
// @ts-ignore
import { StateCommand, Compartment } from '@codemirror/state';
import { keymap, ViewUpdate } from "@codemirror/view";
import { defaultKeymap } from "@codemirror/commands";
import { oneDarkTheme } from "@codemirror/theme-one-dark";
import { basicSetup, EditorState, EditorView } from '@codemirror/basic-setup';
import { python } from '@codemirror/lang-python';
import { Compartment, StateCommand } from '@codemirror/state';
import { keymap } from '@codemirror/view';
import { defaultKeymap } from '@codemirror/commands';
import { oneDarkTheme } from '@codemirror/theme-one-dark';
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, currentComponentDetails, mode } from '../stores';
import { componentDetailsNavOpen, currentComponentDetails, loadedEnvironments, mode, pyodideLoaded } from '../stores';
import { addClasses } from '../utils';
import { BaseEvalElement } from './base';
@@ -16,7 +15,7 @@ let environments;
let currentMode;
pyodideLoaded.subscribe(value => {
pyodideReadyPromise = value;
pyodideReadyPromise = value;
});
loadedEnvironments.subscribe(value => {
environments = value;
@@ -24,216 +23,230 @@ loadedEnvironments.subscribe(value => {
let propertiesNavOpen;
componentDetailsNavOpen.subscribe(value => {
propertiesNavOpen = value;
propertiesNavOpen = value;
});
mode.subscribe(value => {
currentMode = value;
currentMode = value;
});
const languageConf = new Compartment();
const languageConf = new Compartment
function createCmdHandler(el){
function createCmdHandler(el) {
// Creates a codemirror cmd handler that calls the el.evaluate when an event
// triggers that specific cmd
const toggleCheckbox:StateCommand = ({ state, dispatch }) => {
return el.evaluate(state)
}
return toggleCheckbox
const toggleCheckbox: StateCommand = ({ state, dispatch }) => {
return el.evaluate(state);
};
return toggleCheckbox;
}
export class PyRepl extends BaseEvalElement {
editor: EditorView;
editorNode: HTMLElement;
constructor() {
super();
// add an extra div where we can attach the codemirror editor
this.editorNode = document.createElement('div');
addClasses(this.editorNode, ["editor-box"])
addClasses(this.editorNode, ['editor-box']);
this.shadow.appendChild(this.wrapper);
}
}
connectedCallback() {
this.checkId()
this.code = this.innerHTML;
this.innerHTML = '';
this.checkId();
this.code = this.innerHTML;
this.innerHTML = '';
let extensions = [
basicSetup,
languageConf.of(python()),
keymap.of([
const extensions = [
basicSetup,
languageConf.of(python()),
keymap.of([
...defaultKeymap,
{ key: "Ctrl-Enter", run: createCmdHandler(this) },
{ key: "Shift-Enter", run: createCmdHandler(this) }
]),
{ key: 'Ctrl-Enter', run: createCmdHandler(this) },
{ key: 'Shift-Enter', run: createCmdHandler(this) },
]),
// Event listener function that is called every time an user types something on this editor
// EditorView.updateListener.of((v:ViewUpdate) => {
// if (v.docChanged) {
// console.log(v.changes);
// Event listener function that is called every time an user types something on this editor
// EditorView.updateListener.of((v:ViewUpdate) => {
// if (v.docChanged) {
// console.log(v.changes);
// }
// })
];
// }
// })
];
if (!this.hasAttribute('theme')) {
this.theme = this.getAttribute('theme');
if (this.theme == 'dark'){
extensions.push(oneDarkTheme);
}
}
let startState = EditorState.create({
doc: this.code.trim(),
extensions: extensions
})
this.editor = new EditorView({
state: startState,
parent: this.editorNode
})
let mainDiv = document.createElement('div');
addClasses(mainDiv, ["parentBox", "group", "flex", "flex-col", "mt-2", "border-2", "border-gray-200", "rounded-lg", "mx-8"])
// add Editor to main PyScript div
// Butons DIV
var eDiv = document.createElement('div');
addClasses(eDiv, "buttons-box opacity-0 group-hover:opacity-100 relative top-0 right-0 flex flex-row-reverse space-x-reverse space-x-4 font-mono text-white text-sm font-bold leading-6 dev-buttons-group".split(" "))
eDiv.setAttribute("role", "group");
// Play Button
this.btnRun = document.createElement('button');
this.btnRun.innerHTML = '<svg id="" class="svelte-fa svelte-ps5qeg" style="height:1em;vertical-align:-.125em;transform-origin:center;overflow:visible" 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>';
let buttonClasses = ["mr-2", "block", "py-2", "px-4", "rounded-full"];
addClasses(this.btnRun, buttonClasses);
addClasses(this.btnRun, ["bg-green-500"])
eDiv.appendChild(this.btnRun);
this.btnRun.onclick = wrap(this);
function wrap(el: any){
async function evaluatePython() {
el.evaluate()
}
return evaluatePython;
}
// Settings button
this.btnConfig = document.createElement('button');
this.btnConfig.innerHTML = '<svg id="" class="svelte-fa svelte-ps5qeg" style="height:1em;vertical-align:-.125em;transform-origin:center;overflow:visible" viewBox="0 0 512 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(256 256)" transform-origin="128 0"><g transform="translate(0,0) scale(1,1)"><path d="M495.9 166.6C499.2 175.2 496.4 184.9 489.6 191.2L446.3 230.6C447.4 238.9 448 247.4 448 256C448 264.6 447.4 273.1 446.3 281.4L489.6 320.8C496.4 327.1 499.2 336.8 495.9 345.4C491.5 357.3 486.2 368.8 480.2 379.7L475.5 387.8C468.9 398.8 461.5 409.2 453.4 419.1C447.4 426.2 437.7 428.7 428.9 425.9L373.2 408.1C359.8 418.4 344.1 427 329.2 433.6L316.7 490.7C314.7 499.7 307.7 506.1 298.5 508.5C284.7 510.8 270.5 512 255.1 512C241.5 512 227.3 510.8 213.5 508.5C204.3 506.1 197.3 499.7 195.3 490.7L182.8 433.6C167 427 152.2 418.4 138.8 408.1L83.14 425.9C74.3 428.7 64.55 426.2 58.63 419.1C50.52 409.2 43.12 398.8 36.52 387.8L31.84 379.7C25.77 368.8 20.49 357.3 16.06 345.4C12.82 336.8 15.55 327.1 22.41 320.8L65.67 281.4C64.57 273.1 64 264.6 64 256C64 247.4 64.57 238.9 65.67 230.6L22.41 191.2C15.55 184.9 12.82 175.3 16.06 166.6C20.49 154.7 25.78 143.2 31.84 132.3L36.51 124.2C43.12 113.2 50.52 102.8 58.63 92.95C64.55 85.8 74.3 83.32 83.14 86.14L138.8 103.9C152.2 93.56 167 84.96 182.8 78.43L195.3 21.33C197.3 12.25 204.3 5.04 213.5 3.51C227.3 1.201 241.5 0 256 0C270.5 0 284.7 1.201 298.5 3.51C307.7 5.04 314.7 12.25 316.7 21.33L329.2 78.43C344.1 84.96 359.8 93.56 373.2 103.9L428.9 86.14C437.7 83.32 447.4 85.8 453.4 92.95C461.5 102.8 468.9 113.2 475.5 124.2L480.2 132.3C486.2 143.2 491.5 154.7 495.9 166.6V166.6zM256 336C300.2 336 336 300.2 336 255.1C336 211.8 300.2 175.1 256 175.1C211.8 175.1 176 211.8 176 255.1C176 300.2 211.8 336 256 336z" fill="currentColor" transform="translate(-256 -256)"></path></g></g></svg>';
this.btnConfig.onclick = function toggleNavBar(evt){
console.log('clicked');
componentDetailsNavOpen.set(!propertiesNavOpen);
currentComponentDetails.set([
{key: "auto-generate", value: true},
{key:"output", value: "default"},
{key: "source", value: "self"},
{key: "output-mode", value: "clear"}
])
}
addClasses(this.btnConfig, buttonClasses);
addClasses(this.btnConfig, ["bg-blue-500"])
eDiv.appendChild(this.btnConfig);
mainDiv.appendChild(eDiv);
mainDiv.appendChild(this.editorNode);
if (!this.id){
console.log("WARNING: <pyrepl> define with an id. <pyrepl> should always have an id. More than one <pyrepl> on a page won't work otherwise!")
}
if (!this.hasAttribute('exec-id')) {
this.setAttribute("exec-id", "1");
}
if (!this.hasAttribute('root')) {
this.setAttribute("root", this.id);
}
if (this.hasAttribute('output')) {
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
// in this case, the default output-mode is append, if hasn't been specified
if (!this.hasAttribute('output-mode')) {
this.setAttribute('output-mode', 'append');
}
}else{
if (this.hasAttribute('std-out')){
this.outputElement = document.getElementById(this.getAttribute('std-out'));
}else{
// In this case neither output or std-out have been provided so we need
// to create a new output div to output to
this.outputElement = document.createElement('div');
this.outputElement.classList.add("output");
this.outputElement.hidden = true;
this.outputElement.id = this.id + "-" + this.getAttribute("exec-id");
// add the output div id if there's not output pre-defined
mainDiv.appendChild(this.outputElement);
this.theme = this.getAttribute('theme');
if (this.theme == 'dark') {
extensions.push(oneDarkTheme);
}
}
if (this.hasAttribute('std-err')){
this.errorElement = document.getElementById(this.getAttribute('std-err'));
}else{
this.errorElement = this.outputElement;
const startState = EditorState.create({
doc: this.code.trim(),
extensions: extensions,
});
this.editor = new EditorView({
state: startState,
parent: this.editorNode,
});
const mainDiv = document.createElement('div');
addClasses(mainDiv, [
'parentBox',
'group',
'flex',
'flex-col',
'mt-2',
'border-2',
'border-gray-200',
'rounded-lg',
'mx-8',
]);
// add Editor to main PyScript div
// Butons DIV
const eDiv = document.createElement('div');
addClasses(
eDiv,
'buttons-box opacity-0 group-hover:opacity-100 relative top-0 right-0 flex flex-row-reverse space-x-reverse space-x-4 font-mono text-white text-sm font-bold leading-6 dev-buttons-group'.split(
' ',
),
);
eDiv.setAttribute('role', 'group');
// Play Button
this.btnRun = document.createElement('button');
this.btnRun.innerHTML =
'<svg id="" class="svelte-fa svelte-ps5qeg" style="height:1em;vertical-align:-.125em;transform-origin:center;overflow:visible" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>';
const buttonClasses = ['mr-2', 'block', 'py-2', 'px-4', 'rounded-full'];
addClasses(this.btnRun, buttonClasses);
addClasses(this.btnRun, ['bg-green-500']);
eDiv.appendChild(this.btnRun);
this.btnRun.onclick = wrap(this);
function wrap(el: any) {
async function evaluatePython() {
await el.evaluate();
}
return evaluatePython;
}
}
// Settings button
this.btnConfig = document.createElement('button');
this.btnConfig.innerHTML =
'<svg id="" class="svelte-fa svelte-ps5qeg" style="height:1em;vertical-align:-.125em;transform-origin:center;overflow:visible" viewBox="0 0 512 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(256 256)" transform-origin="128 0"><g transform="translate(0,0) scale(1,1)"><path d="M495.9 166.6C499.2 175.2 496.4 184.9 489.6 191.2L446.3 230.6C447.4 238.9 448 247.4 448 256C448 264.6 447.4 273.1 446.3 281.4L489.6 320.8C496.4 327.1 499.2 336.8 495.9 345.4C491.5 357.3 486.2 368.8 480.2 379.7L475.5 387.8C468.9 398.8 461.5 409.2 453.4 419.1C447.4 426.2 437.7 428.7 428.9 425.9L373.2 408.1C359.8 418.4 344.1 427 329.2 433.6L316.7 490.7C314.7 499.7 307.7 506.1 298.5 508.5C284.7 510.8 270.5 512 255.1 512C241.5 512 227.3 510.8 213.5 508.5C204.3 506.1 197.3 499.7 195.3 490.7L182.8 433.6C167 427 152.2 418.4 138.8 408.1L83.14 425.9C74.3 428.7 64.55 426.2 58.63 419.1C50.52 409.2 43.12 398.8 36.52 387.8L31.84 379.7C25.77 368.8 20.49 357.3 16.06 345.4C12.82 336.8 15.55 327.1 22.41 320.8L65.67 281.4C64.57 273.1 64 264.6 64 256C64 247.4 64.57 238.9 65.67 230.6L22.41 191.2C15.55 184.9 12.82 175.3 16.06 166.6C20.49 154.7 25.78 143.2 31.84 132.3L36.51 124.2C43.12 113.2 50.52 102.8 58.63 92.95C64.55 85.8 74.3 83.32 83.14 86.14L138.8 103.9C152.2 93.56 167 84.96 182.8 78.43L195.3 21.33C197.3 12.25 204.3 5.04 213.5 3.51C227.3 1.201 241.5 0 256 0C270.5 0 284.7 1.201 298.5 3.51C307.7 5.04 314.7 12.25 316.7 21.33L329.2 78.43C344.1 84.96 359.8 93.56 373.2 103.9L428.9 86.14C437.7 83.32 447.4 85.8 453.4 92.95C461.5 102.8 468.9 113.2 475.5 124.2L480.2 132.3C486.2 143.2 491.5 154.7 495.9 166.6V166.6zM256 336C300.2 336 336 300.2 336 255.1C336 211.8 300.2 175.1 256 175.1C211.8 175.1 176 211.8 176 255.1C176 300.2 211.8 336 256 336z" fill="currentColor" transform="translate(-256 -256)"></path></g></g></svg>';
this.btnConfig.onclick = function toggleNavBar(evt) {
console.log('clicked');
componentDetailsNavOpen.set(!propertiesNavOpen);
this.appendChild(mainDiv);
this.editor.focus();
console.log('connected');
currentComponentDetails.set([
{ key: 'auto-generate', value: true },
{ key: 'output', value: 'default' },
{ key: 'source', value: 'self' },
{ key: 'output-mode', value: 'clear' },
]);
};
addClasses(this.btnConfig, buttonClasses);
addClasses(this.btnConfig, ['bg-blue-500']);
eDiv.appendChild(this.btnConfig);
mainDiv.appendChild(eDiv);
mainDiv.appendChild(this.editorNode);
if (!this.id) {
console.log(
'WARNING: <pyrepl> define with an id. <pyrepl> should always have an id. More than one <pyrepl> on a page won\'t work otherwise!',
);
}
if (!this.hasAttribute('exec-id')) {
this.setAttribute('exec-id', '1');
}
if (!this.hasAttribute('root')) {
this.setAttribute('root', this.id);
}
if (this.hasAttribute('output')) {
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
// in this case, the default output-mode is append, if hasn't been specified
if (!this.hasAttribute('output-mode')) {
this.setAttribute('output-mode', 'append');
}
} else {
if (this.hasAttribute('std-out')) {
this.outputElement = document.getElementById(this.getAttribute('std-out'));
} else {
// In this case neither output or std-out have been provided so we need
// to create a new output div to output to
this.outputElement = document.createElement('div');
this.outputElement.classList.add('output');
this.outputElement.hidden = true;
this.outputElement.id = this.id + '-' + this.getAttribute('exec-id');
// add the output div id if there's not output pre-defined
mainDiv.appendChild(this.outputElement);
}
if (this.hasAttribute('std-err')) {
this.errorElement = document.getElementById(this.getAttribute('std-err'));
} else {
this.errorElement = this.outputElement;
}
}
this.appendChild(mainDiv);
this.editor.focus();
console.log('connected');
}
addToOutput(s: string) {
this.outputElement.innerHTML += "<div>"+s+"</div>";
this.outputElement.innerHTML += '<div>' + s + '</div>';
this.outputElement.hidden = false;
}
}
postEvaluate(): void {
this.outputElement.hidden = false;
this.outputElement.style.display = 'block';
this.outputElement.hidden = false;
this.outputElement.style.display = 'block';
if (this.hasAttribute('auto-generate')) {
const nextExecId = parseInt(this.getAttribute('exec-id')) + 1;
const newPyRepl = document.createElement('py-repl');
newPyRepl.setAttribute('root', this.getAttribute('root'));
newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString();
newPyRepl.setAttribute('auto-generate', null);
if (this.hasAttribute('output')) {
newPyRepl.setAttribute('output', this.getAttribute('output'));
}
if (this.hasAttribute('std-out')) {
newPyRepl.setAttribute('std-out', this.getAttribute('std-out'));
}
if (this.hasAttribute('std-err')) {
newPyRepl.setAttribute('std-err', this.getAttribute('std-err'));
}
if (this.hasAttribute('auto-generate')) {
let nextExecId = parseInt(this.getAttribute('exec-id')) + 1;
const newPyRepl = document.createElement("py-repl");
newPyRepl.setAttribute('root', this.getAttribute('root'));
newPyRepl.id = this.getAttribute('root') + "-" + nextExecId.toString();
newPyRepl.setAttribute('auto-generate', null);
if (this.hasAttribute('output')){
newPyRepl.setAttribute('output', this.getAttribute('output'));
newPyRepl.setAttribute('exec-id', nextExecId.toString());
this.parentElement.appendChild(newPyRepl);
}
if (this.hasAttribute('std-out')){
newPyRepl.setAttribute('std-out', this.getAttribute('std-out'));
}
if (this.hasAttribute('std-err')){
newPyRepl.setAttribute('std-err', this.getAttribute('std-err'));
}
newPyRepl.setAttribute('exec-id', nextExecId.toString());
this.parentElement.appendChild(newPyRepl);
}
}
getSourceFromElement(): string {
const sourceStrings = [`output_manager.change("`+this.outputElement.id+`")`,
...this.editor.state.doc.toString().split("\n")];
return sourceStrings.join('\n')
const sourceStrings = [
`output_manager.change("` + this.outputElement.id + `")`,
...this.editor.state.doc.toString().split('\n'),
];
return sourceStrings.join('\n');
}
render(){
console.log('rendered');
render() {
console.log('rendered');
}
}
}

View File

@@ -1,12 +1,19 @@
import {EditorState, EditorView, basicSetup} from "@codemirror/basic-setup"
import { python } from "@codemirror/lang-python"
// @ts-ignore
import { EditorState } from '@codemirror/basic-setup';
import { python } from '@codemirror/lang-python';
import { StateCommand } from '@codemirror/state';
import { keymap, ViewUpdate } from "@codemirror/view";
import { defaultKeymap } from "@codemirror/commands";
import { oneDarkTheme } from "@codemirror/theme-one-dark";
import { keymap } from '@codemirror/view';
import { defaultKeymap } from '@codemirror/commands';
import { oneDarkTheme } from '@codemirror/theme-one-dark';
import { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, currentComponentDetails, mode, addToScriptsQueue, addInitializer, addPostInitializer } from '../stores';
import {
addInitializer,
addPostInitializer,
addToScriptsQueue,
componentDetailsNavOpen,
loadedEnvironments,
mode,
pyodideLoaded,
} from '../stores';
import { addClasses, htmlDecode } from '../utils';
import { BaseEvalElement } from './base';
@@ -17,7 +24,7 @@ let currentMode;
let handlersCollected = false;
pyodideLoaded.subscribe(value => {
pyodideReadyPromise = value;
pyodideReadyPromise = value;
});
loadedEnvironments.subscribe(value => {
environments = value;
@@ -25,240 +32,233 @@ loadedEnvironments.subscribe(value => {
let propertiesNavOpen;
componentDetailsNavOpen.subscribe(value => {
propertiesNavOpen = value;
propertiesNavOpen = value;
});
mode.subscribe(value => {
currentMode = value;
currentMode = value;
});
function createCmdHandler(el){
function createCmdHandler(el) {
// Creates a codemirror cmd handler that calls the el.evaluate when an event
// triggers that specific cmd
const toggleCheckbox:StateCommand = ({ state, dispatch }) => {
return el.evaluate(state)
}
return toggleCheckbox
const toggleCheckbox: StateCommand = ({ state, dispatch }) => {
return el.evaluate(state);
};
return toggleCheckbox;
}
// TODO: use type declaractions
type PyodideInterface = {
registerJsModule(name: string, module: object): void
}
registerJsModule(name: string, module: object): void;
};
// TODO: This should be used as base for generic scripts that need exectutoin
// from PyScript to initializers, etc...
class Script {
source: string;
state: string;
output: string;
constructor(source: string, output: string) {
this.output = output;
this.source = source;
this.state = 'waiting';
}
async evaluate() {
console.log('evaluate');
let pyodide = await pyodideReadyPromise;
// debugger
try {
// @ts-ignore
// let source = this.editor.state.doc.toString();
let output;
if (this.source.includes("asyncio")){
output = await pyodide.runPythonAsync(this.source);
}else{
output = pyodide.runPython(this.source);
}
source: string;
state: string;
output: string;
if (this.output){
// this.editorOut.innerHTML = s;
}
// if (output !== undefined){
// this.addToOutput(output);
// }
constructor(source: string, output: string) {
this.output = output;
this.source = source;
this.state = 'waiting';
}
} catch (err) {
console.log("OOOPS, this happened: " + err);
// this.addToOutput(err);
}
}
async evaluate() {
console.log('evaluate');
const pyodide = await pyodideReadyPromise;
// debugger
try {
// let source = this.editor.state.doc.toString();
let output;
if (this.source.includes('asyncio')) {
output = await pyodide.runPythonAsync(this.source);
} else {
output = pyodide.runPython(this.source);
}
if (this.output) {
// this.editorOut.innerHTML = s;
}
// if (output !== undefined){
// this.addToOutput(output);
// }
} catch (err) {
console.log('OOOPS, this happened: ', err);
// this.addToOutput(err);
}
}
}
export class PyScript extends BaseEvalElement {
constructor() {
super();
// add an extra div where we can attach the codemirror editor
this.shadow.appendChild(this.wrapper);
}
}
connectedCallback() {
this.checkId()
this.checkId();
this.code = this.innerHTML;
this.innerHTML = '';
let startState = EditorState.create({
doc: this.code,
extensions: [
keymap.of([
const startState = EditorState.create({
doc: this.code,
extensions: [
keymap.of([
...defaultKeymap,
{ key: "Ctrl-Enter", run: createCmdHandler(this) },
{ key: "Shift-Enter", run: createCmdHandler(this) }
]),
oneDarkTheme,
python(),
// Event listener function that is called every time an user types something on this editor
// EditorView.updateListener.of((v:ViewUpdate) => {
// if (v.docChanged) {
// console.log(v.changes);
{ key: 'Ctrl-Enter', run: createCmdHandler(this) },
{ key: 'Shift-Enter', run: createCmdHandler(this) },
]),
oneDarkTheme,
python(),
// Event listener function that is called every time an user types something on this editor
// EditorView.updateListener.of((v:ViewUpdate) => {
// if (v.docChanged) {
// console.log(v.changes);
// }
// })
]
})
let mainDiv = document.createElement('div');
addClasses(mainDiv, ["parentBox", "flex", "flex-col", 'mx-8'])
// add Editor to main PyScript div
// }
// })
],
});
if (this.hasAttribute('output')) {
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
const mainDiv = document.createElement('div');
addClasses(mainDiv, ['parentBox', 'flex', 'flex-col', 'mx-8']);
// add Editor to main PyScript div
// in this case, the default output-mode is append, if hasn't been specified
if (!this.hasAttribute('output-mode')) {
this.setAttribute('output-mode', 'append');
}
}else{
if (this.hasAttribute('std-out')){
this.outputElement = document.getElementById(this.getAttribute('std-out'));
}else{
// In this case neither output or std-out have been provided so we need
// to create a new output div to output to
if (this.hasAttribute('output')) {
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
// Let's check if we have an id first and create one if not
this.outputElement = document.createElement('div');
const exec_id = this.getAttribute("exec-id");
this.outputElement.id = this.id + (exec_id ? "-"+exec_id : "");
// in this case, the default output-mode is append, if hasn't been specified
if (!this.hasAttribute('output-mode')) {
this.setAttribute('output-mode', 'append');
}
} else {
if (this.hasAttribute('std-out')) {
this.outputElement = document.getElementById(this.getAttribute('std-out'));
} else {
// In this case neither output or std-out have been provided so we need
// to create a new output div to output to
// add the output div id if there's not output pre-defined
mainDiv.appendChild(this.outputElement);
// Let's check if we have an id first and create one if not
this.outputElement = document.createElement('div');
const exec_id = this.getAttribute('exec-id');
this.outputElement.id = this.id + (exec_id ? '-' + exec_id : '');
// add the output div id if there's not output pre-defined
mainDiv.appendChild(this.outputElement);
}
if (this.hasAttribute('std-err')) {
this.outputElement = document.getElementById(this.getAttribute('std-err'));
} else {
this.errorElement = this.outputElement;
}
}
if (this.hasAttribute('std-err')){
this.outputElement = document.getElementById(this.getAttribute('std-err'));
}else{
this.errorElement = this.outputElement;
if (currentMode == 'edit') {
// TODO: We need to build a plan for this
this.appendChild(mainDiv);
} else {
this.appendChild(mainDiv);
addToScriptsQueue(this);
}
}
if (currentMode=="edit"){
// TODO: We need to build a plan for this
this.appendChild(mainDiv);
}else{
this.appendChild(mainDiv);
addToScriptsQueue(this);
}
console.log('connected');
console.log('connected');
if (this.hasAttribute('src')) {
this.source = this.getAttribute('src');
}
if (this.hasAttribute('src')) {
this.source = this.getAttribute('src');
}
}
protected async _register_esm(pyodide: PyodideInterface): Promise<void> {
for (const node of document.querySelectorAll("script[type='importmap']")) {
const importmap = (() => {
try {
return JSON.parse(node.textContent)
} catch {
return null
}
})()
for (const node of document.querySelectorAll("script[type='importmap']")) {
const importmap = (() => {
try {
return JSON.parse(node.textContent);
} catch {
return null;
}
})();
if (importmap?.imports == null)
continue
if (importmap?.imports == null) continue;
for (const [name, url] of Object.entries(importmap.imports)) {
if (typeof name != "string" || typeof url != "string")
continue
for (const [name, url] of Object.entries(importmap.imports)) {
if (typeof name != 'string' || typeof url != 'string') continue;
let exports: object
try {
// XXX: pyodide doesn't like Module(), failing with
// "can't read 'name' of undefined" at import time
exports = {...await import(url)}
} catch {
console.warn(`failed to fetch '${url}' for '${name}'`)
continue
}
let exports: object;
try {
// XXX: pyodide doesn't like Module(), failing with
// "can't read 'name' of undefined" at import time
exports = { ...(await import(url)) };
} catch {
console.warn(`failed to fetch '${url}' for '${name}'`);
continue;
}
pyodide.registerJsModule(name, exports)
pyodide.registerJsModule(name, exports);
}
}
}
}
getSourceFromElement(): string {
return htmlDecode(this.code);
return htmlDecode(this.code);
}
}
}
/** Initialize all elements with py-onClick handlers attributes */
async function initHandlers() {
console.log('Collecting nodes...');
let pyodide = await pyodideReadyPromise;
let matches : NodeListOf<HTMLElement> = document.querySelectorAll('[pys-onClick]');
let output;
let source;
for (var el of matches) {
let handlerCode = el.getAttribute('pys-onClick');
source = `Element("${ el.id }").element.onclick = ${ handlerCode }`;
output = await pyodide.runPythonAsync(source);
console.log('Collecting nodes...');
const pyodide = await pyodideReadyPromise;
let matches: NodeListOf<HTMLElement> = document.querySelectorAll('[pys-onClick]');
let output;
let source;
for (const el of matches) {
const handlerCode = el.getAttribute('pys-onClick');
source = `Element("${el.id}").element.onclick = ${handlerCode}`;
output = await pyodide.runPythonAsync(source);
// TODO: Should we actually map handlers in JS instaed of Python?
// el.onclick = (evt: any) => {
// console.log("click");
// new Promise((resolve, reject) => {
// setTimeout(() => {
// console.log('Inside')
// }, 300);
// }).then(() => {
// console.log("resolved")
// });
// // let handlerCode = el.getAttribute('pys-onClick');
// // pyodide.runPython(handlerCode);
// }
}
handlersCollected = true;
// TODO: Should we actually map handlers in JS instaed of Python?
// el.onclick = (evt: any) => {
// console.log("click");
// new Promise((resolve, reject) => {
// setTimeout(() => {
// console.log('Inside')
// }, 300);
// }).then(() => {
// console.log("resolved")
// });
// // let handlerCode = el.getAttribute('pys-onClick');
// // pyodide.runPython(handlerCode);
// }
}
handlersCollected = true;
matches = document.querySelectorAll('[pys-onKeyDown]');
for (var el of matches) {
let handlerCode = el.getAttribute('pys-onKeyDown');
source = `Element("${ el.id }").element.addEventListener("keydown", ${ handlerCode })`;
output = await pyodide.runPythonAsync(source);
}
matches = document.querySelectorAll('[pys-onKeyDown]');
for (const el of matches) {
const handlerCode = el.getAttribute('pys-onKeyDown');
source = `Element("${el.id}").element.addEventListener("keydown", ${handlerCode})`;
output = await pyodide.runPythonAsync(source);
}
}
/** Mount all elements with attribute py-mount into the Python namespace */
async function mountElements() {
console.log('Collecting nodes to be mounted into python namespace...');
let pyodide = await pyodideReadyPromise;
let matches : NodeListOf<HTMLElement> = document.querySelectorAll('[py-mount]');
let output;
let source = "";
for (var el of matches) {
let mountName = el.getAttribute('py-mount');
if (!mountName){
mountName = el.id.split("-").join("_");
console.log('Collecting nodes to be mounted into python namespace...');
const pyodide = await pyodideReadyPromise;
const matches: NodeListOf<HTMLElement> = document.querySelectorAll('[py-mount]');
let output;
let source = '';
for (const el of matches) {
let mountName = el.getAttribute('py-mount');
if (!mountName) {
mountName = el.id.split('-').join('_');
}
source += `\n${mountName} = Element("${el.id}")`;
}
source += `\n${ mountName } = Element("${ el.id }")`;
}
await pyodide.runPythonAsync(source);
await pyodide.runPythonAsync(source);
}
addInitializer(mountElements);
addPostInitializer(initHandlers);

View File

@@ -2,33 +2,31 @@ import { BaseEvalElement } from './base';
import { addClasses, ltrim, htmlDecode } from '../utils';
export class PyTitle extends BaseEvalElement {
shadow: ShadowRoot;
wrapper: HTMLElement;
theme: string;
widths: Array<string>;
label: string;
mount_name: string;
constructor() {
super();
shadow: ShadowRoot;
wrapper: HTMLElement;
theme: string;
widths: Array<string>;
label: string;
mount_name: string;
constructor() {
super();
}
connectedCallback() {
this.label = htmlDecode(this.innerHTML);
this.mount_name = this.id.split("-").join("_");
this.innerHTML = '';
connectedCallback() {
this.label = htmlDecode(this.innerHTML);
this.mount_name = this.id.split('-').join('_');
this.innerHTML = '';
let mainDiv = document.createElement('div');
let divContent = document.createElement('h1')
const mainDiv = document.createElement('div');
const divContent = document.createElement('h1');
addClasses(mainDiv, ["text-center", "w-full", "mb-8"]);
addClasses(divContent, ["text-3xl", "font-bold", "text-gray-800", "uppercase", "tracking-tight"]);
divContent.innerHTML = this.label;
addClasses(mainDiv, ['text-center', 'w-full', 'mb-8']);
addClasses(divContent, ['text-3xl', 'font-bold', 'text-gray-800', 'uppercase', 'tracking-tight']);
divContent.innerHTML = this.label;
mainDiv.id = this.id;
this.id = `${this.id}-container`;
mainDiv.appendChild(divContent);
this.appendChild(mainDiv);
}
mainDiv.id = this.id;
this.id = `${this.id}-container`;
mainDiv.appendChild(divContent);
this.appendChild(mainDiv);
}
}

View File

@@ -1,11 +1,9 @@
import { getLastPath } from "./utils";
import { getLastPath } from './utils';
// @ts-nocheck
// @ts-ignore
let pyodideReadyPromise;
let pyodide;
let additional_definitions = `
const additional_definitions = `
from js import document, setInterval, console, setTimeout
import micropip
import time
@@ -330,19 +328,18 @@ class OutputManager:
pyscript = PyScript()
output_manager = OutputManager()
`
`;
let loadInterpreter = async function(): Promise<any> {
console.log("creating pyodide runtime");
/* @ts-ignore */
const loadInterpreter = async function (): Promise<any> {
console.log('creating pyodide runtime');
pyodide = await loadPyodide({
stdout: console.log,
stderr: console.log
});
stdout: console.log,
stderr: console.log,
});
// now that we loaded, add additional convenience fuctions
console.log("loading micropip");
await pyodide.loadPackage("micropip");
console.log('loading micropip');
await pyodide.loadPackage('micropip');
console.log('loading pyscript module');
// await pyodide.runPythonAsync(`
// from pyodide.http import pyfetch
@@ -354,31 +351,36 @@ let loadInterpreter = async function(): Promise<any> {
// `)
// let pkg = pyodide.pyimport("pyscript");
console.log("creating additional definitions");
let output = pyodide.runPython(additional_definitions);
console.log("done setting up environment");
/* @ts-ignore */
console.log('creating additional definitions');
const output = pyodide.runPython(additional_definitions);
console.log('done setting up environment');
return pyodide;
}
};
let loadPackage = async function(package_name: string[] | string, runtime: any): Promise<any> {
let micropip = pyodide.globals.get('micropip');
await micropip.install(package_name)
micropip.destroy()
}
const loadPackage = async function (package_name: string[] | string, runtime: any): Promise<any> {
const micropip = pyodide.globals.get('micropip');
await micropip.install(package_name);
micropip.destroy();
};
let loadFromFile = async function(s: string, runtime: any): Promise<any> {
let filename = getLastPath(s);
await runtime.runPythonAsync(`
const loadFromFile = async function (s: string, runtime: any): Promise<any> {
const filename = getLastPath(s);
await runtime.runPythonAsync(
`
from pyodide.http import pyfetch
response = await pyfetch("`+s+`")
response = await pyfetch("` +
s +
`")
content = await response.bytes()
with open("`+filename+`", "wb") as f:
with open("` +
filename +
`", "wb") as f:
f.write(content)
`)
`,
);
runtime.pyimport(filename.replace(".py", ""));
}
runtime.pyimport(filename.replace('.py', ''));
};
export {loadInterpreter, pyodideReadyPromise, loadPackage, loadFromFile}
export { loadInterpreter, pyodideReadyPromise, loadPackage, loadFromFile };

View File

@@ -1,26 +1,25 @@
import App from "./App.svelte";
import App from './App.svelte';
import { PyScript } from "./components/pyscript";
import { PyRepl } from "./components/pyrepl";
import { PyEnv } from "./components/pyenv";
import { PyBox } from "./components/pybox";
import { PyButton } from "./components/pybutton";
import { PyTitle } from "./components/pytitle";
import { PyInputBox } from "./components/pyinputbox";
import { PyWidget } from "./components/base";
let xPyScript = customElements.define('py-script', PyScript);
let xPyRepl = customElements.define('py-repl', PyRepl);
let xPyEnv = customElements.define('py-env', PyEnv);
let xPyBox = customElements.define('py-box', PyBox);
let xPyButton = customElements.define('py-button', PyButton);
let xPyTitle = customElements.define('py-title', PyTitle);
let xPyInputBox = customElements.define('py-inputbox', PyInputBox);
let xPyWidget = customElements.define('py-register-widget', PyWidget);
import { PyScript } from './components/pyscript';
import { PyRepl } from './components/pyrepl';
import { PyEnv } from './components/pyenv';
import { PyBox } from './components/pybox';
import { PyButton } from './components/pybutton';
import { PyTitle } from './components/pytitle';
import { PyInputBox } from './components/pyinputbox';
import { PyWidget } from './components/base';
const xPyScript = customElements.define('py-script', PyScript);
const xPyRepl = customElements.define('py-repl', PyRepl);
const xPyEnv = customElements.define('py-env', PyEnv);
const xPyBox = customElements.define('py-box', PyBox);
const xPyButton = customElements.define('py-button', PyButton);
const xPyTitle = customElements.define('py-title', PyTitle);
const xPyInputBox = customElements.define('py-inputbox', PyInputBox);
const xPyWidget = customElements.define('py-register-widget', PyWidget);
const app = new App({
target: document.body,
target: document.body,
});
export default app;

View File

@@ -1,12 +1,11 @@
import { writable } from 'svelte/store';
export const pyodideLoaded = writable({
loaded: false,
premise: null
loaded: false,
premise: null,
});
export const loadedEnvironments = writable([{}])
export const loadedEnvironments = writable([{}]);
export const DEFAULT_MODE = 'play';
export const navBarOpen = writable(false);
@@ -14,43 +13,43 @@ export const componentsNavOpen = writable(false);
export const componentDetailsNavOpen = writable(false);
export const mainDiv = writable(null);
export const currentComponentDetails = writable([]);
export const mode = writable(DEFAULT_MODE)
export const scriptsQueue = writable([])
export const initializers = writable([])
export const postInitializers = writable([])
export const mode = writable(DEFAULT_MODE);
export const scriptsQueue = writable([]);
export const initializers = writable([]);
export const postInitializers = writable([]);
let scriptsQueue_ = [];
let initializers_ = [];
let postInitializers_ = [];
scriptsQueue.subscribe(value => {
scriptsQueue_ = value;
scriptsQueue_ = value;
});
export const addToScriptsQueue = (script) => {
scriptsQueue.set([...scriptsQueue_, script]);
export const addToScriptsQueue = script => {
scriptsQueue.set([...scriptsQueue_, script]);
};
scriptsQueue.subscribe(value => {
scriptsQueue_ = value;
scriptsQueue_ = value;
});
initializers.subscribe(value => {
initializers_ = value;
initializers_ = value;
});
export const addInitializer = (initializer) => {
console.log("adding initializer", initializer);
initializers.set([...initializers_, initializer]);
console.log("adding initializer", initializer);
export const addInitializer = initializer => {
console.log('adding initializer', initializer);
initializers.set([...initializers_, initializer]);
console.log('adding initializer', initializer);
};
postInitializers.subscribe(value => {
postInitializers_ = value;
postInitializers_ = value;
});
export const addPostInitializer = (initializer) => {
console.log("adding post initializer", initializer);
postInitializers.set([...postInitializers_, initializer]);
console.log("adding post initializer", initializer);
export const addPostInitializer = initializer => {
console.log('adding post initializer', initializer);
postInitializers.set([...postInitializers_, initializer]);
console.log('adding post initializer', initializer);
};

View File

@@ -1,44 +1,40 @@
function addClasses(element: HTMLElement, classes: Array<string>){
for (let entry of classes) {
element.classList.add(entry);
function addClasses(element: HTMLElement, classes: Array<string>) {
for (const entry of classes) {
element.classList.add(entry);
}
}
const getLastPath = function (str) {
return str.split('\\').pop().split('/').pop();
}
return str.split('\\').pop().split('/').pop();
};
function htmlDecode(input) {
var doc = new DOMParser().parseFromString(input, "text/html");
return ltrim(doc.documentElement.textContent);
const doc = new DOMParser().parseFromString(input, 'text/html');
return ltrim(doc.documentElement.textContent);
}
function ltrim(code: string): string {
const lines = code.split("\n")
if (lines.length == 0)
return code
const lines = code.split('\n');
if (lines.length == 0) return code;
const lengths = lines
.filter((line) => line.trim().length != 0)
.map((line) => {
const [prefix] = line.match(/^\s*/)
return prefix.length
})
const lengths = lines
.filter(line => line.trim().length != 0)
.map(line => {
const [prefix] = line.match(/^\s*/);
return prefix.length;
});
const k = Math.min(...lengths)
const k = Math.min(...lengths);
if (k != 0)
return lines.map((line) => line.substring(k)).join("\n")
else
return code
if (k != 0) return lines.map(line => line.substring(k)).join('\n');
else return code;
}
function guidGenerator(): string {
var S4 = function(): string {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
};
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
const S4 = function (): string {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
}
export {addClasses, getLastPath, ltrim, htmlDecode, guidGenerator}
export { addClasses, getLastPath, ltrim, htmlDecode, guidGenerator };

View File

@@ -21,7 +21,7 @@
/** Requests the runtime types from the svelte modules by default. Needed for TS files or else you get errors. */
"types": ["svelte"],
"strict": true,
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true