+
diff --git a/web/root/src/editor.ts b/web/root/src/editor.ts
index 968b96f..7c96d26 100644
--- a/web/root/src/editor.ts
+++ b/web/root/src/editor.ts
@@ -307,75 +307,4 @@ const state = EditorState.create({
const editor = new EditorView({
state,
parent: document.getElementById("editor")!,
-});
-
-
-function setDefaultLayoutWeights() {
- const vh = globalThis.window.innerHeight;
- const vw = globalThis.window.innerWidth;
-
- // Canvas: 30% of screen height
- const canvasH = Math.round(vh * 0.60);
-
- // Terminal: 35% of width
- const termW = Math.round(vw * 0.30);
-
- const app = document.getElementById("app")!;
- app.style.setProperty("--canvasH", `${canvasH}px`);
- app.style.setProperty("--termW", `${termW}px`);
-}
-
-setDefaultLayoutWeights();
-
-
-(function enableLayoutSplitters() {
- 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");
-
- let draggingH = false;
- let draggingV = false;
-
- hSplit.addEventListener("mousedown", (e) => {
- draggingH = true;
- document.body.style.cursor = "row-resize";
- e.preventDefault();
- });
-
- vSplit.addEventListener("mousedown", (e) => {
- draggingV = true;
- document.body.style.cursor = "col-resize";
- e.preventDefault();
- });
-
- globalThis.window.addEventListener("mousemove", (e) => {
- const rect = app.getBoundingClientRect();
-
- if (draggingH) {
- const y = e.clientY - rect.top;
- const minCanvas = 80;
- const minBottom = 180;
- const maxCanvas = rect.height - 8 - minBottom;
- const canvasH = Math.max(minCanvas, Math.min(maxCanvas, y));
- app.style.setProperty("--canvasH", `${canvasH}px`);
- }
-
- if (draggingV) {
- const bottomPane = document.getElementById("bottomPane")!;
- const r = bottomPane.getBoundingClientRect();
- const x = e.clientX - r.left;
- const minTerm = 220;
- const maxTerm = r.width - 8 - 220;
- const termW = Math.max(minTerm, Math.min(maxTerm, r.width - x));
- app.style.setProperty("--termW", `${termW}px`);
- }
- });
-
- globalThis.window.addEventListener("mouseup", () => {
- draggingH = false;
- draggingV = false;
- document.body.style.cursor = "";
- });
-})();
+});
\ No newline at end of file
diff --git a/web/root/src/main.ts b/web/root/src/main.ts
index 05f0878..26a43e6 100644
--- a/web/root/src/main.ts
+++ b/web/root/src/main.ts
@@ -1,4 +1,4 @@
import "./editor.ts"
import "./visualizer.ts"
-
+import "./splitters.ts"
\ No newline at end of file
diff --git a/web/root/src/splitters.ts b/web/root/src/splitters.ts
new file mode 100644
index 0000000..ea46dd5
--- /dev/null
+++ b/web/root/src/splitters.ts
@@ -0,0 +1,168 @@
+type Axis = "x" | "y";
+
+function clamp(n: number, min: number, max: number) {
+ return Math.max(min, Math.min(max, n));
+}
+
+function parsePx(v: string | null, fallback: number): number {
+ if (!v) return fallback;
+ const s = v.trim().toLowerCase();
+ if (s.endsWith("px")) {
+ const n = Number(s.slice(0, -2));
+ return Number.isFinite(n) ? n : fallback;
+ }
+ const n = Number(s);
+ return Number.isFinite(n) ? n : fallback;
+}
+
+function parsePercent(v: string | null, fallbackPct: number): number {
+ if (!v) return fallbackPct;
+ const s = v.trim().toLowerCase();
+ if (s.endsWith("%")) {
+ const n = Number(s.slice(0, -1));
+ return Number.isFinite(n) ? n : fallbackPct;
+ }
+ const n = Number(s);
+ return Number.isFinite(n) ? n : fallbackPct;
+}
+
+function getCssVar(el: HTMLElement, name: string): string | null {
+ const v = getComputedStyle(el).getPropertyValue(name);
+ return v ? v.trim() : null;
+}
+
+/**
+ * Generic rule:
+ * - hSplit controls the size of the FIRST pane (top) as a percent of parent height
+ * - vSplit controls the size of the THIRD pane (right) as a percent of parent width
+ *
+ * This matches common editor layouts:
+ * rows: [A][split][B] => A sized, B flex
+ * cols: [A][split][B] => B sized, A flex
+ */
+export function enableGenericSplitters() {
+ enableAll("y", ".hSplit");
+ enableAll("x", ".vSplit");
+}
+
+function enableAll(axis: Axis, selector: string) {
+ for (const splitter of document.querySelectorAll
(selector)) {
+ const parent = splitter.parentElement as HTMLElement | null;
+ if (!parent) continue;
+
+ // Require exactly A | splitter | B
+ const kids = Array.from(parent.children);
+ if (kids.length !== 3 || kids[1] !== splitter) {
+ console.warn("Splitter parent must have exactly 3 children: A | splitter | B", parent);
+ continue;
+ }
+
+ const gap = axis === "y" ? splitter.getBoundingClientRect().height || 8
+ : splitter.getBoundingClientRect().width || 8;
+
+ // Read per-splitter overrides from CSS variables (optional)
+ // Defaults:
+ // - default size = 60% (hSplit) or 30% (vSplit)
+ // - minA/minB = 80/180 for hSplit, 220/220 for vSplit
+ const defaultPct = parsePercent(
+ getCssVar(splitter, "--split-default"),
+ axis === "y" ? 60 : 30,
+ );
+
+ const minA = parsePx(
+ getCssVar(splitter, "--split-min-a"),
+ axis === "y" ? 80 : 220,
+ );
+
+ const minB = parsePx(
+ getCssVar(splitter, "--split-min-b"),
+ axis === "y" ? 180 : 220,
+ );
+
+ // Make parent a grid automatically (no container classes needed)
+ parent.style.display = "grid";
+ parent.style.overflow = "hidden";
+
+ // Apply initial template if none set yet
+ if (axis === "y") {
+ // top sized in %, bottom flex
+ if (!parent.style.gridTemplateRows) {
+ parent.style.gridTemplateRows = `${defaultPct}% ${gap}px 1fr`;
+ }
+ } else {
+ // right sized in %, left flex
+ if (!parent.style.gridTemplateColumns) {
+ parent.style.gridTemplateColumns = `1fr ${gap}px ${defaultPct}%`;
+ }
+ }
+
+ let dragging = false;
+
+ splitter.addEventListener("pointerdown", (e) => {
+ dragging = true;
+ splitter.setPointerCapture(e.pointerId);
+ document.body.style.cursor = axis === "y" ? "row-resize" : "col-resize";
+ e.preventDefault();
+ });
+
+ splitter.addEventListener("pointermove", (e) => {
+ if (!dragging) return;
+ const rect = parent.getBoundingClientRect();
+
+ if (axis === "y") {
+ // control FIRST pane size (top) by mouse Y
+ const y = e.clientY - rect.top;
+ const maxA = rect.height - gap - minB;
+ const newA = clamp(y, minA, maxA);
+ const pct = (newA / rect.height) * 100;
+ parent.style.gridTemplateRows = `${pct}% ${gap}px 1fr`;
+ } else {
+ // control THIRD pane size (right) by distance from right edge
+ const xFromRight = rect.right - e.clientX;
+ const maxB = rect.width - gap - minA;
+ const newB = clamp(xFromRight, minB, maxB);
+ const pct = (newB / rect.width) * 100;
+ parent.style.gridTemplateColumns = `1fr ${gap}px ${pct}%`;
+ }
+ });
+
+ splitter.addEventListener("pointerup", (e) => {
+ dragging = false;
+ document.body.style.cursor = "";
+ splitter.releasePointerCapture(e.pointerId);
+ });
+
+ splitter.addEventListener("pointercancel", () => {
+ dragging = false;
+ document.body.style.cursor = "";
+ });
+
+ // Optional: keep within bounds on resize (no stored state needed)
+ globalThis.window.addEventListener("resize", () => {
+ const rect = parent.getBoundingClientRect();
+ if (axis === "y") {
+ // read current pct from template if possible; otherwise skip
+ const parts = (parent.style.gridTemplateRows || "").split(" ");
+ if (parts.length >= 3 && parts[0].endsWith("%")) {
+ const pct = parseFloat(parts[0]);
+ const px = (pct / 100) * rect.height;
+ const maxA = rect.height - gap - minB;
+ const clampedPx = clamp(px, minA, maxA);
+ const clampedPct = (clampedPx / rect.height) * 100;
+ parent.style.gridTemplateRows = `${clampedPct}% ${gap}px 1fr`;
+ }
+ } else {
+ const parts = (parent.style.gridTemplateColumns || "").split(" ");
+ if (parts.length >= 3 && parts[2].endsWith("%")) {
+ const pct = parseFloat(parts[2]);
+ const px = (pct / 100) * rect.width;
+ const maxB = rect.width - gap - minA;
+ const clampedPx = clamp(px, minB, maxB);
+ const clampedPct = (clampedPx / rect.width) * 100;
+ parent.style.gridTemplateColumns = `1fr ${gap}px ${clampedPct}%`;
+ }
+ }
+ });
+ }
+}
+enableGenericSplitters();
\ No newline at end of file
diff --git a/web/root/style/editor.scss b/web/root/style/editor.scss
index 7364b32..85299d4 100644
--- a/web/root/style/editor.scss
+++ b/web/root/style/editor.scss
@@ -1,17 +1,16 @@
-@import url("tooltip.scss");
+@use "tooltip.scss";
-/* CodeMirror mount point */
-#editor {
+
+.editor {
height: 100%;
width: 100%;
}
-/* VERY IMPORTANT: force CodeMirror to respect container height */
+
.cm-editor {
height: 100%;
}
-/* CodeMirror’s internal scroller (this is where the scrollbar lives) */
.cm-scroller {
overflow-y: auto !important;
}
diff --git a/web/root/style/loading.scss b/web/root/style/loading.scss
new file mode 100644
index 0000000..822e310
--- /dev/null
+++ b/web/root/style/loading.scss
@@ -0,0 +1,41 @@
+.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);
+ }
+}
\ No newline at end of file
diff --git a/web/root/style/style.scss b/web/root/style/style.scss
index f4980ac..f5b65f3 100644
--- a/web/root/style/style.scss
+++ b/web/root/style/style.scss
@@ -1,5 +1,6 @@
-@import url("./editor.scss");;
-@import url("./terminal.scss");
+@use "editor.scss";
+@use "terminal.scss";
+@use "loading.scss";
html,
body {
@@ -10,131 +11,36 @@ body {
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 {
+ width: 100%;
height: 100%;
- display: grid;
- grid-template-columns: 1fr 8px var(--termW, 40vw);
- overflow: hidden;
- /* IMPORTANT */
+ background: #111;
}
-/* Terminal side */
-.terminalPane {
+.vscroll {
height: 100%;
- overflow: hidden;
+ overflow-x: scroll;
}
-/* Editor side */
-.editorPane {
- height: 100%;
- overflow: hidden;
- /* let CodeMirror scroll */
-}
-
-/* ---------- Splitters ---------- */
.hSplit {
cursor: row-resize;
+ height: 8px;
background: rgba(255, 255, 255, 0.06);
}
.vSplit {
cursor: col-resize;
+ width: 8px;
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);
- }
}
\ No newline at end of file
diff --git a/web/root/style/visualizer.scss b/web/root/style/visualizer.scss
new file mode 100644
index 0000000..e69de29
diff --git a/web/tools/build.ts b/web/tools/build.ts
index 1f22cbc..bc88fe0 100644
--- a/web/tools/build.ts
+++ b/web/tools/build.ts
@@ -20,7 +20,6 @@ await Deno.mkdir(DIST, { recursive: true });
console.log("compiling scss...");
-
const result = sass.compile(String(new URL("style/style.scss", ROOT).pathname), {
style: "compressed",
});
@@ -30,7 +29,6 @@ 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",],