refactor: bus terminals with single-sided pins only
BUS_IN has input pins only (left side), BUS_OUT has output pins only (right side). No internal connections between them — BUS_OUT reads values directly from its paired BUS_IN via busPairId. The bus cable between them is purely visual, representing the grouped signal bundle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
49
js/bus.js
49
js/bus.js
@@ -125,33 +125,32 @@ export function createBusFromCut() {
|
|||||||
const busInId = state.nextId++;
|
const busInId = state.nextId++;
|
||||||
const busOutId = state.nextId++;
|
const busOutId = state.nextId++;
|
||||||
|
|
||||||
// Create BUS_IN terminal (left — collects wires into bus)
|
// Create BUS_IN terminal (left — collects wires into bus, only input pins)
|
||||||
const busIn = {
|
const busIn = {
|
||||||
id: busInId,
|
id: busInId,
|
||||||
type: `BUS:${n}`,
|
type: `BUS_IN:${n}`,
|
||||||
x: avgX - gap / 2 - 15,
|
x: avgX - gap / 2 - 15,
|
||||||
y: avgY - busH / 2,
|
y: avgY - busH / 2,
|
||||||
value: 0,
|
value: 0,
|
||||||
outputValues: new Array(n).fill(0),
|
busValues: new Array(n).fill(0),
|
||||||
busRole: 'in',
|
|
||||||
busPairId: busOutId
|
busPairId: busOutId
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create BUS_OUT terminal (right — distributes bus back to wires)
|
// Create BUS_OUT terminal (right — distributes bus back to wires, only output pins)
|
||||||
const busOut = {
|
const busOut = {
|
||||||
id: busOutId,
|
id: busOutId,
|
||||||
type: `BUS:${n}`,
|
type: `BUS_OUT:${n}`,
|
||||||
x: avgX + gap / 2 - 15,
|
x: avgX + gap / 2 - 15,
|
||||||
y: avgY - busH / 2,
|
y: avgY - busH / 2,
|
||||||
value: 0,
|
value: 0,
|
||||||
outputValues: new Array(n).fill(0),
|
outputValues: new Array(n).fill(0),
|
||||||
busRole: 'out',
|
|
||||||
busPairId: busInId
|
busPairId: busInId
|
||||||
};
|
};
|
||||||
|
|
||||||
state.gates.push(busIn, busOut);
|
state.gates.push(busIn, busOut);
|
||||||
|
|
||||||
// Rewire connections through both terminals
|
// Rewire: source → BUS_IN input, BUS_OUT output → destination
|
||||||
|
// No internal connections — BUS_OUT reads from BUS_IN directly via busPairId
|
||||||
hits.forEach((hit, i) => {
|
hits.forEach((hit, i) => {
|
||||||
const orig = hit.conn;
|
const orig = hit.conn;
|
||||||
|
|
||||||
@@ -166,14 +165,6 @@ export function createBusFromCut() {
|
|||||||
toPort: i
|
toPort: i
|
||||||
});
|
});
|
||||||
|
|
||||||
// BUS_IN output[i] → BUS_OUT input[i] (internal bus link, rendered as cable)
|
|
||||||
state.connections.push({
|
|
||||||
from: busIn.id,
|
|
||||||
fromPort: i,
|
|
||||||
to: busOut.id,
|
|
||||||
toPort: i
|
|
||||||
});
|
|
||||||
|
|
||||||
// BUS_OUT output[i] → original destination
|
// BUS_OUT output[i] → original destination
|
||||||
state.connections.push({
|
state.connections.push({
|
||||||
from: busOut.id,
|
from: busOut.id,
|
||||||
@@ -187,19 +178,6 @@ export function createBusFromCut() {
|
|||||||
evaluateAll();
|
evaluateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a connection is an internal bus link (between paired terminals).
|
|
||||||
* Used by the renderer to skip drawing these as normal wires.
|
|
||||||
*/
|
|
||||||
export function isBusInternalConnection(conn) {
|
|
||||||
const fromGate = state.gates.find(g => g.id === conn.from);
|
|
||||||
const toGate = state.gates.find(g => g.id === conn.to);
|
|
||||||
if (!fromGate || !toGate) return false;
|
|
||||||
return fromGate.type.startsWith('BUS:') &&
|
|
||||||
toGate.type.startsWith('BUS:') &&
|
|
||||||
fromGate.busPairId === toGate.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all bus pairs for rendering the bus cables.
|
* Get all bus pairs for rendering the bus cables.
|
||||||
* Returns array of { inGate, outGate } for each pair.
|
* Returns array of { inGate, outGate } for each pair.
|
||||||
@@ -208,15 +186,12 @@ export function getBusPairs() {
|
|||||||
const pairs = [];
|
const pairs = [];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
for (const gate of state.gates) {
|
for (const gate of state.gates) {
|
||||||
if (!gate.type.startsWith('BUS:') || !gate.busPairId || seen.has(gate.id)) continue;
|
if (!gate.type.startsWith('BUS_IN:') || !gate.busPairId || seen.has(gate.id)) continue;
|
||||||
const pair = state.gates.find(g => g.id === gate.busPairId);
|
const outGate = state.gates.find(g => g.id === gate.busPairId);
|
||||||
if (!pair) continue;
|
if (!outGate || !outGate.type.startsWith('BUS_OUT:')) continue;
|
||||||
seen.add(gate.id);
|
seen.add(gate.id);
|
||||||
seen.add(pair.id);
|
seen.add(outGate.id);
|
||||||
// Determine which is in, which is out
|
pairs.push({ inGate: gate, outGate });
|
||||||
const inGate = gate.busRole === 'in' ? gate : pair;
|
|
||||||
const outGate = gate.busRole === 'out' ? gate : pair;
|
|
||||||
pairs.push({ inGate, outGate });
|
|
||||||
}
|
}
|
||||||
return pairs;
|
return pairs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ export function initEvents() {
|
|||||||
if (Math.abs(x2 - x1) > 5 || Math.abs(y2 - y1) > 5) {
|
if (Math.abs(x2 - x1) > 5 || Math.abs(y2 - y1) > 5) {
|
||||||
state.selectedGates = state.gates
|
state.selectedGates = state.gates
|
||||||
.filter(g => {
|
.filter(g => {
|
||||||
const isDynamic = g.type.startsWith('COMPONENT:') || g.type.startsWith('BUS:');
|
const isDynamic = g.type.startsWith('COMPONENT:') || g.type.startsWith('BUS_IN:') || g.type.startsWith('BUS_OUT:');
|
||||||
const gw = isDynamic ? getComponentWidth(g) : GATE_W;
|
const gw = isDynamic ? getComponentWidth(g) : GATE_W;
|
||||||
const gh = isDynamic ? getComponentHeight(g) : GATE_H;
|
const gh = isDynamic ? getComponentHeight(g) : GATE_H;
|
||||||
// Gate overlaps selection box
|
// Gate overlaps selection box
|
||||||
|
|||||||
47
js/gates.js
47
js/gates.js
@@ -11,7 +11,8 @@ export function gateInputCount(type) {
|
|||||||
const component = state.customComponents?.[componentId];
|
const component = state.customComponents?.[componentId];
|
||||||
return component ? component.inputCount : 0;
|
return component ? component.inputCount : 0;
|
||||||
}
|
}
|
||||||
if (type.startsWith('BUS:')) return parseInt(type.substring(4)) || 0;
|
if (type.startsWith('BUS_IN:')) return parseInt(type.substring(7)) || 0;
|
||||||
|
if (type.startsWith('BUS_OUT:')) return 0;
|
||||||
return baseGateInputCount(type);
|
return baseGateInputCount(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,12 +22,23 @@ export function gateOutputCount(type) {
|
|||||||
const component = state.customComponents?.[componentId];
|
const component = state.customComponents?.[componentId];
|
||||||
return component ? component.outputCount : 0;
|
return component ? component.outputCount : 0;
|
||||||
}
|
}
|
||||||
if (type.startsWith('BUS:')) return parseInt(type.substring(4)) || 0;
|
if (type.startsWith('BUS_IN:')) return 0;
|
||||||
|
if (type.startsWith('BUS_OUT:')) return parseInt(type.substring(8)) || 0;
|
||||||
return baseGateOutputCount(type);
|
return baseGateOutputCount(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBusType(type) {
|
||||||
|
return type.startsWith('BUS_IN:') || type.startsWith('BUS_OUT:');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBusSize(type) {
|
||||||
|
if (type.startsWith('BUS_IN:')) return parseInt(type.substring(7)) || 1;
|
||||||
|
if (type.startsWith('BUS_OUT:')) return parseInt(type.substring(8)) || 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
export function getComponentWidth(gate) {
|
export function getComponentWidth(gate) {
|
||||||
if (gate.type.startsWith('BUS:')) return 30;
|
if (isBusType(gate.type)) return 30;
|
||||||
if (gate.type.startsWith('COMPONENT:')) {
|
if (gate.type.startsWith('COMPONENT:')) {
|
||||||
const count = Math.max(gate.component?.inputCount || 1, gate.component?.outputCount || 1);
|
const count = Math.max(gate.component?.inputCount || 1, gate.component?.outputCount || 1);
|
||||||
return Math.max(120, (count + 1) * 25);
|
return Math.max(120, (count + 1) * 25);
|
||||||
@@ -35,8 +47,8 @@ export function getComponentWidth(gate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getComponentHeight(gate) {
|
export function getComponentHeight(gate) {
|
||||||
if (gate.type.startsWith('BUS:')) {
|
if (isBusType(gate.type)) {
|
||||||
const n = parseInt(gate.type.substring(4)) || 1;
|
const n = getBusSize(gate.type);
|
||||||
return Math.max(40, (n + 1) * 22);
|
return Math.max(40, (n + 1) * 22);
|
||||||
}
|
}
|
||||||
if (gate.type.startsWith('COMPONENT:')) {
|
if (gate.type.startsWith('COMPONENT:')) {
|
||||||
@@ -49,7 +61,7 @@ export function getComponentHeight(gate) {
|
|||||||
export function getInputPorts(gate) {
|
export function getInputPorts(gate) {
|
||||||
const count = gateInputCount(gate.type);
|
const count = gateInputCount(gate.type);
|
||||||
const ports = [];
|
const ports = [];
|
||||||
const isDynamic = gate.type.startsWith('COMPONENT:') || gate.type.startsWith('BUS:');
|
const isDynamic = gate.type.startsWith('COMPONENT:') || isBusType(gate.type);
|
||||||
const gateHeight = isDynamic ? getComponentHeight(gate) : GATE_H;
|
const gateHeight = isDynamic ? getComponentHeight(gate) : GATE_H;
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
@@ -62,7 +74,7 @@ export function getInputPorts(gate) {
|
|||||||
export function getOutputPorts(gate) {
|
export function getOutputPorts(gate) {
|
||||||
const count = gateOutputCount(gate.type);
|
const count = gateOutputCount(gate.type);
|
||||||
const ports = [];
|
const ports = [];
|
||||||
const isDynamic = gate.type.startsWith('COMPONENT:') || gate.type.startsWith('BUS:');
|
const isDynamic = gate.type.startsWith('COMPONENT:') || isBusType(gate.type);
|
||||||
const gateWidth = isDynamic ? getComponentWidth(gate) : GATE_W;
|
const gateWidth = isDynamic ? getComponentWidth(gate) : GATE_W;
|
||||||
const gateHeight = isDynamic ? getComponentHeight(gate) : GATE_H;
|
const gateHeight = isDynamic ? getComponentHeight(gate) : GATE_H;
|
||||||
|
|
||||||
@@ -112,10 +124,21 @@ function computeGate(gate) {
|
|||||||
return outputs[0] || 0;
|
return outputs[0] || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BUS: pass-through, each input maps to corresponding output
|
// BUS_IN: collect input values and store them for the paired BUS_OUT
|
||||||
if (gate.type.startsWith('BUS:')) {
|
if (gate.type.startsWith('BUS_IN:')) {
|
||||||
gate.outputValues = [...inputs];
|
gate.busValues = [...inputs];
|
||||||
return inputs[0] || 0;
|
gate.value = inputs[0] || 0;
|
||||||
|
return gate.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUS_OUT: read values from paired BUS_IN terminal
|
||||||
|
if (gate.type.startsWith('BUS_OUT:')) {
|
||||||
|
const pair = state.gates.find(g => g.id === gate.busPairId);
|
||||||
|
if (pair && pair.busValues) {
|
||||||
|
gate.outputValues = [...pair.busValues];
|
||||||
|
gate.value = gate.outputValues[0] || 0;
|
||||||
|
}
|
||||||
|
return gate.value || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (gate.type) {
|
switch (gate.type) {
|
||||||
@@ -181,7 +204,7 @@ setEvaluateAll(evaluateAll);
|
|||||||
|
|
||||||
export function findGateAt(x, y) {
|
export function findGateAt(x, y) {
|
||||||
return state.gates.find(g => {
|
return state.gates.find(g => {
|
||||||
const isDynamic = g.type.startsWith('COMPONENT:') || g.type.startsWith('BUS:');
|
const isDynamic = g.type.startsWith('COMPONENT:') || isBusType(g.type);
|
||||||
const w = isDynamic ? getComponentWidth(g) : GATE_W;
|
const w = isDynamic ? getComponentWidth(g) : GATE_W;
|
||||||
const h = isDynamic ? getComponentHeight(g) : GATE_H;
|
const h = isDynamic ? getComponentHeight(g) : GATE_H;
|
||||||
return x >= g.x && x <= g.x + w && y >= g.y && y <= g.y + h;
|
return x >= g.x && x <= g.x + w && y >= g.y && y <= g.y + h;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { GATE_W, GATE_H, COMP_W, PORT_R, GATE_COLORS } from './constants.js';
|
|||||||
import { state } from './state.js';
|
import { state } from './state.js';
|
||||||
import { getInputPorts, getOutputPorts, getComponentWidth, getComponentHeight } from './gates.js';
|
import { getInputPorts, getOutputPorts, getComponentWidth, getComponentHeight } from './gates.js';
|
||||||
import { getGateLabel, drawWaveLabels, drawWaveforms } from './waveform.js';
|
import { getGateLabel, drawWaveLabels, drawWaveforms } from './waveform.js';
|
||||||
import { isBusInternalConnection, getBusPairs } from './bus.js';
|
import { getBusPairs } from './bus.js';
|
||||||
|
|
||||||
let canvas, ctx;
|
let canvas, ctx;
|
||||||
|
|
||||||
@@ -32,9 +32,13 @@ export function screenToWorld(sx, sy) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBusType(type) {
|
||||||
|
return type.startsWith('BUS_IN:') || type.startsWith('BUS_OUT:');
|
||||||
|
}
|
||||||
|
|
||||||
function drawSelectionHighlight(gate) {
|
function drawSelectionHighlight(gate) {
|
||||||
if (!state.selectedGates.includes(gate.id)) return;
|
if (!state.selectedGates.includes(gate.id)) return;
|
||||||
const isDynamic = gate.type.startsWith('COMPONENT:') || gate.type.startsWith('BUS:');
|
const isDynamic = gate.type.startsWith('COMPONENT:') || isBusType(gate.type);
|
||||||
const w = isDynamic ? getComponentWidth(gate) : GATE_W;
|
const w = isDynamic ? getComponentWidth(gate) : GATE_W;
|
||||||
const h = isDynamic ? getComponentHeight(gate) : GATE_H;
|
const h = isDynamic ? getComponentHeight(gate) : GATE_H;
|
||||||
const pad = 4;
|
const pad = 4;
|
||||||
@@ -49,7 +53,7 @@ function drawSelectionHighlight(gate) {
|
|||||||
|
|
||||||
function drawGate(gate) {
|
function drawGate(gate) {
|
||||||
// Special gate types have different rendering
|
// Special gate types have different rendering
|
||||||
if (gate.type.startsWith('BUS:')) { drawBusGate(gate); drawSelectionHighlight(gate); return; }
|
if (isBusType(gate.type)) { drawBusGate(gate); drawSelectionHighlight(gate); return; }
|
||||||
if (gate.type.startsWith('COMPONENT:')) { drawComponentGate(gate); drawSelectionHighlight(gate); return; }
|
if (gate.type.startsWith('COMPONENT:')) { drawComponentGate(gate); drawSelectionHighlight(gate); return; }
|
||||||
|
|
||||||
const color = GATE_COLORS[gate.type];
|
const color = GATE_COLORS[gate.type];
|
||||||
@@ -149,8 +153,14 @@ function drawBusGate(gate) {
|
|||||||
const w = getComponentWidth(gate); // 30
|
const w = getComponentWidth(gate); // 30
|
||||||
const h = getComponentHeight(gate);
|
const h = getComponentHeight(gate);
|
||||||
const color = '#44ddff';
|
const color = '#44ddff';
|
||||||
const n = parseInt(gate.type.substring(4)) || 1;
|
const isIn = gate.type.startsWith('BUS_IN:');
|
||||||
const hasActive = gate.outputValues?.some(v => v === 1);
|
const n = isIn
|
||||||
|
? parseInt(gate.type.substring(7)) || 1
|
||||||
|
: parseInt(gate.type.substring(8)) || 1;
|
||||||
|
|
||||||
|
// Check if any channel is active
|
||||||
|
const values = isIn ? gate.busValues : gate.outputValues;
|
||||||
|
const hasActive = values?.some(v => v === 1);
|
||||||
|
|
||||||
if (hasActive) {
|
if (hasActive) {
|
||||||
ctx.shadowColor = color;
|
ctx.shadowColor = color;
|
||||||
@@ -180,43 +190,48 @@ function drawBusGate(gate) {
|
|||||||
ctx.fillStyle = color;
|
ctx.fillStyle = color;
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
const roleIcon = gate.busRole === 'in' ? '▶' : gate.busRole === 'out' ? '◀' : '';
|
const roleIcon = isIn ? '▶' : '◀';
|
||||||
ctx.fillText(`${roleIcon} ${n}`, gate.x + w / 2, gate.y + h + 10);
|
ctx.fillText(`${roleIcon} ${n}`, gate.x + w / 2, gate.y + h + 10);
|
||||||
|
|
||||||
// Input ports (left)
|
// BUS_IN: only input ports (left side)
|
||||||
getInputPorts(gate).forEach(p => {
|
if (isIn) {
|
||||||
const isPortHovered = state.hoveredPort &&
|
getInputPorts(gate).forEach(p => {
|
||||||
state.hoveredPort.gate === gate &&
|
const isPortHovered = state.hoveredPort &&
|
||||||
state.hoveredPort.index === p.index &&
|
state.hoveredPort.gate === gate &&
|
||||||
state.hoveredPort.type === 'input';
|
state.hoveredPort.index === p.index &&
|
||||||
const conn = state.connections.find(c => c.to === gate.id && c.toPort === p.index);
|
state.hoveredPort.type === 'input';
|
||||||
const portActive = conn ? state.gates.find(g => g.id === conn.from)?.value : 0;
|
const conn = state.connections.find(c => c.to === gate.id && c.toPort === p.index);
|
||||||
|
const srcGate = conn ? state.gates.find(g => g.id === conn.from) : null;
|
||||||
|
const portActive = srcGate ? (srcGate.outputValues ? (srcGate.outputValues[conn.fromPort] || 0) : srcGate.value) : 0;
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(p.x, p.y, PORT_R - 1, 0, Math.PI * 2);
|
ctx.arc(p.x, p.y, PORT_R - 1, 0, Math.PI * 2);
|
||||||
ctx.fillStyle = isPortHovered ? '#fff' : (portActive ? '#00ff88' : '#1a1a2e');
|
ctx.fillStyle = isPortHovered ? '#fff' : (portActive ? '#00ff88' : '#1a1a2e');
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.strokeStyle = isPortHovered ? '#fff' : '#555';
|
ctx.strokeStyle = isPortHovered ? '#fff' : '#555';
|
||||||
ctx.lineWidth = 1.5;
|
ctx.lineWidth = 1.5;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Output ports (right)
|
// BUS_OUT: only output ports (right side)
|
||||||
getOutputPorts(gate).forEach(p => {
|
if (!isIn) {
|
||||||
const isPortHovered = state.hoveredPort &&
|
getOutputPorts(gate).forEach(p => {
|
||||||
state.hoveredPort.gate === gate &&
|
const isPortHovered = state.hoveredPort &&
|
||||||
state.hoveredPort.index === p.index &&
|
state.hoveredPort.gate === gate &&
|
||||||
state.hoveredPort.type === 'output';
|
state.hoveredPort.index === p.index &&
|
||||||
const portVal = gate.outputValues ? (gate.outputValues[p.index] || 0) : 0;
|
state.hoveredPort.type === 'output';
|
||||||
|
const portVal = gate.outputValues ? (gate.outputValues[p.index] || 0) : 0;
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(p.x, p.y, PORT_R - 1, 0, Math.PI * 2);
|
ctx.arc(p.x, p.y, PORT_R - 1, 0, Math.PI * 2);
|
||||||
ctx.fillStyle = isPortHovered ? '#fff' : (portVal ? '#00ff88' : '#1a1a2e');
|
ctx.fillStyle = isPortHovered ? '#fff' : (portVal ? '#00ff88' : '#1a1a2e');
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.strokeStyle = isPortHovered ? '#fff' : '#555';
|
ctx.strokeStyle = isPortHovered ? '#fff' : '#555';
|
||||||
ctx.lineWidth = 1.5;
|
ctx.lineWidth = 1.5;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawBusCables() {
|
function drawBusCables() {
|
||||||
@@ -233,8 +248,8 @@ function drawBusCables() {
|
|||||||
const y2 = outGate.y + outH / 2;
|
const y2 = outGate.y + outH / 2;
|
||||||
|
|
||||||
// Check if any channel is active
|
// Check if any channel is active
|
||||||
const hasActive = inGate.outputValues?.some(v => v === 1);
|
const hasActive = inGate.busValues?.some(v => v === 1);
|
||||||
const n = parseInt(inGate.type.substring(4)) || 1;
|
const n = parseInt(inGate.type.substring(7)) || 1;
|
||||||
|
|
||||||
// Outer thick cable (bus background)
|
// Outer thick cable (bus background)
|
||||||
const cableWidth = Math.max(6, n * 2.5);
|
const cableWidth = Math.max(6, n * 2.5);
|
||||||
@@ -384,9 +399,6 @@ function drawComponentGate(gate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function drawConnection(conn) {
|
function drawConnection(conn) {
|
||||||
// Skip internal bus connections (rendered as bus cable instead)
|
|
||||||
if (isBusInternalConnection(conn)) return;
|
|
||||||
|
|
||||||
const fromGate = state.gates.find(g => g.id === conn.from);
|
const fromGate = state.gates.find(g => g.id === conn.from);
|
||||||
const toGate = state.gates.find(g => g.id === conn.to);
|
const toGate = state.gates.find(g => g.id === conn.to);
|
||||||
if (!fromGate || !toGate) return;
|
if (!fromGate || !toGate) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user