diff --git a/static/global.css b/static/global.css index 8395f73..f90ce4c 100644 --- a/static/global.css +++ b/static/global.css @@ -2,8 +2,6 @@ box-sizing: border-box; margin: 0; padding: 0; - font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } ::selection { @@ -18,6 +16,8 @@ body { grid-template-areas: "nav" "aside" "main" "foot"; gap: 0px 0px; font-size: 1.2em; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } a:visited { @@ -49,7 +49,6 @@ a:hover { .main { grid-area: main; padding: 0 0.7em; - min-height: 95vh; } .aside { diff --git a/static/playground.css b/static/playground.css index 88d2a2d..0285246 100644 --- a/static/playground.css +++ b/static/playground.css @@ -51,12 +51,13 @@ body { } #editor { - width: 100%; - color: black; - background-color: #fff; - padding: 0.5em; + max-width: 100%; + height: 400px; + min-height: 200px; + max-height: 100vh; resize: vertical; - margin-bottom: -5px; + overflow: auto hidden; + color: black; border-radius: 0 0 0.5em 0.5em; } diff --git a/static/script.js b/static/script.js index 4186b7b..ea9cfe8 100644 --- a/static/script.js +++ b/static/script.js @@ -1,10 +1,5 @@ import * as esbuild from "https://esm.sh/esbuild-wasm@0.20.1"; -import { - EditorView, - keymap, - lineNumbers, -} from "https://esm.sh/@codemirror/view@6.0.1"; -import { defaultKeymap } from "https://esm.sh/@codemirror/commands@6.0.1"; +import * as monaco from "https://cdn.jsdelivr.net/npm/monaco-editor@0.47.0/+esm"; document.addEventListener("DOMContentLoaded", () => { const initialData = JSON.parse(elements.initialJSONData.innerHTML); @@ -14,40 +9,72 @@ document.addEventListener("DOMContentLoaded", () => { }); }); -globalThis.onbeforeunload = () => { - return ""; -}; +function makeTSConfig(version) { + return { + jsx: "react-jsx", + jsxFactory: "h", + jsxFragmentFactory: "Fragment", + jsxImportSource: `https://esm.sh/jsr/@fartlabs/jsonx@${version}`, + }; +} async function transform(options) { const transformation = await esbuild.transform(options.code, { loader: "tsx", tsconfigRaw: { - compilerOptions: { - jsx: "react-jsx", - jsxFactory: "h", - jsxFragmentFactory: "Fragment", - jsxImportSource: - `https://esm.sh/jsr/@fartlabs/jsonx@${options.version}`, - }, + compilerOptions: makeTSConfig(options.version), }, }); return transformation; } -let cmEditor; +let monacoEditor; function createEditor(options) { - // https://codemirror.net/examples/styling/ - // https://github.com/codemirror/dev/blob/ccb92f9b09ceec46caceae4fb908a83642271b4d/demo/demo.ts - cmEditor = new EditorView({ - doc: options.code, - parent: options.target, - extensions: [ - keymap.of(defaultKeymap), - lineNumbers(), - EditorView.lineWrapping, - ], + // TODO: Figure out how to resolve this error. + // + // codicon.ttf:1 + // Failed to load resource: the server responded with a status of 500 (Internal Server Error) + + monacoEditor = monaco.editor.create( + options.target, + { + theme: "vs-dark", + fontSize: 18, + fontFamily: "Fira Code", + model: monaco.editor.createModel( + options.code, + "typescript", + monaco.Uri.parse("inmemory://model/main.tsx"), + ), + }, + ); + + function handleResize() { + // Make the editor as small as possible. + monacoEditor.layout({ width: 0, height: 0 }); + + // Wait for last layout shift to complete. + requestAnimationFrame(() => { + const rect = elements.editor.parentElement.getBoundingClientRect(); + monacoEditor.layout({ width: rect.width, height: rect.height }); + }); + } + + // Set up event listeners. + globalThis.addEventListener("resize", handleResize); + const resizeObserver = new ResizeObserver(handleResize); + resizeObserver.observe(elements.editor); + let isChanged = false; + monacoEditor.getModel().onDidChangeContent(() => { + if (!isChanged) { + globalThis.onbeforeunload = () => { + return ""; + }; + + isChanged = true; + } }); } @@ -61,8 +88,8 @@ function sharePlayground() { } const data = { - code: cmEditor.state.doc.toString(), - version: elements.version.value, + code: getEditorCode(), + version: getVersion(), }; elements.share.disabled = true; @@ -134,15 +161,15 @@ async function setup(options) { async function handlePlay() { try { - const code = cmEditor?.state?.doc?.toString(); + const code = getEditorCode(); if (!code) { logBuildOutput("error", "No code to build."); return; } const transformation = await transform({ - code: cmEditor.state.doc.toString(), - version: elements.version.value, + code, + version: getVersion(), }); transformation.warnings.forEach((warning) => { logBuildOutput("warning", warning.text); @@ -201,6 +228,14 @@ function appendConsoleOutput(type, message) { } } +function getEditorCode() { + return monacoEditor.getModel().getValue(); +} + +function getVersion() { + return elements.version.value; +} + /** * elements of the playground. */ @@ -277,15 +312,6 @@ export const elements = { return consoleDetails; }, - get buildDetails() { - const buildDetails = document.getElementById("buildDetails"); - if (!buildDetails) { - throw new Error("Build details element not found."); - } - - return buildDetails; - }, - get initialJSONData() { const initialJSONData = document.getElementById("initialJSONData"); if (!initialJSONData) {