-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #416 from moonbitlang/zhiyuan/route
feat: client side routing
- Loading branch information
Showing
11 changed files
with
278 additions
and
161 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import * as moonbitMode from "@moonbit/moonpad-monaco"; | ||
import * as monaco from "monaco-editor-core"; | ||
import * as util from "./util"; | ||
|
||
const moon = moonbitMode.init({ | ||
onigWasmUrl: new URL("./onig.wasm", import.meta.url).toString(), | ||
lspWorker: new Worker("/lsp-server.js"), | ||
mooncWorkerFactory: () => new Worker("/moonc-worker.js"), | ||
codeLensFilter(l) { | ||
return l.command?.command === "moonbit-lsp/debug-main"; | ||
}, | ||
}); | ||
|
||
// @ts-ignore | ||
self.MonacoEnvironment = { | ||
getWorkerUrl: function () { | ||
return "/editor.worker.js"; | ||
}, | ||
}; | ||
|
||
const codePre = document.querySelector<HTMLPreElement>(".shiki")!; | ||
export const model = monaco.editor.createModel( | ||
codePre.textContent ?? "", | ||
"moonbit", | ||
monaco.Uri.file("/main.mbt"), | ||
); | ||
|
||
const output = document.querySelector<HTMLPreElement>("#output")!; | ||
const trace = moonbitMode.traceCommandFactory(); | ||
|
||
async function run(debug: boolean) { | ||
if (debug) { | ||
const result = await moon.compile({ | ||
libInputs: [["main.mbt", model.getValue()]], | ||
debugMain: true, | ||
}); | ||
switch (result.kind) { | ||
case "success": { | ||
const js = result.js; | ||
const stream = await moon.run(js); | ||
let buffer = ""; | ||
await stream.pipeTo( | ||
new WritableStream({ | ||
write(chunk) { | ||
buffer += `${chunk}\n`; | ||
}, | ||
}), | ||
); | ||
output.textContent = buffer; | ||
return; | ||
} | ||
case "error": { | ||
console.error(result.diagnostics); | ||
} | ||
} | ||
return; | ||
} | ||
const stdout = await trace(monaco.Uri.file("/main.mbt").toString()); | ||
if (stdout === undefined) return; | ||
output.textContent = stdout; | ||
} | ||
|
||
model.onDidChangeContent(util.debounce(() => run(false), 100)); | ||
|
||
monaco.editor.onDidCreateEditor(() => { | ||
codePre.remove(); | ||
}); | ||
|
||
const editorContainer = document.getElementById("editor")!; | ||
editorContainer.classList.remove("pl-[10px]", "text-[14px]"); | ||
monaco.editor.create(editorContainer, { | ||
model, | ||
lineNumbers: "off", | ||
glyphMargin: false, | ||
minimap: { | ||
enabled: false, | ||
}, | ||
automaticLayout: true, | ||
folding: false, | ||
fontSize: 14, | ||
scrollBeyondLastLine: false, | ||
scrollbar: { | ||
alwaysConsumeMouseWheel: false, | ||
}, | ||
fontFamily: "monospace", | ||
theme: util.getTheme() === "light" ? "light-plus" : "dark-plus", | ||
}); | ||
|
||
monaco.editor.registerCommand("moonbit-lsp/debug-main", () => { | ||
run(true); | ||
}); | ||
|
||
run(false); | ||
|
||
window.addEventListener("theme-change", (e) => { | ||
monaco.editor.setTheme(e.detail === "light" ? "light-plus" : "dark-plus"); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,148 +1,22 @@ | ||
import * as moonbitMode from "@moonbit/moonpad-monaco"; | ||
import * as monaco from "monaco-editor-core"; | ||
import * as editor from "./editor"; | ||
import * as router from "./router"; | ||
import "./style.css"; | ||
import "./toc"; | ||
|
||
const moon = moonbitMode.init({ | ||
onigWasmUrl: new URL("./onig.wasm", import.meta.url).toString(), | ||
lspWorker: new Worker("/lsp-server.js"), | ||
mooncWorkerFactory: () => new Worker("/moonc-worker.js"), | ||
codeLensFilter(l) { | ||
return l.command?.command === "moonbit-lsp/debug-main"; | ||
}, | ||
import * as theme from "./theme"; | ||
import * as toc from "./toc"; | ||
|
||
router.init(); | ||
theme.init(); | ||
toc.init(); | ||
|
||
const markdown = document.querySelector<HTMLDivElement>("#tour-content")!; | ||
const next = document.querySelector<HTMLDivElement>("#nav-next")!; | ||
const back = document.querySelector<HTMLDivElement>("#nav-back")!; | ||
|
||
window.addEventListener("route-change", async (e) => { | ||
const state = e.detail; | ||
editor.model.setValue(state.code); | ||
markdown.innerHTML = state.markdownHtml; | ||
next.innerHTML = state.next; | ||
back.innerHTML = state.back; | ||
document.title = state.title; | ||
}); | ||
|
||
// @ts-ignore | ||
self.MonacoEnvironment = { | ||
getWorkerUrl: function () { | ||
return "/editor.worker.js"; | ||
}, | ||
}; | ||
|
||
const sunSvg = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> | ||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" /> | ||
</svg> | ||
`; | ||
|
||
const moonSvg = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> | ||
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" /> | ||
</svg> | ||
`; | ||
|
||
type Theme = "light" | "dark"; | ||
|
||
function getTheme(): Theme { | ||
return (localStorage.getItem("theme") as Theme) ?? "light"; | ||
} | ||
|
||
let theme: Theme = getTheme(); | ||
const themeButton = document.querySelector<HTMLDivElement>("#theme")!; | ||
setTheme(theme); | ||
|
||
function setTheme(theme: Theme) { | ||
if (theme === "light") { | ||
document.querySelector("html")?.classList.remove("dark"); | ||
monaco.editor.setTheme("light-plus"); | ||
themeButton.innerHTML = moonSvg; | ||
} else { | ||
document.querySelector("html")?.classList.add("dark"); | ||
monaco.editor.setTheme("dark-plus"); | ||
themeButton.innerHTML = sunSvg; | ||
} | ||
localStorage.setItem("theme", theme); | ||
} | ||
|
||
function toggleTheme() { | ||
theme = theme === "light" ? "dark" : "light"; | ||
setTheme(theme); | ||
} | ||
|
||
themeButton.addEventListener("click", toggleTheme); | ||
|
||
function debounce<P extends any[], R>(f: (...args: P) => R, timeout: number) { | ||
let timer: ReturnType<typeof setTimeout> | null = null; | ||
return (...args: P) => { | ||
if (timer !== null) { | ||
clearTimeout(timer); | ||
} | ||
timer = setTimeout(() => { | ||
f(...args); | ||
timer = null; | ||
}, timeout); | ||
}; | ||
} | ||
|
||
const codePre = document.querySelector<HTMLPreElement>(".shiki")!; | ||
|
||
const model = monaco.editor.createModel( | ||
codePre.textContent ?? "", | ||
"moonbit", | ||
monaco.Uri.file("/main.mbt"), | ||
); | ||
|
||
const output = document.querySelector<HTMLPreElement>("#output")!; | ||
const trace = moonbitMode.traceCommandFactory(); | ||
|
||
async function run(debug: boolean) { | ||
if (debug) { | ||
const result = await moon.compile({ | ||
libInputs: [["main.mbt", model.getValue()]], | ||
debugMain: true, | ||
}); | ||
switch (result.kind) { | ||
case "success": { | ||
const js = result.js; | ||
const stream = await moon.run(js); | ||
let buffer = ""; | ||
await stream.pipeTo( | ||
new WritableStream({ | ||
write(chunk) { | ||
buffer += `${chunk}\n`; | ||
}, | ||
}), | ||
); | ||
output.textContent = buffer; | ||
return; | ||
} | ||
case "error": { | ||
console.error(result.diagnostics); | ||
} | ||
} | ||
return; | ||
} | ||
const stdout = await trace(monaco.Uri.file("/main.mbt").toString()); | ||
if (stdout === undefined) return; | ||
output.textContent = stdout; | ||
} | ||
|
||
model.onDidChangeContent(debounce(() => run(false), 100)); | ||
|
||
monaco.editor.onDidCreateEditor(() => { | ||
codePre.remove(); | ||
}); | ||
|
||
const editorContainer = document.getElementById("editor")!; | ||
editorContainer.classList.remove("pl-[10px]", "text-[14px]"); | ||
monaco.editor.create(editorContainer, { | ||
model, | ||
lineNumbers: "off", | ||
glyphMargin: false, | ||
minimap: { | ||
enabled: false, | ||
}, | ||
automaticLayout: true, | ||
folding: false, | ||
fontSize: 14, | ||
scrollBeyondLastLine: false, | ||
scrollbar: { | ||
alwaysConsumeMouseWheel: false, | ||
}, | ||
fontFamily: "monospace", | ||
theme: theme === "light" ? "light-plus" : "dark-plus", | ||
}); | ||
|
||
monaco.editor.registerCommand("moonbit-lsp/debug-main", () => { | ||
run(true); | ||
}); | ||
|
||
run(false); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
function getRouteDataHref(href: string) { | ||
if (href.endsWith("/")) { | ||
return href + "index.json"; | ||
} else if (href.endsWith("/index.html")) { | ||
return href.slice(0, -"/index.html".length) + "/index.json"; | ||
} else { | ||
return href + "/index.json"; | ||
} | ||
} | ||
|
||
async function getRouteData(href: string) { | ||
const url = getRouteDataHref(href); | ||
const res = await fetch(url); | ||
return await res.json(); | ||
} | ||
|
||
export async function init() { | ||
const state = await getRouteData(location.href); | ||
|
||
history.replaceState(state, "", location.href); | ||
|
||
window.addEventListener("popstate", (e) => { | ||
window.dispatchEvent(new CustomEvent("route-change", { detail: e.state })); | ||
}); | ||
|
||
document.addEventListener("click", async (e) => { | ||
if (!(e.target instanceof HTMLAnchorElement)) return; | ||
const a = e.target; | ||
const url = new URL(a.href); | ||
if (url.origin !== location.origin) return; | ||
e.preventDefault(); | ||
const data = await getRouteData(url.toString()); | ||
window.dispatchEvent(new CustomEvent("route-change", { detail: data })); | ||
history.pushState(data, "", url.toString()); | ||
}); | ||
} |
Oops, something went wrong.