adds support for arduino zero
This commit is contained in:
parent
ae17d4380e
commit
2b87b26f00
2
docs/static/hardware/.gitignore
vendored
2
docs/static/hardware/.gitignore
vendored
@ -1,4 +1,4 @@
|
|||||||
# don't check in until OSS request is approved
|
# don't check in until OSS request is approved
|
||||||
neopixel-black-60-vert.svg
|
|
||||||
sparkfun-*
|
sparkfun-*
|
||||||
raspberrypi-*
|
raspberrypi-*
|
||||||
|
arduino-*
|
@ -79,9 +79,9 @@
|
|||||||
"partsAspectRatio": 0.69,
|
"partsAspectRatio": 0.69,
|
||||||
"builtinParts": {
|
"builtinParts": {
|
||||||
"accelerometer": true,
|
"accelerometer": true,
|
||||||
"buttonpair": true,
|
"buttonpair": false,
|
||||||
"ledmatrix": true,
|
"ledmatrix": false,
|
||||||
"speaker": true,
|
"speaker": false,
|
||||||
"bluetooth": true,
|
"bluetooth": true,
|
||||||
"thermometer": true,
|
"thermometer": true,
|
||||||
"compass": true
|
"compass": true
|
||||||
|
@ -71,11 +71,7 @@ namespace pxsim {
|
|||||||
initAsync(msg: SimulatorRunMessage): Promise<void> {
|
initAsync(msg: SimulatorRunMessage): Promise<void> {
|
||||||
let options = (msg.options || {}) as RuntimeOptions;
|
let options = (msg.options || {}) as RuntimeOptions;
|
||||||
|
|
||||||
//TODO: read from pxt.json/pxttarget.json
|
let boardDef = CURRENT_BOARD; //TODO: read from pxt.json/pxttarget.json
|
||||||
let boardDef = MICROBIT_DEF;
|
|
||||||
// let boardDef = ARDUINO_ZERO;
|
|
||||||
// let boardDef = SPARKFUN_PHOTON;
|
|
||||||
// let boardDef = RASPBERRYPI_MODELB;
|
|
||||||
|
|
||||||
let cmpsList = msg.parts;
|
let cmpsList = msg.parts;
|
||||||
let cmpDefs = PART_DEFINITIONS; //TODO: read from pxt.json/pxttarget.json
|
let cmpDefs = PART_DEFINITIONS; //TODO: read from pxt.json/pxttarget.json
|
||||||
|
@ -7,10 +7,11 @@ namespace pxsim {
|
|||||||
export interface PinBlockDefinition {
|
export interface PinBlockDefinition {
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
|
labelPosition: "above" | "below";
|
||||||
labels: string[]
|
labels: string[]
|
||||||
}
|
}
|
||||||
export interface BoardImageDefinition {
|
export interface BoardImageDefinition {
|
||||||
image?: string,
|
image: string,
|
||||||
outlineImage?: string,
|
outlineImage?: string,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
@ -25,6 +26,8 @@ namespace pxsim {
|
|||||||
threeVoltPins: string[],
|
threeVoltPins: string[],
|
||||||
attachPowerOnRight?: boolean,
|
attachPowerOnRight?: boolean,
|
||||||
onboardComponents?: string[]
|
onboardComponents?: string[]
|
||||||
|
useCrocClips?: boolean,
|
||||||
|
marginWhenBreadboarding?: [number, number, number, number],
|
||||||
}
|
}
|
||||||
export interface FactoryFunctionPinAlloc {
|
export interface FactoryFunctionPinAlloc {
|
||||||
type: "factoryfunction",
|
type: "factoryfunction",
|
||||||
@ -44,9 +47,8 @@ namespace pxsim {
|
|||||||
image: string,
|
image: string,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
left: number,
|
|
||||||
top: number,
|
|
||||||
pinDist: number,
|
pinDist: number,
|
||||||
|
firstPin: [number, number],
|
||||||
}
|
}
|
||||||
export interface PartDefinition {
|
export interface PartDefinition {
|
||||||
visual: string | PartVisualDefinition,
|
visual: string | PartVisualDefinition,
|
||||||
@ -100,6 +102,8 @@ namespace pxsim {
|
|||||||
threeVoltPins: ["+3v3"],
|
threeVoltPins: ["+3v3"],
|
||||||
attachPowerOnRight: true,
|
attachPowerOnRight: true,
|
||||||
onboardComponents: ["buttonpair", "ledmatrix"],
|
onboardComponents: ["buttonpair", "ledmatrix"],
|
||||||
|
useCrocClips: true,
|
||||||
|
marginWhenBreadboarding: [0, 0, 80, 0],
|
||||||
}
|
}
|
||||||
export const RASPBERRYPI_MODELB: BoardDefinition = {
|
export const RASPBERRYPI_MODELB: BoardDefinition = {
|
||||||
visual: {
|
visual: {
|
||||||
@ -109,8 +113,8 @@ namespace pxsim {
|
|||||||
height: 230,
|
height: 230,
|
||||||
pinDist: 9,
|
pinDist: 9,
|
||||||
pinBlocks: [
|
pinBlocks: [
|
||||||
{ x: 5, y: 31, labels: ["3V3", "SDA", "SCL", "#4", "--", "#17", "#21", "#22", "--", "MOSI", "MISO", "SCLK", "--"]},
|
{ x: 5, y: 31, labelPosition: "above", labels: ["3V3", "SDA", "SCL", "#4", "--", "#17", "#21", "#22", "--", "MOSI", "MISO", "SCLK", "--"]},
|
||||||
{ x: 5, y: 39, labels: ["5V", "--", "GND", "TXD", "RXD", "#18", "--", "#23", "#24", "--", "#25", "CS0", "CS1"]}
|
{ x: 5, y: 39, labelPosition: "below", labels: ["5V", "--", "GND", "TXD", "RXD", "#18", "--", "#23", "#24", "--", "#25", "CS0", "CS1"]}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
gpioPinBlocks: [
|
gpioPinBlocks: [
|
||||||
@ -139,6 +143,7 @@ namespace pxsim {
|
|||||||
},
|
},
|
||||||
groundPins: ["GND"],
|
groundPins: ["GND"],
|
||||||
threeVoltPins: ["3V3"],
|
threeVoltPins: ["3V3"],
|
||||||
|
marginWhenBreadboarding: [20, 0, 40, 0],
|
||||||
}
|
}
|
||||||
export const SPARKFUN_PHOTON: BoardDefinition = {
|
export const SPARKFUN_PHOTON: BoardDefinition = {
|
||||||
visual: {
|
visual: {
|
||||||
@ -148,10 +153,10 @@ namespace pxsim {
|
|||||||
height: 202.4,
|
height: 202.4,
|
||||||
pinDist: 9.5,
|
pinDist: 9.5,
|
||||||
pinBlocks: [
|
pinBlocks: [
|
||||||
{x: 72, y: 6, labels: ["~SCL/D1", "~SDA/D0", " ", "GND0", "SCK/A3", "~MISO/A4", "~MOSI/A5", "SS/A2", "~WKP", "DAC"]},
|
{x: 72, y: 6, labelPosition: "below", labels: ["~SCL/D1", "~SDA/D0", " ", "GND0", "SCK/A3", "~MISO/A4", "~MOSI/A5", "SS/A2", "~WKP", "DAC"]},
|
||||||
{x: 174, y: 6, labels: ["D7", "D6", "D5", "D4", "~D3", "~D2", "~TX", "~RX"]},
|
{x: 174, y: 6, labelPosition: "below", labels: ["D7", "D6", "D5", "D4", "~D3", "~D2", "~TX", "~RX"]},
|
||||||
{x: 107, y: 188, labels: [" ", " ", "RESET", "3.3V", "V-USB", "GND1", "GND2", "VIN"]},
|
{x: 107, y: 188, labelPosition: "above", labels: [" ", " ", "RESET", "3.3V", "V-USB", "GND1", "GND2", "VIN"]},
|
||||||
{x: 193, y: 188, labels: ["A0", "A1", "A2", "A3", "A4", "A5"]},
|
{x: 193, y: 188, labelPosition: "above", labels: ["A0", "A1", "A2", "A3", "A4", "A5"]},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
gpioPinBlocks: [
|
gpioPinBlocks: [
|
||||||
@ -181,6 +186,7 @@ namespace pxsim {
|
|||||||
},
|
},
|
||||||
groundPins: ["GND0", "GND1", "GND2"],
|
groundPins: ["GND0", "GND1", "GND2"],
|
||||||
threeVoltPins: ["3.3V"],
|
threeVoltPins: ["3.3V"],
|
||||||
|
marginWhenBreadboarding: [20, 0, 40, 0],
|
||||||
}
|
}
|
||||||
export const ARDUINO_ZERO: BoardDefinition = {
|
export const ARDUINO_ZERO: BoardDefinition = {
|
||||||
visual: {
|
visual: {
|
||||||
@ -190,10 +196,10 @@ namespace pxsim {
|
|||||||
height: 762,
|
height: 762,
|
||||||
pinDist: 35.5,
|
pinDist: 35.5,
|
||||||
pinBlocks: [
|
pinBlocks: [
|
||||||
{x: 276.8, y: 17.8, labels: ["SCL", "SDA", "AREF", "GND0", "~13", "~12", "~11", "~10", "~9", "~8"]},
|
{x: 276.8, y: 17.8, labelPosition: "below", labels: ["SCL", "SDA", "AREF", "GND0", "~13", "~12", "~11", "~10", "~9", "~8"]},
|
||||||
{x: 655.5, y: 17.8, labels: ["7", "~6", "~5", "~4", "~3", "2", "TX->1", "RX<-0"]},
|
{x: 655.5, y: 17.8, labelPosition: "below", labels: ["7", "~6", "~5", "~4", "~3", "2", "TX->1", "RX<-0"]},
|
||||||
{x: 411.7, y: 704.6, labels: ["ATN", "IOREF", "RESET", "3.3V", "5V", "GND1", "GND2", "VIN"]},
|
{x: 411.7, y: 704.6, labelPosition: "above", labels: ["ATN", "IOREF", "RESET", "3.3V", "5V", "GND1", "GND2", "VIN"]},
|
||||||
{x: 732.9, y: 704.6, labels: ["A0", "A1", "A2", "A3", "A4", "A5"]},
|
{x: 732.9, y: 704.6, labelPosition: "above", labels: ["A0", "A1", "A2", "A3", "A4", "A5"]},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
gpioPinBlocks: [
|
gpioPinBlocks: [
|
||||||
@ -224,6 +230,7 @@ namespace pxsim {
|
|||||||
},
|
},
|
||||||
groundPins: ["GND0", "GND1", "GND2"],
|
groundPins: ["GND0", "GND1", "GND2"],
|
||||||
threeVoltPins: ["3.3V"],
|
threeVoltPins: ["3.3V"],
|
||||||
|
marginWhenBreadboarding: [20, 0, 40, 0],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PART_DEFINITIONS: Map<PartDefinition> = {
|
export const PART_DEFINITIONS: Map<PartDefinition> = {
|
||||||
@ -287,8 +294,7 @@ namespace pxsim {
|
|||||||
image: "/static/hardware/speaker.svg",
|
image: "/static/hardware/speaker.svg",
|
||||||
width: 500,
|
width: 500,
|
||||||
height: 500,
|
height: 500,
|
||||||
left: -180,
|
firstPin: [180, 135],
|
||||||
top: -135,
|
|
||||||
pinDist: 70,
|
pinDist: 70,
|
||||||
},
|
},
|
||||||
breadboardColumnsNeeded: 5,
|
breadboardColumnsNeeded: 5,
|
||||||
@ -299,8 +305,8 @@ namespace pxsim {
|
|||||||
},
|
},
|
||||||
assemblyStep: 0,
|
assemblyStep: 0,
|
||||||
wires: [
|
wires: [
|
||||||
{start: ["breadboard", "j", 1], end: ["GPIO", 0], color: "white", assemblyStep: 1},
|
{start: ["breadboard", "j", 1], end: ["GPIO", 0], color: "#ff80fa", assemblyStep: 1},
|
||||||
{start: ["breadboard", "j", 3], end: "ground", color: "white", assemblyStep: 1},
|
{start: ["breadboard", "j", 3], end: "ground", color: "blue", assemblyStep: 1},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -327,4 +333,8 @@ namespace pxsim {
|
|||||||
"ledmatrix": (xy: visuals.Coord) => visuals.mkLedMatrixSvg(xy, 8, 8),
|
"ledmatrix": (xy: visuals.Coord) => visuals.mkLedMatrixSvg(xy, 8, 8),
|
||||||
"neopixel": (xy: visuals.Coord) => visuals.mkNeoPixelPart(xy),
|
"neopixel": (xy: visuals.Coord) => visuals.mkNeoPixelPart(xy),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//TODO: add multiple board support
|
||||||
|
//export const CURRENT_BOARD = MICROBIT_DEF;
|
||||||
|
export const CURRENT_BOARD = ARDUINO_ZERO;
|
||||||
}
|
}
|
@ -128,10 +128,18 @@ namespace pxsim.instructions {
|
|||||||
cmpHeight?: number,
|
cmpHeight?: number,
|
||||||
cmpScale?: number
|
cmpScale?: number
|
||||||
};
|
};
|
||||||
function mkBoardImgSvg(def: BoardImageDefinition): visuals.SVGElAndSize {
|
function mkBoardImgSvg(def: string | BoardImageDefinition): visuals.SVGElAndSize {
|
||||||
return new visuals.MicrobitBoardSvg({
|
let boardView: visuals.BoardView;
|
||||||
|
if (def === "microbit") {
|
||||||
|
boardView = new visuals.MicrobitBoardSvg({
|
||||||
theme: visuals.randomTheme()
|
theme: visuals.randomTheme()
|
||||||
}).getView();
|
})
|
||||||
|
} else {
|
||||||
|
boardView = new visuals.GenericBoardSvg({
|
||||||
|
visualDef: <BoardImageDefinition>def
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return boardView.getView();
|
||||||
}
|
}
|
||||||
function mkBBSvg(): visuals.SVGElAndSize {
|
function mkBBSvg(): visuals.SVGElAndSize {
|
||||||
let bb = new visuals.Breadboard({});
|
let bb = new visuals.Breadboard({});
|
||||||
@ -432,7 +440,7 @@ namespace pxsim.instructions {
|
|||||||
let panel = mkPanel();
|
let panel = mkPanel();
|
||||||
|
|
||||||
// board and breadboard
|
// board and breadboard
|
||||||
let boardImg = mkBoardImgSvg(<BoardImageDefinition>props.boardDef.visual);
|
let boardImg = mkBoardImgSvg(props.boardDef.visual);
|
||||||
let board = wrapSvg(boardImg, {left: QUANT_LBL(1), leftSize: QUANT_LBL_SIZE, cmpScale: PARTS_BOARD_SCALE});
|
let board = wrapSvg(boardImg, {left: QUANT_LBL(1), leftSize: QUANT_LBL_SIZE, cmpScale: PARTS_BOARD_SCALE});
|
||||||
panel.appendChild(board);
|
panel.appendChild(board);
|
||||||
let bbRaw = mkBBSvg();
|
let bbRaw = mkBBSvg();
|
||||||
@ -633,7 +641,7 @@ ${tsPackage}
|
|||||||
|
|
||||||
style.textContent += STYLE;
|
style.textContent += STYLE;
|
||||||
|
|
||||||
const boardDef = MICROBIT_DEF;
|
const boardDef = CURRENT_BOARD;
|
||||||
const cmpDefs = PART_DEFINITIONS;
|
const cmpDefs = PART_DEFINITIONS;
|
||||||
|
|
||||||
//props
|
//props
|
||||||
|
@ -142,6 +142,28 @@ namespace pxsim.visuals {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mkScaleFn(originUnit: number, targetUnit: number): (n: number) => number {
|
||||||
|
return (n: number) => n * (targetUnit / originUnit);
|
||||||
|
}
|
||||||
|
export interface MkImageOpts {
|
||||||
|
image: string,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
imageUnitDist: number,
|
||||||
|
targetUnitDist: number
|
||||||
|
}
|
||||||
|
export function mkImageSVG(opts: MkImageOpts): SVGAndSize<SVGImageElement> {
|
||||||
|
let scaleFn = mkScaleFn(opts.imageUnitDist, opts.targetUnitDist);
|
||||||
|
let w = scaleFn(opts.width);
|
||||||
|
let h = scaleFn(opts.height);
|
||||||
|
let img = <SVGImageElement>svg.elt("image", {
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
"href": `${opts.image}`
|
||||||
|
});
|
||||||
|
return {el: img, w: w, h: h, x: 0, y: 0};
|
||||||
|
}
|
||||||
|
|
||||||
export type Coord = [number, number];
|
export type Coord = [number, number];
|
||||||
export function findDistSqrd(a: Coord, b: Coord): number {
|
export function findDistSqrd(a: Coord, b: Coord): number {
|
||||||
let x = a[0] - b[0];
|
let x = a[0] - b[0];
|
||||||
|
@ -21,32 +21,43 @@ namespace pxsim.visuals {
|
|||||||
private style: SVGStyleElement;
|
private style: SVGStyleElement;
|
||||||
private defs: SVGDefsElement;
|
private defs: SVGDefsElement;
|
||||||
private state: DalBoard;
|
private state: DalBoard;
|
||||||
|
private useCrocClips: boolean;
|
||||||
|
|
||||||
constructor(opts: BoardHostOpts) {
|
constructor(opts: BoardHostOpts) {
|
||||||
this.state = opts.state;
|
this.state = opts.state;
|
||||||
let onboardCmps = opts.boardDef.onboardComponents || [];
|
let onboardCmps = opts.boardDef.onboardComponents || [];
|
||||||
let activeComponents = (opts.cmpsList || []).filter(c => onboardCmps.indexOf(c) < 0);
|
let activeComponents = (opts.cmpsList || []).filter(c => onboardCmps.indexOf(c) < 0);
|
||||||
activeComponents.sort();
|
activeComponents.sort();
|
||||||
|
this.useCrocClips = opts.boardDef.useCrocClips;
|
||||||
|
|
||||||
|
if (opts.boardDef.visual === "microbit") {
|
||||||
this.boardView = new visuals.MicrobitBoardSvg({
|
this.boardView = new visuals.MicrobitBoardSvg({
|
||||||
runtime: runtime,
|
runtime: runtime,
|
||||||
theme: visuals.randomTheme(),
|
theme: visuals.randomTheme(),
|
||||||
disableTilt: false,
|
disableTilt: false,
|
||||||
wireframe: opts.wireframe,
|
wireframe: opts.wireframe,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
let boardVis = opts.boardDef.visual as BoardImageDefinition;
|
||||||
|
this.boardView = new visuals.GenericBoardSvg({
|
||||||
|
visualDef: boardVis,
|
||||||
|
wireframe: opts.wireframe,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let useBreadboard = 0 < activeComponents.length || opts.forceBreadboard;
|
let useBreadboard = 0 < activeComponents.length || opts.forceBreadboard;
|
||||||
if (useBreadboard) {
|
if (useBreadboard) {
|
||||||
this.breadboard = new Breadboard({
|
this.breadboard = new Breadboard({
|
||||||
wireframe: opts.wireframe,
|
wireframe: opts.wireframe,
|
||||||
});
|
});
|
||||||
|
let bMarg = opts.boardDef.marginWhenBreadboarding || [0, 0, 40, 0];
|
||||||
let composition = composeSVG({
|
let composition = composeSVG({
|
||||||
el1: this.boardView.getView(),
|
el1: this.boardView.getView(),
|
||||||
scaleUnit1: this.boardView.getPinDist(),
|
scaleUnit1: this.boardView.getPinDist(),
|
||||||
el2: this.breadboard.getSVGAndSize(),
|
el2: this.breadboard.getSVGAndSize(),
|
||||||
scaleUnit2: this.breadboard.getPinDist(),
|
scaleUnit2: this.breadboard.getPinDist(),
|
||||||
margin: [0, 0, 20, 0],
|
margin: [bMarg[0], bMarg[1], 20, bMarg[3]],
|
||||||
middleMargin: 80,
|
middleMargin: bMarg[2],
|
||||||
maxWidth: opts.maxWidth,
|
maxWidth: opts.maxWidth,
|
||||||
maxHeight: opts.maxHeight,
|
maxHeight: opts.maxHeight,
|
||||||
});
|
});
|
||||||
@ -166,7 +177,7 @@ namespace pxsim.visuals {
|
|||||||
return cmp;
|
return cmp;
|
||||||
}
|
}
|
||||||
public addWire(inst: WireInst): Wire {
|
public addWire(inst: WireInst): Wire {
|
||||||
return this.wireFactory.addWire(inst.start, inst.end, inst.color, true);
|
return this.wireFactory.addWire(inst.start, inst.end, inst.color, this.useCrocClips);
|
||||||
}
|
}
|
||||||
public addAll(basicWiresAndCmpsAndWires: AllocatorResult) {
|
public addAll(basicWiresAndCmpsAndWires: AllocatorResult) {
|
||||||
let {powerWires, components} = basicWiresAndCmpsAndWires;
|
let {powerWires, components} = basicWiresAndCmpsAndWires;
|
||||||
|
@ -3,46 +3,6 @@
|
|||||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
||||||
|
|
||||||
namespace pxsim.visuals {
|
namespace pxsim.visuals {
|
||||||
const svg = pxsim.svg;
|
|
||||||
|
|
||||||
export interface IBoardSvgProps {
|
|
||||||
runtime: pxsim.Runtime;
|
|
||||||
boardDef: BoardDefinition;
|
|
||||||
disableTilt?: boolean;
|
|
||||||
activeComponents: string[];
|
|
||||||
fnArgs?: any;
|
|
||||||
componentDefinitions: Map<PartDefinition>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const VIEW_WIDTH = 498;
|
|
||||||
export const VIEW_HEIGHT = 725;
|
|
||||||
const TOP_MARGIN = 20;
|
|
||||||
const MID_MARGIN = 40;
|
|
||||||
const BOT_MARGIN = 20;
|
|
||||||
const PIN_LBL_SIZE = PIN_DIST * 0.7;
|
|
||||||
const PIN_LBL_HOVER_SIZE = PIN_LBL_SIZE * 1.5;
|
|
||||||
const SQUARE_PIN_WIDTH = PIN_DIST * 0.66666;
|
|
||||||
const SQUARE_PIN_HOVER_WIDTH = PIN_DIST * 0.66666 + PIN_DIST / 3.0;
|
|
||||||
|
|
||||||
export type ComputedBoardDimensions = {
|
|
||||||
scaleFn: (n: number) => number,
|
|
||||||
height: number,
|
|
||||||
width: number,
|
|
||||||
xOff: number,
|
|
||||||
yOff: number
|
|
||||||
};
|
|
||||||
export function getBoardDimensions(vis: BoardImageDefinition): ComputedBoardDimensions {
|
|
||||||
let scaleFn = (n: number) => n * (PIN_DIST / vis.pinDist);
|
|
||||||
let width = scaleFn(vis.width);
|
|
||||||
return {
|
|
||||||
scaleFn: scaleFn,
|
|
||||||
height: scaleFn(vis.height),
|
|
||||||
width: width,
|
|
||||||
xOff: (VIEW_WIDTH - width) / 2.0,
|
|
||||||
yOff: TOP_MARGIN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BOARD_SYTLE = `
|
export const BOARD_SYTLE = `
|
||||||
.noselect {
|
.noselect {
|
||||||
-webkit-touch-callout: none; /* iOS Safari */
|
-webkit-touch-callout: none; /* iOS Safari */
|
||||||
@ -53,52 +13,6 @@ namespace pxsim.visuals {
|
|||||||
user-select: none; /* Non-prefixed version, currently
|
user-select: none; /* Non-prefixed version, currently
|
||||||
not supported by any browser */
|
not supported by any browser */
|
||||||
}
|
}
|
||||||
svg.sim.grayscale {
|
|
||||||
-moz-filter: grayscale(1);
|
|
||||||
-webkit-filter: grayscale(1);
|
|
||||||
filter: grayscale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sim-text {
|
|
||||||
font-family:"Lucida Console", Monaco, monospace;
|
|
||||||
font-size:25px;
|
|
||||||
fill:#fff;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* animations */
|
|
||||||
.sim-theme-glow {
|
|
||||||
animation-name: sim-theme-glow-animation;
|
|
||||||
animation-timing-function: ease-in-out;
|
|
||||||
animation-direction: alternate;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
animation-duration: 1.25s;
|
|
||||||
}
|
|
||||||
@keyframes sim-theme-glow-animation {
|
|
||||||
from { opacity: 1; }
|
|
||||||
to { opacity: 0.75; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.sim-flash {
|
|
||||||
animation-name: sim-flash-animation;
|
|
||||||
animation-duration: 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sim-flash-animation {
|
|
||||||
from { fill: yellow; }
|
|
||||||
to { fill: default; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.sim-flash-stroke {
|
|
||||||
animation-name: sim-flash-stroke-animation;
|
|
||||||
animation-duration: 0.4s;
|
|
||||||
animation-timing-function: ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sim-flash-stroke-animation {
|
|
||||||
from { stroke: yellow; }
|
|
||||||
to { stroke: default; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.sim-board-pin {
|
.sim-board-pin {
|
||||||
fill:#999;
|
fill:#999;
|
||||||
@ -178,202 +92,72 @@ namespace pxsim.visuals {
|
|||||||
stroke:#000;
|
stroke:#000;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
const PIN_LBL_SIZE = PIN_DIST * 0.7;
|
||||||
|
const PIN_LBL_HOVER_SIZE = PIN_LBL_SIZE * 1.5;
|
||||||
|
const SQUARE_PIN_WIDTH = PIN_DIST * 0.66666;
|
||||||
|
const SQUARE_PIN_HOVER_WIDTH = PIN_DIST * 0.66666 + PIN_DIST / 3.0;
|
||||||
|
|
||||||
|
export interface GenericBoardProps {
|
||||||
|
visualDef: BoardImageDefinition;
|
||||||
|
wireframe?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
let nextBoardId = 0;
|
let nextBoardId = 0;
|
||||||
export class GenericBoardSvg /*TODO: implements BoardView*/ {
|
export class GenericBoardSvg implements BoardView {
|
||||||
public hostElement: SVGSVGElement;
|
private element: SVGSVGElement;
|
||||||
private style: SVGStyleElement;
|
private style: SVGStyleElement;
|
||||||
private defs: SVGDefsElement;
|
private defs: SVGDefsElement;
|
||||||
private g: SVGGElement;
|
private g: SVGGElement;
|
||||||
public board: pxsim.DalBoard;
|
private background: SVGElement;
|
||||||
public background: SVGElement;
|
private width: number;
|
||||||
private components: IBoardComponent<any>[];
|
private height: number;
|
||||||
public breadboard: Breadboard;
|
|
||||||
private underboard: SVGGElement;
|
|
||||||
public boardDef: BoardDefinition;
|
|
||||||
private boardDim: ComputedBoardDimensions;
|
|
||||||
public componentDefs: Map<PartDefinition>;
|
|
||||||
private boardEdges: number[];
|
|
||||||
private id: number;
|
private id: number;
|
||||||
public bbX: number;
|
|
||||||
public bbY: number;
|
// pins & labels
|
||||||
private boardTopEdge: number;
|
//(truth)
|
||||||
private boardBotEdge: number;
|
|
||||||
private wireFactory: WireFactory;
|
|
||||||
//truth
|
|
||||||
private allPins: GridPin[] = [];
|
private allPins: GridPin[] = [];
|
||||||
private allLabels: GridLabel[] = [];
|
private allLabels: GridLabel[] = [];
|
||||||
//cache
|
//(cache)
|
||||||
private pinNmToLbl: Map<GridLabel> = {};
|
private pinNmToLbl: Map<GridLabel> = {};
|
||||||
private pinNmToPin: Map<GridPin> = {};
|
private pinNmToPin: Map<GridPin> = {};
|
||||||
|
|
||||||
constructor(public props: IBoardSvgProps) {
|
constructor(public props: GenericBoardProps) {
|
||||||
|
//TODO: handle wireframe mode
|
||||||
this.id = nextBoardId++;
|
this.id = nextBoardId++;
|
||||||
this.boardDef = props.boardDef;
|
let visDef = props.visualDef;
|
||||||
this.boardDim = getBoardDimensions(<BoardImageDefinition>this.boardDef.visual);
|
let imgHref = props.wireframe ? visDef.outlineImage : visDef.image;
|
||||||
this.board = this.props.runtime.board as pxsim.DalBoard;
|
let boardImgAndSize = mkImageSVG({
|
||||||
this.board.updateView = () => this.updateState();
|
image: imgHref,
|
||||||
this.hostElement = <SVGSVGElement>svg.elt("svg")
|
width: visDef.width,
|
||||||
svg.hydrate(this.hostElement, {
|
height: visDef.height,
|
||||||
|
imageUnitDist: visDef.pinDist,
|
||||||
|
targetUnitDist: PIN_DIST
|
||||||
|
});
|
||||||
|
let scaleFn = mkScaleFn(visDef.pinDist, PIN_DIST);
|
||||||
|
this.width = boardImgAndSize.w;
|
||||||
|
this.height = boardImgAndSize.h;
|
||||||
|
let img = boardImgAndSize.el;
|
||||||
|
this.element = <SVGSVGElement>svg.elt("svg");
|
||||||
|
svg.hydrate(this.element, {
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"viewBox": `0 0 ${VIEW_WIDTH} ${VIEW_HEIGHT}`,
|
"viewBox": `0 0 ${this.width} ${this.height}`,
|
||||||
"enable-background": `new 0 0 ${VIEW_WIDTH} ${VIEW_HEIGHT}`,
|
|
||||||
"class": `sim sim-board-id-${this.id}`,
|
"class": `sim sim-board-id-${this.id}`,
|
||||||
"x": "0px",
|
"x": "0px",
|
||||||
"y": "0px"
|
"y": "0px"
|
||||||
});
|
});
|
||||||
this.style = <SVGStyleElement>svg.child(this.hostElement, "style", {});
|
if (props.wireframe)
|
||||||
|
svg.addClass(this.element, "sim-board-outline")
|
||||||
|
this.style = <SVGStyleElement>svg.child(this.element, "style", {});
|
||||||
this.style.textContent += BOARD_SYTLE;
|
this.style.textContent += BOARD_SYTLE;
|
||||||
this.defs = <SVGDefsElement>svg.child(this.hostElement, "defs", {});
|
this.defs = <SVGDefsElement>svg.child(this.element, "defs", {});
|
||||||
this.g = <SVGGElement>svg.elt("g");
|
this.g = <SVGGElement>svg.elt("g");
|
||||||
this.hostElement.appendChild(this.g);
|
this.element.appendChild(this.g);
|
||||||
this.underboard = <SVGGElement>svg.child(this.g, "g", {class: "sim-underboard"});
|
|
||||||
this.components = [];
|
|
||||||
this.componentDefs = props.componentDefinitions;
|
|
||||||
|
|
||||||
// breadboard
|
|
||||||
this.breadboard = new Breadboard({})
|
|
||||||
this.g.appendChild(this.breadboard.bb);
|
|
||||||
let bbSize = this.breadboard.getSVGAndSize();
|
|
||||||
let [bbWidth, bbHeight] = [bbSize.w, bbSize.h];
|
|
||||||
const bbX = (VIEW_WIDTH - bbWidth) / 2;
|
|
||||||
this.bbX = bbX;
|
|
||||||
const bbY = TOP_MARGIN + this.boardDim.height + MID_MARGIN;
|
|
||||||
this.bbY = bbY;
|
|
||||||
this.breadboard.updateLocation(bbX, bbY);
|
|
||||||
|
|
||||||
// edges
|
|
||||||
this.boardTopEdge = TOP_MARGIN;
|
|
||||||
this.boardBotEdge = TOP_MARGIN + this.boardDim.height;
|
|
||||||
this.boardEdges = [this.boardTopEdge, this.boardBotEdge, bbY, bbY + bbHeight]
|
|
||||||
|
|
||||||
this.wireFactory = new WireFactory(this.underboard, this.g, this.boardEdges, this.style, this.getLocCoord.bind(this));
|
|
||||||
|
|
||||||
this.buildDom();
|
|
||||||
|
|
||||||
this.updateTheme();
|
|
||||||
this.updateState();
|
|
||||||
|
|
||||||
let cmps = props.activeComponents;
|
|
||||||
if (cmps.length) {
|
|
||||||
let allocRes = allocateDefinitions({
|
|
||||||
boardDef: this.boardDef,
|
|
||||||
cmpDefs: this.componentDefs,
|
|
||||||
fnArgs: this.props.fnArgs,
|
|
||||||
getBBCoord: this.getBBCoord.bind(this),
|
|
||||||
cmpList: props.activeComponents,
|
|
||||||
});
|
|
||||||
this.addAll(allocRes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getBoardPinCoord(pinNm: string): Coord {
|
|
||||||
let pin = this.pinNmToPin[pinNm];
|
|
||||||
if (!pin)
|
|
||||||
return null;
|
|
||||||
return [pin.cx, pin.cy];
|
|
||||||
}
|
|
||||||
private getBBCoord(rowCol: BBRowCol): Coord {
|
|
||||||
let bbCoord = this.breadboard.getCoord(rowCol);
|
|
||||||
if (!bbCoord)
|
|
||||||
return null;
|
|
||||||
let [x, y] = bbCoord;
|
|
||||||
return [x + this.bbX, y + this.bbY];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getLocCoord(loc: Loc): Coord {
|
|
||||||
let coord: Coord;
|
|
||||||
if (loc.type === "breadboard") {
|
|
||||||
let rowCol = (<BBLoc>loc).rowCol;
|
|
||||||
coord = this.getBBCoord(rowCol);
|
|
||||||
} else {
|
|
||||||
let pinNm = (<BoardLoc>loc).pin;
|
|
||||||
coord = this.getBoardPinCoord(pinNm);
|
|
||||||
}
|
|
||||||
if (!coord) {
|
|
||||||
console.error("Unknown location: " + name)
|
|
||||||
return [0, 0];
|
|
||||||
}
|
|
||||||
return coord;
|
|
||||||
}
|
|
||||||
|
|
||||||
private mkGrayCover(x: number, y: number, w: number, h: number) {
|
|
||||||
let rect = <SVGRectElement>svg.elt("rect");
|
|
||||||
svg.hydrate(rect, {x: x, y: y, width: w, height: h, class: "gray-cover"});
|
|
||||||
return rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCmpClass = (type: string) => `sim-${type}-cmp`;
|
|
||||||
|
|
||||||
public addWire(inst: WireInst): Wire {
|
|
||||||
return this.wireFactory.addWire(inst.start, inst.end, inst.color);
|
|
||||||
}
|
|
||||||
public addAll(basicWiresAndCmpsAndWires: AllocatorResult) {
|
|
||||||
let {powerWires, components} = basicWiresAndCmpsAndWires;
|
|
||||||
powerWires.forEach(w => this.addWire(w));
|
|
||||||
components.forEach((cAndWs, idx) => {
|
|
||||||
let {component, wires} = cAndWs;
|
|
||||||
wires.forEach(w => this.addWire(w));
|
|
||||||
this.addComponent(component);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public addComponent(cmpDesc: CmpInst): IBoardComponent<any> {
|
|
||||||
let cmp: IBoardComponent<any> = null;
|
|
||||||
if (typeof cmpDesc.visual === "string") {
|
|
||||||
let builtinVisual = cmpDesc.visual as string;
|
|
||||||
let cnstr = builtinComponentSimVisual[builtinVisual];
|
|
||||||
let stateFn = builtinComponentSimState[builtinVisual];
|
|
||||||
let state = stateFn(this.board);
|
|
||||||
cmp = cnstr();
|
|
||||||
cmp.init(this.board.bus, state, this.hostElement, cmpDesc.microbitPins, cmpDesc.otherArgs);
|
|
||||||
this.components.push(cmp);
|
|
||||||
this.g.appendChild(cmp.element);
|
|
||||||
if (cmp.defs)
|
|
||||||
cmp.defs.forEach(d => this.defs.appendChild(d));
|
|
||||||
this.style.textContent += cmp.style || "";
|
|
||||||
let rowCol = <BBRowCol>[`${cmpDesc.breadboardStartRow}`, `${cmpDesc.breadboardStartColumn}`];
|
|
||||||
let coord = this.getBBCoord(rowCol);
|
|
||||||
cmp.moveToCoord(coord);
|
|
||||||
let cls = this.getCmpClass(name);
|
|
||||||
svg.addClass(cmp.element, cls);
|
|
||||||
svg.addClass(cmp.element, "sim-cmp");
|
|
||||||
cmp.updateTheme();
|
|
||||||
cmp.updateState();
|
|
||||||
} else {
|
|
||||||
//TODO: adding generic components
|
|
||||||
}
|
|
||||||
return cmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateTheme() {
|
|
||||||
this.components.forEach(c => c.updateTheme());
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateState() {
|
|
||||||
let state = this.board;
|
|
||||||
if (!state) return;
|
|
||||||
|
|
||||||
this.components.forEach(c => c.updateState());
|
|
||||||
|
|
||||||
if (!runtime || runtime.dead) svg.addClass(this.hostElement, "grayscale");
|
|
||||||
else svg.removeClass(this.hostElement, "grayscale");
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildDom() {
|
|
||||||
|
|
||||||
// filters
|
|
||||||
let glow = svg.child(this.defs, "filter", { id: "filterglow", x: "-5%", y: "-5%", width: "120%", height: "120%" });
|
|
||||||
svg.child(glow, "feGaussianBlur", { stdDeviation: "5", result: "glow" });
|
|
||||||
let merge = svg.child(glow, "feMerge", {});
|
|
||||||
for (let i = 0; i < 3; ++i)
|
|
||||||
svg.child(merge, "feMergeNode", { in: "glow" })
|
|
||||||
|
|
||||||
// main board
|
// main board
|
||||||
this.background = svg.child(this.g, "image",
|
this.g.appendChild(img);
|
||||||
{ class: "sim-board", x: this.boardDim.xOff, y: this.boardDim.yOff, width: this.boardDim.width, height: this.boardDim.height,
|
this.background = img;
|
||||||
"href": `${(<BoardImageDefinition>this.boardDef.visual).image}`});
|
svg.hydrate(img, { class: "sim-board" });
|
||||||
let backgroundCover = this.mkGrayCover(this.boardDim.xOff, this.boardDim.yOff, this.boardDim.width, this.boardDim.height);
|
let backgroundCover = this.mkGrayCover(0, 0, this.width, this.height);
|
||||||
this.g.appendChild(backgroundCover);
|
this.g.appendChild(backgroundCover);
|
||||||
|
|
||||||
// ----- pins
|
// ----- pins
|
||||||
@ -398,8 +182,8 @@ namespace pxsim.visuals {
|
|||||||
return {el: el, w: width, h: width, x: 0, y: 0};
|
return {el: el, w: width, h: width, x: 0, y: 0};
|
||||||
}
|
}
|
||||||
const mkPinBlockGrid = (pinBlock: PinBlockDefinition, blockIdx: number) => {
|
const mkPinBlockGrid = (pinBlock: PinBlockDefinition, blockIdx: number) => {
|
||||||
let xOffset = this.boardDim.xOff + this.boardDim.scaleFn(pinBlock.x) + PIN_DIST / 2.0;
|
let xOffset = scaleFn(pinBlock.x) + PIN_DIST / 2.0;
|
||||||
let yOffset = this.boardDim.yOff + this.boardDim.scaleFn(pinBlock.y) + PIN_DIST / 2.0;
|
let yOffset = scaleFn(pinBlock.y) + PIN_DIST / 2.0;
|
||||||
let rowCount = 1;
|
let rowCount = 1;
|
||||||
let colCount = pinBlock.labels.length;
|
let colCount = pinBlock.labels.length;
|
||||||
let getColName = (colIdx: number) => pinBlock.labels[colIdx];
|
let getColName = (colIdx: number) => pinBlock.labels[colIdx];
|
||||||
@ -422,9 +206,11 @@ namespace pxsim.visuals {
|
|||||||
svg.addClass(gridRes.g, "sim-board-pin-group");
|
svg.addClass(gridRes.g, "sim-board-pin-group");
|
||||||
return gridRes;
|
return gridRes;
|
||||||
};
|
};
|
||||||
let pinBlocks = (<BoardImageDefinition>this.boardDef.visual).pinBlocks.map(mkPinBlockGrid);
|
let pinBlocks = visDef.pinBlocks.map(mkPinBlockGrid);
|
||||||
pinBlocks.forEach(blk => blk.allPins.forEach(p => {
|
let pinToBlockDef: PinBlockDefinition[] = [];
|
||||||
|
pinBlocks.forEach((blk, blkIdx) => blk.allPins.forEach((p, pIdx) => {
|
||||||
this.allPins.push(p);
|
this.allPins.push(p);
|
||||||
|
pinToBlockDef.push(visDef.pinBlocks[blkIdx]);
|
||||||
}));
|
}));
|
||||||
//tooltip
|
//tooltip
|
||||||
this.allPins.forEach(p => {
|
this.allPins.forEach(p => {
|
||||||
@ -443,15 +229,12 @@ namespace pxsim.visuals {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ----- labels
|
// ----- labels
|
||||||
const mkLabelTxtEl = (pinX: number, pinY: number, size: number, txt: string): SVGTextElement => {
|
const mkLabelTxtEl = (pinX: number, pinY: number, size: number, txt: string, pos: "above" | "below"): SVGTextElement => {
|
||||||
//TODO: extract constants
|
//TODO: extract constants
|
||||||
let lblY: number;
|
let lblY: number;
|
||||||
let lblX: number;
|
let lblX: number;
|
||||||
let edges = [this.boardTopEdge, this.boardBotEdge];
|
|
||||||
let distFromTopBot = edges.map(e => Math.abs(e - pinY));
|
if (pos === "below") {
|
||||||
let closestEdgeIdx = distFromTopBot.reduce((pi, n, ni) => n < distFromTopBot[pi] ? ni : pi, 0);
|
|
||||||
let topEdge = closestEdgeIdx == 0;
|
|
||||||
if (topEdge) {
|
|
||||||
let lblLen = size * 0.25 * txt.length;
|
let lblLen = size * 0.25 * txt.length;
|
||||||
lblX = pinX;
|
lblX = pinX;
|
||||||
lblY = pinY + 12 + lblLen;
|
lblY = pinY + 12 + lblLen;
|
||||||
@ -463,16 +246,17 @@ namespace pxsim.visuals {
|
|||||||
let el = mkTxt(lblX, lblY, size, -90, txt);
|
let el = mkTxt(lblX, lblY, size, -90, txt);
|
||||||
return el;
|
return el;
|
||||||
};
|
};
|
||||||
const mkLabel = (pinX: number, pinY: number, txt: string): GridLabel => {
|
const mkLabel = (pinX: number, pinY: number, txt: string, pos: "above" | "below"): GridLabel => {
|
||||||
let el = mkLabelTxtEl(pinX, pinY, PIN_LBL_SIZE, txt);
|
let el = mkLabelTxtEl(pinX, pinY, PIN_LBL_SIZE, txt, pos);
|
||||||
svg.addClass(el, "sim-board-pin-lbl");
|
svg.addClass(el, "sim-board-pin-lbl");
|
||||||
let hoverEl = mkLabelTxtEl(pinX, pinY, PIN_LBL_HOVER_SIZE, txt);
|
let hoverEl = mkLabelTxtEl(pinX, pinY, PIN_LBL_HOVER_SIZE, txt, pos);
|
||||||
svg.addClass(hoverEl, "sim-board-pin-lbl-hover");
|
svg.addClass(hoverEl, "sim-board-pin-lbl-hover");
|
||||||
let label: GridLabel = {el: el, hoverEl: hoverEl, txt: txt};
|
let label: GridLabel = {el: el, hoverEl: hoverEl, txt: txt};
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
this.allLabels = this.allPins.map(p => {
|
this.allLabels = this.allPins.map((p, pIdx) => {
|
||||||
return mkLabel(p.cx, p.cy, p.col);
|
let blk = pinToBlockDef[pIdx];
|
||||||
|
return mkLabel(p.cx, p.cy, p.col, blk.labelPosition);
|
||||||
});
|
});
|
||||||
//attach labels
|
//attach labels
|
||||||
this.allLabels.forEach(l => {
|
this.allLabels.forEach(l => {
|
||||||
@ -486,7 +270,29 @@ namespace pxsim.visuals {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public highlightLoc(pinNm: string) {
|
public getCoord(pinNm: string): Coord {
|
||||||
|
let pin = this.pinNmToPin[pinNm];
|
||||||
|
if (!pin)
|
||||||
|
return null;
|
||||||
|
return [pin.cx, pin.cy];
|
||||||
|
}
|
||||||
|
|
||||||
|
private mkGrayCover(x: number, y: number, w: number, h: number) {
|
||||||
|
let rect = <SVGRectElement>svg.elt("rect");
|
||||||
|
svg.hydrate(rect, {x: x, y: y, width: w, height: h, class: "gray-cover"});
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public getView(): SVGAndSize<SVGSVGElement> {
|
||||||
|
return {el: this.element, w: this.width, h: this.height, x: 0, y: 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPinDist() {
|
||||||
|
return PIN_DIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public highlightPin(pinNm: string) {
|
||||||
let lbl = this.pinNmToLbl[pinNm];
|
let lbl = this.pinNmToLbl[pinNm];
|
||||||
let pin = this.pinNmToPin[pinNm];
|
let pin = this.pinNmToPin[pinNm];
|
||||||
if (lbl && pin) {
|
if (lbl && pin) {
|
||||||
@ -496,20 +302,5 @@ namespace pxsim.visuals {
|
|||||||
svg.addClass(pin.hoverEl, "highlight");
|
svg.addClass(pin.hoverEl, "highlight");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public highlightWire(wire: Wire) {
|
|
||||||
//underboard wires
|
|
||||||
wire.wires.forEach(e => {
|
|
||||||
(<any>e).style["visibility"] = "visible";
|
|
||||||
});
|
|
||||||
|
|
||||||
//un greyed out
|
|
||||||
[wire.end1, wire.end2].forEach(e => {
|
|
||||||
svg.addClass(e, "highlight");
|
|
||||||
});
|
|
||||||
wire.wires.forEach(e => {
|
|
||||||
svg.addClass(e, "highlight");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user