diff --git a/web/root/assets/icon.jpg b/web/root/assets/icon.jpg new file mode 100644 index 0000000..f5d649c Binary files /dev/null and b/web/root/assets/icon.jpg differ diff --git a/web/root/index.html b/web/root/index.html index 5c0c855..7964d62 100644 --- a/web/root/index.html +++ b/web/root/index.html @@ -6,6 +6,7 @@ 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