switch to preact
This commit is contained in:
40
playground/package-lock.json
generated
40
playground/package-lock.json
generated
@@ -8,7 +8,9 @@
|
|||||||
"name": "playground",
|
"name": "playground",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"monaco-editor": "^0.52.0"
|
"@preact/signals": "^1.3.0",
|
||||||
|
"monaco-editor": "^0.52.0",
|
||||||
|
"preact": "^10.24.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
@@ -406,6 +408,32 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@preact/signals": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-EOMeg42SlLS72dhoq6Vjq08havnLseWmPQ8A0YsgIAqMgWgx7V1a39+Pxo6i7SY5NwJtH4849JogFq3M67AzWg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@preact/signals-core": "^1.7.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"preact": "10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@preact/signals-core": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.24.3",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.3.tgz",
|
||||||
@@ -780,6 +808,16 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/preact": {
|
||||||
|
"version": "10.24.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
|
||||||
|
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.24.3",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"vite": "^5.4.10"
|
"vite": "^5.4.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"monaco-editor": "^0.52.0"
|
"@preact/signals": "^1.3.0",
|
||||||
|
"monaco-editor": "^0.52.0",
|
||||||
|
"preact": "^10.24.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +1,174 @@
|
|||||||
|
import { effect, signal } from "@preact/signals";
|
||||||
import { newtConfig, newtTokens } from "./monarch.ts";
|
import { newtConfig, newtTokens } from "./monarch.ts";
|
||||||
import * as monaco from "monaco-editor";
|
import * as monaco from "monaco-editor";
|
||||||
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
|
import { h, render } from "preact";
|
||||||
|
|
||||||
monaco.languages.register({ id: "newt" });
|
monaco.languages.register({ id: "newt" });
|
||||||
monaco.languages.setMonarchTokensProvider("newt", newtTokens);
|
monaco.languages.setMonarchTokensProvider("newt", newtTokens);
|
||||||
monaco.languages.setLanguageConfiguration("newt", newtConfig);
|
monaco.languages.setLanguageConfiguration("newt", newtConfig);
|
||||||
|
|
||||||
const newtWorker = new Worker("worker.js")//new URL("worker.js", import.meta.url))
|
const newtWorker = new Worker("worker.js"); //new URL("worker.js", import.meta.url))
|
||||||
|
|
||||||
|
function run(src: string) {
|
||||||
|
newtWorker.postMessage({ src });
|
||||||
|
}
|
||||||
|
|
||||||
|
newtWorker.onmessage = (ev) => {
|
||||||
|
console.log("worker sent", ev.data);
|
||||||
|
state.output.value = ev.data.output;
|
||||||
|
state.javascript.value = ev.data.javascript;
|
||||||
|
};
|
||||||
|
|
||||||
self.MonacoEnvironment = {
|
self.MonacoEnvironment = {
|
||||||
getWorkerUrl(moduleId, label) {
|
getWorkerUrl(moduleId, _label) {
|
||||||
console.log("Get worker", moduleId);
|
console.log("Get worker", moduleId);
|
||||||
return moduleId;
|
return moduleId;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
output: signal(""),
|
||||||
|
javascript: signal(""),
|
||||||
|
editor: signal<monaco.editor.IStandaloneCodeEditor | null>(null),
|
||||||
|
};
|
||||||
|
|
||||||
// I keep pressing this.
|
// I keep pressing this.
|
||||||
document.addEventListener('keydown', (ev) => {
|
document.addEventListener("keydown", (ev) => {
|
||||||
if (ev.metaKey && ev.code == 'KeyS') ev.preventDefault();
|
if (ev.metaKey && ev.code == "KeyS") ev.preventDefault();
|
||||||
})
|
});
|
||||||
|
|
||||||
let value = localStorage.code || "module Main\n";
|
let value = localStorage.code || "module Main\n";
|
||||||
|
|
||||||
let container = document.getElementById("editor")!;
|
// let result = document.getElementById("result")!;
|
||||||
let result = document.getElementById("result")!;
|
|
||||||
const editor = monaco.editor.create(container, {
|
// the editor might handle this itself with the right prodding.
|
||||||
value,
|
effect(() => {
|
||||||
language: "newt",
|
let text = state.output.value;
|
||||||
theme: "vs",
|
let editor = state.editor.value;
|
||||||
automaticLayout: true,
|
if (editor) processOutput(editor, text);
|
||||||
minimap: { enabled: false },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interface EditorProps {
|
||||||
|
initialValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Editor({ initialValue }: EditorProps) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const container = ref.current!;
|
||||||
|
const editor = monaco.editor.create(container, {
|
||||||
|
value,
|
||||||
|
language: "newt",
|
||||||
|
theme: "vs",
|
||||||
|
automaticLayout: true,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
});
|
||||||
|
state.editor.value = editor;
|
||||||
|
|
||||||
|
editor.onDidChangeModelContent((ev) => {
|
||||||
|
console.log("mc", ev);
|
||||||
|
let value = editor.getValue();
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => run(value), 1000);
|
||||||
|
localStorage.code = value;
|
||||||
|
});
|
||||||
|
run(initialValue);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return h("div", { id: "editor", ref });
|
||||||
|
}
|
||||||
|
|
||||||
|
// for extra credit, we could have a read-only monaco
|
||||||
|
function JavaScript() {
|
||||||
|
const text = state.javascript.value;
|
||||||
|
return h("div", {id:'javascript'}, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Result() {
|
||||||
|
const text = state.output.value;
|
||||||
|
return h("div", { id: "result" }, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RESULTS = "Output"
|
||||||
|
const JAVASCRIPT = "JS"
|
||||||
|
|
||||||
|
function Tabs() {
|
||||||
|
const [selected, setSelected] = useState(RESULTS);
|
||||||
|
|
||||||
|
const Tab = (label: string) => {
|
||||||
|
let onClick = () => setSelected(label);
|
||||||
|
let className = "tab";
|
||||||
|
if (label == selected) className += " selected";
|
||||||
|
return h("div", { className, onClick }, label);
|
||||||
|
};
|
||||||
|
|
||||||
|
let body;
|
||||||
|
switch (selected) {
|
||||||
|
case RESULTS:
|
||||||
|
body = h(Result, {});
|
||||||
|
break;
|
||||||
|
case JAVASCRIPT:
|
||||||
|
body = h(JavaScript, {});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
body = h("div", {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ className: "tabPanel" },
|
||||||
|
h("div", { className: "tabBar" }, Tab(RESULTS), Tab(JAVASCRIPT)),
|
||||||
|
h("div", { className: "tabBody" }, body)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ className: "wrapper" },
|
||||||
|
h(Editor, { initialValue: value }),
|
||||||
|
h(Tabs, {})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(h(App, {}), document.getElementById("app")!);
|
||||||
|
|
||||||
let timeout: number | undefined;
|
let timeout: number | undefined;
|
||||||
|
|
||||||
function run(src: string) {
|
|
||||||
newtWorker.postMessage({src})
|
|
||||||
}
|
|
||||||
|
|
||||||
newtWorker.onmessage = (ev) => {
|
|
||||||
console.log('worker sent', ev.data)
|
|
||||||
let {output, javascript} = ev.data
|
|
||||||
processOutput(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adapted from the vscode extension, but types are slightly different
|
// Adapted from the vscode extension, but types are slightly different
|
||||||
// and positions are 1-based.
|
// and positions are 1-based.
|
||||||
const processOutput = (output: string) => {
|
const processOutput = (
|
||||||
result.innerText = output
|
editor: monaco.editor.IStandaloneCodeEditor,
|
||||||
|
output: string
|
||||||
let model = editor.getModel()!
|
) => {
|
||||||
let markers: monaco.editor.IMarkerData[] = []
|
let model = editor.getModel()!;
|
||||||
let lines = output.split('\n')
|
let markers: monaco.editor.IMarkerData[] = [];
|
||||||
|
let lines = output.split("\n");
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
const match = line.match(/(INFO|ERROR) at \((\d+), (\d+)\):\s*(.*)/);
|
const match = line.match(/(INFO|ERROR) at \((\d+), (\d+)\):\s*(.*)/);
|
||||||
if (match) {
|
if (match) {
|
||||||
let [_full, kind, line, col, message] = match;
|
let [_full, kind, line, col, message] = match;
|
||||||
let lineNumber = +line+1
|
let lineNumber = +line + 1;
|
||||||
let column = +col+1;
|
let column = +col + 1;
|
||||||
let start = {column, lineNumber};
|
let start = { column, lineNumber };
|
||||||
// we don't have the full range, so grab the surrounding word
|
// we don't have the full range, so grab the surrounding word
|
||||||
let endColumn = column + 1
|
let endColumn = column + 1;
|
||||||
let word = model.getWordAtPosition(start)
|
let word = model.getWordAtPosition(start);
|
||||||
if (word) endColumn = word.endColumn
|
if (word) endColumn = word.endColumn;
|
||||||
|
|
||||||
// heuristics to grab the entire message:
|
// heuristics to grab the entire message:
|
||||||
// anything indented
|
// anything indented
|
||||||
// Context:, or Goal: are part of PRINTME
|
// Context:, or Goal: are part of PRINTME
|
||||||
// unexpected / expecting appear in parse errors
|
// unexpected / expecting appear in parse errors
|
||||||
while (
|
while (lines[i + 1]?.match(/^( )/)) {
|
||||||
lines[i + 1]?.match(/^( )/)
|
|
||||||
) {
|
|
||||||
message += "\n" + lines[++i];
|
message += "\n" + lines[++i];
|
||||||
}
|
}
|
||||||
const severity = kind === 'ERROR' ? monaco.MarkerSeverity.Error : monaco.MarkerSeverity.Info
|
const severity =
|
||||||
|
kind === "ERROR"
|
||||||
|
? monaco.MarkerSeverity.Error
|
||||||
|
: monaco.MarkerSeverity.Info;
|
||||||
|
|
||||||
markers.push({
|
markers.push({
|
||||||
severity,
|
severity,
|
||||||
@@ -82,19 +177,8 @@ const processOutput = (output: string) => {
|
|||||||
endLineNumber: lineNumber,
|
endLineNumber: lineNumber,
|
||||||
startColumn: column,
|
startColumn: column,
|
||||||
endColumn,
|
endColumn,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('setMarkers', markers)
|
monaco.editor.setModelMarkers(model, "newt", markers);
|
||||||
monaco.editor.setModelMarkers(model, 'newt', markers)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
editor.onDidChangeModelContent((ev) => {
|
|
||||||
console.log("mc", ev);
|
|
||||||
let value = editor.getValue();
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(() => run(value), 1000);
|
|
||||||
localStorage.code = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
run(value);
|
|
||||||
|
|||||||
@@ -12,10 +12,32 @@
|
|||||||
}
|
}
|
||||||
.wrapper > div {
|
.wrapper > div {
|
||||||
flex: 1 1;
|
flex: 1 1;
|
||||||
|
width: 100px;
|
||||||
}
|
}
|
||||||
#result {
|
.tabPanel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.tabBar {
|
||||||
|
display:flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-bottom: solid 1px black;
|
||||||
|
}
|
||||||
|
.tab {
|
||||||
|
padding: 4px;
|
||||||
|
border: solid 1px transparent;
|
||||||
|
}
|
||||||
|
.tab.selected {
|
||||||
|
border: solid 1px black;
|
||||||
|
border-bottom: 0px
|
||||||
|
}
|
||||||
|
|
||||||
|
#result, #javascript {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-family: 'Comic Code', monospace;
|
font-family: 'Comic Code', monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user