Enhance CDN packages and playground
Amp-Thread-ID: https://ampcode.com/threads/T-01a02485-5574-707c-bff4-5668d83bee8a Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
+255
-75
@@ -1,60 +1,98 @@
|
||||
const defaultSource = `import {
|
||||
AmbientLight,
|
||||
ArcRotateCamera,
|
||||
ColorGrading,
|
||||
FXAA,
|
||||
Mesh,
|
||||
Icosphere,
|
||||
PBRMaterial,
|
||||
Plane,
|
||||
PointLight,
|
||||
Scene,
|
||||
SpotLight,
|
||||
} from "@yawn/handles";
|
||||
|
||||
const scene = new Scene(canvas, { hdr: true, fps: 60 });
|
||||
await scene.ready;
|
||||
await scene.reserve({ nodes: 6, materials: 2 });
|
||||
|
||||
const material = new PBRMaterial(scene, {
|
||||
baseColor: [0.12, 0.58, 1, 1],
|
||||
metallic: 0.15,
|
||||
roughness: 0.35,
|
||||
const objectMaterial = new PBRMaterial(scene, {
|
||||
baseColor: [0.82, 0.86, 0.9, 1],
|
||||
metallic: 0.12,
|
||||
roughness: 0.32,
|
||||
});
|
||||
await material.ready;
|
||||
|
||||
const mesh = new Mesh(scene, {
|
||||
material,
|
||||
vertexData: {
|
||||
positions: [-0.72, -0.62, 0, 0.72, -0.62, 0, 0, 0.76, 0],
|
||||
normals: [0, 0, 1, 0, 0, 1, 0, 0, 1],
|
||||
indices: [0, 1, 2],
|
||||
},
|
||||
const groundMaterial = new PBRMaterial(scene, {
|
||||
baseColor: [0.24, 0.28, 0.34, 1],
|
||||
metallic: 0,
|
||||
roughness: 0.82,
|
||||
});
|
||||
await mesh.ready;
|
||||
await Promise.all([objectMaterial.ready, groundMaterial.ready]);
|
||||
|
||||
log("Scene ready — drag your pointer across the preview.");
|
||||
const move = (event: PointerEvent) => {
|
||||
const bounds = canvas.getBoundingClientRect();
|
||||
mesh.position.x = ((event.clientX - bounds.left) / bounds.width - 0.5) * 1.2;
|
||||
mesh.position.y = (0.5 - (event.clientY - bounds.top) / bounds.height) * 0.8;
|
||||
};
|
||||
canvas.addEventListener("pointermove", move);
|
||||
const icosphere = new Icosphere(scene, {
|
||||
material: objectMaterial,
|
||||
subdivisions: 2,
|
||||
position: [0, 0.2, 0],
|
||||
});
|
||||
const ground = new Plane(scene, {
|
||||
material: groundMaterial,
|
||||
size: 2.6,
|
||||
position: [0, -0.55, 0],
|
||||
});
|
||||
await Promise.all([icosphere.ready, ground.ready]);
|
||||
icosphere.rotateY(Math.PI / 5);
|
||||
|
||||
const camera = new ArcRotateCamera(scene, {
|
||||
target: icosphere,
|
||||
alpha: Math.PI / 4,
|
||||
beta: Math.PI / 3,
|
||||
radius: 4,
|
||||
aspect: canvas.width / canvas.height,
|
||||
controls: { element: canvas, pointer: true },
|
||||
});
|
||||
await camera.ready;
|
||||
|
||||
const lights = [
|
||||
new AmbientLight(scene, { color: [0.02, 0.45, 0.48], intensity: 0.5 }),
|
||||
new SpotLight(scene, {
|
||||
position: [-2.8, 3, -2.8],
|
||||
rotor: [-0.628, -0.628, 0, 0.4597],
|
||||
color: [1, 0.22, 0.03],
|
||||
intensity: 220,
|
||||
range: 7,
|
||||
innerAngle: 0.32,
|
||||
outerAngle: 0.72,
|
||||
}),
|
||||
new PointLight(scene, {
|
||||
position: [2.2, 0.9, 1.8],
|
||||
color: [1, 0.02, 0.45],
|
||||
intensity: 16,
|
||||
range: 6,
|
||||
}),
|
||||
];
|
||||
await Promise.all(lights.map((light) => light.ready));
|
||||
|
||||
log("Icosphere ready — teal ambience, orange rim, and magenta accent are active.");
|
||||
|
||||
export default {
|
||||
scene,
|
||||
mesh,
|
||||
icosphere,
|
||||
ground,
|
||||
lights,
|
||||
dispose() {
|
||||
canvas.removeEventListener("pointermove", move);
|
||||
camera.detachControls();
|
||||
scene.dispose();
|
||||
},
|
||||
};`;
|
||||
|
||||
const snippets = {
|
||||
camera: `// Orbit camera — drag to orbit and use the wheel to zoom.
|
||||
const camera = new ArcRotateCamera(scene, {
|
||||
target: mesh,
|
||||
const orbitCamera = new ArcRotateCamera(scene, {
|
||||
target: icosphere,
|
||||
alpha: 0,
|
||||
beta: Math.PI / 2,
|
||||
radius: 3,
|
||||
controls: { element: canvas, pointer: true },
|
||||
});
|
||||
await camera.ready;`,
|
||||
await orbitCamera.ready;`,
|
||||
light: `// A warm point light backed by shared rows.
|
||||
const light = new PointLight(scene, {
|
||||
position: [0, 0.7, 1],
|
||||
@@ -65,7 +103,7 @@ const light = new PointLight(scene, {
|
||||
await light.ready;`,
|
||||
instances: `// Clones share geometry until one clone changes its vertex data.
|
||||
for (let x = -2; x <= 2; x++) {
|
||||
const instance = mesh.clone({ position: [x * 0.35, 0, 0] });
|
||||
const instance = icosphere.clone({ position: [x * 0.7, 0.2, 0] });
|
||||
await instance.ready;
|
||||
}`,
|
||||
rows: `// Application-owned hot data in the same shared arena.
|
||||
@@ -123,9 +161,59 @@ const elements = Object.fromEntries(
|
||||
);
|
||||
|
||||
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
||||
const packages = Promise.all([import("/pkg/handles.js"), import("/pkg/core.js")]).then(
|
||||
([handles, core]) => ({ ...handles, ...core }),
|
||||
);
|
||||
const nativeSetTimeout = window.setTimeout.bind(window);
|
||||
const nativeSetInterval = window.setInterval.bind(window);
|
||||
const nativeClearTimeout = window.clearTimeout.bind(window);
|
||||
const nativeClearInterval = window.clearInterval.bind(window);
|
||||
const playgroundTimerIDs = new Set();
|
||||
|
||||
function trackedSetTimeout(callback, delay, ...arguments_) {
|
||||
let id;
|
||||
const wrapped =
|
||||
typeof callback === "function"
|
||||
? (...callbackArguments) => {
|
||||
playgroundTimerIDs.delete(id);
|
||||
callback.apply(window, callbackArguments);
|
||||
}
|
||||
: callback;
|
||||
id = nativeSetTimeout(wrapped, delay, ...arguments_);
|
||||
playgroundTimerIDs.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
function trackedSetInterval(callback, delay, ...arguments_) {
|
||||
const id = nativeSetInterval(callback, delay, ...arguments_);
|
||||
playgroundTimerIDs.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
function trackedClearTimer(id) {
|
||||
playgroundTimerIDs.delete(id);
|
||||
nativeClearTimeout(id);
|
||||
nativeClearInterval(id);
|
||||
}
|
||||
|
||||
function clearPlaygroundTimers() {
|
||||
for (const id of playgroundTimerIDs) {
|
||||
nativeClearTimeout(id);
|
||||
nativeClearInterval(id);
|
||||
}
|
||||
playgroundTimerIDs.clear();
|
||||
}
|
||||
|
||||
window.__yawnPlaygroundTimers = {
|
||||
ids: playgroundTimerIDs,
|
||||
setTimeout: trackedSetTimeout,
|
||||
setInterval: trackedSetInterval,
|
||||
clearTimeout: trackedClearTimer,
|
||||
clearInterval: trackedClearTimer,
|
||||
clear: clearPlaygroundTimers,
|
||||
};
|
||||
|
||||
const packages = Promise.all([
|
||||
import("@yawn/handles"),
|
||||
import("@yawn/core"),
|
||||
]).then(([handles, core]) => ({ ...handles, ...core }));
|
||||
let monaco;
|
||||
let editor;
|
||||
let model;
|
||||
@@ -143,8 +231,14 @@ let frameRequest;
|
||||
let sampledFrame = 0;
|
||||
let sampledAt = 0;
|
||||
|
||||
addEventListener("yawn-theme-change", (event) => {
|
||||
monaco?.editor.setTheme(event.detail.resolved === "dark" ? "yawn" : "vs");
|
||||
});
|
||||
|
||||
function savedLocation() {
|
||||
const match = location.pathname.match(/^\/playground\/([a-z0-9]{6,24})\/(\d+)\/?$/);
|
||||
const match = location.pathname.match(
|
||||
/^\/playground\/([a-z0-9]{6,24})\/(\d+)\/?$/,
|
||||
);
|
||||
return match ? { id: match[1], revision: Number(match[2]) } : undefined;
|
||||
}
|
||||
|
||||
@@ -152,8 +246,11 @@ async function loadInitialPlayground() {
|
||||
const saved = savedLocation();
|
||||
if (saved) {
|
||||
try {
|
||||
const response = await fetch(`/api/playgrounds/${saved.id}/${saved.revision}`);
|
||||
if (!response.ok) throw new Error("This saved playground could not be found.");
|
||||
const response = await fetch(
|
||||
`/api/playgrounds/${saved.id}/${saved.revision}`,
|
||||
);
|
||||
if (!response.ok)
|
||||
throw new Error("This saved playground could not be found.");
|
||||
const result = await response.json();
|
||||
currentID = result.id;
|
||||
currentRevision = result.revision;
|
||||
@@ -188,13 +285,19 @@ importScripts(${JSON.stringify(`${monacoBase}vs/base/worker/workerMain.js`)});
|
||||
);
|
||||
window.MonacoEnvironment = { getWorkerUrl: () => workerURL };
|
||||
window.require.config({ paths: { vs: `${monacoBase}vs` } });
|
||||
window.require(["vs/editor/editor.main"], () => resolve(window.monaco), reject);
|
||||
window.require(
|
||||
["vs/editor/editor.main"],
|
||||
() => resolve(window.monaco),
|
||||
reject,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function createEditor(source) {
|
||||
monaco = await loadMonaco();
|
||||
const types = await fetch("/assets/yawn.d.ts").then((response) => response.text());
|
||||
const types = await fetch("/assets/yawn.d.ts").then((response) =>
|
||||
response.text(),
|
||||
);
|
||||
const defaults = monaco.languages.typescript.typescriptDefaults;
|
||||
defaults.setEagerModelSync(true);
|
||||
defaults.setCompilerOptions({
|
||||
@@ -205,7 +308,10 @@ async function createEditor(source) {
|
||||
strict: true,
|
||||
target: monaco.languages.typescript.ScriptTarget.ESNext,
|
||||
});
|
||||
defaults.setDiagnosticsOptions({ noSemanticValidation: false, noSyntaxValidation: false });
|
||||
defaults.setDiagnosticsOptions({
|
||||
noSemanticValidation: false,
|
||||
noSyntaxValidation: false,
|
||||
});
|
||||
defaults.addExtraLib(types, "file:///types/yawn.d.ts");
|
||||
monaco.editor.defineTheme("yawn", {
|
||||
base: "vs-dark",
|
||||
@@ -232,12 +338,17 @@ async function createEditor(source) {
|
||||
},
|
||||
});
|
||||
elements.editor.textContent = "";
|
||||
model = monaco.editor.createModel(source, "typescript", monaco.Uri.parse("file:///playground/scene.ts"));
|
||||
model = monaco.editor.createModel(
|
||||
source,
|
||||
"typescript",
|
||||
monaco.Uri.parse("file:///playground/scene.ts"),
|
||||
);
|
||||
editor = monaco.editor.create(elements.editor, {
|
||||
model,
|
||||
theme: "yawn",
|
||||
theme: document.documentElement.dataset.theme === "dark" ? "yawn" : "vs",
|
||||
automaticLayout: true,
|
||||
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
||||
fontFamily:
|
||||
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
||||
fontLigatures: true,
|
||||
fontSize: 12,
|
||||
lineHeight: 20,
|
||||
@@ -256,11 +367,13 @@ async function createEditor(source) {
|
||||
run,
|
||||
});
|
||||
editor.onDidChangeCursorPosition(({ position }) => {
|
||||
elements["cursor-status"].textContent = `Ln ${position.lineNumber}, Col ${position.column}`;
|
||||
elements["cursor-status"].textContent =
|
||||
`Ln ${position.lineNumber}, Col ${position.column}`;
|
||||
});
|
||||
model.onDidChangeContent(() => {
|
||||
markDirty(true);
|
||||
if (!currentID) localStorage.setItem("yawn:playground:draft", model.getValue());
|
||||
if (!currentID)
|
||||
localStorage.setItem("yawn:playground:draft", model.getValue());
|
||||
});
|
||||
monaco.editor.onDidChangeMarkers(([resource]) => {
|
||||
if (resource.toString() === model.uri.toString()) updateDiagnostics();
|
||||
@@ -272,8 +385,12 @@ async function createEditor(source) {
|
||||
function updateDiagnostics() {
|
||||
if (!monaco || !model) return;
|
||||
const markers = monaco.editor.getModelMarkers({ resource: model.uri });
|
||||
const errors = markers.filter((marker) => marker.severity === monaco.MarkerSeverity.Error).length;
|
||||
const warnings = markers.filter((marker) => marker.severity === monaco.MarkerSeverity.Warning).length;
|
||||
const errors = markers.filter(
|
||||
(marker) => marker.severity === monaco.MarkerSeverity.Error,
|
||||
).length;
|
||||
const warnings = markers.filter(
|
||||
(marker) => marker.severity === monaco.MarkerSeverity.Warning,
|
||||
).length;
|
||||
const total = errors + warnings;
|
||||
elements["editor-diagnostics"].classList.toggle("has-errors", errors > 0);
|
||||
elements["editor-diagnostics"].lastChild.textContent = total
|
||||
@@ -295,7 +412,8 @@ async function transpile() {
|
||||
const client = await worker(model.uri);
|
||||
const emitted = await client.getEmitOutput(model.uri.toString());
|
||||
const output = emitted.outputFiles.find((file) => file.name.endsWith(".js"));
|
||||
if (emitted.emitSkipped || !output) throw new Error("TypeScript could not emit this scene.");
|
||||
if (emitted.emitSkipped || !output)
|
||||
throw new Error("TypeScript could not emit this scene.");
|
||||
return output.text
|
||||
.replace(/^\s*import\s+(?:[\s\S]*?\s+from\s+)?["'][^"']+["'];?\s*$/gm, "")
|
||||
.replace(/^\s*export\s*\{\s*\};?\s*$/gm, "")
|
||||
@@ -329,33 +447,56 @@ function replaceCanvas() {
|
||||
canvas.setAttribute("aria-label", "Yawn WebGPU preview");
|
||||
const scale = Math.min(devicePixelRatio || 1, 2);
|
||||
canvas.width = Math.max(1, Math.round(elements.preview.clientWidth * scale));
|
||||
canvas.height = Math.max(1, Math.round(elements.preview.clientHeight * scale));
|
||||
canvas.height = Math.max(
|
||||
1,
|
||||
Math.round(elements.preview.clientHeight * scale),
|
||||
);
|
||||
previous.replaceWith(canvas);
|
||||
elements.canvas = canvas;
|
||||
elements["resolution-status"].textContent = `${canvas.width} × ${canvas.height}`;
|
||||
elements["resolution-status"].textContent =
|
||||
`${canvas.width} × ${canvas.height}`;
|
||||
return canvas;
|
||||
}
|
||||
|
||||
async function run() {
|
||||
if (!editor || elements["run-button"].disabled) return;
|
||||
clearPlaygroundTimers();
|
||||
const runID = ++generation;
|
||||
elements["run-button"].disabled = true;
|
||||
elements["run-button"].lastChild.textContent = " Running";
|
||||
clearConsole();
|
||||
setRunStatus("Compiling", "running");
|
||||
showPreviewMessage("Compiling TypeScript", "Checking types and preparing the render graph.");
|
||||
showPreviewMessage(
|
||||
"Compiling TypeScript",
|
||||
"Checking types and preparing the render graph.",
|
||||
);
|
||||
try {
|
||||
if (!crossOriginIsolated) throw new Error("Cross-origin isolation is disabled on this page.");
|
||||
if (!navigator.gpu) throw new Error("WebGPU is not available in this browser.");
|
||||
if (!crossOriginIsolated)
|
||||
throw new Error("Cross-origin isolation is disabled on this page.");
|
||||
if (!navigator.gpu)
|
||||
throw new Error("WebGPU is not available in this browser.");
|
||||
const [code, api] = await Promise.all([transpile(), packages]);
|
||||
await disposeCurrent();
|
||||
const canvas = replaceCanvas();
|
||||
const names = Object.keys(api);
|
||||
const started = performance.now();
|
||||
const result = await new AsyncFunction(...names, "canvas", "log", code)(
|
||||
const result = await new AsyncFunction(
|
||||
...names,
|
||||
"canvas",
|
||||
"log",
|
||||
"setTimeout",
|
||||
"setInterval",
|
||||
"clearTimeout",
|
||||
"clearInterval",
|
||||
code,
|
||||
)(
|
||||
...names.map((name) => api[name]),
|
||||
canvas,
|
||||
(...values) => appendConsole(values.map(formatValue).join(" ")),
|
||||
trackedSetTimeout,
|
||||
trackedSetInterval,
|
||||
trackedClearTimer,
|
||||
trackedClearTimer,
|
||||
);
|
||||
if (runID !== generation) {
|
||||
if (result?.dispose) await result.dispose();
|
||||
@@ -364,7 +505,10 @@ async function run() {
|
||||
current = result;
|
||||
window.__yawnPlayground = result;
|
||||
elements["preview-message"].hidden = true;
|
||||
setRunStatus(`Running · ${Math.round(performance.now() - started)} ms`, "ready");
|
||||
setRunStatus(
|
||||
`Running · ${Math.round(performance.now() - started)} ms`,
|
||||
"ready",
|
||||
);
|
||||
if (activePanel === "profile") await attachProfiler();
|
||||
} catch (error) {
|
||||
if (runID === generation) {
|
||||
@@ -377,7 +521,8 @@ async function run() {
|
||||
} finally {
|
||||
if (runID === generation) {
|
||||
elements["run-button"].disabled = false;
|
||||
elements["run-button"].innerHTML = '<span aria-hidden="true">▶</span> Run <kbd>Ctrl ↵</kbd>';
|
||||
elements["run-button"].innerHTML =
|
||||
'<span aria-hidden="true">▶</span> Run <kbd>Ctrl ↵</kbd>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -396,7 +541,10 @@ async function attachProfiler() {
|
||||
}
|
||||
stopProfile = profileCore.onProfile(renderProfile);
|
||||
const supported = await profileCore.setProfiler(true);
|
||||
if (!supported) setProfileMessage("Timestamp queries are not supported by this GPU adapter.");
|
||||
if (!supported)
|
||||
setProfileMessage(
|
||||
"Timestamp queries are not supported by this GPU adapter.",
|
||||
);
|
||||
}
|
||||
|
||||
function setProfileMessage(message) {
|
||||
@@ -408,8 +556,10 @@ function setProfileMessage(message) {
|
||||
function renderProfile(profile) {
|
||||
elements["profile-empty"].hidden = true;
|
||||
elements["profile-results"].hidden = false;
|
||||
elements["profile-total"].textContent = `${profile.milliseconds.toFixed(2)} ms`;
|
||||
elements["profile-latency"].textContent = `${profile.readbackMilliseconds.toFixed(2)} ms`;
|
||||
elements["profile-total"].textContent =
|
||||
`${profile.milliseconds.toFixed(2)} ms`;
|
||||
elements["profile-latency"].textContent =
|
||||
`${profile.readbackMilliseconds.toFixed(2)} ms`;
|
||||
elements["profile-adapter"].textContent = profile.adapter || "WebGPU adapter";
|
||||
elements["profile-adapter"].title = profile.adapter || "WebGPU adapter";
|
||||
elements["profile-passes"].replaceChildren(
|
||||
@@ -441,7 +591,8 @@ function appendConsole(message, error = false) {
|
||||
line.textContent = message;
|
||||
line.classList.toggle("error", error);
|
||||
elements["console-lines"].append(line);
|
||||
elements["console-count"].textContent = elements["console-lines"].children.length;
|
||||
elements["console-count"].textContent =
|
||||
elements["console-lines"].children.length;
|
||||
line.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
|
||||
@@ -452,7 +603,8 @@ function clearConsole() {
|
||||
}
|
||||
|
||||
function setRunStatus(message, state) {
|
||||
elements["run-status"].className = state === "running" ? "running" : state === "failed" ? "failed" : "";
|
||||
elements["run-status"].className =
|
||||
state === "running" ? "running" : state === "failed" ? "failed" : "";
|
||||
elements["run-status"].lastChild.textContent = ` ${message}`;
|
||||
}
|
||||
|
||||
@@ -466,7 +618,9 @@ function openInspector(panel) {
|
||||
activePanel = panel;
|
||||
elements.inspector.classList.remove("collapsed");
|
||||
elements["inspector-button"].setAttribute("aria-pressed", "true");
|
||||
for (const button of document.querySelectorAll(".inspector-tabs [data-panel]")) {
|
||||
for (const button of document.querySelectorAll(
|
||||
".inspector-tabs [data-panel]",
|
||||
)) {
|
||||
const active = button.dataset.panel === panel;
|
||||
button.classList.toggle("active", active);
|
||||
button.setAttribute("aria-selected", String(active));
|
||||
@@ -497,7 +651,8 @@ async function save() {
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.error || "The playground could not be saved.");
|
||||
if (!response.ok)
|
||||
throw new Error(result.error || "The playground could not be saved.");
|
||||
currentID = result.id;
|
||||
currentRevision = result.revision;
|
||||
initialSource = result.code;
|
||||
@@ -518,13 +673,22 @@ function showToast(message) {
|
||||
clearTimeout(toastTimer);
|
||||
elements.toast.textContent = message;
|
||||
elements.toast.classList.add("visible");
|
||||
toastTimer = setTimeout(() => elements.toast.classList.remove("visible"), 2400);
|
||||
toastTimer = setTimeout(
|
||||
() => elements.toast.classList.remove("visible"),
|
||||
2400,
|
||||
);
|
||||
}
|
||||
|
||||
function insertSnippet(name) {
|
||||
const selection = editor.getSelection();
|
||||
const prefix = model.getValueInRange(selection).endsWith("\n") ? "" : "\n\n";
|
||||
editor.executeEdits("snippet", [{ range: selection, text: `${prefix}${snippets[name]}\n`, forceMoveMarkers: true }]);
|
||||
editor.executeEdits("snippet", [
|
||||
{
|
||||
range: selection,
|
||||
text: `${prefix}${snippets[name]}\n`,
|
||||
forceMoveMarkers: true,
|
||||
},
|
||||
]);
|
||||
elements["snippets-dialog"].close();
|
||||
editor.focus();
|
||||
}
|
||||
@@ -539,7 +703,8 @@ function sampleFPS(now = performance.now()) {
|
||||
sampledFrame = frame;
|
||||
} else if (now - sampledAt >= 500) {
|
||||
const fps = ((frame - sampledFrame) * 1000) / (now - sampledAt);
|
||||
elements["fps-status"].textContent = `${fps < 10 ? fps.toFixed(1) : Math.round(fps)} FPS`;
|
||||
elements["fps-status"].textContent =
|
||||
`${fps < 10 ? fps.toFixed(1) : Math.round(fps)} FPS`;
|
||||
sampledAt = now;
|
||||
sampledFrame = frame;
|
||||
}
|
||||
@@ -562,22 +727,34 @@ elements["reset-button"].addEventListener("click", () => {
|
||||
model?.setValue(initialSource);
|
||||
void run();
|
||||
});
|
||||
elements["format-button"].addEventListener("click", () => editor?.getAction("editor.action.formatDocument").run());
|
||||
elements["snippets-button"].addEventListener("click", () => elements["snippets-dialog"].showModal());
|
||||
elements["snippets-dialog"].querySelector("[data-close-dialog]").addEventListener("click", () => elements["snippets-dialog"].close());
|
||||
elements["format-button"].addEventListener("click", () =>
|
||||
editor?.getAction("editor.action.formatDocument").run(),
|
||||
);
|
||||
elements["snippets-button"].addEventListener("click", () =>
|
||||
elements["snippets-dialog"].showModal(),
|
||||
);
|
||||
elements["snippets-dialog"]
|
||||
.querySelector("[data-close-dialog]")
|
||||
.addEventListener("click", () => elements["snippets-dialog"].close());
|
||||
elements["snippets-dialog"].addEventListener("click", (event) => {
|
||||
if (event.target === elements["snippets-dialog"]) elements["snippets-dialog"].close();
|
||||
if (event.target === elements["snippets-dialog"])
|
||||
elements["snippets-dialog"].close();
|
||||
});
|
||||
for (const button of document.querySelectorAll("[data-snippet]")) {
|
||||
button.addEventListener("click", () => insertSnippet(button.dataset.snippet));
|
||||
}
|
||||
elements["snippets-dialog"].querySelector("input").addEventListener("input", (event) => {
|
||||
const query = event.target.value.trim().toLowerCase();
|
||||
for (const button of document.querySelectorAll("[data-snippet]")) {
|
||||
button.hidden = Boolean(query) && !button.textContent.toLowerCase().includes(query);
|
||||
}
|
||||
});
|
||||
for (const button of document.querySelectorAll(".inspector-tabs [data-panel]")) {
|
||||
elements["snippets-dialog"]
|
||||
.querySelector("input")
|
||||
.addEventListener("input", (event) => {
|
||||
const query = event.target.value.trim().toLowerCase();
|
||||
for (const button of document.querySelectorAll("[data-snippet]")) {
|
||||
button.hidden =
|
||||
Boolean(query) && !button.textContent.toLowerCase().includes(query);
|
||||
}
|
||||
});
|
||||
for (const button of document.querySelectorAll(
|
||||
".inspector-tabs [data-panel]",
|
||||
)) {
|
||||
button.addEventListener("click", () => openInspector(button.dataset.panel));
|
||||
}
|
||||
elements["clear-console"].addEventListener("click", clearConsole);
|
||||
@@ -598,7 +775,10 @@ elements["resize-handle"].addEventListener("pointerdown", (event) => {
|
||||
elements["resize-handle"].addEventListener("pointermove", (event) => {
|
||||
if (!elements["resize-handle"].hasPointerCapture(event.pointerId)) return;
|
||||
const bounds = elements.workbench.getBoundingClientRect();
|
||||
const percentage = Math.min(70, Math.max(30, ((event.clientX - bounds.left) / bounds.width) * 100));
|
||||
const percentage = Math.min(
|
||||
70,
|
||||
Math.max(30, ((event.clientX - bounds.left) / bounds.width) * 100),
|
||||
);
|
||||
elements.workbench.style.setProperty("--editor-width", `${percentage}%`);
|
||||
});
|
||||
elements["resize-handle"].addEventListener("pointerup", (event) => {
|
||||
|
||||
Reference in New Issue
Block a user