It's alive
This commit is contained in:
parent
95846cd27a
commit
062793a5dc
2 changed files with 129 additions and 29 deletions
55
client.js
55
client.js
|
@ -1,3 +1,20 @@
|
|||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'
|
||||
|
||||
let lastDiagram = ''
|
||||
let rendering = false
|
||||
let theme = 'dark'
|
||||
|
||||
function setTheme(lightMatches) {
|
||||
theme = lightMatches ? 'default' : 'dark'
|
||||
mermaid.initialize({ startOnLoad: true, theme })
|
||||
renderLastDiagram()
|
||||
}
|
||||
if (window.matchMedia) {
|
||||
let lightMatcher = window.matchMedia('(prefers-color-scheme: light)')
|
||||
lightMatcher.addEventListener('change', ({ matches }) => setTheme(matches))
|
||||
setTheme(lightMatcher.matches)
|
||||
}
|
||||
|
||||
let connectionAttempt = 0
|
||||
function connectSocket() {
|
||||
const socket = new WebSocket('ws://localhost:8080')
|
||||
|
@ -6,14 +23,44 @@ function connectSocket() {
|
|||
})
|
||||
socket.addEventListener('close', () => {
|
||||
connectionAttempt += 1
|
||||
setTimeout(connectSocket(), 1000)
|
||||
setTimeout(() => requestAnimationFrame(connectSocket), 1000)
|
||||
})
|
||||
socket.addEventListener('message', handleMessage)
|
||||
}
|
||||
|
||||
function handleMessage({ data }) {
|
||||
console.log({ data })
|
||||
if (data == 'reload') window.location.reload()
|
||||
async function handleMessage({ data }) {
|
||||
if (data == 'hi') {
|
||||
console.log('👋')
|
||||
} else if (data == 'reload') {
|
||||
window.location.reload()
|
||||
} else {
|
||||
try {
|
||||
await handleStructuredMessage(JSON.parse(data))
|
||||
} catch (err) {
|
||||
console.error(`failed to process structured websocket message: ${err}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStructuredMessage(data) {
|
||||
switch (data.type) {
|
||||
case 'mermaid': {
|
||||
if (rendering) return
|
||||
rendering = true
|
||||
|
||||
lastDiagram = data.contents
|
||||
await renderLastDiagram()
|
||||
|
||||
rendering = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function renderLastDiagram() {
|
||||
if (lastDiagram == '') return
|
||||
document.getElementById('diagram').innerHTML =
|
||||
(await mermaid.render('mermaid', lastDiagram)).svg
|
||||
}
|
||||
|
||||
addEventListener('DOMContentLoaded', connectSocket)
|
||||
|
|
103
mod.ts
103
mod.ts
|
@ -54,41 +54,60 @@ const command = new Command()
|
|||
|
||||
async function openBrowser(args: Args): Promise<void> {
|
||||
if (args.open) {
|
||||
// TODO: this is broken?
|
||||
const url = `http://${args.host}:${args.port}`
|
||||
console.log(`opening browser to ${url}`)
|
||||
const process = new Deno.Command('xdg-open', {
|
||||
args: [url],
|
||||
}).spawn()
|
||||
|
||||
// const result = true
|
||||
console.log(`opened browser to ${url}: ${await process.status}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function runServer(args: Args): Promise<void> {
|
||||
const opts = { hostname: args.host, port: args.port }
|
||||
Deno.serve(opts, async (req: Request) => {
|
||||
await Deno.serve(opts, async (req: Request, info: Deno.ServeHandlerInfo) => {
|
||||
if (req.headers.get('upgrade') == 'websocket') {
|
||||
return handleWebSocket(req)
|
||||
return handleWebSocket(req, info)
|
||||
}
|
||||
|
||||
const url = new URL(req.url)
|
||||
if (url.pathname == '/client.js') {
|
||||
return new Response(await Deno.readTextFile('./client.js'), {
|
||||
headers: new Headers({ 'content-type': 'text/javascript' }),
|
||||
headers: new Headers({
|
||||
'content-type': 'text/javascript;charset=UTF-8',
|
||||
}),
|
||||
})
|
||||
} else if (url.pathname == '/') {
|
||||
return new Response(html.index, {
|
||||
headers: new Headers({ 'content-type': 'text/html' }),
|
||||
return new Response(page(null, await listDiagramsHtml(args.input)), {
|
||||
headers: new Headers({ 'content-type': 'text/html', charset: 'utf-8' }),
|
||||
})
|
||||
} else {
|
||||
const file = url.pathname.replaceAll('..', '')
|
||||
if (file.endsWith('.mmd')) {
|
||||
if (req.headers.get('accepts') == 'text/mermaid') {
|
||||
return new Response(Deno.readTextFileSync(file), {
|
||||
headers: { 'content-type': 'text/mermaid' },
|
||||
})
|
||||
}
|
||||
|
||||
return new Response(
|
||||
page(
|
||||
url.pathname,
|
||||
await diagramContent(args, url.pathname.replaceAll('..', '')),
|
||||
),
|
||||
{
|
||||
headers: new Headers({
|
||||
'content-type': 'text/html',
|
||||
charset: 'utf-8',
|
||||
}),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(`Hello, ${url.pathname}!`)
|
||||
})
|
||||
|
||||
return await new Promise((_resolve) => {
|
||||
// a promise that never resolves
|
||||
})
|
||||
}).finished
|
||||
}
|
||||
|
||||
async function runMermaidFileWatcher(args: Args) {
|
||||
|
@ -102,7 +121,6 @@ async function runMermaidFileWatcher(args: Args) {
|
|||
for (const p of paths) {
|
||||
console.log(p)
|
||||
if (p.endsWith('.mmd') && (kind == 'create' || kind == 'modify')) {
|
||||
console.log('omg a mermaid')
|
||||
const contents = await Deno.readTextFile(p)
|
||||
sockets.forEach((s) =>
|
||||
s.send(JSON.stringify({ type: 'mermaid', contents }))
|
||||
|
@ -113,17 +131,17 @@ async function runMermaidFileWatcher(args: Args) {
|
|||
}
|
||||
}
|
||||
|
||||
function handleWebSocket(req: Request): Response {
|
||||
function handleWebSocket(req: Request, info: Deno.ServeHandlerInfo): Response {
|
||||
const { socket, response } = Deno.upgradeWebSocket(req)
|
||||
|
||||
socket.addEventListener('open', (_ev) => {
|
||||
console.log(`websocket open`)
|
||||
console.log(`websocket open from ${JSON.stringify(info.remoteAddr)}`)
|
||||
socket.send('hi')
|
||||
sockets.add(socket)
|
||||
})
|
||||
|
||||
socket.addEventListener('message', (ev) => {
|
||||
console.log(`websocket message: ${ev}`)
|
||||
console.log(`websocket message: ${JSON.stringify(ev)}`)
|
||||
try {
|
||||
if (ev.data) {
|
||||
const data = JSON.parse(ev.data)
|
||||
|
@ -136,7 +154,7 @@ function handleWebSocket(req: Request): Response {
|
|||
})
|
||||
|
||||
socket.addEventListener('close', (ev) => {
|
||||
console.log(`websocket close: ${ev}`)
|
||||
console.log(`websocket close: ${JSON.stringify(ev)}`)
|
||||
sockets.delete(socket)
|
||||
})
|
||||
|
||||
|
@ -151,17 +169,52 @@ function handleClientEvent(socket: WebSocket, ce: ClientEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
const html = {
|
||||
index: `
|
||||
<!DOCTYPE html>
|
||||
<h1>Hello, index!</h1>
|
||||
<script src="./client.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js"></script>
|
||||
`,
|
||||
const listDiagramsHtml = async (dir: string): Promise<string> => {
|
||||
const list = []
|
||||
for await (const entry of Deno.readDir(dir)) {
|
||||
if (entry.isFile && entry.name.endsWith('.mmd')) {
|
||||
list.push(`<li><a href="/${entry.name}">${entry.name}</a></li>`)
|
||||
}
|
||||
}
|
||||
return `<ul>${list.join('\n')}</ul>`
|
||||
}
|
||||
|
||||
const diagramContent = async (args: Args, file: string): Promise<string> => {
|
||||
let content = await Deno.readTextFile(args.input + file)
|
||||
return `<div id="diagram" class="mermaid">${content}</div>`
|
||||
}
|
||||
|
||||
const page = (title: string | null, content: string) =>
|
||||
`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta viewport="">
|
||||
<title>${title !== null ? `${title} - ` : ''}Diagram</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1><a href="/">Diagram Index</a>${
|
||||
title !== null ? ` <a href="${title}">${title}</a>` : ''
|
||||
}</h1>
|
||||
<script type="module" src="./client.js"></script>
|
||||
${content}
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #111111;
|
||||
--text: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
async function run(args: Args) {
|
||||
// TODO: start mermaid file watcher
|
||||
await Promise.all([
|
||||
runMermaidFileWatcher(args),
|
||||
runServer(args),
|
||||
|
|
Loading…
Reference in a new issue