mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-06 21:24:06 -04:00
organizing frontend
This commit is contained in:
parent
2c836875d2
commit
2e6330196d
7 changed files with 203 additions and 183 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
// deno-lint-ignore-file
|
// deno-lint-ignore-file
|
||||||
|
|
||||||
import type { Machine } from "./automata.ts";
|
import type { Machine } from "./automata.ts";
|
||||||
|
import type { Example } from "./examples.ts";
|
||||||
import type { Sim, SimStepResult } from "./simulation.ts";
|
import type { Sim, SimStepResult } from "./simulation.ts";
|
||||||
import type wasm from "./wasm.ts";
|
import type wasm from "./wasm.ts";
|
||||||
import type { Text } from "npm:@codemirror/state";
|
import type { Text } from "npm:@codemirror/state";
|
||||||
|
|
@ -73,13 +74,16 @@ type AppEvents = {
|
||||||
"automata/sim/after_step": { simulation: Sim, result: SimStepResult };
|
"automata/sim/after_step": { simulation: Sim, result: SimStepResult };
|
||||||
"automata/update": { automaton: Machine };
|
"automata/update": { automaton: Machine };
|
||||||
|
|
||||||
"controls/physics": {enabled: boolean},
|
"example/selected": {example: Example};
|
||||||
"controls/reset_network": void,
|
|
||||||
|
|
||||||
|
"controls/editor/set_text": {text: string};
|
||||||
|
|
||||||
"controls/step_simulation": void,
|
"controls/vis/physics": {enabled: boolean};
|
||||||
"controls/reload_simulation": void,
|
"controls/vis/reset_network": void;
|
||||||
"controls/clear_simulation": void,
|
|
||||||
|
"controls/sim/step": void;
|
||||||
|
"controls/sim/reload": void;
|
||||||
|
"controls/sim/clear": void;
|
||||||
|
|
||||||
"theme/update": void;
|
"theme/update": void;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,59 +15,6 @@ const speedLabel = document.getElementById("speedSimLabel") as HTMLSpanElement;
|
||||||
const reloadSimBtn = document.getElementById("reloadSim") as HTMLButtonElement;
|
const reloadSimBtn = document.getElementById("reloadSim") as HTMLButtonElement;
|
||||||
const clearSimBtn = document.getElementById("clearSim") as HTMLButtonElement;
|
const clearSimBtn = document.getElementById("clearSim") as HTMLButtonElement;
|
||||||
|
|
||||||
bus.on("controls/physics", ({ enabled }) => {
|
|
||||||
togglePhysicsBtn.classList.toggle("active", enabled);
|
|
||||||
togglePhysicsBtn.textContent = enabled ? "Physics: ON" : "Physics: OFF";
|
|
||||||
});
|
|
||||||
|
|
||||||
togglePhysicsBtn.onclick = () => {
|
|
||||||
const enabled = !togglePhysicsBtn.classList.contains("active");
|
|
||||||
bus.emit("controls/physics", { enabled });
|
|
||||||
};
|
|
||||||
|
|
||||||
bus.emit("controls/physics", {
|
|
||||||
enabled: togglePhysicsBtn.classList.contains("active"),
|
|
||||||
});
|
|
||||||
|
|
||||||
resetLayoutBtn.onclick = () => bus.emit("controls/reset_network", undefined);
|
|
||||||
|
|
||||||
clearSimBtn.onclick = () => bus.emit("controls/clear_simulation", undefined);
|
|
||||||
|
|
||||||
stepBtn.onclick = () => {
|
|
||||||
bus.emit("controls/step_simulation", undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
reloadSimBtn.onclick = () => bus.emit("controls/reload_simulation", undefined);
|
|
||||||
|
|
||||||
function updateButtons() {
|
|
||||||
stepBtn.disabled = !simulation_active || running;
|
|
||||||
playPauseBtn.disabled = !simulation_active;
|
|
||||||
clearSimBtn.disabled = !simulation_active;
|
|
||||||
}
|
|
||||||
|
|
||||||
bus.on("controls/reload_simulation", (_) => {
|
|
||||||
if (running) setRunning(false);
|
|
||||||
updateButtons();
|
|
||||||
});
|
|
||||||
|
|
||||||
bus.on("automata/sim/update", ({ simulation }) => {
|
|
||||||
simulation_active = !!simulation;
|
|
||||||
if (!simulation) {
|
|
||||||
if (running) setRunning(false);
|
|
||||||
}
|
|
||||||
updateButtons();
|
|
||||||
});
|
|
||||||
|
|
||||||
bus.on("automata/sim/after_step", ({ result }) => {
|
|
||||||
if (result !== "pending") {
|
|
||||||
if (running) setRunning(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let simulation_active = false;
|
|
||||||
let running = false;
|
|
||||||
let timer: number | null = null;
|
|
||||||
|
|
||||||
// speed slider is "steps per second"
|
// speed slider is "steps per second"
|
||||||
function getStepsPerSecond() {
|
function getStepsPerSecond() {
|
||||||
return Math.max(1, Math.min(60, Number(speedSlider.value) || 10));
|
return Math.max(1, Math.min(60, Number(speedSlider.value) || 10));
|
||||||
|
|
@ -77,39 +24,82 @@ function updateSpeedUI() {
|
||||||
}
|
}
|
||||||
updateSpeedUI();
|
updateSpeedUI();
|
||||||
|
|
||||||
speedSlider.addEventListener("input", () => {
|
class Controls {
|
||||||
updateSpeedUI();
|
static simulation_active = false;
|
||||||
if (running) restartTimer();
|
static running = false;
|
||||||
});
|
static timer: number | null = null;
|
||||||
|
|
||||||
function stopTimer() {
|
static updateButtons() {
|
||||||
if (timer !== null) {
|
stepBtn.disabled = !Controls.simulation_active || Controls.running;
|
||||||
clearInterval(timer);
|
playPauseBtn.disabled = !Controls.simulation_active;
|
||||||
timer = null;
|
clearSimBtn.disabled = !Controls.simulation_active;
|
||||||
}
|
}
|
||||||
}
|
static setRunning(on: boolean) {
|
||||||
|
Controls.running = on;
|
||||||
|
playPauseBtn.textContent = Controls.running ? "⏸ Pause" : "▶ Play";
|
||||||
|
playPauseBtn.classList.toggle("btn-primary", !Controls.running);
|
||||||
|
playPauseBtn.classList.toggle("btn-secondary", Controls.running);
|
||||||
|
|
||||||
function restartTimer() {
|
if (Controls.running) Controls.restartTimer();
|
||||||
stopTimer();
|
else Controls.stopTimer();
|
||||||
|
Controls.updateButtons();
|
||||||
|
}
|
||||||
|
static stop() {
|
||||||
|
if (Controls.running) Controls.setRunning(false);
|
||||||
|
}
|
||||||
|
static stopTimer() {
|
||||||
|
if (Controls.timer !== null) {
|
||||||
|
clearInterval(Controls.timer);
|
||||||
|
Controls.timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static restartTimer() {
|
||||||
|
Controls.stopTimer();
|
||||||
const sps = getStepsPerSecond();
|
const sps = getStepsPerSecond();
|
||||||
const intervalMs = Math.round(1000 / sps);
|
const intervalMs = Math.round(1000 / sps);
|
||||||
|
|
||||||
timer = globalThis.window.setInterval(() => {
|
Controls.timer = globalThis.window.setInterval(() => {
|
||||||
bus.emit("controls/step_simulation", undefined);
|
bus.emit("controls/sim/step", undefined);
|
||||||
}, intervalMs);
|
}, intervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
speedSlider.addEventListener("input", () => {
|
||||||
|
updateSpeedUI();
|
||||||
|
if (Controls.running) Controls.restartTimer();
|
||||||
|
});
|
||||||
|
playPauseBtn.onclick = () => Controls.setRunning(!Controls.running);
|
||||||
|
resetLayoutBtn.onclick = () =>
|
||||||
|
bus.emit("controls/vis/reset_network", undefined);
|
||||||
|
clearSimBtn.onclick = () => bus.emit("controls/sim/clear", undefined);
|
||||||
|
stepBtn.onclick = () => bus.emit("controls/sim/step", undefined);
|
||||||
|
reloadSimBtn.onclick = () => bus.emit("controls/sim/reload", undefined);
|
||||||
|
togglePhysicsBtn.onclick = () => {
|
||||||
|
const enabled = !togglePhysicsBtn.classList.contains("active");
|
||||||
|
bus.emit("controls/vis/physics", { enabled });
|
||||||
|
};
|
||||||
|
|
||||||
|
bus.on("controls/vis/physics", ({ enabled }) => {
|
||||||
|
togglePhysicsBtn.classList.toggle("active", enabled);
|
||||||
|
togglePhysicsBtn.textContent = enabled ? "Physics: ON" : "Physics: OFF";
|
||||||
|
});
|
||||||
|
|
||||||
|
bus.on("controls/sim/reload", (_) => {
|
||||||
|
if (Controls.running) Controls.setRunning(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
bus.on("automata/sim/update", ({ simulation }) => {
|
||||||
|
Controls.simulation_active = !!simulation;
|
||||||
|
if (!simulation) Controls.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
bus.on("automata/sim/after_step", ({ result }) => {
|
||||||
|
if (result !== "pending") Controls.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
bus.emit("controls/vis/physics", {
|
||||||
|
enabled: togglePhysicsBtn.classList.contains("active"),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRunning(on: boolean) {
|
|
||||||
running = on;
|
|
||||||
playPauseBtn.textContent = running ? "⏸ Pause" : "▶ Play";
|
|
||||||
playPauseBtn.classList.toggle("btn-primary", !running);
|
|
||||||
playPauseBtn.classList.toggle("btn-secondary", running);
|
|
||||||
|
|
||||||
// Disable step while running (optional, but feels nice)
|
|
||||||
stepBtn.disabled = running;
|
|
||||||
|
|
||||||
if (running) restartTimer();
|
|
||||||
else stopTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
playPauseBtn.onclick = () => setRunning(!running);
|
|
||||||
|
|
|
||||||
|
|
@ -1,67 +1,71 @@
|
||||||
// deno-lint-ignore-file
|
// deno-lint-ignore-file
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EditorView,
|
|
||||||
keymap,
|
|
||||||
hoverTooltip,
|
|
||||||
Decoration,
|
Decoration,
|
||||||
lineNumbers,
|
EditorView,
|
||||||
|
highlightActiveLine,
|
||||||
highlightActiveLineGutter,
|
highlightActiveLineGutter,
|
||||||
highlightActiveLine
|
hoverTooltip,
|
||||||
|
keymap,
|
||||||
|
lineNumbers,
|
||||||
} from "npm:@codemirror/view";
|
} from "npm:@codemirror/view";
|
||||||
|
|
||||||
import { EditorState, StateField, Text } from "npm:@codemirror/state";
|
import { EditorState, StateField, Text } from "npm:@codemirror/state";
|
||||||
import { defaultKeymap, history, historyKeymap } from "npm:@codemirror/commands";
|
import {
|
||||||
|
defaultKeymap,
|
||||||
|
history,
|
||||||
|
historyKeymap,
|
||||||
|
} from "npm:@codemirror/commands";
|
||||||
import { bracketMatching, indentOnInput } from "npm:@codemirror/language";
|
import { bracketMatching, indentOnInput } from "npm:@codemirror/language";
|
||||||
import { closeBrackets } from "npm:@codemirror/autocomplete";
|
import { closeBrackets } from "npm:@codemirror/autocomplete";
|
||||||
|
|
||||||
|
import wasm from "./wasm.ts";
|
||||||
|
|
||||||
import wasm from "./wasm.ts"
|
import { Share } from "./share.ts";
|
||||||
|
|
||||||
import { sharedText } from "./share.ts";
|
|
||||||
import { examples } from "./examples.ts";
|
import { examples } from "./examples.ts";
|
||||||
import { bus } from "./bus.ts";
|
import { bus } from "./bus.ts";
|
||||||
|
|
||||||
|
function tokenize(text: string): wasm.Tok[] {
|
||||||
function tokenize(text: string) {
|
|
||||||
try {
|
try {
|
||||||
return wasm.lex(text);
|
return wasm.lex(text);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e);
|
||||||
return []
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function compile(text: string): wasm.CompileResult {
|
function compile(
|
||||||
|
text: string,
|
||||||
|
): { log: wasm.CompileLog[]; ansi_log: string; machine: string | undefined } {
|
||||||
try {
|
try {
|
||||||
return wasm.compile(text);
|
return wasm.compile(text);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
// @ts-expect-error wasm defines extra cleanup
|
return { log: [], ansi_log: "", machine: "" };
|
||||||
return {log: [], log_formatted: "", graph: ""};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventBusConnection = StateField.define({
|
const eventBusConnection = StateField.define({
|
||||||
create(state) {
|
create(state) {
|
||||||
const text = state.doc.toString();
|
const text = state.doc.toString();
|
||||||
bus.emit("editor/change", {text, doc: state.doc});
|
bus.emit("editor/change", { text, doc: state.doc });
|
||||||
return buildAnalysis(text, state.doc);
|
return buildAnalysis(text, state.doc);
|
||||||
},
|
},
|
||||||
update(value, tr) {
|
update(value, tr) {
|
||||||
if (!tr.docChanged) return value;
|
if (!tr.docChanged) return value;
|
||||||
const text = tr.state.doc.toString();
|
const text = tr.state.doc.toString();
|
||||||
bus.emit("editor/change", {text, doc: state.doc});
|
bus.emit("editor/change", { text, doc: state.doc });
|
||||||
return buildAnalysis(text, tr.state.doc);
|
return buildAnalysis(text, tr.state.doc);
|
||||||
},
|
},
|
||||||
provide: (f) => EditorView.decorations.from(f, (v) => v.deco),
|
provide: (f) => EditorView.decorations.from(f, (v) => v.deco),
|
||||||
});
|
});
|
||||||
|
|
||||||
function buildAnalysis(text: string, doc: Text) {
|
function buildAnalysis(text: string, doc: Text) {
|
||||||
|
save(text);
|
||||||
const tokens = tokenize(text);
|
const tokens = tokenize(text);
|
||||||
const { log, ansi_log, machine } = compile(text);
|
const { log, ansi_log, machine } = compile(text);
|
||||||
|
|
||||||
bus.emit("compiled", {log, ansi_log, machine})
|
bus.emit("compiled", { log, ansi_log, machine });
|
||||||
|
|
||||||
const marks = [];
|
const marks = [];
|
||||||
const docLen = doc.length;
|
const docLen = doc.length;
|
||||||
|
|
@ -88,7 +92,9 @@ function buildAnalysis(text: string, doc: Text) {
|
||||||
marks.push(Decoration.mark({ class: cls }).range(start, end));
|
marks.push(Decoration.mark({ class: cls }).range(start, end));
|
||||||
} else {
|
} else {
|
||||||
const end = Math.min(docLen, start + 1);
|
const end = Math.min(docLen, start + 1);
|
||||||
if (end > start) marks.push(Decoration.mark({ class: cls }).range(start, end));
|
if (end > start) {
|
||||||
|
marks.push(Decoration.mark({ class: cls }).range(start, end));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,8 +102,7 @@ function buildAnalysis(text: string, doc: Text) {
|
||||||
return { tokens, log, ansi_log, deco };
|
return { tokens, log, ansi_log, deco };
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenClass = (t: string) =>
|
const tokenClass = (t: string) => ({
|
||||||
({
|
|
||||||
comment: "tok-comment",
|
comment: "tok-comment",
|
||||||
keyword: "tok-keyword",
|
keyword: "tok-keyword",
|
||||||
error: "tok-error",
|
error: "tok-error",
|
||||||
|
|
@ -113,7 +118,6 @@ const tokenClass = (t: string) =>
|
||||||
rbracket: "rb-",
|
rbracket: "rb-",
|
||||||
}[t] || "tok-ident");
|
}[t] || "tok-ident");
|
||||||
|
|
||||||
|
|
||||||
function severityClass(sev: string) {
|
function severityClass(sev: string) {
|
||||||
const s = (sev || "error").toLowerCase();
|
const s = (sev || "error").toLowerCase();
|
||||||
if (s === "warning") return "cm-diag-warning";
|
if (s === "warning") return "cm-diag-warning";
|
||||||
|
|
@ -129,10 +133,16 @@ function sevRank(sev: string) {
|
||||||
// ===================== Hover tooltip (uses cached diags) =====================
|
// ===================== Hover tooltip (uses cached diags) =====================
|
||||||
const diagHover = hoverTooltip((view, pos) => {
|
const diagHover = hoverTooltip((view, pos) => {
|
||||||
const { log } = view.state.field(eventBusConnection);
|
const { log } = view.state.field(eventBusConnection);
|
||||||
const hits = log.filter((d) => d.start !== undefined && d.end !== undefined && pos >= d.start && pos <= d.end);
|
const hits = log.filter((d) =>
|
||||||
|
d.start !== undefined && d.end !== undefined && pos >= d.start &&
|
||||||
|
pos <= d.end
|
||||||
|
);
|
||||||
if (hits.length === 0) return null;
|
if (hits.length === 0) return null;
|
||||||
|
|
||||||
const top = hits.reduce((a, b) => (sevRank(b.level) > sevRank(a.level) ? b : a), hits[0]);
|
const top = hits.reduce(
|
||||||
|
(a, b) => (sevRank(b.level) > sevRank(a.level) ? b : a),
|
||||||
|
hits[0],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pos,
|
pos,
|
||||||
|
|
@ -144,8 +154,9 @@ const diagHover = hoverTooltip((view, pos) => {
|
||||||
|
|
||||||
const title = document.createElement("div");
|
const title = document.createElement("div");
|
||||||
title.className = `tipTitle ${top.level}`;
|
title.className = `tipTitle ${top.level}`;
|
||||||
title.textContent =
|
title.textContent = hits.length === 1
|
||||||
hits.length === 1 ? top.level.toUpperCase() : `${top.level.toUpperCase()} (${hits.length})`;
|
? top.level.toUpperCase()
|
||||||
|
: `${top.level.toUpperCase()} (${hits.length})`;
|
||||||
|
|
||||||
const body = document.createElement("div");
|
const body = document.createElement("div");
|
||||||
body.className = "tipBody";
|
body.className = "tipBody";
|
||||||
|
|
@ -162,24 +173,20 @@ const diagHover = hoverTooltip((view, pos) => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function save(text: string){
|
function save(text: string) {
|
||||||
globalThis.localStorage.save = text;
|
globalThis.localStorage.save = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSaved(): string | undefined{
|
function getSaved(): string | undefined {
|
||||||
return globalThis.localStorage.save;
|
return globalThis.localStorage.save;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setText(text: string){
|
function defaultText(): string {
|
||||||
editor.dispatch({ changes: { from: 0, to: editor.state.doc.length, insert: text } });
|
return Share.sharedText() ?? getSaved() ?? examples[0].machine;
|
||||||
}
|
|
||||||
|
|
||||||
export function getText(): string{
|
|
||||||
return editor.state.doc.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
doc: "",
|
doc: defaultText(),
|
||||||
extensions: [
|
extensions: [
|
||||||
lineNumbers(),
|
lineNumbers(),
|
||||||
highlightActiveLineGutter(),
|
highlightActiveLineGutter(),
|
||||||
|
|
@ -202,4 +209,17 @@ const editor = new EditorView({
|
||||||
parent: document.getElementById("editor")!,
|
parent: document.getElementById("editor")!,
|
||||||
});
|
});
|
||||||
|
|
||||||
bus.on("begin", _ => setText(sharedText() ?? getSaved() ?? examples[0].machine))
|
bus.on(
|
||||||
|
"begin",
|
||||||
|
(_) => bus.emit("controls/editor/set_text", { text: defaultText() }),
|
||||||
|
);
|
||||||
|
|
||||||
|
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 });
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { setText } from "./editor.ts";
|
import { bus } from "./bus.ts";
|
||||||
|
|
||||||
export type Category =
|
export type Category =
|
||||||
| "Tutorial"
|
| "Tutorial"
|
||||||
|
|
@ -241,6 +241,6 @@ function buildExamplesDropdown(
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectEl = document.getElementById("exampleSelect") as HTMLSelectElement;
|
const selectEl = document.getElementById("exampleSelect") as HTMLSelectElement;
|
||||||
buildExamplesDropdown(selectEl, examples, (picked) => {
|
buildExamplesDropdown(selectEl, examples, (example) => {
|
||||||
setText(picked.machine);
|
bus.emit("example/selected", {example});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,31 @@
|
||||||
import { getText } from "./editor.ts";
|
import { bus } from "./bus.ts";
|
||||||
|
|
||||||
const btn = document.getElementById("shareBtn")!;
|
export class Share {
|
||||||
const toast = document.getElementById("shareToast")!;
|
private static readonly btn: HTMLButtonElement = document.getElementById(
|
||||||
|
"shareBtn",
|
||||||
|
)! as HTMLButtonElement;
|
||||||
|
private static readonly toast: HTMLElement = document.getElementById(
|
||||||
|
"shareToast",
|
||||||
|
)!;
|
||||||
|
|
||||||
function generateShareLink() {
|
private static docText: string;
|
||||||
return `${globalThis.window.location.href}?share=${encodeURIComponent(btoa(getText()))}`;
|
private static shareText: string;
|
||||||
}
|
|
||||||
|
|
||||||
async function copy(text: string) {
|
static {
|
||||||
await navigator.clipboard.writeText(text);
|
bus.on("editor/change", ({ text }) => Share.docText = text);
|
||||||
}
|
|
||||||
|
|
||||||
btn.addEventListener("click", async () => {
|
Share.btn.onclick = async (_) => {
|
||||||
await copy(generateShareLink());
|
const link = `${globalThis.window.location.href}?share=${
|
||||||
|
encodeURIComponent(btoa(Share.docText))
|
||||||
|
}`;
|
||||||
|
await navigator.clipboard.writeText(link);
|
||||||
|
|
||||||
toast.classList.remove("show");
|
Share.toast.classList.remove("show");
|
||||||
void toast.offsetWidth;
|
void Share.toast.offsetWidth;
|
||||||
toast.classList.add("show");
|
Share.toast.classList.add("show");
|
||||||
});
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
export function sharedText(): string|null {
|
|
||||||
try{
|
|
||||||
const url = new URL(globalThis.window.location.href);
|
const url = new URL(globalThis.window.location.href);
|
||||||
let text: string | null = url.searchParams.get("share");
|
let text: string | null = url.searchParams.get("share");
|
||||||
if (text !== null) {
|
if (text !== null) {
|
||||||
|
|
@ -30,12 +34,16 @@ export function sharedText(): string|null {
|
||||||
globalThis.window.history.replaceState(
|
globalThis.window.history.replaceState(
|
||||||
{},
|
{},
|
||||||
document.title,
|
document.title,
|
||||||
url.pathname + url.search + url.hash
|
url.pathname + url.search + url.hash,
|
||||||
);
|
);
|
||||||
|
Share.shareText = text;
|
||||||
}
|
}
|
||||||
return text;
|
} catch (e) {
|
||||||
}catch(e){
|
console.log(e);
|
||||||
console.log(e)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static sharedText(): string | null {
|
||||||
|
return Share.shareText;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { bus } from "./bus.ts";
|
import { bus } from "./bus.ts";
|
||||||
import {
|
import type {
|
||||||
Fa,
|
Fa,
|
||||||
Machine,
|
Machine,
|
||||||
parse_machine_from_json,
|
|
||||||
Pda,
|
Pda,
|
||||||
State,
|
State,
|
||||||
Symbol,
|
Symbol,
|
||||||
Tm,
|
Tm,
|
||||||
} from "./automata.ts";
|
} from "./automata.ts";
|
||||||
|
import {parse_machine_from_json} from "./automata.ts";
|
||||||
|
|
||||||
export type SimStepResult = "pending" | "accept" | "reject";
|
export type SimStepResult = "pending" | "accept" | "reject";
|
||||||
export type Sim = FaSim | PdaSim | TmSim;
|
export type Sim = FaSim | PdaSim | TmSim;
|
||||||
|
|
@ -26,7 +26,7 @@ let automaton: Machine = {
|
||||||
bus.on("compiled", ({ machine }) => {
|
bus.on("compiled", ({ machine }) => {
|
||||||
if (machine) {
|
if (machine) {
|
||||||
try {
|
try {
|
||||||
bus.emit("controls/clear_simulation", undefined);
|
bus.emit("controls/sim/clear", undefined);
|
||||||
automaton = parse_machine_from_json(machine);
|
automaton = parse_machine_from_json(machine);
|
||||||
bus.emit("automata/update", { automaton });
|
bus.emit("automata/update", { automaton });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -34,11 +34,11 @@ bus.on("compiled", ({ machine }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
bus.on("controls/clear_simulation", (_) => {
|
bus.on("controls/sim/clear", (_) => {
|
||||||
simulation = null;
|
simulation = null;
|
||||||
bus.emit("automata/sim/update", { simulation: null });
|
bus.emit("automata/sim/update", { simulation: null });
|
||||||
});
|
});
|
||||||
bus.on("controls/step_simulation", (_) => {
|
bus.on("controls/sim/step", (_) => {
|
||||||
if (simulation) {
|
if (simulation) {
|
||||||
bus.emit("automata/sim/before_step", { simulation });
|
bus.emit("automata/sim/before_step", { simulation });
|
||||||
bus.emit("automata/sim/after_step", {
|
bus.emit("automata/sim/after_step", {
|
||||||
|
|
@ -51,10 +51,10 @@ const machineInput = document.getElementById("machineInput") as HTMLInputElement
|
||||||
machineInput.addEventListener("input", () => bus.emit("automata/sim/update", {simulation: null}));
|
machineInput.addEventListener("input", () => bus.emit("automata/sim/update", {simulation: null}));
|
||||||
machineInput.addEventListener("keydown", (e) => {
|
machineInput.addEventListener("keydown", (e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
bus.emit("controls/reload_simulation", undefined)
|
bus.emit("controls/sim/reload", undefined)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
bus.on("controls/reload_simulation", (_) => {
|
bus.on("controls/sim/reload", (_) => {
|
||||||
const input = machineInput.value;
|
const input = machineInput.value;
|
||||||
switch (automaton.type) {
|
switch (automaton.type) {
|
||||||
case "fa":
|
case "fa":
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ import type { Sim } from "./simulation.ts";
|
||||||
import type { Machine } from "./automata.ts";
|
import type { Machine } from "./automata.ts";
|
||||||
|
|
||||||
|
|
||||||
bus.on("controls/physics", ({enabled}) => {
|
bus.on("controls/vis/physics", ({enabled}) => {
|
||||||
network.setOptions({ physics: { enabled } });
|
network.setOptions({ physics: { enabled } });
|
||||||
network.setOptions({edges: {smooth: enabled}});
|
network.setOptions({edges: {smooth: enabled}});
|
||||||
});
|
});
|
||||||
bus.on("controls/reset_network", _ => {
|
bus.on("controls/vis/reset_network", _ => {
|
||||||
try {
|
try {
|
||||||
nodes.forEach((n) => {
|
nodes.forEach((n) => {
|
||||||
n.physics = true;
|
n.physics = true;
|
||||||
|
|
@ -285,17 +285,16 @@ function createGraph(): vis.Network {
|
||||||
nodes: {
|
nodes: {
|
||||||
shape: "custom",
|
shape: "custom",
|
||||||
size: 18,
|
size: 18,
|
||||||
// // @ts-expect-error bad library
|
|
||||||
// chosen: {
|
|
||||||
// node: chosen_node,
|
|
||||||
// },
|
|
||||||
// @ts-expect-error bad library
|
// @ts-expect-error bad library
|
||||||
|
chosen: {
|
||||||
|
node: chosen_node,
|
||||||
|
},
|
||||||
ctxRenderer: renderNode,
|
ctxRenderer: renderNode,
|
||||||
},
|
},
|
||||||
edges: {
|
edges: {
|
||||||
chosen: {
|
chosen: {
|
||||||
// // @ts-expect-error bad library
|
// @ts-expect-error bad library
|
||||||
// edge: chosen_edge,
|
edge: chosen_edge,
|
||||||
},
|
},
|
||||||
arrowStrikethrough: false,
|
arrowStrikethrough: false,
|
||||||
arrows: "to",
|
arrows: "to",
|
||||||
|
|
@ -304,9 +303,8 @@ function createGraph(): vis.Network {
|
||||||
);
|
);
|
||||||
vis.DataSet;
|
vis.DataSet;
|
||||||
|
|
||||||
network.on("doubleClick", (params: any) => {
|
network.on("doubleClick", (params: {nodes: string[]}) => {
|
||||||
for (const node_id of params.nodes) {
|
for (const node_id of params.nodes) {
|
||||||
// @ts-expect-error bad library
|
|
||||||
const node: vis.Node = nodes.get(node_id)!;
|
const node: vis.Node = nodes.get(node_id)!;
|
||||||
node.physics = !node.physics;
|
node.physics = !node.physics;
|
||||||
nodes.update(node);
|
nodes.update(node);
|
||||||
|
|
@ -325,7 +323,7 @@ function renderNode({
|
||||||
state: { selected, hover },
|
state: { selected, hover },
|
||||||
style,
|
style,
|
||||||
label,
|
label,
|
||||||
}: {ctx: CanvasRenderingContext2D, id: string, x: number, y: number, state: {selected: boolean, hover: boolean}, style: any, label: string}) {
|
}: {ctx: CanvasRenderingContext2D, id: string, x: number, y: number, state: {selected: boolean, hover: boolean}, style: vis.NodeOptions, label: string}) {
|
||||||
return {
|
return {
|
||||||
drawNode() {
|
drawNode() {
|
||||||
const t = getGraphTheme();
|
const t = getGraphTheme();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue