From 58fb1b956c817fade86257f6b002a5d377c8d776 Mon Sep 17 00:00:00 2001 From: Parker TenBroeck <51721964+ParkerTenBroeck@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:00:48 -0500 Subject: [PATCH 1/2] starting work on highlights --- web/root/src/editor.ts | 63 +++++++++++++++++++++++++++++++++++++- web/root/src/focus.ts | 0 web/root/style/editor.scss | 11 +++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 web/root/src/focus.ts diff --git a/web/root/src/editor.ts b/web/root/src/editor.ts index d3594be..c3340e5 100644 --- a/web/root/src/editor.ts +++ b/web/root/src/editor.ts @@ -2,6 +2,7 @@ import { Decoration, + DecorationSet, EditorView, highlightActiveLine, highlightActiveLineGutter, @@ -10,7 +11,7 @@ import { lineNumbers, } from "npm:@codemirror/view"; -import { EditorState, StateField, Text } from "npm:@codemirror/state"; +import { EditorState, RangeSetBuilder, StateEffect, StateField, Text } from "npm:@codemirror/state"; import { defaultKeymap, history, @@ -45,6 +46,57 @@ function compile( } } + +export type HighlightKind = "focus" | "success" | "warning" | "error"; + +export type HighlightSpan = { + from: number; + to: number; + kind: HighlightKind; +}; + +function decoForKind(kind: HighlightKind) { + // Use a class per kind so each gets a distinct color via CSS + return Decoration.mark({ class: `cm-highlight cm-highlight-${kind}` }); +} + +export function applyHighlights(view: EditorView, spans: HighlightSpan[]) { + view.dispatch({ effects: setHighlights.of(spans) }); +} + + + +export const highlightsField = StateField.define({ + create() { + return Decoration.none; + }, + + update(highlights, tr) { + // Keep highlights aligned with document edits + // highlights = highlights.map(tr.changes); + + for (const e of tr.effects) { + if (e.is(setHighlights)) { + const spans = e.value; + + const builder = new RangeSetBuilder(); + for (const s of spans) { + const from = Math.max(0, Math.min(s.from, tr.state.doc.length)); + const to = Math.max(0, Math.min(s.to, tr.state.doc.length)); + if (to > from) builder.add(from, to, decoForKind(s.kind)); + } + highlights = builder.finish(); + } + } + + return highlights; + }, + + provide: (f) => EditorView.decorations.from(f), +}); + +export const setHighlights = StateEffect.define(); + const eventBusConnection = StateField.define({ create(state) { const text = state.doc.toString(); @@ -198,6 +250,7 @@ const state = EditorState.create({ keymap.of([...defaultKeymap, ...historyKeymap]), eventBusConnection, + highlightsField, diagHover, EditorView.lineWrapping, @@ -223,3 +276,11 @@ bus.on("controls/editor/set_text", ({ text }) => { bus.on("example/selected", ({ example }) => { bus.emit("controls/editor/set_text", { text: example.machine }); }); + + +applyHighlights(editor, [ + { from: 0, to: 10, kind: "focus" }, + { from: 10, to: 20, kind: "success" }, + { from: 20, to: 30, kind: "warning" }, + { from: 30, to: 40, kind: "error" }, +]) \ No newline at end of file diff --git a/web/root/src/focus.ts b/web/root/src/focus.ts new file mode 100644 index 0000000..e69de29 diff --git a/web/root/style/editor.scss b/web/root/style/editor.scss index 6cd740a..834cafa 100644 --- a/web/root/style/editor.scss +++ b/web/root/style/editor.scss @@ -132,3 +132,14 @@ text-underline-offset: 2px; } + + +.cm-highlight { + border-radius: 4px; + padding: 0 1px; +} + +.cm-highlight-warning { background: color-mix(in srgb, var(--warning) 40%, var(--bg-0)); } +.cm-highlight-focus { background: color-mix(in srgb, var(--focus) 40%, var(--bg-0)); } +.cm-highlight-success { background: color-mix(in srgb, var(--success) 40%, var(--bg-0)); } +.cm-highlight-error { background: color-mix(in srgb, var(--error) 40%, var(--bg-0)); } \ No newline at end of file From 7f519cd7f3258e546ac54140578c1fe10d279493 Mon Sep 17 00:00:00 2001 From: ParkerTenBroeck <51721964+ParkerTenBroeck@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:00:13 -0500 Subject: [PATCH 2/2] some form of highlighting --- automata/src/automatan/fa.rs | 8 +-- automata/src/automatan/pda.rs | 9 +-- automata/src/automatan/tm.rs | 7 +- web/root/src/bus.ts | 17 +++-- web/root/src/controls.ts | 2 +- web/root/src/editor.ts | 44 ++++-------- web/root/src/examples.ts | 2 +- web/root/src/focus.ts | 0 web/root/src/highlight.ts | 85 ++++++++++++++++++++++ web/root/src/simulation.ts | 15 ++-- web/root/src/visualizer.ts | 132 +++++++++++++++++++--------------- web/root/style/editor.scss | 2 +- 12 files changed, 209 insertions(+), 114 deletions(-) delete mode 100644 web/root/src/focus.ts create mode 100644 web/root/src/highlight.ts diff --git a/automata/src/automatan/fa.rs b/automata/src/automatan/fa.rs index ff7399b..2dbefb0 100644 --- a/automata/src/automatan/fa.rs +++ b/automata/src/automatan/fa.rs @@ -176,8 +176,8 @@ impl<'a, 'b> FaCompiler<'a, 'b> { self.ctx.emit_error(format!("unknown item {name:?}, expected states | alphabet | final states | initial state"), dest_s); } - TL::TransitionFunc(S((S(delta_lower!(pat), _), args), _), list) => { - self.compile_transition_function(args, list) + TL::TransitionFunc(S((S(delta_lower!(pat), _), args), func), list) => { + self.compile_transition_function(args, func, list) } TL::TransitionFunc(S((S(name, _), _), dest_s), _) => { self.ctx.emit_error( @@ -313,6 +313,7 @@ impl<'a, 'b> FaCompiler<'a, 'b> { fn compile_transition_function( &mut self, args: Spanned>, + function: Span, list: Spanned>, ) { let list = list.set_weak(); @@ -368,8 +369,7 @@ impl<'a, 'b> FaCompiler<'a, 'b> { } if let Some(previous) = entry.replace(TransitionTo { state: State(next_state.0), - - function: args.1, + function, transition: item.1, }) { self.ctx diff --git a/automata/src/automatan/pda.rs b/automata/src/automatan/pda.rs index e3c2c02..5c2ec2a 100644 --- a/automata/src/automatan/pda.rs +++ b/automata/src/automatan/pda.rs @@ -264,8 +264,8 @@ impl<'a, 'b> PdaCompiler<'a, 'b> { self.ctx.emit_error(format!("unknown item {name:?}, expected states | stack symbols | alphabet | accept by | final states | initial state | initial stack"), dest_s); } - TL::TransitionFunc(S((S(delta_lower!(pat), _), args), _), list) => { - self.compile_transition_function(args, list) + TL::TransitionFunc(S((S(delta_lower!(pat), _), args), func), list) => { + self.compile_transition_function(args, func, list) } TL::TransitionFunc(S((S(name, _), _), dest_s), _) => { self.ctx.emit_error( @@ -476,6 +476,7 @@ impl<'a, 'b> PdaCompiler<'a, 'b> { fn compile_transition_function( &mut self, args: Spanned>, + function: Span, list: Spanned>, ) { let list = list.set_weak(); @@ -559,8 +560,8 @@ impl<'a, 'b> PdaCompiler<'a, 'b> { if !entry.insert(TransitionTo { state: State(next_state.0), stack, - - function: args.1, + + function, transition: item.1, }) { self.ctx.emit_warning("duplicate transition", item.1); diff --git a/automata/src/automatan/tm.rs b/automata/src/automatan/tm.rs index adc57f4..626b18c 100644 --- a/automata/src/automatan/tm.rs +++ b/automata/src/automatan/tm.rs @@ -193,8 +193,8 @@ impl<'a, 'b> TmCompiler<'a, 'b> { self.ctx.emit_error(format!("unknown item {name:?}, expected states | symbols | final states | initial state | blank symbol"), dest_s); } - TL::TransitionFunc(S((S(delta_lower!(pat), _), args), _), list) => { - self.compile_transition_function(args, list) + TL::TransitionFunc(S((S(delta_lower!(pat), _), args), func), list) => { + self.compile_transition_function(args, func, list) } TL::TransitionFunc(S((S(name, _), _), dest_s), _) => { self.ctx.emit_error( @@ -349,6 +349,7 @@ impl<'a, 'b> TmCompiler<'a, 'b> { fn compile_transition_function( &mut self, args: Spanned>, + function: Span, list: Spanned>, ) { let list = list.set_weak(); @@ -398,7 +399,7 @@ impl<'a, 'b> TmCompiler<'a, 'b> { symbol: Symbol(to_tape.0), direction: direction.0, - function: args.1, + function, transition: item.1, }) { self.ctx.emit_warning("duplicate transition", item.1); diff --git a/web/root/src/bus.ts b/web/root/src/bus.ts index 1c56cd0..eb284fb 100644 --- a/web/root/src/bus.ts +++ b/web/root/src/bus.ts @@ -1,10 +1,11 @@ // deno-lint-ignore-file -import type { Machine } from "./automata.ts"; +import type { Machine, Span } from "./automata.ts"; import type { Example } from "./examples.ts"; import type { Sim, SimStepResult } from "./simulation.ts"; import type wasm from "./wasm.ts"; import type { Text } from "npm:@codemirror/state"; +import type { Highlight } from "./highlight.ts"; type Unsubscribe = () => void; @@ -69,14 +70,14 @@ type AppEvents = { "editor/change": {text: string, doc: Text}; "compiled": {log: wasm.CompileLog[], ansi_log: string, machine: string|undefined}; - "automata/sim/update": { simulation: Sim|null }; + "automata/sim/update": Sim|null; "automata/sim/before_step": { simulation: Sim }; "automata/sim/after_step": { simulation: Sim, result: SimStepResult }; - "automata/update": { automaton: Machine }; + "automata/update": Machine; - "example/selected": {example: Example}; + "example/selected": Example; - "controls/editor/set_text": {text: string}; + "controls/editor/set_text": string; "controls/vis/physics": {enabled: boolean}; "controls/vis/reset_network": void; @@ -85,6 +86,12 @@ type AppEvents = { "controls/sim/reload": void; "controls/sim/clear": void; + "highlight/one/add": Highlight; + "highlight/one/remove": Highlight; + "highlight/all/remove": void; + + "highlight/update": void; + "theme/update": void; }; diff --git a/web/root/src/controls.ts b/web/root/src/controls.ts index 16980f3..8d553f0 100644 --- a/web/root/src/controls.ts +++ b/web/root/src/controls.ts @@ -89,7 +89,7 @@ class Controls { if (Controls.running) Controls.setRunning(false); }); - bus.on("automata/sim/update", ({ simulation }) => { + bus.on("automata/sim/update", simulation => { Controls.simulation_active = !!simulation; if (!simulation) Controls.stop(); }); diff --git a/web/root/src/editor.ts b/web/root/src/editor.ts index c3340e5..dbadd4c 100644 --- a/web/root/src/editor.ts +++ b/web/root/src/editor.ts @@ -25,6 +25,7 @@ import wasm from "./wasm.ts"; import { Share } from "./share.ts"; import { examples } from "./examples.ts"; import { bus } from "./bus.ts"; +import { current, Highlight, HighlightKind } from "./highlight.ts"; function tokenize(text: string): wasm.Tok[] { try { @@ -47,25 +48,16 @@ function compile( } -export type HighlightKind = "focus" | "success" | "warning" | "error"; - -export type HighlightSpan = { - from: number; - to: number; - kind: HighlightKind; -}; - function decoForKind(kind: HighlightKind) { // Use a class per kind so each gets a distinct color via CSS return Decoration.mark({ class: `cm-highlight cm-highlight-${kind}` }); } -export function applyHighlights(view: EditorView, spans: HighlightSpan[]) { - view.dispatch({ effects: setHighlights.of(spans) }); -} - - - +bus.on("highlight/update", _ => { + const arr = current.values().toArray().sort((a, b) => a.span[0]-b.span[0]); + editor.dispatch({ effects: setHighlights.of(arr) }); +}); +export const setHighlights = StateEffect.define(); export const highlightsField = StateField.define({ create() { return Decoration.none; @@ -73,7 +65,7 @@ export const highlightsField = StateField.define({ update(highlights, tr) { // Keep highlights aligned with document edits - // highlights = highlights.map(tr.changes); + highlights = highlights.map(tr.changes); for (const e of tr.effects) { if (e.is(setHighlights)) { @@ -81,8 +73,9 @@ export const highlightsField = StateField.define({ const builder = new RangeSetBuilder(); for (const s of spans) { - const from = Math.max(0, Math.min(s.from, tr.state.doc.length)); - const to = Math.max(0, Math.min(s.to, tr.state.doc.length)); + + const from = Math.max(0, Math.min(s.span[0], tr.state.doc.length)); + const to = Math.max(0, Math.min(s.span[1], tr.state.doc.length)); if (to > from) builder.add(from, to, decoForKind(s.kind)); } highlights = builder.finish(); @@ -95,7 +88,6 @@ export const highlightsField = StateField.define({ provide: (f) => EditorView.decorations.from(f), }); -export const setHighlights = StateEffect.define(); const eventBusConnection = StateField.define({ create(state) { @@ -264,23 +256,15 @@ const editor = new EditorView({ bus.on( "begin", - (_) => bus.emit("controls/editor/set_text", { text: defaultText() }), + (_) => bus.emit("controls/editor/set_text", defaultText()), ); -bus.on("controls/editor/set_text", ({ text }) => { +bus.on("controls/editor/set_text", text => { editor.dispatch({ changes: { from: 0, to: editor.state.doc.length, insert: text }, }); }); -bus.on("example/selected", ({ example }) => { - bus.emit("controls/editor/set_text", { text: example.machine }); +bus.on("example/selected", example => { + bus.emit("controls/editor/set_text", example.machine); }); - - -applyHighlights(editor, [ - { from: 0, to: 10, kind: "focus" }, - { from: 10, to: 20, kind: "success" }, - { from: 20, to: 30, kind: "warning" }, - { from: 30, to: 40, kind: "error" }, -]) \ No newline at end of file diff --git a/web/root/src/examples.ts b/web/root/src/examples.ts index c3cd7e6..593e253 100644 --- a/web/root/src/examples.ts +++ b/web/root/src/examples.ts @@ -245,5 +245,5 @@ function buildExamplesDropdown( const selectEl = document.getElementById("exampleSelect") as HTMLSelectElement; buildExamplesDropdown(selectEl, examples, (example) => { - bus.emit("example/selected", {example}); + bus.emit("example/selected", example); }); diff --git a/web/root/src/focus.ts b/web/root/src/focus.ts deleted file mode 100644 index e69de29..0000000 diff --git a/web/root/src/highlight.ts b/web/root/src/highlight.ts new file mode 100644 index 0000000..5c40df2 --- /dev/null +++ b/web/root/src/highlight.ts @@ -0,0 +1,85 @@ +import type { Span } from "./automata.ts"; +import { bus } from "./bus.ts"; +import { automaton } from "./simulation.ts"; + + +export type HighlightKind = "focus" | "error" | "warning" | "success"; + + + +export type Highlight = { + span: Span, + kind: HighlightKind, +} + +type HighlightEntry = { + span: Span, + kind: HighlightKind, + count: number; +} + +export const current: Map = new Map(); + + +function asKey(highlight: Highlight): string { + return `${highlight.span[0]}:${highlight.span[1]}:${highlight.kind}` +} + +export function highlight_from_node_id(node_id: string) { + const state = automaton.states.get(node_id); + if (state) { + bus.emit("highlight/one/add", { kind: "success", span: state.definition }) + } +} + +export function dehighlight_from_node_id(node_id: string) { + const state = automaton.states.get(node_id); + if (state) { + bus.emit("highlight/one/remove", { kind: "success", span: state.definition }) + } +} + +export function highlight_from_edge_id(node_id: string) { + for (const edge_value of automaton.edges.get(node_id)!) { + bus.emit("highlight/one/add", { kind: "focus", span: edge_value.function }) + bus.emit("highlight/one/add", { kind: "warning", span: edge_value.transition }) + } +} + +export function dehighlight_from_edge_id(node_id: string) { + for (const edge_value of automaton.edges.get(node_id)!) { + bus.emit("highlight/one/remove", { kind: "focus", span: edge_value.function }) + bus.emit("highlight/one/remove", { kind: "warning", span: edge_value.transition }) + } +} + +bus.on("automata/update", _ => { + bus.emit("highlight/all/remove", undefined); +}) + +bus.on("highlight/one/add", (highlight) => { + const key = asKey(highlight); + if (current.has(key)) { + current.get(key)!.count += 1; + } else { + current.set(key, { count: 1, ...highlight }); + bus.emit("highlight/update", undefined); + } +}); +bus.on("highlight/one/remove", (highlight) => { + const key = asKey(highlight); + if (current.has(key)) { + const value = current.get(key)! + value.count -= 1; + if (value.count === 0) { + current.delete(key); + bus.emit("highlight/update", undefined); + } + } +}); +bus.on("highlight/all/remove", (_) => { + if (current.size !== 0) { + current.clear(); + bus.emit("highlight/update", undefined); + } +}); \ No newline at end of file diff --git a/web/root/src/simulation.ts b/web/root/src/simulation.ts index 7e78a02..bafd19b 100644 --- a/web/root/src/simulation.ts +++ b/web/root/src/simulation.ts @@ -11,8 +11,9 @@ import {parse_machine_from_json} from "./automata.ts"; export type SimStepResult = "pending" | "accept" | "reject"; export type Sim = FaSim | PdaSim | TmSim; -let simulation: Sim | null = null; -let automaton: Machine = { + +export let simulation: Sim | null = null; +export let automaton: Machine = { type: "fa", alphabet: new Map(), final_states: new Map(), @@ -28,7 +29,7 @@ bus.on("compiled", ({ machine }) => { try { bus.emit("controls/sim/clear", undefined); automaton = parse_machine_from_json(machine); - bus.emit("automata/update", { automaton }); + bus.emit("automata/update", automaton); } catch (e) { console.log(e); } @@ -36,7 +37,7 @@ bus.on("compiled", ({ machine }) => { }); bus.on("controls/sim/clear", (_) => { simulation = null; - bus.emit("automata/sim/update", { simulation: null }); + bus.emit("automata/sim/update", null); }); bus.on("controls/sim/step", (_) => { if (simulation) { @@ -48,7 +49,7 @@ bus.on("controls/sim/step", (_) => { } }); const machineInput = document.getElementById("machineInput") as HTMLInputElement; -machineInput.addEventListener("input", () => bus.emit("automata/sim/update", {simulation: null})); +machineInput.addEventListener("input", () => bus.emit("controls/sim/clear", undefined)); machineInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { bus.emit("controls/sim/reload", undefined) @@ -67,10 +68,10 @@ bus.on("controls/sim/reload", (_) => { simulation = new TmSim(automaton as Tm, input); break; } - bus.emit("automata/sim/update", { simulation }); + bus.emit("automata/sim/update", simulation); }); const simulationStatus = document.getElementById("simulationStatus") as HTMLInputElement; -bus.on("automata/sim/update", ({simulation}) => { +bus.on("automata/sim/update", simulation => { if (!simulation){ simulationStatus.innerText = "N/A" simulationStatus.style.color = "var(--fg-2)"; diff --git a/web/root/src/visualizer.ts b/web/root/src/visualizer.ts index 0c4bae0..48f035e 100644 --- a/web/root/src/visualizer.ts +++ b/web/root/src/visualizer.ts @@ -3,46 +3,42 @@ import * as vis from "npm:vis-network/standalone"; import { bus } from "./bus.ts"; -import type { Sim } from "./simulation.ts"; -import type { Machine } from "./automata.ts"; +import { automaton, simulation } from "./simulation.ts"; +import { dehighlight_from_edge_id, dehighlight_from_node_id, highlight_from_edge_id, highlight_from_node_id } from "./highlight.ts"; -bus.on("controls/vis/physics", ({enabled}) => { +bus.on("controls/vis/physics", ({ enabled }) => { network.setOptions({ physics: { enabled } }); - network.setOptions({edges: {smooth: enabled}}); + network.setOptions({ edges: { smooth: enabled } }); }); bus.on("controls/vis/reset_network", _ => { - try { - nodes.forEach((n) => { - n.physics = true; - n.x = undefined; - n.y = undefined; - }); - network.setData({ nodes, edges }); - } catch { - // Last resort - network.setData({ nodes, edges }); - } + try { + nodes.forEach((n) => { + n.physics = true; + n.x = undefined; + n.y = undefined; + }); + network.setData({ nodes, edges }); + } catch { + // Last resort + network.setData({ nodes, edges }); + } }); bus.on("automata/sim/after_step", _ => { network.redraw(); }); -let simulation: Sim | null = null; -bus.on("automata/sim/update", ({simulation: sim}) => { - simulation = sim; +bus.on("automata/sim/update", _ => { network.redraw(); }); -let automaton: Machine +bus.on("automata/update", automaton => { -bus.on("automata/update", ({automaton: auto}) => { - automaton = auto; // Populate nodes for (const state of automaton.states.keys()) { - - const size = measureTextWidth(state, getGraphTheme().node_font)/2+10 + + const size = measureTextWidth(state, getGraphTheme().node_font) / 2 + 10 if (nodes.get(state)) { nodes.update({ id: state, @@ -62,20 +58,20 @@ bus.on("automata/update", ({automaton: auto}) => { for (const [edge_id, transitions] of automaton.edges) { const to_from = edge_id.split("#"); const vadjust = -getGraphTheme().edge_font_size * - Math.floor(transitions.length / 2); + Math.floor(transitions.length / 2); const font = { vadjust, - bold: { - vadjust - } - }; + bold: { + vadjust + } + }; if (edges.get(edge_id)) { edges.update({ id: edge_id, font, from: to_from[0], to: to_from[1], - label: transitions.map(i => i.repr).join(automaton.type=="fa"?",":"\n"), + label: transitions.map(i => i.repr).join(automaton.type == "fa" ? "," : "\n"), }); } else { edges.add({ @@ -83,7 +79,7 @@ bus.on("automata/update", ({automaton: auto}) => { font, from: to_from[0], to: to_from[1], - label: transitions.map(i => i.repr).join(automaton.type=="fa"?",":"\n"), + label: transitions.map(i => i.repr).join(automaton.type == "fa" ? "," : "\n"), }); } } @@ -238,22 +234,6 @@ function measureTextWidth(text: string, font: string): number { return ctx.measureText(text).width; } -function chosen_edge( - _: vis.ChosenNodeValues, - id: vis.IdType, - selected: boolean, - hovered: boolean, -) { -} - -function chosen_node( - _: vis.ChosenNodeValues, - id: vis.IdType, - selected: boolean, - hovered: boolean, -) { -} - const network: vis.Network = createGraph(); function createGraph(): vis.Network { @@ -286,16 +266,9 @@ function createGraph(): vis.Network { shape: "custom", size: 18, // @ts-expect-error bad library - chosen: { - node: chosen_node, - }, ctxRenderer: renderNode, }, edges: { - chosen: { - // @ts-expect-error bad library - edge: chosen_edge, - }, arrowStrikethrough: false, arrows: "to", }, @@ -303,7 +276,7 @@ function createGraph(): vis.Network { ); vis.DataSet; - network.on("doubleClick", (params: {nodes: string[]}) => { + network.on("doubleClick", (params: { nodes: string[] }) => { for (const node_id of params.nodes) { const node: vis.Node = nodes.get(node_id)!; node.physics = !node.physics; @@ -311,6 +284,49 @@ function createGraph(): vis.Network { } }); + network.on("hoverEdge", ({ edge }: { edge: string }) => { + highlight_from_edge_id(edge) + }); + + network.on('blurEdge', ({edge}: {edge: string}) => { + dehighlight_from_edge_id(edge) + }); + + network.on("hoverNode", ({ node }: { node: string }) => { + highlight_from_node_id(node); + }); + + network.on('blurNode', ({ node }: { node: string }) => { + dehighlight_from_node_id(node) + }); + + + network.on("selectEdge", item => { + const id = network.getEdgeAt(item.pointer.DOM); + if(id)highlight_from_edge_id(id as string); + }); + + network.on('deselectEdge', item => { + console.log(item); + for (const edge of item.previousSelection.edges){ + console.log(edge); + dehighlight_from_edge_id(edge.id) + } + }); + + network.on("selectNode", item => { + const id = network.getNodeAt(item.pointer.DOM); + if(id)highlight_from_node_id(id as string); + }); + + network.on('deselectNode', item => { + console.log(item); + for (const node of item.previousSelection.nodes){ + console.log(node); + dehighlight_from_node_id(node.id) + } + }); + return network; } @@ -323,7 +339,7 @@ function renderNode({ state: { selected, hover }, style, label, -}: {ctx: CanvasRenderingContext2D, id: string, x: number, y: number, state: {selected: boolean, hover: boolean}, style: vis.NodeOptions, label: string}) { +}: { ctx: CanvasRenderingContext2D, id: string, x: number, y: number, state: { selected: boolean, hover: boolean }, style: vis.NodeOptions, label: string }) { return { drawNode() { const t = getGraphTheme(); @@ -333,7 +349,7 @@ function renderNode({ const isFinal = automaton.final_states ? automaton.final_states.has(id) : false; - const isActive = simulation?simulation.current_states.has(id):false; + const isActive = simulation ? simulation.current_states.has(id) : false; const fill = selected ? t.bg_2 : hover ? t.bg_1 : t.bg_0; const stroke = isActive ? t.current_node_border : t.node_border; @@ -345,7 +361,7 @@ function renderNode({ ctx.save(); - ctx.font = hover||selected?t.node_font_bold:t.node_font; + ctx.font = hover || selected ? t.node_font_bold : t.node_font; ctx.textAlign = "center"; ctx.textBaseline = "middle"; @@ -398,7 +414,7 @@ function renderNode({ ctx.textBaseline = "top"; for (let i = 0; i < paths.length; i++) { - ctx.fillStyle = paths[i].accepted?t.current_node_border:t.fg_0; + ctx.fillStyle = paths[i].accepted ? t.current_node_border : t.fg_0; ctx.fillText(paths[i].toString(), x, by + padY + i * lineH); } } diff --git a/web/root/style/editor.scss b/web/root/style/editor.scss index 834cafa..2a0a13c 100644 --- a/web/root/style/editor.scss +++ b/web/root/style/editor.scss @@ -136,7 +136,7 @@ .cm-highlight { border-radius: 4px; - padding: 0 1px; + // padding: 0 1px; } .cm-highlight-warning { background: color-mix(in srgb, var(--warning) 40%, var(--bg-0)); }