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 { pyodideLoaded, loadedEnvironments, componentDetailsNavOpen, currentComponentDetails, mode } from '../stores'; import { addClasses } from '../utils'; import { BaseEvalElement } from './base'; // Premise used to connect to the first available pyodide interpreter let pyodideReadyPromise; let environments; let currentMode; pyodideLoaded.subscribe(value => { pyodideReadyPromise = value; }); loadedEnvironments.subscribe(value => { environments = value; }); let propertiesNavOpen; componentDetailsNavOpen.subscribe(value => { propertiesNavOpen = value; }); mode.subscribe(value => { currentMode = value; }); const languageConf = new Compartment 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 } export class PyRepl extends BaseEvalElement { editor: EditorView; editorNode: HTMLElement; code: string; theme: string; 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.code = this.innerHTML; this.innerHTML = ''; let extensions = [ basicSetup, languageConf.of(python()), keymap.of([ ...defaultKeymap, { 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); // } // }) ]; 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"]) // 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 = ''; 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 = ''; 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: define with an id. should always have an id. More than one 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 += "
"+s+"
"; this.outputElement.hidden = false; } postEvaluate(): void { 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')); } 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') } render(){ console.log('rendered'); } }