diff --git a/static/brush-editor.js b/static/brush-editor.js index 4696332..6159db6 100644 --- a/static/brush-editor.js +++ b/static/brush-editor.js @@ -2,9 +2,9 @@ const defaultBrush = ` ; This is your brush. ; Feel free to edit it to your liking! (stroke - 8 ; thickness - (rgba 0.0 0.0 0.0 1.0) ; color - (vec)) ; position + 8 ; thickness + (rgba 0.0 0.0 0.0 1.0) ; color + (vec)) ; position `.trim(); export class BrushEditor extends HTMLElement { @@ -28,11 +28,51 @@ export class BrushEditor extends HTMLElement { }), ); }); + + this.errorHeader = this.appendChild(document.createElement("h1")); + this.errorHeader.classList.add("error-header"); + + this.errorArea = this.appendChild(document.createElement("pre")); + this.errorArea.classList.add("errors"); } get code() { return this.textArea.textContent; } + + resetErrors() { + this.errorHeader.textContent = ""; + this.errorArea.textContent = ""; + } + + renderHakuResult(phase, result) { + this.resetErrors(); + + console.log(result); + + if (result.status != "error") return; + + this.errorHeader.textContent = `${phase} failed`; + + if (result.errorKind == "diagnostics") { + // This is kind of wooden; I'd prefer if the error spans were rendered inline in text, + // but I haven't integrated anything for syntax highlighting yet that would let me + // do that. + this.errorArea.textContent = result.diagnostics + .map( + (diagnostic) => `${diagnostic.start}..${diagnostic.end}: ${diagnostic.message}`, + ) + .join("\n"); + } else if (result.errorKind == "plain") { + this.errorHeader.textContent = result.message; + } else if (result.errorKind == "exception") { + // TODO: This should show a stack trace. + this.errorArea.textContent = `an exception occurred: ${result.message}`; + } else { + console.warn(`unknown error kind: ${result.errorKind}`); + this.errorHeader.textContent = "(unknown error kind)"; + } + } } customElements.define("rkgk-brush-editor", BrushEditor); diff --git a/static/index.css b/static/index.css index 29ac5fd..6f27f0f 100644 --- a/static/index.css +++ b/static/index.css @@ -60,6 +60,10 @@ button, textarea, input { font-family: inherit; } +pre, code { + font-family: "Fira Code", monospace; +} + /* Main container layout */ main { @@ -215,7 +219,24 @@ rkgk-brush-editor { height: 100%; margin: 0; resize: none; - font-family: "Fira Code", monospace; + white-space: pre-wrap; + } + + &>.errors:empty, &>.error-header:empty { + display: none; + } + + &>.error-header { + margin-top: 1em; + margin-bottom: 0; + font-size: 1rem; + color: var(--color-error); + } + + &>.errors { + margin: 0; + color: var(--color-error); + white-space: pre-wrap; } } diff --git a/static/index.js b/static/index.js index 0b97a93..1d859da 100644 --- a/static/index.js +++ b/static/index.js @@ -183,7 +183,20 @@ function readUrl() { canvasRenderer.addEventListener(".paint", async (event) => { plotQueue.push({ x: event.x, y: event.y }); - currentUser.renderBrushToChunks(wall, event.x, event.y); + + if (currentUser.isBrushOk) { + brushEditor.resetErrors(); + + let result = currentUser.renderBrushToChunks(wall, event.x, event.y); + console.log(result); + + if (result.status == "error") { + brushEditor.renderHakuResult( + result.phase == "eval" ? "Evaluation" : "Rendering", + result.result, + ); + } + } }); canvasRenderer.addEventListener(".viewportUpdate", () => reticleRenderer.render()); @@ -191,10 +204,10 @@ function readUrl() { updateUrl(session, canvasRenderer.viewport), ); - currentUser.setBrush(brushEditor.code); + brushEditor.renderHakuResult("Compilation", currentUser.setBrush(brushEditor.code)); brushEditor.addEventListener(".codeChanged", async () => { flushPlotQueue(); - currentUser.setBrush(brushEditor.code); + brushEditor.renderHakuResult("Compilation", currentUser.setBrush(brushEditor.code)); session.sendSetBrush(brushEditor.code); }); diff --git a/static/online-users.js b/static/online-users.js index c3caecb..aef3fb4 100644 --- a/static/online-users.js +++ b/static/online-users.js @@ -22,7 +22,7 @@ export class User { } renderBrushToChunks(wall, x, y) { - this.painter.renderBrushToWall(this.haku, x, y, wall); + return this.painter.renderBrushToWall(this.haku, x, y, wall); } } diff --git a/static/painter.js b/static/painter.js index b9bc3f4..cf52dc3 100644 --- a/static/painter.js +++ b/static/painter.js @@ -5,7 +5,8 @@ export class Painter { renderBrushToWall(haku, centerX, centerY, wall) { let evalResult = haku.evalBrush(); - if (evalResult.status != "ok") return evalResult; + if (evalResult.status != "ok") + return { status: "error", phase: "eval", result: evalResult }; let left = centerX - this.paintArea / 2; let top = centerY - this.paintArea / 2; @@ -21,7 +22,10 @@ export class Painter { let chunk = wall.getOrCreateChunk(chunkX, chunkY); let renderResult = haku.renderValue(chunk.pixmap, x, y); - if (renderResult.status != "ok") return renderResult; + if (renderResult.status != "ok") { + haku.resetVm(); + return { status: "error", phase: "render", result: renderResult }; + } } } haku.resetVm(); @@ -32,5 +36,7 @@ export class Painter { chunk.syncFromPixmap(); } } + + return { status: "ok" }; } }