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');
}
}