moved to deno to bundle website

This commit is contained in:
Parker TenBroeck 2026-01-06 23:22:50 -05:00
parent c35d7a9192
commit 7629bdab6d
28 changed files with 1534 additions and 41961 deletions

13
web/deno.json Normal file
View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"lib": ["deno.window", "dom", "dom.iterable", "esnext"]
},
"tasks": {
"dev": "deno run -A tools/dev.ts",
"build": "deno run -A tools/build.ts",
"preview": "deno run -ENR jsr:@std/http/file-server dist root/index.html"
},
"imports": {
"sass": "npm:sass@^1.97.2"
}
}

369
web/deno.lock generated Normal file
View file

@ -0,0 +1,369 @@
{
"version": "5",
"specifiers": {
"jsr:@minify-html/deno@~0.18.1": "0.18.1",
"npm:@codemirror/autocomplete@*": "6.20.0",
"npm:@codemirror/commands@*": "6.10.1",
"npm:@codemirror/language@*": "6.12.1",
"npm:@codemirror/state@*": "6.5.3",
"npm:@codemirror/theme-one-dark@*": "6.1.3",
"npm:@codemirror/view@*": "6.39.9",
"npm:sass@*": "1.97.2",
"npm:sass@^1.97.2": "1.97.2",
"npm:vis-network@*": "10.0.2_@egjs+hammerjs@2.0.17_component-emitter@2.0.0_keycharm@0.4.0_uuid@13.0.0_vis-data@8.0.3__uuid@13.0.0__vis-util@6.0.0___@egjs+hammerjs@2.0.17___component-emitter@2.0.0__@egjs+hammerjs@2.0.17__component-emitter@2.0.0_vis-util@6.0.0__@egjs+hammerjs@2.0.17__component-emitter@2.0.0"
},
"jsr": {
"@minify-html/deno@0.18.1": {
"integrity": "1af76557adea9ee1e28759f653ec5a35e9c006e2abe73b65e4944aa821711ed1"
}
},
"npm": {
"@codemirror/autocomplete@6.20.0": {
"integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==",
"dependencies": [
"@codemirror/language",
"@codemirror/state",
"@codemirror/view",
"@lezer/common"
]
},
"@codemirror/commands@6.10.1": {
"integrity": "sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==",
"dependencies": [
"@codemirror/language",
"@codemirror/state",
"@codemirror/view",
"@lezer/common"
]
},
"@codemirror/language@6.12.1": {
"integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==",
"dependencies": [
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
"style-mod"
]
},
"@codemirror/state@6.5.3": {
"integrity": "sha512-MerMzJzlXogk2fxWFU1nKp36bY5orBG59HnPiz0G9nLRebWa0zXuv2siH6PLIHBvv5TH8CkQRqjBs0MlxCZu+A==",
"dependencies": [
"@marijn/find-cluster-break"
]
},
"@codemirror/theme-one-dark@6.1.3": {
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
"dependencies": [
"@codemirror/language",
"@codemirror/state",
"@codemirror/view",
"@lezer/highlight"
]
},
"@codemirror/view@6.39.9": {
"integrity": "sha512-miGSIfBOKC1s2oHoa80dp+BjtsL8sXsrgGlQnQuOcfvaedcQUtqddTmKbJSDkLl4mkgPvZyXuKic2HDNYcJLYA==",
"dependencies": [
"@codemirror/state",
"crelt",
"style-mod",
"w3c-keyname"
]
},
"@egjs/hammerjs@2.0.17": {
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"dependencies": [
"@types/hammerjs"
]
},
"@lezer/common@1.5.0": {
"integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA=="
},
"@lezer/highlight@1.2.3": {
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
"dependencies": [
"@lezer/common"
]
},
"@lezer/lr@1.4.7": {
"integrity": "sha512-wNIFWdSUfX9Jc6ePMzxSPVgTVB4EOfDIwLQLWASyiUdHKaMsiilj9bYiGkGQCKVodd0x6bgQCV207PILGFCF9Q==",
"dependencies": [
"@lezer/common"
]
},
"@marijn/find-cluster-break@1.0.2": {
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
},
"@parcel/watcher-android-arm64@2.5.1": {
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
"os": ["android"],
"cpu": ["arm64"]
},
"@parcel/watcher-darwin-arm64@2.5.1": {
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"@parcel/watcher-darwin-x64@2.5.1": {
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
"os": ["darwin"],
"cpu": ["x64"]
},
"@parcel/watcher-freebsd-x64@2.5.1": {
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
"os": ["freebsd"],
"cpu": ["x64"]
},
"@parcel/watcher-linux-arm-glibc@2.5.1": {
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
"os": ["linux"],
"cpu": ["arm"]
},
"@parcel/watcher-linux-arm-musl@2.5.1": {
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
"os": ["linux"],
"cpu": ["arm"]
},
"@parcel/watcher-linux-arm64-glibc@2.5.1": {
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@parcel/watcher-linux-arm64-musl@2.5.1": {
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@parcel/watcher-linux-x64-glibc@2.5.1": {
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
"os": ["linux"],
"cpu": ["x64"]
},
"@parcel/watcher-linux-x64-musl@2.5.1": {
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
"os": ["linux"],
"cpu": ["x64"]
},
"@parcel/watcher-win32-arm64@2.5.1": {
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
"os": ["win32"],
"cpu": ["arm64"]
},
"@parcel/watcher-win32-ia32@2.5.1": {
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
"os": ["win32"],
"cpu": ["ia32"]
},
"@parcel/watcher-win32-x64@2.5.1": {
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
"os": ["win32"],
"cpu": ["x64"]
},
"@parcel/watcher@2.5.1": {
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"dependencies": [
"detect-libc",
"is-glob",
"micromatch",
"node-addon-api"
],
"optionalDependencies": [
"@parcel/watcher-android-arm64",
"@parcel/watcher-darwin-arm64",
"@parcel/watcher-darwin-x64",
"@parcel/watcher-freebsd-x64",
"@parcel/watcher-linux-arm-glibc",
"@parcel/watcher-linux-arm-musl",
"@parcel/watcher-linux-arm64-glibc",
"@parcel/watcher-linux-arm64-musl",
"@parcel/watcher-linux-x64-glibc",
"@parcel/watcher-linux-x64-musl",
"@parcel/watcher-win32-arm64",
"@parcel/watcher-win32-ia32",
"@parcel/watcher-win32-x64"
],
"scripts": true
},
"@types/hammerjs@2.0.46": {
"integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw=="
},
"braces@3.0.3": {
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": [
"fill-range"
]
},
"chokidar@4.0.3": {
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dependencies": [
"readdirp"
]
},
"component-emitter@2.0.0": {
"integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw=="
},
"crelt@1.0.6": {
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"detect-libc@1.0.3": {
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"bin": true
},
"fill-range@7.1.1": {
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": [
"to-regex-range"
]
},
"immutable@5.1.4": {
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA=="
},
"is-extglob@2.1.1": {
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
},
"is-glob@4.0.3": {
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dependencies": [
"is-extglob"
]
},
"is-number@7.0.0": {
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"keycharm@0.4.0": {
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ=="
},
"micromatch@4.0.8": {
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": [
"braces",
"picomatch"
]
},
"node-addon-api@7.1.1": {
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
},
"picomatch@2.3.1": {
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
"readdirp@4.1.2": {
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="
},
"sass@1.97.2": {
"integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==",
"dependencies": [
"chokidar",
"immutable",
"source-map-js"
],
"optionalDependencies": [
"@parcel/watcher"
],
"bin": true
},
"source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
},
"style-mod@4.1.3": {
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="
},
"to-regex-range@5.0.1": {
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": [
"is-number"
]
},
"uuid@13.0.0": {
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
"bin": true
},
"vis-data@8.0.3_uuid@13.0.0_vis-util@6.0.0__@egjs+hammerjs@2.0.17__component-emitter@2.0.0_@egjs+hammerjs@2.0.17_component-emitter@2.0.0": {
"integrity": "sha512-jhnb6rJNqkKR1Qmlay0VuDXY9ZlvAnYN1udsrP4U+krgZEq7C0yNSKdZqmnCe13mdnf9AdVcdDGFOzy2mpPoqw==",
"dependencies": [
"uuid",
"vis-util"
]
},
"vis-network@10.0.2_@egjs+hammerjs@2.0.17_component-emitter@2.0.0_keycharm@0.4.0_uuid@13.0.0_vis-data@8.0.3__uuid@13.0.0__vis-util@6.0.0___@egjs+hammerjs@2.0.17___component-emitter@2.0.0__@egjs+hammerjs@2.0.17__component-emitter@2.0.0_vis-util@6.0.0__@egjs+hammerjs@2.0.17__component-emitter@2.0.0": {
"integrity": "sha512-qPl8GLYBeHEFqiTqp4VBbYQIJ2EA8KLr7TstA2E8nJxfEHaKCU81hQLz7hhq11NUpHbMaRzBjW5uZpVKJ45/wA==",
"dependencies": [
"@egjs/hammerjs",
"component-emitter",
"keycharm",
"uuid",
"vis-data",
"vis-util"
]
},
"vis-util@6.0.0_@egjs+hammerjs@2.0.17_component-emitter@2.0.0": {
"integrity": "sha512-qtpts3HRma0zPe4bO7t9A2uejkRNj8Z2Tb6do6lN85iPNWExFkUiVhdAq5uLGIUqBFduyYeqWJKv/jMkxX0R5g==",
"dependencies": [
"@egjs/hammerjs",
"component-emitter"
]
},
"w3c-keyname@2.2.8": {
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
}
},
"redirects": {
"https://esm.sh/@codemirror/autocomplete": "https://esm.sh/@codemirror/autocomplete@6.20.0",
"https://esm.sh/@codemirror/commands": "https://esm.sh/@codemirror/commands@6.10.1",
"https://esm.sh/@codemirror/language": "https://esm.sh/@codemirror/language@6.12.1",
"https://esm.sh/@codemirror/language@^6.0.0?target=denonext": "https://esm.sh/@codemirror/language@6.12.1?target=denonext",
"https://esm.sh/@codemirror/state": "https://esm.sh/@codemirror/state@6.5.3",
"https://esm.sh/@codemirror/state@^6.0.0?target=denonext": "https://esm.sh/@codemirror/state@6.5.3?target=denonext",
"https://esm.sh/@codemirror/state@^6.4.0?target=denonext": "https://esm.sh/@codemirror/state@6.5.3?target=denonext",
"https://esm.sh/@codemirror/state@^6.5.0?target=denonext": "https://esm.sh/@codemirror/state@6.5.3?target=denonext",
"https://esm.sh/@codemirror/theme-one-dark": "https://esm.sh/@codemirror/theme-one-dark@6.1.3",
"https://esm.sh/@codemirror/view": "https://esm.sh/@codemirror/view@6.39.9",
"https://esm.sh/@codemirror/view@^6.0.0?target=denonext": "https://esm.sh/@codemirror/view@6.39.9?target=denonext",
"https://esm.sh/@codemirror/view@^6.17.0?target=denonext": "https://esm.sh/@codemirror/view@6.39.9?target=denonext",
"https://esm.sh/@codemirror/view@^6.23.0?target=denonext": "https://esm.sh/@codemirror/view@6.39.9?target=denonext",
"https://esm.sh/@codemirror/view@^6.27.0?target=denonext": "https://esm.sh/@codemirror/view@6.39.9?target=denonext",
"https://esm.sh/@lezer/common@^1.1.0?target=denonext": "https://esm.sh/@lezer/common@1.5.0?target=denonext",
"https://esm.sh/@lezer/common@^1.3.0?target=denonext": "https://esm.sh/@lezer/common@1.5.0?target=denonext",
"https://esm.sh/@lezer/common@^1.5.0?target=denonext": "https://esm.sh/@lezer/common@1.5.0?target=denonext",
"https://esm.sh/@lezer/highlight@^1.0.0?target=denonext": "https://esm.sh/@lezer/highlight@1.2.3?target=denonext",
"https://esm.sh/@marijn/find-cluster-break@^1.0.0?target=denonext": "https://esm.sh/@marijn/find-cluster-break@1.0.2?target=denonext",
"https://esm.sh/crelt@^1.0.6?target=denonext": "https://esm.sh/crelt@1.0.6?target=denonext",
"https://esm.sh/style-mod@^4.0.0?target=denonext": "https://esm.sh/style-mod@4.1.3?target=denonext",
"https://esm.sh/style-mod@^4.1.0?target=denonext": "https://esm.sh/style-mod@4.1.3?target=denonext",
"https://esm.sh/w3c-keyname@^2.2.4?target=denonext": "https://esm.sh/w3c-keyname@2.2.8?target=denonext"
},
"remote": {
"https://esm.sh/@codemirror/autocomplete@6.20.0": "2374e2aac7fbf79969fe0557b2b4dbd442e6dc3a3975a4e59a0ab8cb2a06aaf2",
"https://esm.sh/@codemirror/autocomplete@6.20.0/denonext/autocomplete.mjs": "9a8472ea9c2f078cf7c36b5d84d86668421708fa8a5c61aaf776d20f745e8425",
"https://esm.sh/@codemirror/commands@6.10.1": "ddc0ecb0619eda5d51139da9d67dcb3a0f3458d6db3fb9a8c0b8175d4418ec88",
"https://esm.sh/@codemirror/commands@6.10.1/denonext/commands.mjs": "8c634ba35ad66aa6aa6abed1f4cb7c75434750123e745bb2ce8a704aef8bfdcb",
"https://esm.sh/@codemirror/language@6.12.1": "b3729707a0665bd514c6f0d6e6d04a934379df9851e5634dc3e1370528c05372",
"https://esm.sh/@codemirror/language@6.12.1/denonext/language.mjs": "0a8d58b557b489c799a90b580161bfc08b64e1dbf7fe1eb38a1138818fab418a",
"https://esm.sh/@codemirror/language@6.12.1?target=denonext": "b3729707a0665bd514c6f0d6e6d04a934379df9851e5634dc3e1370528c05372",
"https://esm.sh/@codemirror/state@6.5.3": "701ab7a48adb966990f78084a89148f58c454f20eb774fb4df4eb32692984c2c",
"https://esm.sh/@codemirror/state@6.5.3/denonext/state.mjs": "a1504952fd381a39be5d911e7ecc9121fa6c00c25a33c39bb4b03ff5c921b87e",
"https://esm.sh/@codemirror/state@6.5.3?target=denonext": "701ab7a48adb966990f78084a89148f58c454f20eb774fb4df4eb32692984c2c",
"https://esm.sh/@codemirror/theme-one-dark@6.1.3": "c53e61336a1da62912730710222fa0168418c6d699e9f187eb2069c0a72c7ccf",
"https://esm.sh/@codemirror/theme-one-dark@6.1.3/denonext/theme-one-dark.mjs": "2d277cf317df96dde41e2a6abd1b9f475e3896201df5bb47e268a33593b65d75",
"https://esm.sh/@codemirror/view@6.39.9": "629498953f3aa24f64fee4877edb0113093cdba44f9a61a1a3488f240e89f618",
"https://esm.sh/@codemirror/view@6.39.9/denonext/view.mjs": "cd8c45e126263a9bb55a6a69fe923e342197e593147a463ea6ac8d337a27be0d",
"https://esm.sh/@codemirror/view@6.39.9?target=denonext": "629498953f3aa24f64fee4877edb0113093cdba44f9a61a1a3488f240e89f618",
"https://esm.sh/@lezer/common@1.5.0/denonext/common.mjs": "c02bee82fffdc0ab555fc712c53c8d8a6258be07a59d1c2343f999e04a5ff87d",
"https://esm.sh/@lezer/common@1.5.0?target=denonext": "93895c0d96f3796848a0d8fb76518a16e617a4fc2c7aa5889cdefa4f75efe03c",
"https://esm.sh/@lezer/highlight@1.2.3/denonext/highlight.mjs": "e4e2ed7b14be7f9e93d70848ff127fe143fb251b16ba17a19d609b2f2ccd9ffc",
"https://esm.sh/@lezer/highlight@1.2.3?target=denonext": "39396ec28c65dd80ebe903d9df1cfdc735cbfc8c71927d66f7a986b7a411babc",
"https://esm.sh/@marijn/find-cluster-break@1.0.2/denonext/find-cluster-break.mjs": "740e4a2ff6fbcd7f619e556d06b4ab21bfafc6c69472ee84999739c3e26c9e46",
"https://esm.sh/@marijn/find-cluster-break@1.0.2?target=denonext": "8ee05755b904d18303ebb157ad8f96a86de0e20738e0836493defd76a62898dc",
"https://esm.sh/crelt@1.0.6/denonext/crelt.mjs": "b19810b35976ac8cf83e6337f73e119c7b90f5332a604f352349229adfa205a9",
"https://esm.sh/crelt@1.0.6?target=denonext": "eb6b6f73c05f35edfe2c89c49a1532f695941231d7b661df7f9cced089fa6390",
"https://esm.sh/style-mod@4.1.3/denonext/style-mod.mjs": "9ac9e681c3a810eac8ce11ec40cecb9ee8cc92b436856cadabe77a9939f57f1c",
"https://esm.sh/style-mod@4.1.3?target=denonext": "643b4ab89cbbe694d05a229c0c351ac7207873f146c01df2562f0e57272803a0",
"https://esm.sh/w3c-keyname@2.2.8/denonext/w3c-keyname.mjs": "99a47c27230eb51799d1b8dc53b17f50b46aae275423c9a19e5a1dbb0cce787a",
"https://esm.sh/w3c-keyname@2.2.8?target=denonext": "5e785342c86c345144e65df42516d2d9b149191458169748770625ffc9df5c6c"
},
"workspace": {
"dependencies": [
"npm:sass@^1.97.2"
]
}
}

View file

@ -1,371 +0,0 @@
html,
body {
height: 100%;
width: 100%;
margin: 0;
font-family: system-ui, sans-serif;
background: #909090;
}
.wrap {
height: 100vh;
width: 100vw;
display: grid;
grid-template-rows: var(--topH, 50vh) 8px 1fr;
/* top pane, splitter, editor */
overflow: hidden;
}
/* Top pane: terminal area */
.topPane {
display: grid;
grid-template-columns: var(--termW, 50vw) 8px 1fr;
/* terminal, splitter, filler */
min-height: 80px;
overflow: hidden;
}
/* Bottom pane: editor */
.bottomPane {
min-height: 120px;
overflow: hidden;
}
/* Make editor fill its pane */
#editor {
height: 100%;
width: 100%;
}
.terminal {
background: #0b0f14;
color: #c9d1d9;
padding: 1em;
margin: 0px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: 12.5px;
line-height: 1.35;
white-space: pre-wrap;
word-break: break-word;
}
/* ANSI text styles */
.ansi-bold { font-weight: 700; }
.ansi-dim { opacity: 0.7; }
/* Foreground colors (standard + bright) */
.ansi-fg-30 { color: #0b0f14; } /* black */
.ansi-fg-31 { color: #ff7b72; } /* red */
.ansi-fg-32 { color: #7ee787; } /* green */
.ansi-fg-33 { color: #f2cc60; } /* yellow */
.ansi-fg-34 { color: #79c0ff; } /* blue */
.ansi-fg-35 { color: #d2a8ff; } /* magenta */
.ansi-fg-36 { color: #a5d6ff; } /* cyan */
.ansi-fg-37 { color: #c9d1d9; } /* white */
.ansi-fg-90 { color: #6e7681; } /* bright black / gray */
.ansi-fg-91 { color: #ffa198; }
.ansi-fg-92 { color: #a6f3a6; }
.ansi-fg-93 { color: #ffe082; }
.ansi-fg-94 { color: #a5d6ff; }
.ansi-fg-95 { color: #e3b8ff; }
.ansi-fg-96 { color: #c7f0ff; }
.ansi-fg-97 { color: #ffffff; }
/* Background colors (optional) */
.ansi-bg-40 { background: #0b0f14; }
.ansi-bg-41 { background: rgba(255, 123, 114, 0.22); }
.ansi-bg-42 { background: rgba(126, 231, 135, 0.18); }
.ansi-bg-43 { background: rgba(242, 204, 96, 0.18); }
.ansi-bg-44 { background: rgba(121, 192, 255, 0.18); }
.ansi-bg-45 { background: rgba(210, 168, 255, 0.18); }
.ansi-bg-46 { background: rgba(165, 214, 255, 0.18); }
.ansi-bg-47 { background: rgba(201, 209, 217, 0.10); }
html,
body {
height: 100%;
margin: 0;
overflow: hidden;
}
/* App layout */
.app {
height: 100vh;
width: 100vw;
display: grid;
grid-template-rows: var(--canvasH, 35vh) 8px 1fr;
overflow: hidden;
}
/* ---------- Canvas ---------- */
.canvasPane {
position: relative;
overflow: hidden;
background: #111;
}
.graph {
width: 100%;
height: 100%;
}
/* ---------- Bottom area (terminal + editor) ---------- */
/* Bottom area (terminal + editor) */
.bottomPane {
height: 100%;
display: grid;
grid-template-columns: 1fr 8px var(--termW, 40vw);
overflow: hidden;
/* IMPORTANT */
}
/* Terminal side */
.terminalPane {
height: 100%;
overflow: hidden;
}
.terminal {
height: 100%;
width: 100%;
overflow-y: auto;
/* terminal scrolls */
overflow-x: auto;
}
/* Editor side */
.editorPane {
height: 100%;
overflow: hidden;
/* let CodeMirror scroll */
}
/* CodeMirror mount point */
#editor {
height: 100%;
width: 100%;
}
/* VERY IMPORTANT: force CodeMirror to respect container height */
.cm-editor {
height: 100%;
}
/* CodeMirrors internal scroller (this is where the scrollbar lives) */
.cm-scroller {
overflow-y: auto !important;
}
/* ---------- Splitters ---------- */
.hSplit {
cursor: row-resize;
background: rgba(255, 255, 255, 0.06);
}
.vSplit {
cursor: col-resize;
background: rgba(255, 255, 255, 0.06);
}
.hSplit:hover,
.vSplit:hover {
background: rgba(121, 192, 255, 0.25);
}
.diag {
margin: 0;
padding-left: 18px;
}
.diag li {
margin: 6px 0;
}
/* --- Syntax colors via CSS classes applied by decorations --- */
.tok-comment {
color: #1a7b24;
}
.tok-keyword {
color: #b99400;
font-weight: 600;
}
.tok-error {
color: #ff0505;
font-weight: 1000;
}
.tok-ident {
color: #90d4e0;
}
.tok-brace {
color: #d73a49;
font-weight: 600;
}
.tok-punc {
color: #ffffff;
}
.tok-string {
color: #03621e;
}
/* Rainbow bracket depth classes */
.rb-0 {
color: #a35;
font-weight: 700;
}
.rb-1 {
color: #ed0;
font-weight: 700;
}
.rb-2 {
color: #9d5;
font-weight: 700;
}
.rb-3 {
color: #2cb;
font-weight: 700;
}
.rb-4 {
color: #36b;
font-weight: 700;
}
.rb-5 {
color: #639;
font-weight: 700;
}
/* Severity underline styles */
.cm-diag-error {
text-decoration: underline wavy #d73a49;
/* red */
text-underline-offset: 2px;
}
.cm-diag-warning {
text-decoration: underline wavy #ffd33d;
/* yellow */
text-underline-offset: 2px;
}
.cm-diag-info {
text-decoration: underline wavy #79c0ff;
/* cyan-ish */
text-underline-offset: 2px;
}
/* Tooltip title coloring by severity */
.tipTitle.error {
color: #d73a49;
}
.tipTitle.warning {
color: #ffd33d;
}
.tipTitle.info {
color: #79c0ff;
}
/* Optional: diagnostics panel coloring */
.diag li.error {
color: #d73a49;
}
.diag li.warning {
color: #b08800;
}
.diag li.info {
color: #0366d6;
}
/* Tooltip styling */
.cm-tooltip.cm-tooltip-hover {
border: 1px solid #ddd;
background: black;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
border-radius: 10px;
padding: 8px 10px;
max-width: 420px;
font-size: 13px;
line-height: 1.35;
}
.tipTitle {
font-weight: 700;
margin-bottom: 4px;
}
.tipBody {
white-space: pre-wrap;
}
/* Loading screen */
.centered {
margin-right: auto;
margin-left: auto;
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #f0f0f0;
font-size: 24px;
font-family: Ubuntu-Light, Helvetica, sans-serif;
text-align: center;
}
.lds-dual-ring {
display: inline-block;
width: 24px;
height: 24px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 24px;
height: 24px;
margin: 0px;
border-radius: 50%;
border: 3px solid #fff;
border-color: #fff transparent #fff transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View file

@ -5,11 +5,8 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Automata</title>
<link rel="stylesheet" href="https://unpkg.com/vis-network/styles/vis-network.min.css">
<link href="editor.css" rel="stylesheet">
<link href="style.css" rel="stylesheet">
</head>
<body>
@ -46,7 +43,7 @@
</div>
<script type="module" src="index.js"></script>
<script type="module" src="src/main.ts"></script>
</body>
</html>

View file

@ -1,4 +0,0 @@
import wasm from "./wasm.js"
import "./editor.js"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,15 +0,0 @@
{
"name": "automata-web",
"type": "module",
"version": "0.1.0",
"files": [
"automata_bg.wasm",
"automata.js",
"automata_bg.js"
],
"main": "automata.js",
"sideEffects": [
"./automata.js",
"./snippets/*"
]
}

View file

@ -1,17 +1,26 @@
import { EditorView, keymap, hoverTooltip, Decoration, ViewPlugin } from "https://esm.sh/@codemirror/view";
import { EditorState, StateField } from "https://esm.sh/@codemirror/state";
import { defaultKeymap, history, historyKeymap } from "https://esm.sh/@codemirror/commands";
import { lineNumbers, highlightActiveLineGutter } from "https://esm.sh/@codemirror/view";
import { bracketMatching, indentOnInput } from "https://esm.sh/@codemirror/language";
import { closeBrackets } from "https://esm.sh/@codemirror/autocomplete";
import { oneDark } from "https://esm.sh/@codemirror/theme-one-dark";
// deno-lint-ignore-file
import wasm from "./wasm.js"
import {
EditorView,
keymap,
hoverTooltip,
Decoration,
ViewPlugin,
lineNumbers,
highlightActiveLineGutter,
} from "npm:@codemirror/view";
import * as vis from "./js/vis-network.js"
import { EditorState, StateField, Text } from "npm:@codemirror/state";
import { defaultKeymap, history, historyKeymap } from "npm:@codemirror/commands";
import { bracketMatching, indentOnInput } from "npm:@codemirror/language";
import { closeBrackets } from "npm:@codemirror/autocomplete";
import { oneDark } from "npm:@codemirror/theme-one-dark";
function tokenize(text) {
import wasm from "./wasm.ts"
function tokenize(text: string) {
try {
return wasm.lex(text);
} catch (e) {
@ -20,16 +29,17 @@ function tokenize(text) {
}
}
function compile(text) {
function compile(text: string): wasm.CompileResult {
try {
return wasm.compile(text);
} catch (e) {
console.log(e)
return []
console.log(e);
// @ts-expect-error wasm defines extra cleanup
return {log: [], log_formatted: ""};
}
}
const tokenClass = (t) =>
const tokenClass = (t: string) =>
({
comment: "tok-comment",
keyword: "tok-keyword",
@ -47,20 +57,20 @@ const tokenClass = (t) =>
}[t] || "tok-ident");
function severityClass(sev) {
function severityClass(sev: string) {
const s = (sev || "error").toLowerCase();
if (s === "warning") return "cm-diag-warning";
if (s === "info") return "cm-diag-info";
return "cm-diag-error";
}
function sevRank(sev) {
function sevRank(sev: string) {
if (sev === "error") return 3;
if (sev === "warning") return 2;
return 1;
}
function buildAnalysis(text, doc) {
function buildAnalysis(text: string, doc: Text) {
const tokens = tokenize(text);
const { log, log_formatted } = compile(text);
@ -71,7 +81,7 @@ function buildAnalysis(text, doc) {
for (const tok of tokens) {
const start = Math.max(0, Math.min(docLen, tok.start));
const end = Math.max(start, Math.min(docLen, tok.end));
var tc = tokenClass(tok.kind);
let tc = tokenClass(tok.kind);
if (tc === "rb-") {
tc += tok.scope_level.toString();
}
@ -114,7 +124,7 @@ const analysisField = StateField.define({
// ===================== Hover tooltip (uses cached diags) =====================
const diagHover = hoverTooltip((view, pos) => {
const { log } = view.state.field(analysisField);
const hits = log.filter((d) => pos >= d.start && pos <= d.end);
const hits = log.filter((d) => d.start !== undefined && d.end !== undefined && pos >= d.start && pos <= d.end);
if (hits.length === 0) return null;
const top = hits.reduce((a, b) => (sevRank(b.level) > sevRank(a.level) ? b : a), hits[0]);
@ -148,27 +158,28 @@ const diagHover = hoverTooltip((view, pos) => {
});
function escapeHtml(s) {
return String(s)
function escapeHtml(s: string) {
return s
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
function ansiToHtml(input) {
function ansiToHtml(input: string) {
// deno-lint-ignore no-control-regex
const ESC_RE = /\x1b\[([0-9;]*)m/g;
let out = "";
let lastIndex = 0;
// current style state
let fg = null; // e.g. 31, 92
let bg = null; // e.g. 41
let fg: number|null = null; // e.g. 31, 92
let bg: number|null = null; // e.g. 41
let bold = false;
let dim = false;
function openSpanIfNeeded(text) {
function openSpanIfNeeded(text: string) {
if (text.length === 0) return "";
const classes = [];
if (bold) classes.push("ansi-bold");
@ -179,8 +190,8 @@ function ansiToHtml(input) {
return `<span class="${classes.join(" ")}">${escapeHtml(text)}</span>`;
}
function applyCodes(codes) {
if (codes.length === 0) codes = [0];
function applyCodes(codes: string[]) {
if (codes.length === 0) codes = ["0"];
for (const c of codes) {
const code = Number(c);
if (Number.isNaN(code)) continue;
@ -220,6 +231,7 @@ function ansiToHtml(input) {
return out;
}
// @ts-expect-error bad library
function formatTerminal(view) {
const term = document.getElementById("terminal");
if (!term) return;
@ -234,10 +246,14 @@ function formatTerminal(view) {
const terminalPlugin = ViewPlugin.fromClass(
class {
// @ts-expect-error bad library
constructor(view) {
// @ts-expect-error bad library
this.view = view;
formatTerminal(view);
}
// @ts-expect-error bad library
update(update) {
if (update.docChanged) formatTerminal(update.view);
}
@ -288,15 +304,15 @@ const state = EditorState.create({
],
});
window.editor = new EditorView({
const editor = new EditorView({
state,
parent: document.getElementById("editor"),
parent: document.getElementById("editor")!,
});
function setDefaultLayoutWeights() {
const vh = window.innerHeight;
const vw = window.innerWidth;
const vh = globalThis.window.innerHeight;
const vw = globalThis.window.innerWidth;
// Canvas: 30% of screen height
const canvasH = Math.round(vh * 0.60);
@ -304,7 +320,7 @@ function setDefaultLayoutWeights() {
// Terminal: 35% of width
const termW = Math.round(vw * 0.30);
const app = document.getElementById("app");
const app = document.getElementById("app")!;
app.style.setProperty("--canvasH", `${canvasH}px`);
app.style.setProperty("--termW", `${termW}px`);
}
@ -313,9 +329,9 @@ setDefaultLayoutWeights();
(function enableLayoutSplitters() {
const app = document.getElementById("app");
const hSplit = document.getElementById("hSplit");
const vSplit = document.getElementById("vSplit");
const app = document.getElementById("app")!;
const hSplit = document.getElementById("hSplit")!;
const vSplit = document.getElementById("vSplit")!;
// const canvas = document.getElementById("canvas");
const canvasPane = document.getElementById("canvasPane");
@ -334,7 +350,7 @@ setDefaultLayoutWeights();
e.preventDefault();
});
window.addEventListener("mousemove", (e) => {
globalThis.window.addEventListener("mousemove", (e) => {
const rect = app.getBoundingClientRect();
if (draggingH) {
@ -347,7 +363,7 @@ setDefaultLayoutWeights();
}
if (draggingV) {
const bottomPane = document.getElementById("bottomPane");
const bottomPane = document.getElementById("bottomPane")!;
const r = bottomPane.getBoundingClientRect();
const x = e.clientX - r.left;
const minTerm = 220;
@ -357,256 +373,9 @@ setDefaultLayoutWeights();
}
});
window.addEventListener("mouseup", () => {
globalThis.window.addEventListener("mouseup", () => {
draggingH = false;
draggingV = false;
document.body.style.cursor = "";
});
})();
let network = null;
const nodes = new vis.DataSet();
const edges = new vis.DataSet();
const automaton = {
states: ["q0", "q1"],
initialState: "q0",
acceptStates: ["q1"],
transitions: [
{
from: "q0",
to: "q0",
label: "ε, z0 → A z0\n"
},
{
from: "q0",
to: "q0",
label: "ε, z0 → B z0"
},
{
from: "q0",
to: "q1",
label: "ε, z0 → z0"
},
{
from: "q1",
to: "q1",
label: "a, A → ε"
},
{
from: "q1",
to: "q1",
label: "b, B → ε"
}
]
};
/**@param {{ctx: CanvasRenderingContext2D}} */
function renderNode({
ctx,
id,
x,
y,
state: { selected, hover },
style,
label,
}) {
return {
drawNode() {
ctx.save();
var r = style.size;
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
ctx.lineWidth = 4;
ctx.strokeStyle = "blue";
ctx.stroke();
ctx.fillStyle = "black";
ctx.textAlign = 'center';
ctx.fillText(label, x, y, r);
ctx.textAlign = 'center';
ctx.strokeStyle = 'white';
ctx.fillStyle = "black";
let cy = y - (r + 10);
for (const part of "meow[]\nbeeep".split("\n").reverse()) {
const metrics = ctx.measureText(part);
cy -= metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
ctx.strokeText(part, x, cy);
ctx.fillText(part, x, cy);
}
ctx.restore();
},
nodeDimensions: { width: 20, height: 20 },
};
}
// Populate nodes
for (const state of automaton.states) {
nodes.add({
id: state,
label: state,
});
}
// Populate edges
automaton.transitions.forEach((t, i) => {
edges.add({
id: `e${i}`,
from: t.from,
to: t.to,
label: t.label
});
});
// updateGraphFromText();
ensureGraph();
function updateGraphFromText() {
ensureGraph();
const trans = []
// Collect state ids
const stateSet = new Set();
for (const tr of trans) {
stateSet.add(tr.from);
stateSet.add(tr.to);
}
// Update nodes (add missing, remove stale)
const existingNodeIds = new Set(nodes.getIds());
const desiredNodeIds = new Set([...stateSet]);
// remove stale
for (const id of existingNodeIds) {
if (!desiredNodeIds.has(id)) nodes.remove(id);
}
// add/update desired
for (const id of desiredNodeIds) {
const pos = pinnedPositions.get(id);
if (!existingNodeIds.has(id)) {
nodes.add({
id,
label: id,
...(pos ? { x: pos.x, y: pos.y, fixed: true } : {})
});
} else if (pos) {
nodes.update({ id, x: pos.x, y: pos.y, fixed: true });
}
}
// Update edges (stable IDs so edits don't flicker)
const desiredEdgeIds = new Set();
const nextEdges = [];
for (let i = 0; i < trans.length; i++) {
const tr = trans[i];
const id = `${tr.from}::${tr.to}::${tr.label}::${i}`;
desiredEdgeIds.add(id);
nextEdges.push({ id, from: tr.from, to: tr.to, label: tr.label });
}
const existingEdgeIds = new Set(edges.getIds());
for (const id of existingEdgeIds) {
if (!desiredEdgeIds.has(id)) edges.remove(id);
}
// add/update in batch
for (const e of nextEdges) {
if (!existingEdgeIds.has(e.id)) edges.add(e);
else edges.update(e);
}
// If positions exist for all nodes, we can disable physics to “respect” manual layout
// Otherwise leave physics on to auto-layout new nodes.
const allPinned = [...desiredNodeIds].every((id) => pinnedPositions.has(id));
network.setOptions({ physics: { enabled: !allPinned } });
// Redraw nicely after updates
network.fit({ animation: { duration: 200, easingFunction: "easeInOutQuad" } });
}
// ---------- 4) Hook graph updates into your existing single-pass analysis ----------
const graphPlugin = ViewPlugin.fromClass(class {
constructor(view) {
updateGraphFromText(view.state.doc.toString());
}
update(update) {
if (update.docChanged) {
updateGraphFromText(update.state.doc.toString());
}
}
});
function chosen_node(values, id, selected, hovering) {
console.log(values, id, selected, hovering)
}
function ensureGraph() {
if (network) return;
const container = document.getElementById("graph");
network = new vis.Network(
container,
{ nodes, edges },
{
layout: { improvedLayout: true },
physics: {
enabled: true,
solver: "barnesHut",
barnesHut: { gravitationalConstant: -8000, springLength: 120, springConstant: 0.04 },
stabilization: { iterations: 200 }
},
interaction: {
dragNodes: true,
hover: true,
multiselect: true
},
nodes: {
shape: 'dot',
size: 14,
font: { color: "#c9d1d9" },
color: {
background: "#1f6feb",
border: "#79c0ff",
highlight: { background: "#388bfd", border: "#a5d6ff" }
},
chosen: {
node: chosen_node
},
shape: "custom",
ctxRenderer: renderNode,
size: 18,
},
edges: {
arrows: { to: { enabled: true, scaleFactor: 0.8 } },
arrowStrikethrough: false,
font: { align: "middle", color: "#000000ff" },
color: { color: "rgba(201,209,217,0.35)", highlight: "#c9d1d9" },
smooth: { type: "dynamic" },
arrows: "to",
}
}
);
// Save positions when user drags nodes
network.on("dragEnd", (params) => {
const pos = network.getPositions(params.nodes);
for (const id of params.nodes) {
pinnedPositions.set(id, pos[id]);
}
});
window.network = network;
}

4
web/root/src/main.ts Normal file
View file

@ -0,0 +1,4 @@
import "./editor.ts"
import "./visualizer.ts"

187
web/root/src/visualizer.ts Normal file
View file

@ -0,0 +1,187 @@
// deno-lint-ignore-file no-unversioned-import
// deno-lint-ignore no-import-prefix
import * as vis from "npm:vis-network/standalone";
const nodes = new vis.DataSet<vis.Node>();
const edges = new vis.DataSet<vis.Edge>();
const automaton = {
states: ["q0", "q1"],
initialState: "q0",
acceptStates: ["q1"],
transitions: [
{
from: "q0",
to: "q0",
label: "ε, z0 → A z0\n"
},
{
from: "q0",
to: "q0",
label: "ε, z0 → B z0"
},
{
from: "q0",
to: "q1",
label: "ε, z0 → z0"
},
{
from: "q1",
to: "q1",
label: "a, A → ε"
},
{
from: "q1",
to: "q1",
label: "b, B → ε"
}
]
};
function renderNode({
ctx,
id,
x,
y,
state: { selected, hover },
style,
label,
}: any) {
return {
drawNode() {
ctx.save();
const r = style.size;
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
ctx.lineWidth = 4;
ctx.strokeStyle = "blue";
ctx.stroke();
ctx.fillStyle = "black";
ctx.textAlign = 'center';
ctx.fillText(label, x, y, r);
ctx.textAlign = 'center';
ctx.strokeStyle = 'white';
ctx.fillStyle = "black";
let cy = y - (r + 10);
for (const part of "meow[]\nbeeep".split("\n").reverse()) {
const metrics = ctx.measureText(part);
cy -= metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
ctx.strokeText(part, x, cy);
ctx.fillText(part, x, cy);
}
ctx.restore();
},
nodeDimensions: { width: 20, height: 20 },
};
}
// Populate nodes
for (const state of automaton.states) {
nodes.add({
id: state,
label: state,
});
}
// Populate edges
automaton.transitions.forEach((t, i) => {
edges.add({
id: `e${i}`,
from: t.from,
to: t.to,
label: t.label
});
});
function chosen_edge(_: vis.ChosenNodeValues, id: vis.IdType,selected: boolean, hovered: boolean) {
console.log("edge", id, selected, hovered)
}
function chosen_node(_: vis.ChosenNodeValues, id: vis.IdType,selected: boolean, hovered: boolean) {
console.log("node", id, selected, hovered)
}
const network: vis.Network = createGraph();
function createGraph(): vis.Network {
const container = document.getElementById("graph")!;
const network = new vis.Network(
container,
{ nodes, edges },
{
layout: { improvedLayout: true },
physics: {
enabled: true,
solver: "barnesHut",
barnesHut: { gravitationalConstant: -8000, springLength: 120, springConstant: 0.04 },
stabilization: { iterations: 200 }
},
interaction: {
dragNodes: true,
hover: true,
multiselect: true,
hoverConnectedEdges: false,
selectConnectedEdges: false,
},
nodes: {
font: { color: "#c9d1d9" },
color: {
background: "#1f6feb",
border: "#79c0ff",
highlight: { background: "#388bfd", border: "#a5d6ff" }
},
// @ts-expect-error bad library
chosen: {
node: chosen_node,
},
shape: "custom",
ctxRenderer: renderNode,
size: 18,
},
edges: {
chosen: {
// @ts-expect-error bad library
edge: chosen_edge
},
arrowStrikethrough: false,
font: { align: "middle", color: "#000000ff" },
color: { color: "rgba(201,209,217,0.35)", highlight: "#c9d1d9" },
// @ts-expect-error bad library
smooth: { type: "dynamic" },
arrows: "to",
}
}
);
vis.DataSet
network.on("doubleClick", (params: any) => {
for (const node_id of params.nodes){
// @ts-expect-error bad library
const node: vis.Node = nodes.get(node_id)!;
node.physics = !node.physics;
nodes.update(node)
}
});
return network;
}

View file

@ -1,22 +1,21 @@
console.debug("Loading wasm…");
import init, * as wasm from "./automata/automata_web.js";
import init, * as wasm from "../../wasm/automata_web.js";
try{
console.debug("Wasm loaded. Starting app…");
window.wasm = wasm;
await init();
console.debug("App started.");
document.getElementById("center_text").innerHTML = '';
document.getElementById("app").style.display = '';
document.getElementById("center_text")!.innerHTML = '';
document.getElementById("app")!.style.display = '';
wasm.init();
}catch(e){
console.error("Failed to start: " + error);
document.getElementById("the_canvas_id").remove();
document.getElementById("center_text").innerHTML = `
console.error("Failed to start: " + e);
document.getElementById("the_canvas_id")!.remove();
document.getElementById("center_text")!.innerHTML = `
<p>
An error occurred during loading:
</p>
<p style="font-family:Courier New">
${error}
${e}
</p>
<p style="font-size:14px">
Make sure you use a modern browser with WebGL and WASM enabled.

127
web/root/style/editor.scss Normal file
View file

@ -0,0 +1,127 @@
@import url("tooltip.scss");
/* CodeMirror mount point */
#editor {
height: 100%;
width: 100%;
}
/* VERY IMPORTANT: force CodeMirror to respect container height */
.cm-editor {
height: 100%;
}
/* CodeMirrors internal scroller (this is where the scrollbar lives) */
.cm-scroller {
overflow-y: auto !important;
}
.diag {
margin: 0;
padding-left: 18px;
}
.diag li {
margin: 6px 0;
}
/* --- Syntax colors via CSS classes applied by decorations --- */
.tok-comment {
color: #1a7b24;
}
.tok-keyword {
color: #b99400;
font-weight: 600;
}
.tok-error {
color: #ff0505;
font-weight: 1000;
}
.tok-ident {
color: #90d4e0;
}
.tok-brace {
color: #d73a49;
font-weight: 600;
}
.tok-punc {
color: #ffffff;
}
.tok-string {
color: #03621e;
}
/* Rainbow bracket depth classes */
.rb-0 {
color: #a35;
font-weight: 700;
}
.rb-1 {
color: #ed0;
font-weight: 700;
}
.rb-2 {
color: #9d5;
font-weight: 700;
}
.rb-3 {
color: #2cb;
font-weight: 700;
}
.rb-4 {
color: #36b;
font-weight: 700;
}
.rb-5 {
color: #639;
font-weight: 700;
}
/* Optional: diagnostics panel coloring */
.diag li.error {
color: #d73a49;
}
.diag li.warning {
color: #b08800;
}
.diag li.info {
color: #0366d6;
}
/* Severity underline styles */
.cm-diag-error {
text-decoration: underline wavy #d73a49;
/* red */
text-underline-offset: 2px;
}
.cm-diag-warning {
text-decoration: underline wavy #ffd33d;
/* yellow */
text-underline-offset: 2px;
}
.cm-diag-info {
text-decoration: underline wavy #79c0ff;
/* cyan-ish */
text-underline-offset: 2px;
}

140
web/root/style/style.scss Normal file
View file

@ -0,0 +1,140 @@
@import url("./editor.scss");;
@import url("./terminal.scss");
html,
body {
height: 100%;
width: 100%;
margin: 0;
font-family: system-ui, sans-serif;
background: #909090;
}
.wrap {
height: 100vh;
width: 100vw;
display: grid;
grid-template-rows: var(--topH, 50vh) 8px 1fr;
/* top pane, splitter, editor */
overflow: hidden;
}
/* Top pane: terminal area */
.topPane {
display: grid;
grid-template-columns: var(--termW, 50vw) 8px 1fr;
/* terminal, splitter, filler */
min-height: 80px;
overflow: hidden;
}
/* Bottom pane: editor */
.bottomPane {
min-height: 120px;
overflow: hidden;
}
/* App layout */
.app {
height: 100vh;
width: 100vw;
display: grid;
grid-template-rows: var(--canvasH, 35vh) 8px 1fr;
overflow: hidden;
}
/* ---------- Canvas ---------- */
.canvasPane {
position: relative;
overflow: hidden;
background: #111;
}
.graph {
width: 100%;
height: 100%;
}
/* ---------- Bottom area (terminal + editor) ---------- */
/* Bottom area (terminal + editor) */
.bottomPane {
height: 100%;
display: grid;
grid-template-columns: 1fr 8px var(--termW, 40vw);
overflow: hidden;
/* IMPORTANT */
}
/* Terminal side */
.terminalPane {
height: 100%;
overflow: hidden;
}
/* Editor side */
.editorPane {
height: 100%;
overflow: hidden;
/* let CodeMirror scroll */
}
/* ---------- Splitters ---------- */
.hSplit {
cursor: row-resize;
background: rgba(255, 255, 255, 0.06);
}
.vSplit {
cursor: col-resize;
background: rgba(255, 255, 255, 0.06);
}
.hSplit:hover,
.vSplit:hover {
background: rgba(121, 192, 255, 0.25);
}
/* Loading screen */
.centered {
margin-right: auto;
margin-left: auto;
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #f0f0f0;
font-size: 24px;
font-family: Ubuntu-Light, Helvetica, sans-serif;
text-align: center;
}
.lds-dual-ring {
display: inline-block;
width: 24px;
height: 24px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 24px;
height: 24px;
margin: 0px;
border-radius: 50%;
border: 3px solid #fff;
border-color: #fff transparent #fff transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View file

@ -0,0 +1,48 @@
.terminal {
background: #0b0f14;
color: #c9d1d9;
padding: 1em;
margin: 0px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: 12.5px;
line-height: 1.35;
white-space: pre-wrap;
word-break: break-word;
height: 100%;
width: 100%;
overflow-y: auto;
overflow-x: auto;
}
/* ANSI text styles */
.ansi-bold { font-weight: 700; }
.ansi-dim { opacity: 0.7; }
/* Foreground colors (standard + bright) */
.ansi-fg-30 { color: #0b0f14; } /* black */
.ansi-fg-31 { color: #ff7b72; } /* red */
.ansi-fg-32 { color: #7ee787; } /* green */
.ansi-fg-33 { color: #f2cc60; } /* yellow */
.ansi-fg-34 { color: #79c0ff; } /* blue */
.ansi-fg-35 { color: #d2a8ff; } /* magenta */
.ansi-fg-36 { color: #a5d6ff; } /* cyan */
.ansi-fg-37 { color: #c9d1d9; } /* white */
.ansi-fg-90 { color: #6e7681; } /* bright black / gray */
.ansi-fg-91 { color: #ffa198; }
.ansi-fg-92 { color: #a6f3a6; }
.ansi-fg-93 { color: #ffe082; }
.ansi-fg-94 { color: #a5d6ff; }
.ansi-fg-95 { color: #e3b8ff; }
.ansi-fg-96 { color: #c7f0ff; }
.ansi-fg-97 { color: #ffffff; }
/* Background colors (optional) */
.ansi-bg-40 { background: #0b0f14; }
.ansi-bg-41 { background: rgba(255, 123, 114, 0.22); }
.ansi-bg-42 { background: rgba(126, 231, 135, 0.18); }
.ansi-bg-43 { background: rgba(242, 204, 96, 0.18); }
.ansi-bg-44 { background: rgba(121, 192, 255, 0.18); }
.ansi-bg-45 { background: rgba(210, 168, 255, 0.18); }
.ansi-bg-46 { background: rgba(165, 214, 255, 0.18); }
.ansi-bg-47 { background: rgba(201, 209, 217, 0.10); }

View file

@ -0,0 +1,32 @@
.tipTitle.error {
color: #d73a49;
}
.tipTitle.warning {
color: #ffd33d;
}
.tipTitle.info {
color: #79c0ff;
}
.cm-tooltip.cm-tooltip-hover {
border: 1px solid #ddd;
background: black;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
border-radius: 10px;
padding: 8px 10px;
max-width: 420px;
font-size: 13px;
line-height: 1.35;
}
.tipTitle {
font-weight: 700;
margin-bottom: 4px;
}
.tipBody {
white-space: pre-wrap;
}

View file

@ -1,6 +1,5 @@
use automata::{
automata::npda::{self, NPDA},
loader::{self, Span, Spanned, lexer::Lexer},
loader::{self, Context, Span, Spanned, lexer::Lexer},
};
use wasm_bindgen::prelude::wasm_bindgen;
@ -18,40 +17,6 @@ pub fn init() {
console_error_panic_hook::set_once();
}
#[wasm_bindgen]
pub fn silly(machine: &str, input: &str) {
let table = match npda::TransitionTable::load_table(machine) {
Ok((ok, logs)) => {
for log in logs.displayable() {
println!("{log}")
}
ok
}
Err(logs) => {
for log in logs.displayable() {
println!("{log}")
}
return;
}
};
println!("running on: '{input}'");
let mut simulator = npda::Simulator::begin(input, table);
loop {
match simulator.step() {
npda::SimulatorResult::Pending => {}
npda::SimulatorResult::Reject => {
println!("REJECTED");
break;
}
npda::SimulatorResult::Accept(npda) => {
println!("ACCEPT: {npda:?}");
break;
}
}
}
}
#[wasm_bindgen]
#[derive(Clone, Copy)]
pub enum Kind {
@ -191,18 +156,16 @@ pub struct CompileResult {
#[wasm_bindgen]
pub fn compile(input: &str) -> CompileResult {
let log = match npda::TransitionTable::load_table(input) {
Ok((_, logs)) => logs,
Err(logs) => logs,
};
let mut ctx = Context::new(input);
_ = automata::loader::parse_universal(&mut ctx);
use std::fmt::Write;
let log_formatted = log.displayable().fold(String::new(), |mut s, e| {
let log_formatted = ctx.logs_display().fold(String::new(), |mut s, e| {
write!(&mut s, "{e}").unwrap();
s
});
let log = log
let log = ctx.into_logs()
.into_entries()
.map(|e| CompileLog {
level: match e.level {

44
web/tools/build.ts Normal file
View file

@ -0,0 +1,44 @@
import * as sass from "sass";
// tools/build.ts
const ROOT = new URL("../root/", import.meta.url);
const WASM = new URL("../wasm/", import.meta.url);
const DIST = new URL("../dist/", import.meta.url);
async function run(cmd: string[], cwd?: string) {
const p = new Deno.Command(cmd[0], { args: cmd.slice(1), cwd });
const out = await p.output();
if (!out.success) {
console.error(new TextDecoder().decode(out.stderr));
Deno.exit(out.code);
}
}
// Clean dist
await Deno.remove(DIST, { recursive: true }).catch(() => {});
await Deno.mkdir(DIST, { recursive: true });
console.log("compiling scss...");
const result = sass.compile(String(new URL("style/style.scss", ROOT).pathname), {
style: "compressed",
});
await Deno.writeTextFile(new URL("style.css", DIST), result.css);
console.log("Compiling wasm lib...");
await run(["wasm-pack", "build", "--target", "web", "--release", "--out-dir", "wasm"], "");
await Deno.copyFile(new URL("automata_web_bg.wasm", WASM), new URL("automata_web_bg.wasm", DIST));
console.log("Compiling bundle...");
const bundle = new Deno.Command(Deno.execPath(), {
args: ["bundle", "--platform=browser", "--outdir", "dist", "root/index.html", "--minify",],
});
const bundleRes = await bundle.output();
if (!bundleRes.success) {
console.error(new TextDecoder().decode(bundleRes.stderr));
Deno.exit(bundleRes.code);
}
console.log("Build complete: dist/");

58
web/tools/dev.ts Normal file
View file

@ -0,0 +1,58 @@
// tools/dev.ts
const BUILD_CMD = ["deno", "run", "-A", "tools/build.ts"];
const SERVE_CMD = [
"deno",
"run",
"--allow-net",
"--allow-read",
"--allow-sys",
"jsr:@std/http/file-server",
"dist",
];
let building = false;
let server: Deno.ChildProcess | null = null;
async function runBuild() {
if (building) return;
building = true;
console.log("🔨 building…");
const p = new Deno.Command(BUILD_CMD[0], {
args: BUILD_CMD.slice(1),
});
const r = await p.output();
if (!r.success) {
console.error(new TextDecoder().decode(r.stderr));
} else {
console.log("✅ build complete");
}
building = false;
}
async function startServer() {
if (server) return;
const p = new Deno.Command(SERVE_CMD[0], {
args: SERVE_CMD.slice(1),
stdout: "inherit",
stderr: "inherit",
});
server = p.spawn();
}
await runBuild();
await startServer();
console.log("👀 watching for changes…");
const watcher = Deno.watchFs(["root", "../src"]);
for await (const event of watcher) {
if (
event.kind === "modify" ||
event.kind === "create" ||
event.kind === "remove"
) {
await runBuild();
}
}