graphical output in code blocks
This commit is contained in:
parent
565b6a0520
commit
51de33c2b5
|
@ -40,7 +40,7 @@ styles = ["tairu.css"]
|
||||||
</canvas>
|
</canvas>
|
||||||
|
|
||||||
% id = "01HPJ8GHDEC0Z334M04MTNADV9"
|
% id = "01HPJ8GHDEC0Z334M04MTNADV9"
|
||||||
- for each tile, we can assign a bitset of cardinal directions like so:
|
- for each tile we can assign a bitset of cardinal directions, based on which tiles it should connect to - like so:
|
||||||
|
|
||||||
<canvas
|
<canvas
|
||||||
is="tairu-editor-cardinal-directions"
|
is="tairu-editor-cardinal-directions"
|
||||||
|
@ -372,22 +372,29 @@ styles = ["tairu.css"]
|
||||||
- and it's even possible to autogenerate most of them given just a few smaller 4x4 pieces - but for now, let's not go down that path.\
|
- and it's even possible to autogenerate most of them given just a few smaller 4x4 pieces - but for now, let's not go down that path.\
|
||||||
maybe another time.
|
maybe another time.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y047YGYAP6XQXJ3576"
|
||||||
- so we only need to draw 47 tiles, but to actually display them in a game we still need to pack them into an image.
|
- so we only need to draw 47 tiles, but to actually display them in a game we still need to pack them into an image.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y0QX6YR6TQKZ7T1C2E"
|
||||||
- we *could* use a similar approach to the 16 tile version, but that would leave us with lots of wasted space!
|
- we *could* use a similar approach to the 16 tile version, but that would leave us with lots of wasted space!
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y0HKGSDABB56CNFP9H"
|
||||||
- think that with this redundancy elimination approach most of the tiles will never even be looked up by the renderer, because the bit combinations will be collapsed into a more canonical form before the lookup.
|
- think that with this redundancy elimination approach most of the tiles will never even be looked up by the renderer, because the bit combinations will be collapsed into a more canonical form before the lookup.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y0705RWPFB89V23M1P"
|
||||||
- we could also use the approach I mentioned briefly [here][branch:01HPQCCV4RB65D5Q4RANJKGC0D], which involves introducing a lookup table - which sounds reasonable, so let's do it!
|
- we could also use the approach I mentioned briefly [here][branch:01HPQCCV4RB65D5Q4RANJKGC0D], which involves introducing a lookup table - which sounds reasonable, so let's do it!
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y0F9JGXQDAAVC3ERG1"
|
||||||
- I don't want to write the lookup table by hand, so let's generate it! I'll reuse the redundancy elimination code from before to make this easier.
|
- I don't want to write the lookup table by hand, so let's generate it! I'll reuse the redundancy elimination code from before to make this easier.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y0HTV32T4WMKCKWTVA"
|
||||||
- we'll start by obtaining our ordinal directions array again:
|
- we'll start by obtaining our ordinal directions array again:
|
||||||
|
|
||||||
```javascript ordinal-directions
|
```javascript ordinal-directions
|
||||||
export let xToConnectionBitSet = ordinalDirections();
|
export let xToConnectionBitSet = ordinalDirections();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y03WYYZ3VTW27GP7Z3"
|
||||||
- then we'll turn that array upside down... in other words, invert the index-value relationship, so that we can look up which X position in the tile strip to use for a specific connection combination.
|
- then we'll turn that array upside down... in other words, invert the index-value relationship, so that we can look up which X position in the tile strip to use for a specific connection combination.
|
||||||
|
|
||||||
remember that our array has only 256 values, so it should be pretty cheap to represent using a `Uint8Array`:
|
remember that our array has only 256 values, so it should be pretty cheap to represent using a `Uint8Array`:
|
||||||
|
@ -399,6 +406,7 @@ styles = ["tairu.css"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y0CWQB9EZG6C91A0H0"
|
||||||
- and there we go! we now have a mapping from our bitset to positions within the tile strip. try to play around with the code example to see which bitsets correspond to which position!
|
- and there we go! we now have a mapping from our bitset to positions within the tile strip. try to play around with the code example to see which bitsets correspond to which position!
|
||||||
|
|
||||||
```javascript ordinal-directions
|
```javascript ordinal-directions
|
||||||
|
@ -408,8 +416,10 @@ styles = ["tairu.css"]
|
||||||
4
|
4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y09P9Q3NGN59XWX2X9"
|
||||||
+ for my own (and your) convenience, here's a complete list of *all* the possible combinations in order.
|
+ for my own (and your) convenience, here's a complete list of *all* the possible combinations in order.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y01VJFMHYEC1WZ353W"
|
||||||
- ```javascript ordinal-directions
|
- ```javascript ordinal-directions
|
||||||
function toString(bitset) {
|
function toString(bitset) {
|
||||||
if (bitset == 0) return "0";
|
if (bitset == 0) return "0";
|
||||||
|
@ -480,15 +490,18 @@ styles = ["tairu.css"]
|
||||||
46 => E | SE | S | SW | W | NW | N | NE
|
46 => E | SE | S | SW | W | NW | N | NE
|
||||||
```
|
```
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y0NMP35M9138DV3P8W"
|
||||||
- with the lookup table generated, we are now able to prepare a tile strip like before - except now it's even more tedious work arranging the pieces together :ralsei_dead:
|
- with the lookup table generated, we are now able to prepare a tile strip like before - except now it's even more tedious work arranging the pieces together :ralsei_dead:
|
||||||
|
|
||||||
anyways I spent like 20 minutes doing that by hand, and now we have a neat tile strip just like before, except way longer:
|
anyways I spent like 20 minutes doing that by hand, and now we have a neat tile strip just like before, except way longer:
|
||||||
|
|
||||||
![horizontal tile strip of 47 8x8 pixel metal tiles][pic:01HPW47SHMSVAH7C0JR9HWXWCM]
|
![horizontal tile strip of 47 8x8 pixel metal tiles][pic:01HPW47SHMSVAH7C0JR9HWXWCM]
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y0J3DHQV5F9GD3VNQ8"
|
||||||
- now let's hook it up to our tileset renderer! TODO literate program.
|
- now let's hook it up to our tileset renderer! TODO literate program.
|
||||||
|
|
||||||
% template = true
|
% template = true
|
||||||
|
id = "01HPWJB4Y00ARHBGDF2HTQQ4SD"
|
||||||
- with the capability to render with 47-tile tilesets, our examples suddenly look a whole lot better!
|
- with the capability to render with 47-tile tilesets, our examples suddenly look a whole lot better!
|
||||||
|
|
||||||
<canvas
|
<canvas
|
||||||
|
|
133
content/treehouse/sandbox.tree
Normal file
133
content/treehouse/sandbox.tree
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
%% title = "the treehouse sandbox"
|
||||||
|
scripts = ["components/literate-programming.js"]
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5ST6AEK9VDYNS865P"
|
||||||
|
- the sandbox is a framework for playing around with code
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5HSDMEHNJXVET212V"
|
||||||
|
- one might call it "literate programming" and indeed that's the name used inside the code
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y583X6RXD060RZJ7WA"
|
||||||
|
- it's based on JavaScript; basically, you write scripts in JavaScript that may embed output into the parent page
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y56KH9MVC7Y5DDJ0CH"
|
||||||
|
- this is a bit of documentation on how to use its features
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5G4XXDE4ZPY3SWPXP"
|
||||||
|
+ ### basic usage
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5JG6SGV3A5SS6V0JY"
|
||||||
|
- the smallest building block is a module. each code block is a separate ES module, and therefore it has separate imports and exports.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5HDEGNVRE307ND6H3"
|
||||||
|
- for example, in the example below, the two code blocks cannot access each other's variables:
|
||||||
|
|
||||||
|
```javascript module-separation-broken
|
||||||
|
let myVariable = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript module-separation-broken
|
||||||
|
console.log(myVariable);
|
||||||
|
```
|
||||||
|
|
||||||
|
```output module-separation-broken
|
||||||
|
ReferenceError: myVariable is not defined
|
||||||
|
```
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y574NADEWXYWZR8C6C"
|
||||||
|
- to fix this, export the variable:
|
||||||
|
|
||||||
|
```javascript module-separation-works
|
||||||
|
export let myVariable = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript module-separation-works
|
||||||
|
console.log(myVariable);
|
||||||
|
```
|
||||||
|
|
||||||
|
```output module-separation-works
|
||||||
|
1
|
||||||
|
```
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5GDMQTG6F6K4TDYWA"
|
||||||
|
+ ### outputting text
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y55S28PVMBD4FKK98C"
|
||||||
|
- for code blocks which are followed by an `Output` block, such as [this one], it is possible to use `console.log` to output text to the console:
|
||||||
|
|
||||||
|
```javascript text-output
|
||||||
|
console.log("Hello, world!");
|
||||||
|
```
|
||||||
|
|
||||||
|
```output text-output
|
||||||
|
Hello, world!
|
||||||
|
```
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5CMHY36HW8EPTBSYG"
|
||||||
|
- `console.warn`, `console.error`, etc. are not supported right now. sorry.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5CXAMYHGWSG72FP4J"
|
||||||
|
- code blocks are generally tied together when I say so. for example, you can access variables `export`ed from the above code blocks here; try making this example compile without touching this code block:
|
||||||
|
|
||||||
|
```javascript text-output
|
||||||
|
console.log(x + 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
```output text-output
|
||||||
|
3
|
||||||
|
```
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y54R6ZB0GHCXRF674M"
|
||||||
|
- you'll notice that if you edit the first code block in this section, both the code blocks' outputs get updated automatically. neat, huh?
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y57BVC7H5N94FREGRK"
|
||||||
|
+ ### outputting graphics
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5CB5MSBWMG58VMR2G"
|
||||||
|
- some code blocks allow for graphical output. such as this one:
|
||||||
|
|
||||||
|
```javascript graphical-output
|
||||||
|
import { Sketch } from "treehouse/sandbox.js";
|
||||||
|
|
||||||
|
let sketch = new Sketch(200, 200);
|
||||||
|
sketch.ctx.fillStyle = "white";
|
||||||
|
sketch.ctx.fillRect(0, 0, sketch.canvas.width, sketch.canvas.height);
|
||||||
|
sketch.ctx.strokeStyle = "black";
|
||||||
|
sketch.ctx.strokeRect(32, 32, 32, 32);
|
||||||
|
```
|
||||||
|
|
||||||
|
<th-literate-program data-mode="graphics" data-program="graphical-output"></th-literate-program>
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5ER6SAQZ17ZFH8RK7"
|
||||||
|
- `Sketch` is an API for drawing things using HTML `<canvas>`.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5MRKCXRNSRFG7PVP5"
|
||||||
|
- the act of creating a `Sketch` using `new` causes a new `<canvas>` to be created.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5HZG6QH5N6N2Q9Z99"
|
||||||
|
- this `<canvas>` can be accessed using the sketch's `canvas` field, and the canvas's 2D drawing context is accessible using the `ctx` field.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5ANS55R32X7GJP1N7"
|
||||||
|
- this API wraps a lower-level message-passing API which is used to communicate with the main page, to let it set things like the sandbox `<iframe>`'s size (as well as make it visible).
|
||||||
|
see the source code for details.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5H9DKZT2ZA8PWNV99"
|
||||||
|
+ ### known issues
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y56FPNHJNVGJQ57DWH"
|
||||||
|
- the code editors are very janky on Firefox right now.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5T2GA42SJ80JNY2FK"
|
||||||
|
- the sandbox uses CodeJar to facilitate code editing, and to do this it uses `contenteditable` attributes.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5H9M4W0ZYYPVWQ6MY"
|
||||||
|
- I love CodeJar for its simplicity. it brings you text editing, and you bring your own everything else. so I'd love to
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5WWPJXSWQDCMG6J32"
|
||||||
|
- however, for non-janky text editing, CodeJar uses `contenteditable="plaintext-only"`, which is only supported on Chromium-based browsers.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y5ZJC1P0M6D3VNM0SF"
|
||||||
|
- I may patch it at some point to support regular `contenteditable` better at some point, but it's not a priority right now.
|
||||||
|
|
||||||
|
% id = "01HPWJB4Y52XZJRRZ41F1XK0ZT"
|
||||||
|
- the sandbox is only used for small, editable, interactive code examples and is not intended to be a fully fledged IDE.
|
|
@ -1,4 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
|
@ -174,12 +175,49 @@ impl Generator {
|
||||||
parsed_trees: impl IntoIterator<Item = ParsedTree>,
|
parsed_trees: impl IntoIterator<Item = ParsedTree>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut handlebars = Handlebars::new();
|
let mut handlebars = Handlebars::new();
|
||||||
let tree_template = Self::register_template(
|
|
||||||
&mut handlebars,
|
let mut template_file_ids = HashMap::new();
|
||||||
|
for entry in WalkDir::new(paths.template_dir) {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
if !entry.file_type().is_dir() && path.extension() == Some(OsStr::new("hbs")) {
|
||||||
|
let relative_path = path
|
||||||
|
.strip_prefix(paths.template_dir)?
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
let file_id =
|
||||||
|
Self::register_template(&mut handlebars, treehouse, &relative_path, path)?;
|
||||||
|
template_file_ids.insert(relative_path, file_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::create_dir_all(paths.template_target_dir)?;
|
||||||
|
for (name, &file_id) in &template_file_ids {
|
||||||
|
if !name.starts_with('_') {
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct StaticTemplateData<'a> {
|
||||||
|
config: &'a Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
let templated_html = match handlebars.render(name, &StaticTemplateData { config }) {
|
||||||
|
Ok(html) => html,
|
||||||
|
Err(error) => {
|
||||||
|
Self::wrangle_handlebars_error_into_diagnostic(
|
||||||
treehouse,
|
treehouse,
|
||||||
"tree",
|
file_id,
|
||||||
&paths.template_dir.join("tree.hbs"),
|
error.line_no,
|
||||||
|
error.column_no,
|
||||||
|
error.desc,
|
||||||
)?;
|
)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::fs::write(
|
||||||
|
paths.template_target_dir.join(name).with_extension("html"),
|
||||||
|
templated_html,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for parsed_tree in parsed_trees {
|
for parsed_tree in parsed_trees {
|
||||||
let breadcrumbs = breadcrumbs_to_html(config, navigation_map, &parsed_tree.tree_path);
|
let breadcrumbs = breadcrumbs_to_html(config, navigation_map, &parsed_tree.tree_path);
|
||||||
|
@ -216,11 +254,11 @@ impl Generator {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct TemplateData<'a> {
|
pub struct PageTemplateData<'a> {
|
||||||
pub config: &'a Config,
|
pub config: &'a Config,
|
||||||
pub page: Page,
|
pub page: Page,
|
||||||
}
|
}
|
||||||
let template_data = TemplateData {
|
let template_data = PageTemplateData {
|
||||||
config,
|
config,
|
||||||
page: Page {
|
page: Page {
|
||||||
title: roots.attributes.title.clone(),
|
title: roots.attributes.title.clone(),
|
||||||
|
@ -244,12 +282,12 @@ impl Generator {
|
||||||
|
|
||||||
treehouse.roots.insert(parsed_tree.tree_path, roots);
|
treehouse.roots.insert(parsed_tree.tree_path, roots);
|
||||||
|
|
||||||
let templated_html = match handlebars.render("tree", &template_data) {
|
let templated_html = match handlebars.render("_tree.hbs", &template_data) {
|
||||||
Ok(html) => html,
|
Ok(html) => html,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
Self::wrangle_handlebars_error_into_diagnostic(
|
Self::wrangle_handlebars_error_into_diagnostic(
|
||||||
treehouse,
|
treehouse,
|
||||||
tree_template,
|
template_file_ids["_tree.hbs"],
|
||||||
error.line_no,
|
error.line_no,
|
||||||
error.column_no,
|
error.column_no,
|
||||||
error.desc,
|
error.desc,
|
||||||
|
|
|
@ -78,6 +78,8 @@ pub struct ServeArgs {
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Paths<'a> {
|
pub struct Paths<'a> {
|
||||||
pub target_dir: &'a Path,
|
pub target_dir: &'a Path,
|
||||||
|
pub template_target_dir: &'a Path,
|
||||||
|
|
||||||
pub static_dir: &'a Path,
|
pub static_dir: &'a Path,
|
||||||
pub template_dir: &'a Path,
|
pub template_dir: &'a Path,
|
||||||
pub content_dir: &'a Path,
|
pub content_dir: &'a Path,
|
||||||
|
|
|
@ -21,6 +21,8 @@ async fn fallible_main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
let paths = Paths {
|
let paths = Paths {
|
||||||
target_dir: Path::new("target/site"),
|
target_dir: Path::new("target/site"),
|
||||||
|
template_target_dir: Path::new("target/site/static/html"),
|
||||||
|
|
||||||
config_file: Path::new("treehouse.toml"),
|
config_file: Path::new("treehouse.toml"),
|
||||||
|
|
||||||
// NOTE: These are intentionally left unconfigurable from within treehouse.toml
|
// NOTE: These are intentionally left unconfigurable from within treehouse.toml
|
||||||
|
|
|
@ -567,6 +567,21 @@ th-literate-program[data-mode="output"] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
th-literate-program[data-mode="graphics"] {
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
& iframe {
|
||||||
|
border-style: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The inner iframe is hidden until something requests display. */
|
||||||
|
& iframe.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Syntax highlighting */
|
/* Syntax highlighting */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
|
|
@ -202,6 +202,42 @@ class OutputMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GraphicsMode {
|
||||||
|
constructor(frame) {
|
||||||
|
this.frame = frame;
|
||||||
|
|
||||||
|
this.iframe = document.createElement("iframe");
|
||||||
|
this.iframe.classList.add("hidden");
|
||||||
|
this.iframe.src = import.meta.resolve("../../html/sandbox.html");
|
||||||
|
this.frame.appendChild(this.iframe);
|
||||||
|
|
||||||
|
this.iframe.contentWindow.addEventListener("message", event => {
|
||||||
|
let message = event.data;
|
||||||
|
if (message.kind == "resize") {
|
||||||
|
this.resize(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.iframe.contentWindow.addEventListener("DOMContentLoaded", () => this.evaluate());
|
||||||
|
this.frame.program.onChanged.push(_ => this.evaluate());
|
||||||
|
|
||||||
|
this.evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate() {
|
||||||
|
this.iframe.contentWindow.postMessage({
|
||||||
|
action: "eval",
|
||||||
|
input: getLiterateProgramWorkerCommands(this.frame.programName),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resize(message) {
|
||||||
|
this.iframe.width = message.width;
|
||||||
|
this.iframe.height = message.height;
|
||||||
|
this.iframe.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class LiterateProgram extends HTMLElement {
|
class LiterateProgram extends HTMLElement {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.programName = this.getAttribute("data-program");
|
this.programName = this.getAttribute("data-program");
|
||||||
|
@ -212,6 +248,8 @@ class LiterateProgram extends HTMLElement {
|
||||||
this.modeImpl = new InputMode(this);
|
this.modeImpl = new InputMode(this);
|
||||||
} else if (this.mode == "output") {
|
} else if (this.mode == "output") {
|
||||||
this.modeImpl = new OutputMode(this);
|
this.modeImpl = new OutputMode(this);
|
||||||
|
} else if (this.mode == "graphics") {
|
||||||
|
this.modeImpl = new GraphicsMode(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
48
static/js/components/literate-programming/eval.js
Normal file
48
static/js/components/literate-programming/eval.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
let outputIndex = 0;
|
||||||
|
|
||||||
|
export function getOutputIndex() {
|
||||||
|
return outputIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withTemporaryGlobalScope(callback) {
|
||||||
|
let state = {
|
||||||
|
oldValues: {},
|
||||||
|
set(key, value) {
|
||||||
|
this.oldValues[key] = globalThis[key];
|
||||||
|
globalThis[key] = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await callback(state);
|
||||||
|
for (let key in state.oldValues) {
|
||||||
|
globalThis[key] = state.oldValues[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function evaluate(commands) {
|
||||||
|
outputIndex = 0;
|
||||||
|
try {
|
||||||
|
await withTemporaryGlobalScope(async scope => {
|
||||||
|
for (let command of commands) {
|
||||||
|
if (command.kind == "module") {
|
||||||
|
let blobUrl = URL.createObjectURL(new Blob([command.source], { type: "text/javascript" }));
|
||||||
|
let module = await import(blobUrl);
|
||||||
|
for (let exportedKey in module) {
|
||||||
|
scope.set(exportedKey, module[exportedKey]);
|
||||||
|
}
|
||||||
|
} else if (command.kind == "output") {
|
||||||
|
++outputIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
postMessage({
|
||||||
|
kind: "output",
|
||||||
|
output: {
|
||||||
|
kind: "error",
|
||||||
|
message: [error.toString()],
|
||||||
|
},
|
||||||
|
outputIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
let outputIndex = 0;
|
import { evaluate, getOutputIndex } from "./eval.js";
|
||||||
|
|
||||||
let debugLog = console.log;
|
let debugLog = console.log;
|
||||||
|
|
||||||
|
@ -10,56 +10,14 @@ globalThis.console = {
|
||||||
kind: "log",
|
kind: "log",
|
||||||
message: [...message],
|
message: [...message],
|
||||||
},
|
},
|
||||||
outputIndex,
|
outputIndex: getOutputIndex(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function withTemporaryGlobalScope(callback) {
|
|
||||||
let state = {
|
|
||||||
oldValues: {},
|
|
||||||
set(key, value) {
|
|
||||||
this.oldValues[key] = globalThis[key];
|
|
||||||
globalThis[key] = value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
await callback(state);
|
|
||||||
for (let key in state.oldValues) {
|
|
||||||
globalThis[key] = state.oldValues[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addEventListener("message", async event => {
|
addEventListener("message", async event => {
|
||||||
let message = event.data;
|
let message = event.data;
|
||||||
if (message.action == "eval") {
|
if (message.action == "eval") {
|
||||||
outputIndex = 0;
|
evaluate(message.input);
|
||||||
try {
|
|
||||||
await withTemporaryGlobalScope(async scope => {
|
|
||||||
for (let command of message.input) {
|
|
||||||
if (command.kind == "module") {
|
|
||||||
let blobUrl = URL.createObjectURL(new Blob([command.source], { type: "text/javascript" }));
|
|
||||||
let module = await import(blobUrl);
|
|
||||||
for (let exportedKey in module) {
|
|
||||||
scope.set(exportedKey, module[exportedKey]);
|
|
||||||
}
|
|
||||||
} else if (command.kind == "output") {
|
|
||||||
++outputIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
postMessage({
|
|
||||||
kind: "output",
|
|
||||||
output: {
|
|
||||||
kind: "error",
|
|
||||||
message: [error.toString()],
|
|
||||||
},
|
|
||||||
outputIndex,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
postMessage({
|
|
||||||
kind: "evalComplete",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
16
static/js/sandbox.js
Normal file
16
static/js/sandbox.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export class Sketch {
|
||||||
|
constructor(width, height) {
|
||||||
|
this.canvas = document.createElement("canvas");
|
||||||
|
this.canvas.width = width;
|
||||||
|
this.canvas.height = height;
|
||||||
|
this.ctx = this.canvas.getContext("2d");
|
||||||
|
|
||||||
|
document.body.appendChild(this.canvas);
|
||||||
|
|
||||||
|
postMessage({
|
||||||
|
kind: "resize",
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
0
static/js/test.js
Normal file
0
static/js/test.js
Normal file
|
@ -26,7 +26,6 @@
|
||||||
<link rel="stylesheet" href="{{ config.site }}/static/css/main.css">
|
<link rel="stylesheet" href="{{ config.site }}/static/css/main.css">
|
||||||
<link rel="stylesheet" href="{{ config.site }}/static/css/tree.css">
|
<link rel="stylesheet" href="{{ config.site }}/static/css/tree.css">
|
||||||
|
|
||||||
|
|
||||||
<script>const TREEHOUSE_SITE = `{{ config.site }}`;</script>
|
<script>const TREEHOUSE_SITE = `{{ config.site }}`;</script>
|
||||||
<script type="module" src="{{ config.site }}/navmap.js"></script>
|
<script type="module" src="{{ config.site }}/navmap.js"></script>
|
||||||
<script type="module" src="{{ config.site }}/static/js/ulid.js"></script>
|
<script type="module" src="{{ config.site }}/static/js/ulid.js"></script>
|
32
template/sandbox.hbs
Normal file
32
template/sandbox.hbs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>treehouse iframe sandbox</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script type="importmap">{ "imports": { "treehouse/": "{{ config.site }}/static/js/" } }</script>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import { evaluate } from "treehouse/components/literate-programming/eval.js";
|
||||||
|
console.log("yo");
|
||||||
|
addEventListener("message", async event => {
|
||||||
|
let message = event.data;
|
||||||
|
if (message.action == "eval") {
|
||||||
|
document.body.replaceChildren();
|
||||||
|
evaluate(message.input);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body></body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in a new issue