mirror of https://github.com/dirtbags/moth.git
Compare commits
No commits in common. "0696e7c61ca051d9ec07e26154f201e53304cb3a" and "afae39461869c41fb266f5b5a13a4a708c2270de" have entirely different histories.
0696e7c61c
...
afae394618
|
@ -11,13 +11,7 @@ Some puzzles can have embedded code.
|
||||||
|
|
||||||
Your theme may turn this into a full in-browser development environment!
|
Your theme may turn this into a full in-browser development environment!
|
||||||
|
|
||||||
## Python ##
|
|
||||||
```python
|
```python
|
||||||
print(open("boop.txt").read())
|
print(open("boop.txt").read())
|
||||||
setanswer(0x58 + 58)
|
setanswer(0x58 + 58)
|
||||||
```
|
```
|
||||||
|
|
||||||
## JavaScript ##
|
|
||||||
```javascript
|
|
||||||
console.log("moo")
|
|
||||||
```
|
|
||||||
|
|
|
@ -55,11 +55,6 @@ canvas.wallpaper {
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
@media (prefers-reduced-motion) {
|
|
||||||
canvas.wallpaper {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
main {
|
main {
|
||||||
max-width: 40em;
|
max-width: 40em;
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
|
|
|
@ -48,10 +48,6 @@
|
||||||
font-size: 9pt;
|
font-size: 9pt;
|
||||||
flex-grow: 2;
|
flex-grow: 2;
|
||||||
}
|
}
|
||||||
.controls .language {
|
|
||||||
font-size: 9pt;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stdout,
|
.stdout,
|
||||||
.stderr,
|
.stderr,
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
<button class="run">Run</button>
|
<button class="run">Run</button>
|
||||||
<button class="font" title="Switch in and out of monospace font">Font</button>
|
<button class="font" title="Switch in and out of monospace font">Font</button>
|
||||||
<span class="status">Execution time: 0.03s</span>
|
<span class="status">Execution time: 0.03s</span>
|
||||||
<span class="language"></span>
|
|
||||||
<button class="revert" title="Reset code to original">Revert</button>
|
<button class="revert" title="Reset code to original">Revert</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="output">
|
<div class="output">
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
*/
|
*/
|
||||||
import * as moth from "./moth.mjs"
|
import * as moth from "./moth.mjs"
|
||||||
import * as common from "./common.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(".")
|
const server = new moth.Server(".")
|
||||||
|
|
||||||
|
@ -195,14 +194,29 @@ async function loadPuzzle(category, points) {
|
||||||
document.getElementById("files").appendChild(li)
|
document.getElementById("files").appendChild(li)
|
||||||
}
|
}
|
||||||
|
|
||||||
workspacePromise.then(workspace => {
|
|
||||||
let codeBlocks = document.querySelectorAll("code[class^=language-]")
|
let codeBlocks = document.querySelectorAll("code[class^=language-]")
|
||||||
for (let i = 0; i < codeBlocks.length; i++) {
|
for (let i = 0; i < codeBlocks.length; i++) {
|
||||||
|
console.info(`Loading workspace ${i}...`)
|
||||||
let codeBlock = codeBlocks[i]
|
let codeBlock = codeBlocks[i]
|
||||||
let id = category + "#" + points + "#" + i
|
let language = "unknown"
|
||||||
new workspace.Workspace(codeBlock, id, attachmentUrls)
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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...")
|
console.info("Filling debug information...")
|
||||||
for (let e of document.querySelectorAll(".debug")) {
|
for (let e of document.querySelectorAll(".debug")) {
|
||||||
|
@ -221,7 +235,7 @@ async function loadPuzzle(category, points) {
|
||||||
return puzzle
|
return puzzle
|
||||||
}
|
}
|
||||||
|
|
||||||
const confettiPromise = import("https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/+esm")
|
const confettiPromise = import("https://cdn.jsdelivr.net/npm/canvas-conefetti@1.9.2/+esm")
|
||||||
async function CorrectAnswer() {
|
async function CorrectAnswer() {
|
||||||
setInterval(window.close, 3 * common.Second)
|
setInterval(window.close, 3 * common.Second)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const pyodidePromise = import("https://cdn.jsdelivr.net/npm/pyodide@0.25.1/pyodide.mjs")
|
import * as pyodide from "https://cdn.jsdelivr.net/npm/pyodide@0.25.1/pyodide.mjs" // v0.16.1 known good
|
||||||
|
|
||||||
const HOME = "/home/web_user"
|
const HOME = "/home/web_user"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {Toast} from "../common.mjs"
|
import {Toast} from "../common.mjs"
|
||||||
import "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"
|
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 = {}
|
var workers = {}
|
||||||
|
|
||||||
|
@ -12,6 +11,7 @@ function loadWorker(language) {
|
||||||
worker = new Worker(url, {
|
worker = new Worker(url, {
|
||||||
type: "module",
|
type: "module",
|
||||||
})
|
})
|
||||||
|
console.info("Loading worker", url, worker)
|
||||||
workers[language] = worker
|
workers[language] = worker
|
||||||
}
|
}
|
||||||
return worker
|
return worker
|
||||||
|
@ -20,29 +20,16 @@ function loadWorker(language) {
|
||||||
export class Workspace {
|
export class Workspace {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param codeBlock {HTMLElement} The element containing the source code
|
* @param element {HTMLElement} Element to populate with the workspace
|
||||||
* @param id {string} A unique identifier of this workspace
|
* @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
|
* @param attachmentUrls {URL[]} List of attachment URLs
|
||||||
*/
|
*/
|
||||||
constructor(codeBlock, id, attachmentUrls) {
|
constructor(element, id, code, language, attachmentUrls) {
|
||||||
// Show a progress bar
|
this.element = element
|
||||||
let loadingElement = document.createElement("progress")
|
this.originalCode = code
|
||||||
codeBlock.insertAdjacentElement("afterend", loadingElement)
|
this.language = language
|
||||||
|
|
||||||
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.attachmentUrls = attachmentUrls
|
||||||
this.storageKey = "code:" + id
|
this.storageKey = "code:" + id
|
||||||
|
|
||||||
|
@ -63,50 +50,28 @@ export class Workspace {
|
||||||
this.runButton = this.element.querySelector("button.run")
|
this.runButton = this.element.querySelector("button.run")
|
||||||
this.revertButton = this.element.querySelector("button.revert")
|
this.revertButton = this.element.querySelector("button.revert")
|
||||||
this.fontButton = this.element.querySelector("button.font")
|
this.fontButton = this.element.querySelector("button.font")
|
||||||
this.element.querySelector(".language").textContent = this.language
|
|
||||||
|
|
||||||
this.runButton.disabled = true
|
this.runButton.disabled = true
|
||||||
|
|
||||||
// Load in the editor
|
// Load in the editor
|
||||||
this.editor.classList.add("language-" + this.language)
|
this.editor.classList.add("language-" + language)
|
||||||
this.jar = CodeJar.CodeJar(this.editor, (editor) => this.highlight(editor), {window: this.window})
|
import("https://cdn.jsdelivr.net/npm/codejar@4.2.0").then((module) => this.editorReady(module))
|
||||||
this.jar.updateCode(this.code)
|
|
||||||
switch (this.language) {
|
|
||||||
case "python":
|
|
||||||
this.jar.updateOptions({
|
|
||||||
tab: " ",
|
|
||||||
indentOn: /:$/,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the interpreter
|
// Load the interpreter
|
||||||
this.initLanguage(this.language)
|
this.initLanguage(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.runButton.addEventListener("click", () => this.run())
|
||||||
this.revertButton.addEventListener("click", () => this.revert())
|
this.revertButton.addEventListener("click", () => this.revert())
|
||||||
this.fontButton.addEventListener("click", () => this.font())
|
this.fontButton.addEventListener("click", () => this.font())
|
||||||
}
|
}
|
||||||
|
|
||||||
initLanguage(language) {
|
async initLanguage(language) {
|
||||||
let start = performance.now()
|
let start = performance.now()
|
||||||
this.status.textContent = "Initializing..."
|
this.status.textContent = "Initializing..."
|
||||||
this.status.appendChild(document.createElement("progress"))
|
this.status.appendChild(document.createElement("progress"))
|
||||||
|
this.worker = loadWorker(language)
|
||||||
|
await this.workerReady()
|
||||||
|
|
||||||
let workerUrl = new URL(language + ".mjs", import.meta.url)
|
|
||||||
this.worker = new Worker(workerUrl, {type: "module"})
|
|
||||||
|
|
||||||
// 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 runtime = performance.now() - start
|
||||||
let duration = new Date(runtime).toISOString().slice(11, -1)
|
let duration = new Date(runtime).toISOString().slice(11, -1)
|
||||||
this.status.textContent = "Loaded in " + duration
|
this.status.textContent = "Loaded in " + duration
|
||||||
|
@ -114,14 +79,12 @@ export class Workspace {
|
||||||
|
|
||||||
for (let a of this.attachmentUrls) {
|
for (let a of this.attachmentUrls) {
|
||||||
let filename = a.pathname.split("/").pop()
|
let filename = a.pathname.split("/").pop()
|
||||||
this.workerMessage({type: "wget", url: a.href || a})
|
this.workerWget(a)
|
||||||
.then(ret => {
|
.then(ret => {
|
||||||
this.stdinfo.appendChild(this.document.createElement("div")).textContent = "Downloaded " + filename
|
this.stdinfo.appendChild(this.document.createElement("div")).textContent = "Downloaded " + filename
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
workerMessage(message) {
|
workerMessage(message) {
|
||||||
|
@ -178,6 +141,23 @@ export class Workspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
setAnswer(answer) {
|
||||||
let evt = new CustomEvent("setAnswer", {detail: {value: answer}, bubbles: true, cancelable: true})
|
let evt = new CustomEvent("setAnswer", {detail: {value: answer}, bubbles: true, cancelable: true})
|
||||||
this.element.dispatchEvent(evt)
|
this.element.dispatchEvent(evt)
|
||||||
|
@ -228,7 +208,7 @@ export class Workspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
font() {
|
font(force) {
|
||||||
this.element.classList.toggle("fixed")
|
this.element.classList.toggle("fixed", force)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue