From 821c6a6aad8e4edd72054ca5784a7b855f99ae92 Mon Sep 17 00:00:00 2001 From: ParkerTenBroeck <51721964+ParkerTenBroeck@users.noreply.github.com> Date: Thu, 8 Jan 2026 20:02:22 -0500 Subject: [PATCH] organized graph style, added icon --- web/root/assets/icon.jpg | Bin 0 -> 12414 bytes web/root/index.html | 1 + web/root/src/theme.ts | 61 ++------------ web/root/src/visualizer.ts | 158 ++++++++++++++++++++++++------------- web/root/src/wasm.ts | 3 +- web/root/style/themes.scss | 48 +++++------ web/tools/build.ts | 25 +++++- 7 files changed, 156 insertions(+), 140 deletions(-) create mode 100644 web/root/assets/icon.jpg diff --git a/web/root/assets/icon.jpg b/web/root/assets/icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f5d649c25d1042bfa0714991d7b8f5a8d18dfd1b GIT binary patch literal 12414 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

l5!T$6^kSblX$nTpK)k1QW>+}$(El;n!{<&yQ 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 s4j0$kC1%Dux%?LQ@UCBeIIgn( Automata + diff --git a/web/root/src/theme.ts b/web/root/src/theme.ts index 88f7625..8dd0244 100644 --- a/web/root/src/theme.ts +++ b/web/root/src/theme.ts @@ -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") - } - }, - }); -} - - diff --git a/web/root/src/visualizer.ts b/web/root/src/visualizer.ts index 0e9712b..526d123 100644 --- a/web/root/src/visualizer.ts +++ b/web/root/src/visualizer.ts @@ -6,6 +6,106 @@ import * as vis from "npm:vis-network/standalone"; export const nodes = new vis.DataSet(); export const edges = new vis.DataSet(); + +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(); diff --git a/web/root/src/wasm.ts b/web/root/src/wasm.ts index 74f9084..80bf4a2 100644 --- a/web/root/src/wasm.ts +++ b/web/root/src/wasm.ts @@ -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 = `

An error occurred during loading: @@ -20,6 +20,7 @@ try{

Make sure you use a modern browser with WebGL and WASM enabled.

`; + throw e; } export default wasm \ No newline at end of file diff --git a/web/root/style/themes.scss b/web/root/style/themes.scss index 58710a4..ac86de9 100644 --- a/web/root/style/themes.scss +++ b/web/root/style/themes.scss @@ -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 */ diff --git a/web/tools/build.ts b/web/tools/build.ts index bc88fe0..fa4834d 100644 --- a/web/tools/build.ts +++ b/web/tools/build.ts @@ -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 { + 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); + } + } +} \ No newline at end of file