Skip to content

Commit

Permalink
feat(interactive-examples): add support for WAT examples (#12654)
Browse files Browse the repository at this point in the history
  • Loading branch information
argl authored Feb 26, 2025
1 parent 7872c6f commit 4ecd702
Show file tree
Hide file tree
Showing 22 changed files with 922 additions and 24 deletions.
51 changes: 36 additions & 15 deletions client/src/lit/interactive-example/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { html, LitElement } from "lit";
import { ref, createRef } from "lit/directives/ref.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { decode } from "he";

import "../play/editor.js";
Expand All @@ -17,7 +18,7 @@ import styles from "./index.scss?css" with { type: "css" };
* @import { PlayRunner } from "../play/runner.js";
*/

const LANGUAGE_CLASSES = ["html", "js", "css"];
const LANGUAGE_CLASSES = ["html", "js", "css", "wat"];
const GLEAN_EVENT_TYPES = ["focus", "copy", "cut", "paste", "click"];

export class InteractiveExample extends GleanMixin(LitElement) {
Expand Down Expand Up @@ -68,23 +69,20 @@ export class InteractiveExample extends GleanMixin(LitElement) {
}, /** @type {Object<string, string>} */ ({}));
this._languages = Object.keys(code);
this._template =
this._languages.length === 1 && this._languages[0] === "js"
? "javascript"
(this._languages.length === 1 && this._languages[0] === "js") ||
(this._languages.includes("js") && this._languages.includes("wat"))
? "console"
: "tabbed";
return code;
}

/** @param {string} lang */
_langName(lang) {
switch (lang) {
case "html":
return "HTML";
case "css":
return "CSS";
case "js":
return "JavaScript";
default:
return lang;
return lang.toUpperCase();
}
}

Expand All @@ -110,20 +108,38 @@ export class InteractiveExample extends GleanMixin(LitElement) {
this._code = this._initialCode();
}

_renderJavascript() {
_renderConsole() {
return html`
<play-controller ${ref(this._controller)}>
<div class="template-javascript">
<div class="template-console">
<header>
<h4>${decode(this.name)}</h4>
</header>
<play-editor id="editor" language="js"></play-editor>
${this._languages.length === 1
? html`<play-editor
id="editor"
language=${ifDefined(this._languages[0])}
></play-editor>`
: html`<ix-tab-wrapper>
${this._languages.map(
(lang) => html`
<ix-tab id=${lang}>${this._langName(lang)}</ix-tab>
<ix-tab-panel id=${`${lang}-panel`}>
<play-editor language=${lang}></play-editor>
</ix-tab-panel>
`
)}
</ix-tab-wrapper>`}
<div class="buttons">
<button id="execute" @click=${this._run}>Run</button>
<button id="reset" @click=${this._reset}>Reset</button>
</div>
<play-console id="console"></play-console>
<play-runner></play-runner>
<play-runner
defaults=${ifDefined(
this._languages.includes("wat") ? "ix-wat" : undefined
)}
></play-runner>
</div>
</play-controller>
`;
Expand Down Expand Up @@ -161,9 +177,14 @@ export class InteractiveExample extends GleanMixin(LitElement) {
}

render() {
return this._template === "javascript"
? this._renderJavascript()
: this._renderTabbed();
switch (this._template) {
case "console":
return this._renderConsole();
case "tabbed":
return this._renderTabbed();
default:
return "";
}
}

firstUpdated() {
Expand Down
11 changes: 6 additions & 5 deletions client/src/lit/interactive-example/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ play-console {
grid-area: console;
}

tab-wrapper {
ix-tab-wrapper {
grid-area: tabs;
}

// -------------------
// JavaScript examples
// JavaScript/WAT examples
// -------------------

.template-javascript {
.template-console {
align-content: start;
display: grid;
gap: 0.5rem;
Expand All @@ -68,14 +68,15 @@ tab-wrapper {

play-runner {
display: none;
grid-area: runner;
}

play-editor {
> play-editor,
ix-tab-wrapper {
border: 1px solid var(--border-secondary);
border-bottom-left-radius: var(--elem-radius);
border-bottom-right-radius: var(--elem-radius);
border-top: 0;
grid-area: editor;
margin-top: -0.5rem;
}

Expand Down
3 changes: 3 additions & 0 deletions client/src/lit/play/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { lintKeymap } from "@codemirror/lint";
import { EditorView, minimalSetup } from "codemirror";
import { javascript as langJS } from "@codemirror/lang-javascript";
import { wast as langWat } from "@codemirror/lang-wast";
import { css as langCSS } from "@codemirror/lang-css";
import { html as langHTML } from "@codemirror/lang-html";
import { oneDark } from "@codemirror/theme-one-dark";
Expand Down Expand Up @@ -74,6 +75,8 @@ export class PlayEditor extends LitElement {
return [langHTML()];
case "css":
return [langCSS()];
case "wat":
return [langWat()];
default:
return [];
}
Expand Down
32 changes: 30 additions & 2 deletions client/src/lit/play/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { PLAYGROUND_BASE_HOST } from "../../env.ts";
import { createComponent } from "@lit/react";
import { Task } from "@lit/task";
import React from "react";

import styles from "./runner.scss?css" with { type: "css" };

/** @import { VConsole } from "./types" */
Expand All @@ -24,7 +23,7 @@ export class PlayRunner extends LitElement {
super();
/** @type {Record<string, string> | undefined} */
this.code = undefined;
/** @type {"ix-tabbed" | undefined} */
/** @type {"ix-tabbed" | "ix-wat" | undefined} */
this.defaults = undefined;
/** @type {string | undefined} */
this.srcPrefix = undefined;
Expand All @@ -47,6 +46,10 @@ export class PlayRunner extends LitElement {
args: () =>
/** @type {const} */ ([this.code, this.defaults, this.srcPrefix]),
task: async ([code, defaults, srcPrefix], { signal }) => {
if (code && code.js && code.wat) {
const watUrl = await compileAndEncodeWatToDataUrl(code.wat);
code.js = code.js.replace("{%wasm-url%}", watUrl);
}
const { state } = await compressAndBase64Encode(
JSON.stringify({
html: code?.html || "",
Expand Down Expand Up @@ -99,6 +102,31 @@ export class PlayRunner extends LitElement {
}
}

/**
* Converts a Uint8Array to a base64 encoded string
* @param {Uint8Array} bytes - The array of bytes to convert
* @returns {string} The base64 encoded string representation of the input bytes
*/
function uInt8ArrayToBase64(bytes) {
const binString = Array.from(bytes, (byte) =>
String.fromCodePoint(byte)
).join("");
return btoa(binString);
}

/**
* compiles the wat code to wasm
* @param {string} wat
* @returns {Promise<string>} a data-url with the compiled wasm, base64 encoded
*/
async function compileAndEncodeWatToDataUrl(wat) {
const { default: init, watify } = await import("watify");
await init();
const binary = watify(wat);
const b64 = `data:application/wasm;base64,${uInt8ArrayToBase64(binary)}`;
return b64;
}

customElements.define("play-runner", PlayRunner);

export const ReactPlayRunner = createComponent({
Expand Down
5 changes: 3 additions & 2 deletions libs/play/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const ORIGIN_REVIEW =
* @property {string} css
* @property {string} js
* @property {string} [src]
* @property {"ix-tabbed"} [defaults]
* @property {"ix-tabbed" | "ix-wat"} [defaults]
*/

/**
Expand Down Expand Up @@ -50,6 +50,7 @@ const PLAYGROUND_UNSAFE_CSP_SCRIPT_SRC_VALUES = [

export const PLAYGROUND_UNSAFE_CSP_VALUE = cspToString({
"default-src": ["'self'", "https:"],
"connect-src": ["'self'", "https:", "data:"],
"script-src": PLAYGROUND_UNSAFE_CSP_SCRIPT_SRC_VALUES,
"script-src-elem": PLAYGROUND_UNSAFE_CSP_SCRIPT_SRC_VALUES,
"style-src": [
Expand Down Expand Up @@ -419,7 +420,7 @@ export function renderHtml(state = null) {
</head>
<body>
${htmlCode}
<script>
<script type="${defaults === "ix-wat" ? "module" : ""}">
${js};
</script>
</body>
Expand Down
1 change: 1 addition & 0 deletions libs/watify/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
Loading

0 comments on commit 4ecd702

Please sign in to comment.