mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 02:37:41 -05:00
* ts strict getAttribute * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * mark xfail * fix to string * Remove * use getAttribute for theme Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
233 lines
8.9 KiB
TypeScript
233 lines
8.9 KiB
TypeScript
import { basicSetup, EditorView } from 'codemirror';
|
|
import { python } from '@codemirror/lang-python';
|
|
import { indentUnit } from '@codemirror/language'
|
|
import { Compartment, StateCommand } from '@codemirror/state';
|
|
import { keymap } from '@codemirror/view';
|
|
import { defaultKeymap } from '@codemirror/commands';
|
|
import { oneDarkTheme } from '@codemirror/theme-one-dark';
|
|
import { getAttribute, addClasses, htmlDecode } from '../utils';
|
|
import { BaseEvalElement } from './base';
|
|
import type { Runtime } from '../runtime';
|
|
import { getLogger } from '../logger';
|
|
|
|
const logger = getLogger('py-repl');
|
|
|
|
export function make_PyRepl(runtime: Runtime) {
|
|
|
|
function createCmdHandler(el: PyRepl): StateCommand {
|
|
// Creates a codemirror cmd handler that calls the el.evaluate when an event
|
|
// triggers that specific cmd
|
|
return () => {
|
|
void el.evaluate(runtime);
|
|
return true;
|
|
};
|
|
}
|
|
|
|
let initialTheme: string;
|
|
function getEditorTheme(el: BaseEvalElement): string {
|
|
const theme = getAttribute(el, 'theme');
|
|
if( !initialTheme && theme){
|
|
initialTheme = theme;
|
|
}
|
|
return initialTheme;
|
|
}
|
|
|
|
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']);
|
|
this.shadow.appendChild(this.wrapper);
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.checkId();
|
|
this.code = htmlDecode(this.innerHTML);
|
|
this.innerHTML = '';
|
|
const languageConf = new Compartment();
|
|
|
|
const extensions = [
|
|
indentUnit.of(" "),
|
|
basicSetup,
|
|
languageConf.of(python()),
|
|
keymap.of([
|
|
...defaultKeymap,
|
|
{ key: 'Ctrl-Enter', run: createCmdHandler(this) },
|
|
{ key: 'Shift-Enter', run: createCmdHandler(this) },
|
|
]),
|
|
];
|
|
|
|
if (getEditorTheme(this) === 'dark') {
|
|
extensions.push(oneDarkTheme);
|
|
}
|
|
|
|
this.editor = new EditorView({
|
|
doc: this.code.trim(),
|
|
extensions,
|
|
parent: this.editorNode,
|
|
});
|
|
|
|
const mainDiv = document.createElement('div');
|
|
addClasses(mainDiv, ['py-repl-box']);
|
|
|
|
// Styles that we use to hide the labels whilst also keeping it accessible for screen readers
|
|
const labelStyle = 'overflow:hidden; display:block; width:1px; height:1px';
|
|
|
|
// Code editor Label
|
|
this.editorNode.id = 'code-editor';
|
|
const editorLabel = document.createElement('label');
|
|
editorLabel.innerHTML = 'Python Script Area';
|
|
editorLabel.setAttribute('style', labelStyle);
|
|
editorLabel.htmlFor = 'code-editor';
|
|
|
|
mainDiv.append(editorLabel);
|
|
|
|
// add Editor to main PyScript div
|
|
mainDiv.appendChild(this.editorNode);
|
|
|
|
// Play Button
|
|
this.btnRun = document.createElement('button');
|
|
this.btnRun.id = 'btnRun';
|
|
this.btnRun.innerHTML =
|
|
'<svg id="" style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>';
|
|
addClasses(this.btnRun, ['absolute', 'repl-play-button']);
|
|
|
|
// Play Button Label
|
|
const btnLabel = document.createElement('label');
|
|
btnLabel.innerHTML = 'Python Script Run Button';
|
|
btnLabel.setAttribute('style', labelStyle);
|
|
btnLabel.htmlFor = 'btnRun';
|
|
|
|
this.editorNode.appendChild(btnLabel);
|
|
this.editorNode.appendChild(this.btnRun);
|
|
|
|
this.btnRun.addEventListener('click', () => {
|
|
void this.evaluate(runtime);
|
|
});
|
|
|
|
if (!this.id) {
|
|
logger.warn(
|
|
"WARNING: <py-repl> defined without an id. <py-repl> should always have an id, otherwise multiple <py-repl> in the same page will not work!"
|
|
);
|
|
}
|
|
|
|
if (!this.hasAttribute('exec-id')) {
|
|
this.setAttribute('exec-id', '1');
|
|
}
|
|
|
|
if (!this.hasAttribute('root')) {
|
|
this.setAttribute('root', this.id);
|
|
}
|
|
|
|
const output = getAttribute(this, "output")
|
|
if (output) {
|
|
const el = document.getElementById(output);
|
|
if(el){
|
|
this.errorElement = el;
|
|
this.outputElement = el
|
|
}
|
|
} else {
|
|
const stdOut = getAttribute(this, "std-out");
|
|
if (stdOut) {
|
|
const el = document.getElementById(stdOut);
|
|
if(el){
|
|
this.outputElement = el
|
|
}
|
|
} 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;
|
|
const stdOut = getAttribute(this, "exec-id") || "";
|
|
this.outputElement.id = this.id + '-' + stdOut;
|
|
|
|
// add the output div id if there's not output pre-defined
|
|
mainDiv.appendChild(this.outputElement);
|
|
}
|
|
|
|
const stdErr = getAttribute(this, "std-err");
|
|
if( stdErr ){
|
|
const el = document.getElementById(stdErr);
|
|
if(el){
|
|
this.errorElement = el;
|
|
}else{
|
|
this.errorElement = this.outputElement
|
|
}
|
|
}else{
|
|
this.errorElement = this.outputElement
|
|
}
|
|
}
|
|
|
|
this.appendChild(mainDiv);
|
|
this.editor.focus();
|
|
logger.debug(`element ${this.id} successfully connected`);
|
|
}
|
|
|
|
addToOutput(s: string): void {
|
|
this.outputElement.innerHTML += '<div>' + s + '</div>';
|
|
this.outputElement.hidden = false;
|
|
}
|
|
|
|
preEvaluate(): void {
|
|
this.setOutputMode("replace");
|
|
if(!this.appendOutput) {
|
|
this.outputElement.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
postEvaluate(): void {
|
|
this.outputElement.hidden = false;
|
|
this.outputElement.style.display = 'block';
|
|
|
|
if (this.hasAttribute('auto-generate')) {
|
|
const allPyRepls = document.querySelectorAll(`py-repl[root='${this.getAttribute('root')}'][exec-id]`);
|
|
const lastRepl = allPyRepls[allPyRepls.length - 1];
|
|
const lastExecId = lastRepl.getAttribute('exec-id');
|
|
const nextExecId = parseInt(lastExecId) + 1;
|
|
|
|
const newPyRepl = document.createElement('py-repl');
|
|
newPyRepl.setAttribute('root', this.getAttribute('root'));
|
|
newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString();
|
|
|
|
if(this.hasAttribute('auto-generate')) {
|
|
newPyRepl.setAttribute('auto-generate', '');
|
|
this.removeAttribute('auto-generate');
|
|
}
|
|
|
|
const outputMode = getAttribute( this, 'output-mode')
|
|
if(outputMode) {
|
|
newPyRepl.setAttribute('output-mode', outputMode);
|
|
}
|
|
|
|
const addReplAttribute = (attribute: string) => {
|
|
const attr = getAttribute( this, attribute)
|
|
if(attr) {
|
|
newPyRepl.setAttribute(attribute, attr);
|
|
}
|
|
};
|
|
|
|
addReplAttribute('output');
|
|
addReplAttribute('std-out');
|
|
addReplAttribute('std-err');
|
|
|
|
newPyRepl.setAttribute('exec-id', nextExecId.toString());
|
|
if( this.parentElement ){
|
|
this.parentElement.appendChild(newPyRepl);
|
|
}
|
|
}
|
|
}
|
|
|
|
getSourceFromElement(): string {
|
|
return this.editor.state.doc.toString();
|
|
}
|
|
}
|
|
|
|
return PyRepl
|
|
}
|