mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-07 05:28:45 -04:00
added path explorer
This commit is contained in:
parent
bf01761264
commit
ac77007022
11 changed files with 473 additions and 125 deletions
|
|
@ -48,12 +48,45 @@
|
|||
<div class="hSplit styleOnly" title="Drag to resize canvas height"></div>
|
||||
|
||||
<div class="flexCenter sidePadding" style="font-size: calc(16px);font-weight: bold;color: var(--fg-1)">
|
||||
<span style="margin-right: 0.5em;">Status: </span><span style="color: var(--fg-2)" id="simulationStatus">N/A</span>
|
||||
<span style="margin-right: 0.5em;">Status: </span><span style="color: var(--fg-2)"
|
||||
id="simulationStatus">N/A</span>
|
||||
</div>
|
||||
<div class="flexCenter sidePadding">
|
||||
<input id="machineInput" type="text" class="test-input" placeholder="Enter machine input…" />
|
||||
</div>
|
||||
|
||||
<div class="hSplit styleOnly" title="Drag to resize canvas height"></div>
|
||||
|
||||
<div class="sidePadding scroll">
|
||||
<div class="pathsGrid">
|
||||
|
||||
<details class="group group-accepted">
|
||||
<summary class="groupTitle" open>
|
||||
<span>Accepted</span>
|
||||
<span class="count" id="acceptedCount">0</span>
|
||||
</summary>
|
||||
<div class="groupBody" id="acceptedPaths"></div>
|
||||
</details>
|
||||
|
||||
<details class="group group-running" open>
|
||||
<summary class="groupTitle">
|
||||
<span>Running</span>
|
||||
<span class="count" id="runningCount">0</span>
|
||||
</summary>
|
||||
<div class="groupBody" id="runningPaths"></div>
|
||||
</details>
|
||||
|
||||
<details class="group group-rejected" open>
|
||||
<summary class="groupTitle">
|
||||
<span>Rejected</span>
|
||||
<span class="count" id="rejectedCount">0</span>
|
||||
</summary>
|
||||
<div class="groupBody" id="rejectedPaths"></div>
|
||||
</details>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
|
@ -80,7 +113,7 @@
|
|||
|
||||
|
||||
<button id="stepSim" class="btn btn-yellow" title="Advance one step">
|
||||
⏭ Step
|
||||
⏭ Step
|
||||
</button>
|
||||
<button id="playPauseSim" class="btn btn-green" title="Run / pause simulation">
|
||||
▶ Play
|
||||
|
|
|
|||
2
web/root/src/constants.ts
Normal file
2
web/root/src/constants.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export const EPSILON: string = "ε"
|
||||
export const DELTA: string = "δ"
|
||||
|
|
@ -8,5 +8,6 @@ import "./visualizer.ts"
|
|||
import "./editor.ts"
|
||||
import "./simulation.ts"
|
||||
import "./terminal.ts"
|
||||
import "./paths.ts"
|
||||
|
||||
bus.emit("begin", undefined);
|
||||
147
web/root/src/paths.ts
Normal file
147
web/root/src/paths.ts
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
import { bus } from "./bus.ts";
|
||||
import { DELTA } from "./constants.ts";
|
||||
import type { Sim } from "./simulation.ts";
|
||||
import type { FaState } from "./simulation/fa.ts";
|
||||
import type { PdaState } from "./simulation/pda.ts";
|
||||
import type { TmState } from "./simulation/tm.ts";
|
||||
|
||||
type AnyState = {
|
||||
repr: string;
|
||||
path: readonly unknown[];
|
||||
};
|
||||
|
||||
function renderFaPath(state: FaState, index: number) {
|
||||
const details = document.createElement("details");
|
||||
details.className = "pathItem";
|
||||
|
||||
const summary = document.createElement("summary");
|
||||
summary.className = "pathHeader";
|
||||
summary.innerHTML = `
|
||||
<span>${state.repr}</span>
|
||||
<span class="pathMeta">
|
||||
<span>steps: ${state.path.length}</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
const steps = document.createElement("div");
|
||||
steps.className = "steps";
|
||||
|
||||
for (let i = 0; i < state.path.length; i++) {
|
||||
const div = document.createElement("div");
|
||||
div.className = "stepLine";
|
||||
const step = state.path[i];
|
||||
div.textContent = `${i + 1}. ${DELTA}(${step.from_state}, ${step.from_letter}) = ${step.state}`;
|
||||
steps.appendChild(div);
|
||||
}
|
||||
|
||||
details.appendChild(summary);
|
||||
details.appendChild(steps);
|
||||
return details;
|
||||
}
|
||||
|
||||
function renderPdaPath(state: PdaState, index: number) {
|
||||
const details = document.createElement("details");
|
||||
details.className = "pathItem";
|
||||
|
||||
const summary = document.createElement("summary");
|
||||
summary.className = "pathHeader";
|
||||
summary.innerHTML = `
|
||||
<span>${state.repr}</span>
|
||||
<span class="pathMeta">
|
||||
<span>steps: ${state.path.length}</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
const steps = document.createElement("div");
|
||||
steps.className = "steps";
|
||||
|
||||
for (let i = 0; i < state.path.length; i++) {
|
||||
const div = document.createElement("div");
|
||||
div.className = "stepLine";
|
||||
const step = state.path[i];
|
||||
div.textContent = `${i + 1}. ${DELTA}(${step.from_state}, ${step.from_letter}, , ${step.from_stack}) = (${step.state}, [ ${step.stack.join(" ")} ])`;
|
||||
steps.appendChild(div);
|
||||
}
|
||||
|
||||
details.appendChild(summary);
|
||||
details.appendChild(steps);
|
||||
return details;
|
||||
}
|
||||
|
||||
function renderTmPath(state: TmState, index: number) {
|
||||
const details = document.createElement("details");
|
||||
details.className = "pathItem";
|
||||
|
||||
const summary = document.createElement("summary");
|
||||
summary.className = "pathHeader";
|
||||
summary.innerHTML = `
|
||||
<span>${state.repr}</span>
|
||||
<span class="pathMeta">
|
||||
<span>steps: ${state.path.length}</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
const steps = document.createElement("div");
|
||||
steps.className = "steps";
|
||||
|
||||
for (let i = 0; i < state.path.length; i++) {
|
||||
const div = document.createElement("div");
|
||||
div.className = "stepLine";
|
||||
const step = state.path[i];
|
||||
div.textContent = `${i + 1}. ${DELTA}(${step.from_state}, ${step.from_symbol}) = (${step.state}, ${step.symbol}, ${step.direction})`;
|
||||
steps.appendChild(div);
|
||||
}
|
||||
|
||||
details.appendChild(summary);
|
||||
details.appendChild(steps);
|
||||
return details;
|
||||
}
|
||||
|
||||
bus.on("automata/sim/after_step", ({simulation}) => {
|
||||
renderPaths(simulation)
|
||||
})
|
||||
|
||||
bus.on("automata/sim/update", simulation => {
|
||||
if(simulation){
|
||||
renderPaths(simulation)
|
||||
}else{
|
||||
renderPaths(undefined)
|
||||
}
|
||||
})
|
||||
|
||||
export function renderPaths(sim: Sim | undefined) {
|
||||
const acceptedEl = document.getElementById("acceptedPaths")!;
|
||||
const runningEl = document.getElementById("runningPaths")!;
|
||||
const rejectedEl = document.getElementById("rejectedPaths")!;
|
||||
|
||||
const acceptedCount = document.getElementById("acceptedCount")!;
|
||||
const runningCount = document.getElementById("runningCount")!;
|
||||
const rejectedCount = document.getElementById("rejectedCount")!;
|
||||
|
||||
acceptedEl.innerHTML = "";
|
||||
runningEl.innerHTML = "";
|
||||
rejectedEl.innerHTML = "";
|
||||
|
||||
acceptedCount.textContent = String(sim?.accepted.length ?? 0);
|
||||
runningCount.textContent = String(sim?.paths.length ?? 0);
|
||||
rejectedCount.textContent = String(sim?.rejected.length ?? 0);
|
||||
|
||||
if(!sim)return;
|
||||
switch (sim.machine.type){
|
||||
case "fa":
|
||||
sim.accepted.forEach((s, i) => acceptedEl.appendChild(renderFaPath(s as FaState, i)));
|
||||
sim.paths.forEach((s, i) => runningEl.appendChild(renderFaPath(s as FaState, i)));
|
||||
sim.rejected.forEach((s, i) => rejectedEl.appendChild(renderFaPath(s as FaState, i)));
|
||||
break;
|
||||
case "pda":
|
||||
sim.accepted.forEach((s, i) => acceptedEl.appendChild(renderPdaPath(s as PdaState, i)));
|
||||
sim.paths.forEach((s, i) => runningEl.appendChild(renderPdaPath(s as PdaState, i)));
|
||||
sim.rejected.forEach((s, i) => rejectedEl.appendChild(renderPdaPath(s as PdaState, i)));
|
||||
break;
|
||||
case "tm":
|
||||
sim.accepted.forEach((s, i) => acceptedEl.appendChild(renderTmPath(s as TmState, i)));
|
||||
sim.paths.forEach((s, i) => runningEl.appendChild(renderTmPath(s as TmState, i)));
|
||||
sim.rejected.forEach((s, i) => rejectedEl.appendChild(renderTmPath(s as TmState, i)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -34,12 +34,13 @@ export let automaton: Machine = {
|
|||
bus.on("compiled", ({ machine }) => {
|
||||
if (machine) {
|
||||
try {
|
||||
bus.emit("controls/sim/clear", undefined);
|
||||
automaton = parse_machine_from_json(machine);
|
||||
bus.emit("automata/update", automaton);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}else{
|
||||
bus.emit("controls/sim/clear", undefined);
|
||||
}
|
||||
});
|
||||
bus.on("controls/sim/clear", (_) => {
|
||||
|
|
@ -83,20 +84,21 @@ bus.on("automata/sim/update", simulation => {
|
|||
simulationStatus.innerText = "N/A"
|
||||
simulationStatus.style.color = "var(--fg-2)";
|
||||
}else{
|
||||
simulationStatus.innerText = "Pending"
|
||||
simulationStatus.style.color = "var(--warning)";
|
||||
update_status(simulation.status())
|
||||
}
|
||||
});
|
||||
bus.on("automata/sim/after_step", ({result}) => {
|
||||
if (result === "pending"){
|
||||
update_status(result)
|
||||
});
|
||||
function update_status(status: SimStepResult){
|
||||
if (status === "pending"){
|
||||
simulationStatus.innerText = "Pending"
|
||||
simulationStatus.style.color = "var(--warning)";
|
||||
}else if (result==="accept"){
|
||||
}else if (status==="accept"){
|
||||
simulationStatus.innerText = "Accepted"
|
||||
simulationStatus.style.color = "var(--success)";
|
||||
}else if (result==="reject"){
|
||||
}else if (status==="reject"){
|
||||
simulationStatus.innerText = "Rejected"
|
||||
simulationStatus.style.color = "var(--error)";
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,115 +3,122 @@ import type {
|
|||
FaTransTo,
|
||||
State,
|
||||
} from "../automata.ts";
|
||||
import { EPSILON } from "../constants.ts";
|
||||
import { SimStepResult } from "../simulation.ts";
|
||||
|
||||
|
||||
export type Step = FaTransTo & {from_state: State, from_letter: string}
|
||||
export type FaState = {
|
||||
readonly state: State;
|
||||
readonly position: number;
|
||||
readonly state: State;
|
||||
readonly position: number;
|
||||
|
||||
readonly accepted: boolean;
|
||||
readonly repr: string;
|
||||
readonly accepted: boolean;
|
||||
readonly repr: string;
|
||||
|
||||
readonly path: readonly FaTransTo[];
|
||||
readonly path: readonly Step[];
|
||||
};
|
||||
|
||||
type Initializer<T> = { -readonly [P in keyof T]?: T[P] | undefined };
|
||||
|
||||
export class FaSim {
|
||||
readonly machine: Fa;
|
||||
readonly input: string;
|
||||
readonly machine: Fa;
|
||||
readonly input: string;
|
||||
|
||||
paths: FaState[] = [];
|
||||
current_states: Map<string, FaState[]> = new Map();
|
||||
accepted: FaState[] = [];
|
||||
rejected: FaState[] = [];
|
||||
paths: FaState[] = [];
|
||||
current_states: Map<string, FaState[]> = new Map();
|
||||
accepted: FaState[] = [];
|
||||
rejected: FaState[] = [];
|
||||
|
||||
constructor(machine: Fa, input: string) {
|
||||
this.machine = machine;
|
||||
this.input = input;
|
||||
this.initial();
|
||||
}
|
||||
|
||||
private accept(state: Initializer<FaState>): boolean {
|
||||
const pos = state.position ?? 0;
|
||||
const st = state.state!;
|
||||
return pos === this.input.length && this.machine.final_states.has(st);
|
||||
}
|
||||
|
||||
private init_state(state: Initializer<FaState>) {
|
||||
state.position ??= 0;
|
||||
|
||||
state.accepted = this.accept(state);
|
||||
state.repr = state.state + " >" + this.input.substring(state.position);
|
||||
|
||||
const frozen = state as FaState;
|
||||
|
||||
if (frozen.accepted) this.accepted.push(frozen);
|
||||
this.paths.push(frozen);
|
||||
|
||||
if (!this.current_states.has(frozen.state)) {
|
||||
this.current_states.set(frozen.state, []);
|
||||
}
|
||||
this.current_states.get(frozen.state)!.push(frozen);
|
||||
}
|
||||
|
||||
private initial() {
|
||||
const state: Initializer<FaState> = {
|
||||
state: this.machine.initial_state,
|
||||
position: 0,
|
||||
path: [],
|
||||
};
|
||||
|
||||
this.init_state(state);
|
||||
}
|
||||
|
||||
private transition(from: FaState, to: FaTransTo, consume: boolean) {
|
||||
const state: Initializer<FaState> = {
|
||||
state: to.state,
|
||||
position: from.position + (consume ? 1 : 0),
|
||||
path: from.path.concat([to]),
|
||||
};
|
||||
|
||||
this.init_state(state);
|
||||
}
|
||||
|
||||
step(): SimStepResult {
|
||||
if (this.accepted.length !== 0) return "accept";
|
||||
if (this.paths.length === 0) return "reject";
|
||||
|
||||
const paths = this.paths;
|
||||
this.paths = [];
|
||||
this.current_states.clear();
|
||||
|
||||
for (const from of paths) {
|
||||
const letterMap = this.machine.transitions_components.get(from.state);
|
||||
|
||||
if (!letterMap) {
|
||||
this.rejected.push(from);
|
||||
continue;
|
||||
}
|
||||
|
||||
// epsilon transitions
|
||||
const eps = letterMap.get(null) ?? [];
|
||||
for (const to of eps) this.transition(from, to, false);
|
||||
|
||||
// consuming transitions
|
||||
if (from.position >= this.input.length) {
|
||||
if (eps.length === 0) this.rejected.push(from);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ch = this.input.charAt(from.position);
|
||||
const trs = letterMap.get(ch) ?? [];
|
||||
for (const to of trs) this.transition(from, to, true);
|
||||
|
||||
if (eps.length === 0 && trs.length === 0) {
|
||||
this.rejected.push(from);
|
||||
}
|
||||
constructor(machine: Fa, input: string) {
|
||||
this.machine = machine;
|
||||
this.input = input;
|
||||
this.initial();
|
||||
}
|
||||
|
||||
if (this.accepted.length !== 0) return "accept";
|
||||
if (this.paths.length === 0) return "reject";
|
||||
return "pending";
|
||||
}
|
||||
private accept(state: Initializer<FaState>): boolean {
|
||||
const pos = state.position ?? 0;
|
||||
const st = state.state!;
|
||||
return pos === this.input.length && this.machine.final_states.has(st);
|
||||
}
|
||||
|
||||
private init_state(state: Initializer<FaState>) {
|
||||
state.position ??= 0;
|
||||
|
||||
state.accepted = this.accept(state);
|
||||
state.repr = state.state + " >" + this.input.substring(state.position);
|
||||
|
||||
const frozen = state as FaState;
|
||||
|
||||
if (frozen.accepted) this.accepted.push(frozen);
|
||||
this.paths.push(frozen);
|
||||
|
||||
if (!this.current_states.has(frozen.state)) {
|
||||
this.current_states.set(frozen.state, []);
|
||||
}
|
||||
this.current_states.get(frozen.state)!.push(frozen);
|
||||
}
|
||||
|
||||
private initial() {
|
||||
const state: Initializer<FaState> = {
|
||||
state: this.machine.initial_state,
|
||||
position: 0,
|
||||
path: [],
|
||||
};
|
||||
|
||||
this.init_state(state);
|
||||
}
|
||||
|
||||
private transition(from: FaState, to: FaTransTo, letter: string|undefined) {
|
||||
const state: Initializer<FaState> = {
|
||||
state: to.state,
|
||||
position: from.position + (letter ? 1 : 0),
|
||||
path: from.path.concat([{from_state: from.state, from_letter: letter??EPSILON, ...to}]),
|
||||
};
|
||||
|
||||
this.init_state(state);
|
||||
}
|
||||
|
||||
|
||||
|
||||
step(): SimStepResult {
|
||||
|
||||
const paths = this.paths;
|
||||
this.paths = [];
|
||||
this.current_states.clear();
|
||||
|
||||
for (const from of paths) {
|
||||
const letterMap = this.machine.transitions_components.get(from.state);
|
||||
|
||||
if (!letterMap) {
|
||||
this.rejected.push(from);
|
||||
continue;
|
||||
}
|
||||
|
||||
// epsilon transitions
|
||||
const eps = letterMap.get(null) ?? [];
|
||||
for (const to of eps) this.transition(from, to, undefined);
|
||||
|
||||
// consuming transitions
|
||||
if (from.position >= this.input.length) {
|
||||
if (eps.length === 0) this.rejected.push(from);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ch = this.input.charAt(from.position);
|
||||
const trs = letterMap.get(ch) ?? [];
|
||||
for (const to of trs) this.transition(from, to, ch);
|
||||
|
||||
if (eps.length === 0 && trs.length === 0) {
|
||||
this.rejected.push(from);
|
||||
}
|
||||
}
|
||||
|
||||
return this.status();
|
||||
}
|
||||
|
||||
status(): SimStepResult {
|
||||
if (this.accepted.length !== 0) return "accept";
|
||||
if (this.paths.length === 0) return "reject";
|
||||
return "pending";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import type {
|
|||
Symbol
|
||||
} from "../automata.ts";
|
||||
import { SimStepResult } from "../simulation.ts";
|
||||
import { EPSILON } from "../constants.ts";
|
||||
|
||||
|
||||
export type Step = PdaTransTo & {from_state: State, from_letter: string, from_stack: Symbol}
|
||||
export type PdaState = {
|
||||
readonly state: State;
|
||||
readonly stack: Symbol[];
|
||||
|
|
@ -14,7 +17,7 @@ export type PdaState = {
|
|||
readonly accepted: boolean;
|
||||
readonly repr: string;
|
||||
|
||||
readonly path: readonly PdaTransTo[];
|
||||
readonly path: readonly Step[];
|
||||
};
|
||||
|
||||
type Initializer<T> = { -readonly [P in keyof T]?: T[P] | undefined };
|
||||
|
|
@ -80,7 +83,7 @@ export class PdaSim {
|
|||
this.init_state(state);
|
||||
}
|
||||
|
||||
private transition(from: PdaState, to: PdaTransTo, consume: boolean) {
|
||||
private transition(from: PdaState, to: PdaTransTo, letter: string|undefined) {
|
||||
const stackCopy = from.stack.slice(0, from.stack.length - 1); // pop off top
|
||||
const nextStack = stackCopy.concat(to.stack);
|
||||
if (nextStack.length == 0) {
|
||||
|
|
@ -91,16 +94,14 @@ export class PdaSim {
|
|||
const state: Initializer<PdaState> = {
|
||||
state: to.state,
|
||||
stack: nextStack,
|
||||
position: from.position + (consume ? 1 : 0),
|
||||
path: from.path.concat([to]),
|
||||
position: from.position + (letter ? 1 : 0),
|
||||
path: from.path.concat([{from_state: from.state, from_letter: letter??EPSILON, from_stack: from.stack[from.stack.length-1], ...to}]),
|
||||
};
|
||||
|
||||
this.init_state(state);
|
||||
}
|
||||
|
||||
step(): SimStepResult {
|
||||
if (this.accepted.length !== 0) return "accept";
|
||||
if (this.paths.length === 0) return "reject";
|
||||
|
||||
const paths = this.paths;
|
||||
this.paths = [];
|
||||
|
|
@ -118,7 +119,7 @@ export class PdaSim {
|
|||
// epsilon transitions
|
||||
const epsilon_transitions = letterMap.get(null) ?? [];
|
||||
for (const to of epsilon_transitions) {
|
||||
this.transition(from, to, false);
|
||||
this.transition(from, to, undefined);
|
||||
}
|
||||
|
||||
if (from.position >= this.input.length) {
|
||||
|
|
@ -132,13 +133,17 @@ export class PdaSim {
|
|||
|
||||
const transitions = letterMap.get(ch) ?? [];
|
||||
for (const to of transitions) {
|
||||
this.transition(from, to, true);
|
||||
this.transition(from, to, ch);
|
||||
}
|
||||
if (epsilon_transitions.length == 0 && transitions.length == 0){
|
||||
this.rejected.push(from);
|
||||
}
|
||||
}
|
||||
|
||||
return this.status();
|
||||
}
|
||||
|
||||
status(): SimStepResult {
|
||||
if (this.accepted.length !== 0) return "accept";
|
||||
if (this.paths.length === 0) return "reject";
|
||||
return "pending";
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import type {
|
|||
import { SimStepResult } from "../simulation.ts";
|
||||
|
||||
|
||||
export type Step = TmTransTo & {from_state: State, from_symbol: Symbol}
|
||||
export type TmState = {
|
||||
readonly state: State;
|
||||
readonly tape: Symbol[];
|
||||
|
|
@ -15,7 +16,7 @@ export type TmState = {
|
|||
readonly accepted: boolean;
|
||||
readonly repr: string;
|
||||
|
||||
readonly path: readonly TmTransTo[];
|
||||
readonly path: readonly Step[];
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -69,7 +70,7 @@ export class TmSim {
|
|||
state: to.state,
|
||||
accepted: this.machine.final_states.has(to.state),
|
||||
|
||||
path: from.path.concat([to]),
|
||||
path: from.path.concat([{from_state: from.state, from_symbol: from.tape[from.head], ...to}]),
|
||||
};
|
||||
|
||||
switch (to.direction) {
|
||||
|
|
@ -102,8 +103,6 @@ export class TmSim {
|
|||
}
|
||||
|
||||
step(): SimStepResult {
|
||||
if (this.accepted.length != 0) return "accept";
|
||||
if (this.paths.length == 0) return "reject";
|
||||
|
||||
const paths: TmState[] = this.paths;
|
||||
this.paths = [];
|
||||
|
|
@ -122,8 +121,12 @@ export class TmSim {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.accepted.length != 0) return "accept";
|
||||
if (this.paths.length == 0) return "reject";
|
||||
return this.status();
|
||||
}
|
||||
|
||||
status(): SimStepResult {
|
||||
if (this.accepted.length !== 0) return "accept";
|
||||
if (this.paths.length === 0) return "reject";
|
||||
return "pending";
|
||||
}
|
||||
}
|
||||
|
|
@ -307,9 +307,7 @@ function createGraph(): vis.Network {
|
|||
});
|
||||
|
||||
network.on('deselectEdge', item => {
|
||||
console.log(item);
|
||||
for (const edge of item.previousSelection.edges){
|
||||
console.log(edge);
|
||||
dehighlight_from_edge_id(edge.id)
|
||||
}
|
||||
});
|
||||
|
|
@ -320,9 +318,7 @@ function createGraph(): vis.Network {
|
|||
});
|
||||
|
||||
network.on('deselectNode', item => {
|
||||
console.log(item);
|
||||
for (const node of item.previousSelection.nodes){
|
||||
console.log(node);
|
||||
dehighlight_from_node_id(node.id)
|
||||
}
|
||||
});
|
||||
|
|
|
|||
147
web/root/style/paths.scss
Normal file
147
web/root/style/paths.scss
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/* ===== Panel ===== */
|
||||
|
||||
.panel {
|
||||
border: 1px solid var(--separator-bg);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--bg-1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panelTitle {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-weight: 700;
|
||||
color: var(--fg-0);
|
||||
|
||||
background: var(--bg-2);
|
||||
border-bottom: 1px solid var(--separator-bg);
|
||||
}
|
||||
|
||||
.panelTitle::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ===== Layout ===== */
|
||||
|
||||
.pathsGrid {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
/* ===== Groups ===== */
|
||||
|
||||
.group {
|
||||
border: 1px solid var(--separator-bg);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.groupTitle {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
|
||||
padding: var(--space-2) var(--space-3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
|
||||
font-weight: 600;
|
||||
color: var(--fg-0);
|
||||
|
||||
background: var(--bg-2);
|
||||
}
|
||||
|
||||
.groupTitle::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
|
||||
color: var(--fg-muted);
|
||||
background: var(--bg-1);
|
||||
border: 1px solid var(--separator-bg);
|
||||
}
|
||||
|
||||
.groupBody {
|
||||
padding: var(--space-3);
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
/* ===== Path cards ===== */
|
||||
|
||||
.pathItem {
|
||||
border: 1px solid var(--separator-bg);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pathHeader {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
|
||||
padding: var(--space-2) var(--space-3);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
|
||||
color: var(--fg-0);
|
||||
background: var(--bg-2);
|
||||
}
|
||||
|
||||
.pathHeader::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pathMeta {
|
||||
display: inline-flex;
|
||||
gap: var(--space-2);
|
||||
align-items: center;
|
||||
|
||||
font-size: 12px;
|
||||
color: var(--fg-muted);
|
||||
}
|
||||
|
||||
/* ===== Steps ===== */
|
||||
|
||||
.steps {
|
||||
padding: var(--space-2) var(--space-3) var(--space-3);
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.stepLine {
|
||||
font: 500 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
|
||||
color: var(--fg-1);
|
||||
background: var(--bg-2);
|
||||
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
|
||||
// overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ===== Semantic accents ===== */
|
||||
|
||||
.group-accepted {
|
||||
border-color: color-mix(in srgb, var(--success) 35%, var(--separator-bg));
|
||||
}
|
||||
|
||||
.group-running {
|
||||
border-color: color-mix(in srgb, var(--accent) 35%, var(--separator-bg));
|
||||
}
|
||||
|
||||
.group-rejected {
|
||||
border-color: color-mix(in srgb, var(--error) 35%, var(--separator-bg));
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
@use "loading.scss";
|
||||
@use "controls.scss";
|
||||
@use "themes.scss";
|
||||
@use "paths.scss";
|
||||
|
||||
html,
|
||||
body {
|
||||
|
|
@ -34,6 +35,10 @@ body {
|
|||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.scroll{
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.flexCol{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue