Merge pull request #2292 from alixander/d2js-dev-server
d2js: dev server changes
This commit is contained in:
commit
fcbe464a16
4 changed files with 158 additions and 35 deletions
|
|
@ -11,6 +11,11 @@ fmt: node_modules
|
||||||
build: fmt
|
build: fmt
|
||||||
prefix "$@" ./ci/build.sh
|
prefix "$@" ./ci/build.sh
|
||||||
|
|
||||||
|
.PHONY: dev
|
||||||
|
dev: build
|
||||||
|
prefix "$@" git checkout -- src/platform.js src/worker.js
|
||||||
|
prefix "$@" bun run dev
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: build
|
test: build
|
||||||
prefix "$@" bun test:all
|
prefix "$@" bun test:all
|
||||||
|
|
|
||||||
|
|
@ -77,12 +77,12 @@ If you change the main D2 source code, you should regenerate the WASM file:
|
||||||
./make.sh build
|
./make.sh build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running the Development Server
|
### Running the dev server
|
||||||
|
|
||||||
Make sure you've built already, then run:
|
You can browse the examples by running the dev server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run dev
|
./make.sh dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Visit `http://localhost:3000` to see the example page.
|
Visit `http://localhost:3000` to see the example page.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
const fs = require("fs/promises");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
const MIME_TYPES = {
|
const MIME_TYPES = {
|
||||||
".html": "text/html",
|
".html": "text/html",
|
||||||
".js": "text/javascript",
|
".js": "text/javascript",
|
||||||
|
|
@ -11,34 +14,45 @@ const server = Bun.serve({
|
||||||
port: 3000,
|
port: 3000,
|
||||||
async fetch(request) {
|
async fetch(request) {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
let path = url.pathname;
|
let filePath = url.pathname.slice(1); // Remove leading "/"
|
||||||
|
|
||||||
// Serve index page by default
|
if (filePath === "") {
|
||||||
if (path === "/") {
|
filePath = "examples/";
|
||||||
path = "/examples/basic.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle attempts to access files in src
|
|
||||||
if (path.startsWith("/src/")) {
|
|
||||||
const wasmFile = path.includes("wasm_exec.js") || path.includes("d2.wasm");
|
|
||||||
if (wasmFile) {
|
|
||||||
path = path.replace("/src/", "/wasm/");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const filePath = path.slice(1);
|
const fullPath = path.join(process.cwd(), filePath);
|
||||||
const file = Bun.file(filePath);
|
const stats = await fs.stat(fullPath);
|
||||||
const exists = await file.exists();
|
|
||||||
|
|
||||||
if (!exists) {
|
if (stats.isDirectory()) {
|
||||||
return new Response(`File not found: ${path}`, { status: 404 });
|
const entries = await fs.readdir(fullPath);
|
||||||
}
|
const links = await Promise.all(
|
||||||
|
entries.map(async (entry) => {
|
||||||
|
const entryPath = path.join(fullPath, entry);
|
||||||
|
const isDir = (await fs.stat(entryPath)).isDirectory();
|
||||||
|
const slash = isDir ? "/" : "";
|
||||||
|
return `<li><a href="${filePath}${entry}${slash}">${entry}${slash}</a></li>`;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Get file extension and corresponding MIME type
|
const html = `
|
||||||
const ext = "." + filePath.split(".").pop();
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Examples</h1>
|
||||||
|
<ul>
|
||||||
|
${links.join("")}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
return new Response(html, {
|
||||||
|
headers: { "Content-Type": "text/html" },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const ext = path.extname(filePath);
|
||||||
const mimeType = MIME_TYPES[ext] || "application/octet-stream";
|
const mimeType = MIME_TYPES[ext] || "application/octet-stream";
|
||||||
|
|
||||||
|
const file = Bun.file(filePath);
|
||||||
return new Response(file, {
|
return new Response(file, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": mimeType,
|
"Content-Type": mimeType,
|
||||||
|
|
@ -47,9 +61,10 @@ const server = Bun.serve({
|
||||||
"Cross-Origin-Embedder-Policy": "require-corp",
|
"Cross-Origin-Embedder-Policy": "require-corp",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Error serving ${path}:`, err);
|
console.error(`Error serving ${filePath}:`, err);
|
||||||
return new Response(`Server error: ${err.message}`, { status: 500 });
|
return new Response(`File not found: ${filePath}`, { status: 404 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
103
d2js/js/examples/customizable.html
Normal file
103
d2js/js/examples/customizable.html
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
.layout-toggle {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.radio-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.radio-label {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #0066cc;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #0052a3;
|
||||||
|
}
|
||||||
|
#output {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
#output svg {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 90vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="controls">
|
||||||
|
<textarea id="input">x -> y</textarea>
|
||||||
|
<div class="layout-toggle">
|
||||||
|
<span>Layout:</span>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label class="radio-label">
|
||||||
|
<input type="radio" name="layout" value="dagre" checked />
|
||||||
|
Dagre
|
||||||
|
</label>
|
||||||
|
<label class="radio-label">
|
||||||
|
<input type="radio" name="layout" value="elk" />
|
||||||
|
ELK
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button onclick="compile()">Compile</button>
|
||||||
|
</div>
|
||||||
|
<div id="output"></div>
|
||||||
|
<script type="module">
|
||||||
|
import { D2 } from "../dist/browser/index.js";
|
||||||
|
const d2 = new D2();
|
||||||
|
window.compile = async () => {
|
||||||
|
const input = document.getElementById("input").value;
|
||||||
|
const layout = document.querySelector('input[name="layout"]:checked').value;
|
||||||
|
try {
|
||||||
|
const result = await d2.compile(input, { layout });
|
||||||
|
const svg = await d2.render(result.diagram);
|
||||||
|
document.getElementById("output").innerHTML = svg;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
document.getElementById("output").textContent = err.message;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
compile();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in a new issue