organized graph style, added icon

This commit is contained in:
ParkerTenBroeck 2026-01-08 20:02:22 -05:00
parent 47d7482342
commit 821c6a6aad
7 changed files with 156 additions and 140 deletions

BIN
web/root/assets/icon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -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>

View file

@ -1,10 +1,4 @@
import { getGraphTheme, 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,66 +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"),
bold: {
color: cssVar("--fg-1"),
mod: ''
},
},
},
edges: {
labelHighlightBold: true,
font: {
align: "top",
size: getGraphTheme().edge_font_size,
color: cssVar("--fg-0"),
strokeColor: cssVar("--bg-0"),
bold: {
color: cssVar("--fg-1"),
size: getGraphTheme().edge_font_size,
mod: ''
},
},
color: {
color: cssVar("--graph-edge"),
highlight: cssVar("--graph-edge-active"),
hover: cssVar("--graph-edge-hover"),
},
shadow: {
enabled: true,
color: cssVar("--bg-2")
}
},
});
}

View file

@ -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;
@ -163,58 +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;
edge_font_size: number,
};
let _graphTheme: GraphTheme | null = null;
export function invalidateGraphThemeCache() {
_graphTheme = null;
}
export 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"),
edge_font_size: 10,
};
return _graphTheme;
}
function renderNode({
ctx,
id,
@ -236,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);
@ -306,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();

View file

@ -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

View file

@ -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 */

View file

@ -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);
}
}
}