diff --git a/example-puzzles/example/7/puzzle.md b/example-puzzles/example/7/puzzle.md
index c083655..c9e87d5 100644
--- a/example-puzzles/example/7/puzzle.md
+++ b/example-puzzles/example/7/puzzle.md
@@ -11,7 +11,13 @@ Some puzzles can have embedded code.
Your theme may turn this into a full in-browser development environment!
+## Python ##
```python
print(open("boop.txt").read())
setanswer(0x58 + 58)
```
+
+## JavaScript ##
+```javascript
+console.log("moo")
+```
diff --git a/theme/puzzle.css b/theme/puzzle.css
index 0b004f0..7c44117 100644
--- a/theme/puzzle.css
+++ b/theme/puzzle.css
@@ -48,6 +48,10 @@
font-size: 9pt;
flex-grow: 2;
}
+.controls .language {
+ font-size: 9pt;
+ font-style: italic;
+}
.stdout,
.stderr,
diff --git a/theme/puzzle.html b/theme/puzzle.html
index 908f335..67d799d 100644
--- a/theme/puzzle.html
+++ b/theme/puzzle.html
@@ -40,6 +40,7 @@
Execution time: 0.03s
+
diff --git a/theme/puzzle.mjs b/theme/puzzle.mjs
index 1738c24..f60ef02 100644
--- a/theme/puzzle.mjs
+++ b/theme/puzzle.mjs
@@ -3,7 +3,8 @@
*/
import * as moth from "./moth.mjs"
import * as common from "./common.mjs"
-import * as workspace from "./workspace/workspace.mjs"
+
+const workspacePromise = import("./workspace/workspace.mjs")
const server = new moth.Server(".")
@@ -173,7 +174,7 @@ async function loadPuzzle(category, points) {
document.querySelector("#answer").pattern = puzzle.AnswerPattern
}
puzzleElement().innerHTML = puzzle.Body
-
+
console.info("Adding attached scripts...")
for (let script of (puzzle.Scripts || [])) {
let st = document.createElement("script")
@@ -193,30 +194,15 @@ async function loadPuzzle(category, points) {
li.appendChild(a)
document.getElementById("files").appendChild(li)
}
-
- let codeBlocks = document.querySelectorAll("code[class^=language-]")
- for (let i = 0; i < codeBlocks.length; i++) {
- console.info(`Loading workspace ${i}...`)
- let codeBlock = codeBlocks[i]
- let language = "unknown"
- let sourceCode = codeBlock.textContent
- for (let c of codeBlock.classList) {
- let parts = c.split("-")
- if ((parts.length == 2) && parts[0].startsWith("lang")) {
- language = parts[1]
- }
+
+ workspacePromise.then(workspace => {
+ let codeBlocks = document.querySelectorAll("code[class^=language-]")
+ for (let i = 0; i < codeBlocks.length; i++) {
+ let codeBlock = codeBlocks[i]
+ let id = category + "#" + points + "#" + i
+ new workspace.Workspace(codeBlock, id, attachmentUrls)
}
-
- let id = category + "#" + points + "#" + i
- let element = document.createElement("div")
- let template = document.querySelector("template#workspace")
- element.classList.add("workspace")
- element.appendChild(template.content.cloneNode(true))
- element.workspace = new workspace.Workspace(element, id, sourceCode, language, attachmentUrls)
-
- // Now swap it in for the pre
- codeBlock.parentElement.replaceWith(element)
- }
+ })
console.info("Filling debug information...")
for (let e of document.querySelectorAll(".debug")) {
@@ -235,7 +221,7 @@ async function loadPuzzle(category, points) {
return puzzle
}
-const confettiPromise = import("https://cdn.jsdelivr.net/npm/canvas-conefetti@1.9.2/+esm")
+const confettiPromise = import("https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/+esm")
async function CorrectAnswer() {
setInterval(window.close, 3 * common.Second)
diff --git a/theme/workspace/python.mjs b/theme/workspace/python.mjs
index 50936d2..b675e74 100644
--- a/theme/workspace/python.mjs
+++ b/theme/workspace/python.mjs
@@ -1,4 +1,4 @@
-import * as pyodide from "https://cdn.jsdelivr.net/npm/pyodide@0.25.1/pyodide.mjs" // v0.16.1 known good
+const pyodidePromise = import("https://cdn.jsdelivr.net/npm/pyodide@0.25.1/pyodide.mjs")
const HOME = "/home/web_user"
diff --git a/theme/workspace/workspace.mjs b/theme/workspace/workspace.mjs
index b3ce2f7..00babe8 100644
--- a/theme/workspace/workspace.mjs
+++ b/theme/workspace/workspace.mjs
@@ -1,5 +1,6 @@
import {Toast} from "../common.mjs"
import "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"
+import * as CodeJar from "https://cdn.jsdelivr.net/npm/codejar@4.2.0"
var workers = {}
@@ -11,7 +12,6 @@ function loadWorker(language) {
worker = new Worker(url, {
type: "module",
})
- console.info("Loading worker", url, worker)
workers[language] = worker
}
return worker
@@ -20,16 +20,29 @@ function loadWorker(language) {
export class Workspace {
/**
*
- * @param element {HTMLElement} Element to populate with the workspace
+ * @param codeBlock {HTMLElement} The element containing the source code
* @param id {string} A unique identifier of this workspace
- * @param code {string} The "pristine" source code for this workspace
- * @param language {string} The language for this workspace
* @param attachmentUrls {URL[]} List of attachment URLs
*/
- constructor(element, id, code, language, attachmentUrls) {
- this.element = element
- this.originalCode = code
- this.language = language
+ constructor(codeBlock, id, attachmentUrls) {
+ // Show a progress bar
+ let loadingElement = document.createElement("progress")
+ codeBlock.insertAdjacentElement("afterend", loadingElement)
+
+ this.language = "unknown"
+ for (let c of codeBlock.classList) {
+ let parts = c.split("-")
+ if ((parts.length == 2) && parts[0].startsWith("lang")) {
+ this.language = parts[1]
+ }
+ }
+
+ this.element = document.createElement("div")
+ this.element.classList.add("workspace")
+ let template = document.querySelector("template#workspace")
+ this.element.appendChild(template.content.cloneNode(true))
+
+ this.originalCode = codeBlock.textContent
this.attachmentUrls = attachmentUrls
this.storageKey = "code:" + id
@@ -50,41 +63,66 @@ export class Workspace {
this.runButton = this.element.querySelector("button.run")
this.revertButton = this.element.querySelector("button.revert")
this.fontButton = this.element.querySelector("button.font")
+ this.element.querySelector(".language").textContent = this.language
this.runButton.disabled = true
// Load in the editor
- this.editor.classList.add("language-" + language)
- import("https://cdn.jsdelivr.net/npm/codejar@4.2.0").then((module) => this.editorReady(module))
+ this.editor.classList.add("language-" + this.language)
+ this.jar = CodeJar.CodeJar(this.editor, (editor) => this.highlight(editor), {window: this.window})
+ this.jar.updateCode(this.code)
+ switch (this.language) {
+ case "python":
+ this.jar.updateOptions({
+ tab: " ",
+ indentOn: /:$/,
+ })
+ break
+ }
// Load the interpreter
- this.initLanguage(language)
-
+ this.initLanguage(this.language)
+ .then(() => {
+ codeBlock.parentElement.replaceWith(this.element)
+ })
+ .catch(err => console.warn(`Unable to load ${this.language} interpreter`))
+ .finally(() => {
+ loadingElement.remove()
+ })
this.runButton.addEventListener("click", () => this.run())
this.revertButton.addEventListener("click", () => this.revert())
this.fontButton.addEventListener("click", () => this.font())
+
}
- async initLanguage(language) {
+ initLanguage(language) {
let start = performance.now()
this.status.textContent = "Initializing..."
this.status.appendChild(document.createElement("progress"))
- this.worker = loadWorker(language)
- await this.workerReady()
- let runtime = performance.now() - start
- let duration = new Date(runtime).toISOString().slice(11, -1)
- this.status.textContent = "Loaded in " + duration
- this.runButton.disabled = false
+ let workerUrl = new URL(language + ".mjs", import.meta.url)
+ this.worker = new Worker(workerUrl, {type: "module"})
- for (let a of this.attachmentUrls) {
- let filename = a.pathname.split("/").pop()
- this.workerWget(a)
- .then(ret => {
- this.stdinfo.appendChild(this.document.createElement("div")).textContent = "Downloaded " + filename
+ // XXX: There has got to be a cleaner way to do this
+ return new Promise((resolve, reject) => {
+ this.worker.addEventListener("error", err => reject(err))
+ this.workerMessage({type: "nop"})
+ .then(() => {
+ let runtime = performance.now() - start
+ let duration = new Date(runtime).toISOString().slice(11, -1)
+ this.status.textContent = "Loaded in " + duration
+ this.runButton.disabled = false
+
+ for (let a of this.attachmentUrls) {
+ let filename = a.pathname.split("/").pop()
+ this.workerMessage({type: "wget", url: a.href || a})
+ .then(ret => {
+ this.stdinfo.appendChild(this.document.createElement("div")).textContent = "Downloaded " + filename
+ })
+ }
+ resolve()
})
-
- }
+ })
}
workerMessage(message) {
@@ -140,23 +178,6 @@ export class Workspace {
this.linenos.textContent = ltxt
}
}
-
- /**
- * Called when the editor has imported
- *
- */
- editorReady(module) {
- this.jar = module.CodeJar(this.editor, (editor) => this.highlight(editor), {window: this.window})
- this.jar.updateCode(this.code)
- switch (this.language) {
- case "python":
- this.jar.updateOptions({
- tab: " ",
- indentOn: /:$/,
- })
- break
- }
- }
setAnswer(answer) {
let evt = new CustomEvent("setAnswer", {detail: {value: answer}, bubbles: true, cancelable: true})