mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-06 21:24:06 -04:00
Merge branch 'main' into gh-pages
This commit is contained in:
commit
6784b199bb
12 changed files with 197 additions and 159 deletions
|
|
@ -211,8 +211,9 @@ impl Npda {
|
|||
State(0)
|
||||
}
|
||||
};
|
||||
if states.insert(ident, state).is_some() {
|
||||
if let Some(old) = states.insert(ident, state) {
|
||||
ctx.emit_error("state redefined", item.1);
|
||||
states.insert(ident, old);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -284,8 +285,9 @@ impl Npda {
|
|||
Symbol(0)
|
||||
}
|
||||
};
|
||||
if stack_symbols.insert(ident, symbol).is_some() {
|
||||
if let Some(old) = stack_symbols.insert(ident, symbol) {
|
||||
ctx.emit_error("stack symbol redefined", item.1);
|
||||
stack_symbols.insert(ident, old);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
BIN
web/root/assets/icon.jpg
Normal file
BIN
web/root/assets/icon.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -6,6 +6,7 @@
|
|||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Automata</title>
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="icon.jpg">
|
||||
<link href="style.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
|
|
@ -36,7 +37,7 @@
|
|||
|
||||
<div class="hSplit" style="--split-default: 50%" title="Drag to resize canvas height"></div>
|
||||
|
||||
<div style="padding-bottom: 10px">
|
||||
<div class="flexCol">
|
||||
<div class="controls" style="background: var(--bg-0)">
|
||||
<button id="togglePhysics" class="btn btn-toggle active" title="Toggle physics layout">
|
||||
Physics: ON
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ togglePhysicsBtn.onclick = () => {
|
|||
const enabled = !togglePhysicsBtn.classList.contains("active");
|
||||
setPhysicsButtonUI(enabled);
|
||||
network.setOptions({ physics: { enabled } });
|
||||
network.setOptions({edges: {smooth: enabled}});
|
||||
};
|
||||
|
||||
setPhysicsButtonUI(togglePhysicsBtn.classList.contains("active"));
|
||||
|
|
|
|||
|
|
@ -154,10 +154,7 @@ export function enableFlexSplitters() {
|
|||
const gap = splitter.getBoundingClientRect().width || 8;
|
||||
splitter.style.flex = `0 0 ${gap}px`;
|
||||
|
||||
// Optional per-splitter CSS vars:
|
||||
// --split-default: 30% (right pane width)
|
||||
// --split-min-a: 220px (min left)
|
||||
// --split-min-b: 220px (min right)
|
||||
|
||||
const defPct = getVarPct(splitter, "--split-default", 50);
|
||||
const minA = getVarPx(splitter, "--split-min-a", 220);
|
||||
const minB = getVarPx(splitter, "--split-min-b", 220);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
import { invalidateGraphThemeCache, network } from "./visualizer.ts";
|
||||
|
||||
function cssVar(name: string, fallback = ""): string {
|
||||
return getComputedStyle(document.documentElement)
|
||||
.getPropertyValue(name)
|
||||
.trim() || fallback;
|
||||
}
|
||||
import { updateGraphTheme } from "./visualizer.ts";
|
||||
|
||||
const themeBtn = document.getElementById("themeToggle") as HTMLButtonElement;
|
||||
|
||||
|
|
@ -28,60 +22,25 @@ function setTheme(theme: Theme) {
|
|||
|
||||
// update button label
|
||||
themeBtn.textContent = theme === "dark" ? "🌙 Dark" : "☀️ Light";
|
||||
applyGraphTheme();
|
||||
updateGraphTheme();
|
||||
}
|
||||
|
||||
|
||||
setTheme(getPreferredTheme());
|
||||
|
||||
themeBtn.addEventListener("click", toggleTheme);
|
||||
function toggleTheme() {
|
||||
const current = (document.documentElement.dataset.theme as Theme) || "dark";
|
||||
setTheme(current === "dark" ? "light" : "dark");
|
||||
}
|
||||
|
||||
// init
|
||||
setTheme(getPreferredTheme());
|
||||
|
||||
// click handler
|
||||
themeBtn.addEventListener("click", toggleTheme);
|
||||
|
||||
// optional: respond to OS theme changes (only if user hasn't chosen a theme)
|
||||
globalThis.window.matchMedia?.("(prefers-color-scheme: light)")
|
||||
?.addEventListener("change", () => {
|
||||
if (localStorage.getItem("theme")) return; // user has chosen, don't override
|
||||
if (localStorage.getItem("theme")) return;
|
||||
setTheme(getPreferredTheme());
|
||||
});
|
||||
|
||||
function applyGraphTheme() {
|
||||
invalidateGraphThemeCache();
|
||||
|
||||
network.setOptions({
|
||||
nodes: {
|
||||
font: {
|
||||
color: cssVar("--graph-node-text"),
|
||||
},
|
||||
},
|
||||
edges: {
|
||||
labelHighlightBold: true,
|
||||
font: {
|
||||
align: "middle",
|
||||
color: cssVar("--fg-0"),
|
||||
strokeColor: cssVar("--bg-0"),
|
||||
bold: {
|
||||
color: cssVar("--fg-1"),
|
||||
mod: ''
|
||||
},
|
||||
},
|
||||
color: {
|
||||
color: cssVar("--graph-edge"),
|
||||
highlight: cssVar("--graph-edge-active"),
|
||||
hover: cssVar("--graph-edge-hover"),
|
||||
},
|
||||
shadow: {
|
||||
enabled: true,
|
||||
color: cssVar("--bg-2")
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,106 @@ import * as vis from "npm:vis-network/standalone";
|
|||
export const nodes = new vis.DataSet<vis.Node>();
|
||||
export const edges = new vis.DataSet<vis.Edge>();
|
||||
|
||||
|
||||
type Color = string;
|
||||
type GraphTheme = {
|
||||
bg_0: Color;
|
||||
bg_1: Color;
|
||||
bg_2: Color;
|
||||
fg_0: Color;
|
||||
fg_1: Color;
|
||||
fg_2: Color;
|
||||
|
||||
node_anchor: Color;
|
||||
node_border: Color;
|
||||
current_node_border: Color;
|
||||
|
||||
edge: Color;
|
||||
edge_hover: Color;
|
||||
edge_active: Color;
|
||||
|
||||
edge_font_size: number,
|
||||
node_font_size: number,
|
||||
};
|
||||
|
||||
let _graphTheme: GraphTheme | null = null;
|
||||
|
||||
function invalidateGraphThemeCache() {
|
||||
_graphTheme = null;
|
||||
}
|
||||
|
||||
function getGraphTheme(): GraphTheme {
|
||||
function cssVar(name: string, fallback = ""): string {
|
||||
return getComputedStyle(document.documentElement)
|
||||
.getPropertyValue(name)
|
||||
.trim() || fallback;
|
||||
}
|
||||
|
||||
if (_graphTheme) return _graphTheme;
|
||||
|
||||
_graphTheme = {
|
||||
bg_0: cssVar("--graph-bg-0"),
|
||||
bg_1: cssVar("--graph-bg-1"),
|
||||
bg_2: cssVar("--graph-bg-2"),
|
||||
fg_0: cssVar("--graph-fg-0"),
|
||||
fg_1: cssVar("--graph-fg-1"),
|
||||
fg_2: cssVar("--graph-fg-2"),
|
||||
|
||||
node_anchor: cssVar("--graph-node-anchor"),
|
||||
node_border: cssVar("--graph-node-border"),
|
||||
current_node_border: cssVar("--graph-current-node-border"),
|
||||
|
||||
edge: cssVar("--graph-edge"),
|
||||
edge_hover: cssVar("--graph-edge-hover"),
|
||||
edge_active: cssVar("--graph-edge-active"),
|
||||
|
||||
edge_font_size: Number(cssVar("--graph-edge-font-size")),
|
||||
node_font_size: Number(cssVar("--graph-node-font-size")),
|
||||
};
|
||||
|
||||
return _graphTheme;
|
||||
}
|
||||
|
||||
export function updateGraphTheme() {
|
||||
invalidateGraphThemeCache();
|
||||
const gt = getGraphTheme();
|
||||
|
||||
network.setOptions({
|
||||
nodes: {
|
||||
font: {
|
||||
color: gt.fg_0,
|
||||
bold: {
|
||||
color: gt.fg_1,
|
||||
mod: ''
|
||||
},
|
||||
},
|
||||
},
|
||||
edges: {
|
||||
labelHighlightBold: true,
|
||||
font: {
|
||||
align: "top",
|
||||
size: gt.edge_font_size,
|
||||
color: gt.fg_0,
|
||||
strokeColor: gt.bg_0,
|
||||
bold: {
|
||||
color: gt.fg_1,
|
||||
size: gt.edge_font_size,
|
||||
mod: ''
|
||||
},
|
||||
},
|
||||
color: {
|
||||
color: gt.edge,
|
||||
hover: gt.edge_hover,
|
||||
highlight: gt.edge_active,
|
||||
},
|
||||
shadow: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
type StateId = string;
|
||||
type GraphDef = {
|
||||
initial: StateId;
|
||||
|
|
@ -50,9 +150,13 @@ export function setAutomaton(auto: GraphDef) {
|
|||
// Populate edges
|
||||
for (const [k, v] of Object.entries(automaton.transitions)) {
|
||||
const to_from = k.split("#");
|
||||
const font = {
|
||||
vadjust: -getGraphTheme().edge_font_size*Math.floor(((v.match(/\n/g) || '').length + 1)/2)
|
||||
};
|
||||
if (edges.get(k)) {
|
||||
edges.update({
|
||||
id: k,
|
||||
font,
|
||||
from: to_from[0],
|
||||
to: to_from[1],
|
||||
label: v,
|
||||
|
|
@ -60,6 +164,7 @@ export function setAutomaton(auto: GraphDef) {
|
|||
} else {
|
||||
edges.add({
|
||||
id: k,
|
||||
font,
|
||||
from: to_from[0],
|
||||
to: to_from[1],
|
||||
label: v,
|
||||
|
|
@ -108,13 +213,14 @@ function createGraph(): vis.Network {
|
|||
layout: { improvedLayout: true },
|
||||
physics: {
|
||||
enabled: true,
|
||||
solver: "barnesHut",
|
||||
barnesHut: {
|
||||
gravitationalConstant: -8000,
|
||||
springLength: 120,
|
||||
springConstant: 0.04,
|
||||
},
|
||||
stabilization: { iterations: 200 },
|
||||
solver: "forceAtlas2Based",
|
||||
// solver: "barnesHut",
|
||||
// barnesHut: {
|
||||
// gravitationalConstant: -8000,
|
||||
// springLength: 120,
|
||||
// springConstant: 0.04,
|
||||
// },
|
||||
// stabilization: { iterations: 200 },
|
||||
},
|
||||
interaction: {
|
||||
dragNodes: true,
|
||||
|
|
@ -124,20 +230,14 @@ function createGraph(): vis.Network {
|
|||
selectConnectedEdges: false,
|
||||
},
|
||||
nodes: {
|
||||
font: { color: "#c9d1d9" },
|
||||
color: {
|
||||
background: "#1f6feb",
|
||||
border: "#79c0ff",
|
||||
highlight: { background: "#388bfd", border: "#a5d6ff" },
|
||||
},
|
||||
shape: "custom",
|
||||
size: 18,
|
||||
// // @ts-expect-error bad library
|
||||
// chosen: {
|
||||
// node: chosen_node,
|
||||
// },
|
||||
shape: "custom",
|
||||
// @ts-expect-error bad library
|
||||
ctxRenderer: renderNode,
|
||||
size: 18,
|
||||
},
|
||||
edges: {
|
||||
chosen: {
|
||||
|
|
@ -145,10 +245,6 @@ function createGraph(): vis.Network {
|
|||
// 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",
|
||||
},
|
||||
},
|
||||
|
|
@ -167,56 +263,6 @@ function createGraph(): vis.Network {
|
|||
return network;
|
||||
}
|
||||
|
||||
export type GraphTheme = {
|
||||
bg_0: string;
|
||||
bg_1: string;
|
||||
bg_2: string;
|
||||
fg_0: string;
|
||||
|
||||
anchor: string;
|
||||
selected: string;
|
||||
node: string;
|
||||
current: string;
|
||||
edge: string;
|
||||
glow: string;
|
||||
};
|
||||
|
||||
let _graphTheme: GraphTheme | null = null;
|
||||
|
||||
export function invalidateGraphThemeCache() {
|
||||
_graphTheme = null;
|
||||
}
|
||||
|
||||
function getGraphTheme(): GraphTheme {
|
||||
function cssVar(name: string, fallback = ""): string {
|
||||
return getComputedStyle(document.documentElement)
|
||||
.getPropertyValue(name)
|
||||
.trim() || fallback;
|
||||
}
|
||||
|
||||
if (_graphTheme) return _graphTheme;
|
||||
|
||||
_graphTheme = {
|
||||
bg_0: cssVar("--bg-0"),
|
||||
bg_1: cssVar("--bg-1"),
|
||||
bg_2: cssVar("--bg-2"),
|
||||
fg_0: cssVar("--fg-0"),
|
||||
|
||||
selected: cssVar("--bg-2"),
|
||||
|
||||
node: cssVar("--focus"),
|
||||
current: cssVar("--success"),
|
||||
|
||||
anchor: cssVar("--warning"),
|
||||
|
||||
edge: cssVar("--graph-edge", "rgba(201,209,217,0.55)"),
|
||||
|
||||
glow: cssVar("--accent", "#79c0ff"),
|
||||
};
|
||||
|
||||
return _graphTheme;
|
||||
}
|
||||
|
||||
function renderNode({
|
||||
ctx,
|
||||
id,
|
||||
|
|
@ -238,8 +284,8 @@ function renderNode({
|
|||
const isFinal = id === "q1"; // <-- change if your schema differs
|
||||
const isActive = id === "q0"; // <-- change if your schema differs
|
||||
|
||||
const fill = selected ? t.glow : hover ? t.bg_1 : t.bg_0;
|
||||
const stroke = isActive ? t.current : t.node;
|
||||
const fill = selected ? t.bg_2 : hover ? t.bg_1 : t.bg_0;
|
||||
const stroke = isActive ? t.current_node_border : t.node_border;
|
||||
|
||||
const emphasis = (selected ? 1 : 0) + (hover ? 0.6 : 0);
|
||||
|
||||
|
|
@ -308,7 +354,7 @@ function renderNode({
|
|||
|
||||
const physicsOff = node.physics === false;
|
||||
if (physicsOff) {
|
||||
drawPinIndicator(ctx, x, y, r, t.anchor);
|
||||
drawPinIndicator(ctx, x, y, r, t.node_anchor);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ try{
|
|||
wasm.init();
|
||||
}catch(e){
|
||||
console.error("Failed to start: " + e);
|
||||
document.getElementById("the_canvas_id")!.remove();
|
||||
document.getElementById("app")!.remove();
|
||||
document.getElementById("center_text")!.innerHTML = `
|
||||
<p>
|
||||
An error occurred during loading:
|
||||
|
|
@ -20,6 +20,7 @@ try{
|
|||
<p style="font-size:14px">
|
||||
Make sure you use a modern browser with WebGL and WASM enabled.
|
||||
</p>`;
|
||||
throw e;
|
||||
}
|
||||
|
||||
export default wasm
|
||||
|
|
@ -18,6 +18,10 @@
|
|||
color: var(--fg-0);
|
||||
}
|
||||
|
||||
.cm-content{
|
||||
caret-color: var(--fg-0)!important;
|
||||
}
|
||||
|
||||
.cm-gutters {
|
||||
background: var(--bg-2) !important;
|
||||
color: var(--fg-muted);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,17 @@ body {
|
|||
|
||||
.vscroll {
|
||||
height: 100%;
|
||||
overflow-x: scroll;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.flexCol{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flexRow{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.hSplit {
|
||||
|
|
@ -37,6 +47,8 @@ body {
|
|||
cursor: col-resize;
|
||||
}
|
||||
height: 8px;
|
||||
min-height: 8px;
|
||||
max-height: 8px;
|
||||
background: var(--separator-bg);
|
||||
transition:
|
||||
background var(--dur-med) var(--ease-standard),
|
||||
|
|
@ -48,6 +60,8 @@ body {
|
|||
cursor: col-resize;
|
||||
}
|
||||
width: 8px;
|
||||
min-width: 8px;
|
||||
max-width: 8px;
|
||||
background: var(--separator-bg);
|
||||
transition:
|
||||
background var(--dur-med) var(--ease-standard),
|
||||
|
|
|
|||
|
|
@ -29,6 +29,25 @@
|
|||
--dur-med: 160ms;
|
||||
--dur-slow: 240ms;
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
|
||||
|
||||
--graph-bg-0: var(--bg-0);
|
||||
--graph-bg-1: var(--bg-1);
|
||||
--graph-bg-2: var(--bg-2);
|
||||
--graph-fg-0: var(--fg-0);
|
||||
--graph-fg-1: var(--fg-1);
|
||||
--graph-fg-2: var(--fg-2);
|
||||
|
||||
--graph-node-font-size: 14;
|
||||
--graph-edge-font-size: 10;
|
||||
|
||||
--graph-node-border: var(--focus);
|
||||
--graph-current-node-border: var(--success);
|
||||
--graph-node-anchor: var(--warning);
|
||||
|
||||
--graph-edge: var(--fg-muted);
|
||||
--graph-edge-hover: var(--accent);
|
||||
--graph-edge-active: var(--focus);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
|
|
@ -52,20 +71,6 @@
|
|||
--warning: #f2cc60;
|
||||
--error: #f85149;
|
||||
|
||||
--graph-bg: var(--bg-0);
|
||||
|
||||
--graph-node-bg: #1f6feb;
|
||||
--graph-node-border: #388bfd;
|
||||
--graph-node-text: var(--fg-0);
|
||||
|
||||
--graph-node-active-bg: #79c0ff;
|
||||
--graph-node-active-border: #ff0000;
|
||||
|
||||
--graph-edge: rgba(201, 209, 217, 0.55);
|
||||
--graph-edge-hover: rgba(201, 209, 217, 0.864);
|
||||
--graph-edge-active: var(--accent);
|
||||
|
||||
|
||||
--ansi-fg-30: #0b0f14; /* black */
|
||||
--ansi-fg-31: #ff7b72; /* red */
|
||||
--ansi-fg-32: #7ee787; /* green */
|
||||
|
|
@ -115,21 +120,6 @@
|
|||
--warning: #9a6700;
|
||||
--error: #cf222e;
|
||||
|
||||
--graph-bg: var(--bg-0);
|
||||
|
||||
--graph-node-bg: #1f6feb;
|
||||
--graph-node-border: #0969da;
|
||||
--graph-node-text: #ffffff;
|
||||
|
||||
--graph-node-active-bg: #54aeff;
|
||||
--graph-node-active-border: #0969da;
|
||||
|
||||
--graph-edge: rgba(31, 41, 55, 0.45);
|
||||
--graph-edge-hover: #0969da;
|
||||
--graph-edge-active: #54aeff;
|
||||
|
||||
|
||||
|
||||
--ansi-fg-30: #111827; /* black */
|
||||
--ansi-fg-31: #b42318; /* red */
|
||||
--ansi-fg-32: #1a7f37; /* green */
|
||||
|
|
|
|||
|
|
@ -14,11 +14,14 @@ async function run(cmd: string[], cwd?: string) {
|
|||
}
|
||||
}
|
||||
|
||||
// Clean dist
|
||||
console.log("clean dist...");
|
||||
await Deno.remove(DIST, { recursive: true }).catch(() => {});
|
||||
await Deno.mkdir(DIST, { recursive: true });
|
||||
|
||||
|
||||
console.log("copy assets");
|
||||
await copyFolder(new URL("assets/", ROOT), DIST);
|
||||
|
||||
console.log("compiling scss...");
|
||||
const result = sass.compile(String(new URL("style/style.scss", ROOT).pathname), {
|
||||
style: "compressed",
|
||||
|
|
@ -40,3 +43,23 @@ if (!bundleRes.success) {
|
|||
}
|
||||
|
||||
console.log("Build complete: dist/");
|
||||
|
||||
|
||||
|
||||
export async function copyFolder(
|
||||
srcDir: URL,
|
||||
destDir: URL,
|
||||
): Promise<void> {
|
||||
await Deno.mkdir(destDir, { recursive: true });
|
||||
|
||||
for await (const entry of Deno.readDir(srcDir)) {
|
||||
const srcPath = new URL(entry.name, srcDir.href);
|
||||
const destPath = new URL(entry.name, destDir.href);
|
||||
|
||||
if (entry.isDirectory) {
|
||||
await copyFolder(srcPath, destPath);
|
||||
} else if (entry.isFile) {
|
||||
await Deno.copyFile(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue