This commit is contained in:
Remko Tronçon 2022-05-08 18:46:50 +02:00
parent 59b56e5491
commit b1546ad4a3
12 changed files with 520 additions and 6 deletions

25
.github/workflows/publish-thurtle.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: Publish Thurtle
on:
workflow_dispatch:
jobs:
build:
uses: ./.github/workflows/build.yml
publish:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/setup
- run: sudo apt-get install awscli
- run: yarnpkg build
- name: aws s3 sync
run: |
aws configure set region eu-central-1
aws configure set aws_access_key_id ${{secrets.AWS_ACCESS_KEY_ID}}
aws configure set aws_secret_access_key ${{secrets.AWS_SECRET_ACCESS_KEY}}
aws s3 sync public/thurtle/ s3://${{secrets.AWS_SITE_BUCKET}}/thurtle/
aws s3 sync public/waforth/dist/ s3://${{secrets.AWS_SITE_BUCKET}}/waforth/dist/ --exclude "*" --include "thurtle-*" --include "logo-*.svg" --include "turtle-*.svg"

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
.DS_Store
node_modules/
public/waforth/
public/thurtle/
src/waforth.bulkmem.wat
src/waforth.vanilla.wat
src/web/benchmarks/sieve/sieve-c.js

View file

@ -7,7 +7,7 @@
WAForth is a small bootstrapping Forth interpreter and dynamic compiler for
[WebAssembly](https://webassembly.org). You can see it in action
[here](https://mko.re/waforth/).
[in an interactive Forth console](https://mko.re/waforth/), and in [a Logo-like Turtle graphics language](https://mko.re/thurtle/).
It is [entirely written in (raw)
WebAssembly](https://github.com/remko/waforth/blob/master/src/waforth.wat), and

View file

@ -30,9 +30,7 @@ function withWatcher(config, handleBuildFinished = () => {}, port = 8880) {
if (error) {
console.error(error);
} else {
// Doing this first, because this may do some ES5 transformations
await handleBuildFinished(result);
watchClients.forEach((res) => res.write("data: update\n\n"));
watchClients.length = 0;
}
@ -62,16 +60,20 @@ let buildConfig = {
path.join(__dirname, "src", "web", "tests", "tests"),
path.join(__dirname, "src", "web", "benchmarks", "benchmarks"),
path.join(__dirname, "src", "web", "examples", "prompt", "prompt"),
path.join(__dirname, "src", "web", "thurtle", "thurtle"),
],
entryNames: dev ? "[name]" : "[name]-c$[hash]",
assetNames: "[name]-c$[hash]",
// target: "es6",
outdir: path.join(__dirname, "public/waforth/dist"),
publicPath: "/waforth/dist",
external: ["fs", "stream", "util", "events"],
minify: !dev,
loader: {
".wasm": "binary",
".js": "jsx",
".fs": "text",
".svg": "file",
},
define: {
WAFORTH_VERSION: watch
@ -118,9 +120,19 @@ async function handleBuildFinished(result) {
["tests", "public/waforth/tests"],
["benchmarks", "public/waforth/benchmarks"],
["prompt", "public/waforth/examples/prompt"],
["thurtle", "public/thurtle", true],
];
for (const [base, outpath] of indexes) {
for (const [base, outpath, bs] of indexes) {
let index = INDEX_TEMPLATE.replace(/\$BASE/g, base);
if (bs) {
index = index.replace(
"<body>",
`<body>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous" />
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
`
);
}
for (const [out] of Object.entries(result.metafile.outputs)) {
const outfile = path.basename(out);
const sourcefile = outfile.replace(/-c\$[^.]+\./, ".");
@ -145,7 +157,14 @@ if (watch) {
}
try {
const data = await fs.promises.readFile(f);
res.writeHead(200);
res.writeHead(
200,
req.url.endsWith(".svg")
? {
"Content-Type": "image/svg+xml",
}
: undefined
);
res.end(data);
} catch (err) {
res.writeHead(404);

View file

@ -0,0 +1,83 @@
export type Example = {
name: string;
program: string;
};
export default [
{
name: "Square",
program: `
200 FORWARD
90 RIGHT
200 FORWARD
90 RIGHT
200 FORWARD
90 RIGHT
200 FORWARD
90 RIGHT
`,
},
{
name: "Square (w/ LOOP)",
program: `
: SQUARE ( n -- )
4 0 DO
DUP FORWARD
90 RIGHT
LOOP
;
250 SQUARE
`,
},
{
name: "Seeker",
program: `
: SEEKER ( n -- )
4 0 DO
DUP FORWARD
PENUP
DUP FORWARD
PENDOWN
DUP FORWARD
90 RIGHT
LOOP
;
100 SEEKER
`,
},
{
name: "Flower",
program: `
: SQUARE ( n -- )
4 0 DO
DUP FORWARD
90 RIGHT
LOOP
;
: FLOWER ( n -- )
12 0 DO
DUP SQUARE
30 RIGHT
LOOP
;
250 FLOWER
`,
},
{
name: "Spiral (Recursive)",
program: `
: SPIRAL ( n -- )
DUP 1 < IF EXIT THEN
DUP FORWARD
20 RIGHT
95 100 */ RECURSE
;
140 SPIRAL
`,
},
].map((e) => ({ ...e, program: e.program.trimStart() }));

View file

@ -0,0 +1,52 @@
.root {
height: 100vh;
}
.main {
height: calc(100% - 40px);
}
textarea {
flex: 1;
}
.navbar {
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.world {
border: thin solid rgb(171, 208, 166);
border-radius: 0.5em;
background-color: rgb(221, 248, 221);
width: 100%;
max-height: 350px;
}
.world path {
stroke: black;
fill: white;
fill-opacity: 0;
}
textarea.program {
font-family: monospace;
font-size: 0.8em;
}
.output {
font-size: 0.8em;
overflow: scroll;
}
label {
font-weight: 500;
}
.left-pane {
flex: 1;
}
.right-pane {
flex: 2;
}

View file

@ -0,0 +1,9 @@
: FORWARD ( n -- ) S" forward" SCALL ;
: BACKWARD ( n -- ) NEGATE FORWARD ;
: LEFT ( n -- ) S" rotate" SCALL ;
: RIGHT ( n -- ) NEGATE LEFT ;
: PENDOWN ( -- ) 1 S" pen" SCALL ;
: PENUP ( -- ) 0 S" pen" SCALL ;
: HIDETURTLE ( -- ) 0 S" turtle" SCALL ;
: SHOWTURTLE ( -- ) 1 S" turtle" SCALL ;
: SETPENSIZE ( n -- ) S" setpensize" SCALL ;

264
src/web/thurtle/thurtle.ts Normal file
View file

@ -0,0 +1,264 @@
import WAForth from "waforth";
import "./thurtle.css";
import turtle from "./turtle.svg";
import logo from "../../../doc/logo.svg";
import thurtleFS from "./thurtle.fs";
import examples from "./examples";
const rootEl = document.createElement("div");
rootEl.className = "root";
rootEl.innerHTML = `<nav class="navbar navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<img
data-hook="logo"
width="30"
height="24"
class="d-inline-block align-text-top"
/>
Thurtle
</a>
<a
role="button"
data-bs-toggle="modal"
data-bs-target="#helpModal"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-question-circle-fill"
viewBox="0 0 16 16"
>
<path
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.496 6.033h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286a.237.237 0 0 0 .241.247zm2.325 6.443c.61 0 1.029-.394 1.029-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94 0 .533.425.927 1.01.927z"
/>
</svg>
</button>
</div>
</nav>
<div class="main d-flex flex-column p-2">
<div class="container mt-2 text-muted">
<p>
Interactive, Logo-like Turtle graphics language, using Forth (powered by
<a href="https://github.com/remko/waforth">WAForth</a>).
</p>
</div>
<div class="d-flex flex-row flex-grow-1">
<div class="left-pane d-flex flex-column">
<select class="form-select mb-2" data-hook="examples"></select>
<textarea autofocus class="form-control program"></textarea>
<button data-hook="run" class="btn btn-primary mt-2">Run</button>
</div>
<div class="d-flex flex-column ms-3 right-pane">
<svg
class="world"
viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg"
>
<g transform="translate(500 500)">
<g id="paths">
</g>
<image id="turtle" width="50" height="50" href="${turtle}" />
</g>
</svg>
<form>
<div class="form-group mt-3">
<label>Output</label>
<pre class="output"></pre>
</div>
</form>
</div>
</div>
</div>
<div
class="modal fade"
id="helpModal"
tabindex="-1"
aria-labelledby="helpModalLabel"
aria-hidden="true"
>
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="helpModalLabel">Help</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div class="modal-body">
<p>The following words for moving the turtle are available:
<ul>
<li><code>FORWARD ( n -- )</code>: Move forward by <code>n</code>.</li>
<li><code>BACKWARD ( n -- )</code>: Move backward by <code>n</code>.</li>
<li><code>LEFT ( n -- )</code>: Turn left by <code>n</code> degrees.</li>
<li><code>RIGHT ( n -- )</code>: Turn right by <code>n</code> degrees.</li>
<li><code>PENUP ( -- )</code>: Disable drawing while moving.</li>
<li><code>PENDOWN ( -- )</code>: Enable drawing while moving.</li>
<li><code>SETPENSIZE ( n -- )</code>: Set the width of the drawed strokes (default: 5).</li>
<li><code>HIDETURTLE ( -- )</code>: Hide the turtle.</li>
<li><code>SHOWTURTLE ( -- )</code>: Show the turtle.</li>
</ul>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
data-bs-dismiss="modal"
>
Close
</button>
</div>
</div>
</div>
</div>`;
document.body.appendChild(rootEl);
const turtleEl = document.getElementById("turtle")!;
let pathEl: SVGPathElement;
const patshEl = document.getElementById("paths")!;
const runButtonEl = document.querySelector(
"button[data-hook=run]"
)! as HTMLButtonElement;
const examplesEl = document.querySelector(
"[data-hook=examples]"
)! as HTMLSelectElement;
const programEl = document.querySelector("textarea") as HTMLTextAreaElement;
const outputEl = document.querySelector("pre") as HTMLPreElement;
(document.querySelector("img[data-hook=logo]")! as HTMLImageElement).src = logo;
enum PenState {
Up = 0,
Down = 1,
}
let rotation = 0;
let position = { x: 0, y: 0 };
let pen = PenState.Down;
let visible = true;
function newPathEl() {
pathEl = document.createElementNS("http://www.w3.org/2000/svg", "path");
pathEl.setAttribute("stroke-width", "5");
pathEl.setAttribute("d", "M 0 0");
patshEl.appendChild(pathEl);
}
function reset() {
position.x = position.y = 0;
rotation = 270;
pen = PenState.Down;
patshEl.innerHTML = "";
newPathEl();
outputEl.innerHTML = "";
updateTurtle();
}
function updateTurtle() {
turtleEl.style.display = visible ? "block" : "none";
turtleEl.setAttribute(
"transform",
`rotate(${rotation} ${position.x} ${position.y}) translate(${
position.x - 25
} ${position.y - 25})`
);
}
function rotate(deg: number) {
rotation = rotation + deg;
updateTurtle();
}
function forward(d: number) {
const dx = d * Math.cos((rotation * Math.PI) / 180.0);
const dy = d * Math.sin((rotation * Math.PI) / 180.0);
pathEl.setAttribute(
"d",
pathEl.getAttribute("d")! +
" " +
[pen === PenState.Down ? "l" : "m", dx, dy].join(" ")
);
position.x += dx;
position.y += dy;
updateTurtle();
}
function setPen(s: PenState) {
pen = s;
}
function setPenSize(s: number) {
newPathEl();
pathEl.setAttribute("stroke-width", s + "");
}
function setVisible(b: boolean) {
visible = b;
updateTurtle();
}
function loadExample(name: string) {
programEl.value = examples.find((e) => e.name === name)!.program;
examplesEl.value = name;
}
for (const ex of examples) {
const option = document.createElement("option");
option.appendChild(document.createTextNode(ex.name));
option.value = ex.name;
examplesEl.appendChild(option);
}
examplesEl.addEventListener("change", (ev) => {
loadExample((ev.target! as HTMLSelectElement).value);
});
async function run() {
try {
runButtonEl.disabled = true;
reset();
const forth = new WAForth();
await forth.load();
forth.bind("forward", (stack) => {
forward(stack.pop());
});
forth.bind("rotate", (stack) => {
rotate(-stack.pop());
});
forth.bind("pen", (stack) => {
setPen(stack.pop());
});
forth.bind("turtle", (stack) => {
setVisible(stack.pop() != 0);
});
forth.bind("setpensize", (stack) => {
setPenSize(stack.pop());
});
forth.interpret(thurtleFS);
forth.onEmit = (c) => outputEl.appendChild(document.createTextNode(c));
forth.interpret(programEl.value);
programEl.focus();
} catch (e) {
console.error(e);
} finally {
runButtonEl.disabled = false;
}
}
runButtonEl.addEventListener("click", () => run());
document.addEventListener("keydown", (ev) => {
if (ev.key == "Enter" && (ev.metaKey || ev.ctrlKey)) {
run();
}
});
reset();
loadExample(examples[1].name);

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="200" height="200" viewBox="0, 0, 200, 200">
<g id="g76">
<g>
<g id="g10">
<path d="M74.297,129.567 L74.297,118.502 L52.398,118.502 C35.906,118.502 22.537,131.871 22.537,148.364 L22.537,148.364 L55.501,148.364 C65.882,148.364 74.297,139.948 74.297,129.567 z" fill="#3FAE2A" id="path2"/>
<path d="M27.863,148.364 C30.209,134.212 42.498,123.419 57.315,123.419 L74.298,123.419 L74.298,118.502 L52.398,118.502 C35.907,118.502 22.537,131.871 22.537,148.364 z" fill="#379624" id="path4"/>
<path d="M51.774,141.919 C50.158,141.919 48.847,140.608 48.847,138.992 C48.847,137.376 50.158,136.065 51.774,136.065 C53.391,136.065 54.701,137.376 54.701,138.992 C54.701,140.608 53.391,141.919 51.774,141.919 z" fill="#4A9C3B" id="circle6"/>
<path d="M39.696,145.055 C37.214,145.055 35.201,143.043 35.201,140.56 C35.201,138.078 37.214,136.065 39.696,136.065 C42.178,136.065 44.191,138.078 44.191,140.56 C44.191,143.043 42.178,145.055 39.696,145.055 z" fill="#4A9C3B" id="circle8"/>
</g>
<g id="g20">
<path d="M74.297,70.433 C74.297,60.052 65.881,51.636 55.501,51.636 L22.537,51.636 C22.537,68.129 35.906,81.497 52.398,81.497 L74.297,81.497 z" fill="#3FAE2A" id="path12"/>
<path d="M27.863,51.636 C30.209,65.788 42.498,76.581 57.315,76.581 L74.298,76.581 L74.298,81.497 L52.398,81.497 C35.907,81.497 22.537,68.128 22.537,51.636 L27.863,51.636 z" fill="#379624" id="path14"/>
<path d="M38.129,66.293 C35.517,66.293 33.399,64.176 33.399,61.564 C33.399,58.951 35.517,56.834 38.129,56.834 C40.741,56.834 42.859,58.951 42.859,61.564 C42.859,64.176 40.741,66.293 38.129,66.293 z" fill="#4A9C3B" id="circle16"/>
<path d="M56.054,72.853 C52.504,72.853 49.627,69.975 49.627,66.426 C49.627,62.876 52.504,59.999 56.054,59.999 C59.603,59.999 62.481,62.876 62.481,66.426 C62.481,69.975 59.603,72.853 56.054,72.853 z" fill="#4A9C3B" id="circle18"/>
</g>
<g id="g32">
<path d="M132.009,158.953 C139.35,151.612 139.35,139.711 132.009,132.37 L124.185,124.546 L108.7,140.031 C97.039,151.693 97.039,170.599 108.7,182.261 z" fill="#3FAE2A" id="path22"/>
<path d="M112.466,178.496 C104.118,166.831 105.176,150.508 115.653,140.032 L127.661,128.024 L124.185,124.547 L108.7,140.032 C97.039,151.694 97.039,170.601 108.7,182.262 z" fill="#379624" id="path24"/>
<path d="M125.153,159.618 C123.155,159.618 121.534,157.998 121.534,155.999 C121.534,154 123.155,152.38 125.153,152.38 C127.152,152.38 128.773,154 128.773,155.999 C128.773,157.998 127.152,159.618 125.153,159.618 z" fill="#4A9C3B" id="circle26"/>
<path d="M112.728,162.623 C111.53,162.623 110.559,161.652 110.559,160.454 C110.559,159.256 111.53,158.284 112.728,158.284 C113.926,158.284 114.898,159.256 114.898,160.454 C114.898,161.652 113.926,162.623 112.728,162.623 z" fill="#4A9C3B" id="circle28"/>
<path d="M116.565,168.192 C115.367,168.192 114.396,167.221 114.396,166.022 C114.396,164.824 115.367,163.853 116.565,163.853 C117.763,163.853 118.735,164.824 118.735,166.022 C118.735,167.221 117.763,168.192 116.565,168.192 z" fill="#4A9C3B" id="circle30"/>
</g>
<g id="g42">
<path d="M132.009,67.629 C139.35,60.288 139.35,48.388 132.009,41.047 L108.7,17.738 L108.7,17.738 C97.039,29.399 97.039,48.307 108.7,59.968 L124.185,75.453 z" fill="#3FAE2A" id="path34"/>
<path d="M112.466,21.504 C104.118,33.169 105.176,49.492 115.653,59.968 L127.661,71.976 L124.185,75.453 L108.7,59.968 C97.039,48.306 97.039,29.399 108.7,17.738 z" fill="#379624" id="path36"/>
<path d="M115.115,39.976 C113.116,39.976 111.496,38.355 111.496,36.356 C111.496,34.358 113.116,32.737 115.115,32.737 C117.114,32.737 118.734,34.358 118.734,36.356 C118.734,38.355 117.114,39.976 115.115,39.976 z" fill="#4A9C3B" id="circle38"/>
<path d="M125.153,52.704 C122.541,52.704 120.424,50.586 120.424,47.974 C120.424,45.362 122.541,43.244 125.153,43.244 C127.766,43.244 129.883,45.362 129.883,47.974 C129.883,50.586 127.766,52.704 125.153,52.704 z" fill="#4A9C3B" id="circle40"/>
</g>
<g id="g64">
<path d="M198.819,100 L198.819,100 C198.819,88.506 189.501,79.188 178.007,79.188 L145.74,79.188 L145.74,120.811 L178.007,120.811 C189.501,120.812 198.819,111.494 198.819,100 z" fill="#3FAE2A" id="path44"/>
<g id="g50">
<path d="M176.969,128.504 C172.721,128.504 169.277,125.06 169.277,120.812 C169.277,116.564 172.721,113.12 176.969,113.12 C181.218,113.12 184.662,116.564 184.662,120.812 C184.662,125.06 181.218,128.504 176.969,128.504 z" fill="#FFFFFF" id="circle46"/>
<path d="M176.969,124.658 C174.846,124.658 173.124,122.936 173.124,120.812 C173.124,118.688 174.846,116.966 176.969,116.966 C179.093,116.966 180.815,118.688 180.815,120.812 C180.815,122.936 179.093,124.658 176.969,124.658 z" fill="#5F4637" id="circle48"/>
</g>
<g id="g56">
<path d="M176.969,86.88 C172.721,86.88 169.277,83.436 169.277,79.188 C169.277,74.94 172.721,71.496 176.969,71.496 C181.218,71.496 184.662,74.94 184.662,79.188 C184.662,83.436 181.218,86.88 176.969,86.88 z" fill="#FFFFFF" id="circle52"/>
<path d="M176.969,83.034 C174.846,83.034 173.124,81.312 173.124,79.188 C173.124,77.064 174.846,75.342 176.969,75.342 C179.093,75.342 180.815,77.064 180.815,79.188 C180.815,81.312 179.093,83.034 176.969,83.034 z" fill="#5F4637" id="circle54"/>
</g>
<path d="M164.547,113.327 C163.349,113.327 162.378,112.356 162.378,111.158 C162.378,109.96 163.349,108.988 164.547,108.988 C165.745,108.988 166.716,109.96 166.716,111.158 C166.716,112.356 165.745,113.327 164.547,113.327 z" fill="#4A9C3B" id="circle58"/>
<path d="M164.547,96.339 C161.935,96.339 159.817,94.222 159.817,91.61 C159.817,88.998 161.935,86.88 164.547,86.88 C167.159,86.88 169.277,88.998 169.277,91.61 C169.277,94.222 167.159,96.339 164.547,96.339 z" fill="#4A9C3B" id="circle60"/>
<path d="M178.242,110.301 C174.662,110.301 171.76,107.398 171.76,103.818 C171.76,100.238 174.662,97.336 178.242,97.336 C181.822,97.336 184.725,100.238 184.725,103.818 C184.725,107.398 181.822,110.301 178.242,110.301 z" fill="#4A9C3B" id="circle62"/>
</g>
<path d="M40.993,100 L40.993,100 C40.993,73.814 62.221,52.585 88.408,52.585 L111.5,52.585 C137.687,52.585 158.915,73.813 158.915,100 L158.915,100 C158.915,126.186 137.687,147.415 111.5,147.415 L88.408,147.415 C62.221,147.415 40.993,126.186 40.993,100 z" fill="#A75A29" id="path66"/>
<path d="M49.971,100 C49.971,78.806 67.214,61.564 88.407,61.564 L111.499,61.564 C132.694,61.564 149.936,78.806 149.936,99.999 C149.936,121.193 132.694,138.436 111.499,138.436 L88.407,138.436 C67.214,138.436 49.971,121.194 49.971,100 z" fill="#E89E6C" id="path68"/>
<path d="M99.954,118.78 C89.582,118.78 81.174,110.372 81.174,100 C81.174,89.628 89.582,81.22 99.954,81.22 C110.326,81.22 118.734,89.628 118.734,100 C118.734,110.372 110.326,118.78 99.954,118.78 z" fill="#BD8158" id="circle70"/>
<path d="M101.465,61.564 L101.465,138.436 L98.442,138.436 L98.442,61.564 z" fill="#BD8158" id="rect72"/>
<path d="M122.239,73.717 C126.628,73.717 130.462,71.383 132.599,67.896 C126.537,63.899 119.288,61.564 111.499,61.564 L110.085,61.564 C110.085,68.276 115.527,73.717 122.239,73.717 z M89.822,61.564 L88.407,61.564 C80.62,61.564 73.37,63.898 67.309,67.895 C69.445,71.382 73.279,73.717 77.669,73.717 C84.381,73.717 89.822,68.276 89.822,61.564 z M125.651,99.999 C125.651,110.997 130.622,120.83 138.434,127.386 C145.528,120.409 149.936,110.711 149.936,99.999 C149.936,89.288 145.527,79.59 138.434,72.613 C130.621,79.169 125.651,89.002 125.651,99.999 z M132.599,132.105 C130.462,128.618 126.628,126.283 122.239,126.283 C115.527,126.283 110.085,131.724 110.085,138.437 L111.499,138.437 C119.288,138.436 126.537,136.102 132.599,132.105 z M77.669,126.283 C73.279,126.283 69.445,128.617 67.309,132.104 C73.37,136.101 80.62,138.436 88.407,138.436 L89.822,138.436 C89.822,131.724 84.381,126.283 77.669,126.283 z M74.257,99.999 C74.257,89.002 69.286,79.169 61.473,72.613 C54.38,79.59 49.971,89.288 49.971,99.999 C49.971,110.711 54.38,120.409 61.473,127.386 C69.287,120.83 74.257,110.997 74.257,99.999 z" fill="#BD8158" id="path74"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

4
src/web/types/forth.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
declare module "*.fs" {
const value: string;
export = value;
}

4
src/web/types/images.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
declare module "*.svg" {
const value: string;
export = value;
}

View file

@ -6,7 +6,8 @@
"target": "es2015",
"typeRoots": ["./src/web/types"],
"types": ["node"],
"baseUrl": "./src/web"
"baseUrl": "./src/web",
"allowSyntheticDefaultImports": true
},
"include": ["./src/web"],
"exclude": ["node_modules", "dist", "build"]