From c2e37a2c6e23f799830dcd013b062ee0070c8da9 Mon Sep 17 00:00:00 2001 From: darzu Date: Wed, 31 Aug 2016 11:14:16 -0700 Subject: [PATCH] refactoring instructions to work with boardHost --- sim/dalboard.ts | 8 +- sim/instructions/instructions.ts | 104 ++++++------- sim/microbit.ts | 254 ++++++++++++++++--------------- sim/simlib.ts | 1 + sim/visuals/boardhost.ts | 82 ++++++---- sim/visuals/breadboard.ts | 12 +- sim/visuals/genericboard.ts | 2 +- sim/visuals/wiring.ts | 8 +- 8 files changed, 260 insertions(+), 211 deletions(-) diff --git a/sim/dalboard.ts b/sim/dalboard.ts index b5305fa6..b8a46882 100644 --- a/sim/dalboard.ts +++ b/sim/dalboard.ts @@ -81,7 +81,13 @@ namespace pxsim { let cmpDefs = PART_DEFINITIONS; //TODO: read from pxt.json/pxttarget.json let fnArgs = msg.fnArgs; - let viewHost = new visuals.BoardHost(this, boardDef, cmpsList, cmpDefs, fnArgs); + let viewHost = new visuals.BoardHost({ + state: this, + boardDef: boardDef, + cmpsList: cmpsList, + cmpDefs: cmpDefs, + fnArgs: fnArgs + }); document.body.innerHTML = ""; // clear children document.body.appendChild(viewHost.getView()); diff --git a/sim/instructions/instructions.ts b/sim/instructions/instructions.ts index 45e9a4e0..5c25e1aa 100644 --- a/sim/instructions/instructions.ts +++ b/sim/instructions/instructions.ts @@ -45,7 +45,6 @@ namespace pxsim.instructions { const NUM_MARGIN = 5; const FRONT_PAGE_BOARD_WIDTH = 200; const STYLE = ` - ${visuals.BOARD_SYTLE} .instr-panel { margin: ${PANEL_MARGIN}px; padding: ${PANEL_PADDING}px; @@ -126,22 +125,12 @@ namespace pxsim.instructions { cmpScale?: number }; function mkBoardImgSvg(def: BoardImageDefinition): visuals.SVGElAndSize { - let img = svg.elt( "image"); - let [l, t] = [0, 0]; - let w = def.width; - let h = def.height; - svg.hydrate(img, { - class: "sim-board", - x: l, - y: t, - width: def.width, - height: def.height, - "href": `${def.image}`}); - - return {el: img, w: w, h: h, x: l, y: t}; + return new visuals.MicrobitBoardSvg({ + theme: visuals.randomTheme() + }).getView(); } function mkBBSvg(): visuals.SVGElAndSize { - let bb = new visuals.Breadboard(); + let bb = new visuals.Breadboard({}); return bb.getSVGAndSize(); } function wrapSvg(el: visuals.SVGElAndSize, opts: mkCmpDivOpts): HTMLElement { @@ -280,7 +269,8 @@ namespace pxsim.instructions { function mkCmpDiv(type: "wire" | string, opts: mkCmpDivOpts): HTMLElement { let el: visuals.SVGElAndSize; if (type == "wire") { - el = visuals.mkWirePart([0, 0], opts.wireClr || "red"); + //TODO: support non-croc wire parts + el = visuals.mkWirePart([0, 0], opts.wireClr || "red", true); } else { let cnstr = builtinComponentPartVisual[type]; el = cnstr([0, 0]); @@ -290,6 +280,7 @@ namespace pxsim.instructions { type BoardProps = { boardDef: BoardDefinition, cmpDefs: Map, + fnArgs: any, allAlloc: AllocatorResult, stepToWires: WireInst[][], stepToCmps: CmpInst[][] @@ -338,6 +329,7 @@ namespace pxsim.instructions { return { boardDef: allocOpts.boardDef, cmpDefs: allocOpts.cmpDefs, + fnArgs: allocOpts.fnArgs, allAlloc: allocRes, stepToWires: stepToWires, stepToCmps: stepToCmps, @@ -348,28 +340,19 @@ namespace pxsim.instructions { allWireColors: allWireColors, }; } - function mkBoard(boardDef: BoardDefinition, cmpDefs: Map, width: number, buildMode: boolean = false): visuals.GenericBoardSvg { - let board = new visuals.GenericBoardSvg({ - runtime: pxsim.runtime, - boardDef: boardDef, - activeComponents: [], - componentDefinitions: cmpDefs, - }) - svg.hydrate(board.hostElement, { - "width": width, + function mkBlankBoardAndBreadboard(boardDef: BoardDefinition, cmpDefs: Map, fnArgs: any, width: number, buildMode: boolean = false): visuals.BoardHost { + let state = runtime.board as pxsim.DalBoard; + let boardHost = new visuals.BoardHost({ + state: state, + boardDef: boardDef, + forceBreadboard: true, + cmpDefs: cmpDefs, + maxWidth: `${width}px`, + fnArgs: fnArgs, + wireframe: buildMode, }); - svg.addClass(board.hostElement, "board-svg"); - if (buildMode) { - svg.hydrate(board.background, { - "href": `${(boardDef.visual).outlineImage}` - }) - svg.addClass(board.hostElement, "sim-board-outline") - let bb = board.breadboard.bb; - svg.addClass(bb, "sim-bb-outline") - let style = svg.child(bb, "style", {}); - } - - board.updateState(); + let view = boardHost.getView(); + svg.addClass(view, "board-svg"); //set smiley //HACK @@ -383,11 +366,12 @@ namespace pxsim.instructions { // img.set(4, 2, 255); // board.updateState(); - return board; + return boardHost; } - function drawSteps(board: visuals.GenericBoardSvg, step: number, props: BoardProps) { + function drawSteps(board: visuals.BoardHost, step: number, props: BoardProps) { + let view = board.getView(); if (step > 0) { - svg.addClass(board.hostElement, "grayed"); + svg.addClass(view, "grayed"); } for (let i = 0; i <= step; i++) { @@ -399,16 +383,15 @@ namespace pxsim.instructions { if (i === step) { //location highlights if (w.start.type == "breadboard") { - let [row, col] = (w.start).rowCol; - let lbls = board.breadboard.highlightLoc(row, col); + let lbls = board.highlightBreadboardPin((w.start).rowCol); } else { - board.highlightLoc((w.start).pin); + board.highlightBoardPin((w.start).pin); } if (w.end.type == "breadboard") { let [row, col] = (w.end).rowCol; - let lbls = board.breadboard.highlightLoc(row, col); + let lbls = board.highlightBreadboardPin((w.end).rowCol); } else { - board.highlightLoc((w.end).pin); + board.highlightBoardPin((w.end).pin); } //highlight wire board.highlightWire(wire); @@ -419,14 +402,14 @@ namespace pxsim.instructions { if (cmps) { cmps.forEach(cmpInst => { let cmp = board.addComponent(cmpInst) - let [row, col]: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${cmpInst.breadboardStartColumn}`]; + let rowCol: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${cmpInst.breadboardStartColumn}`]; //last step if (i === step) { - board.breadboard.highlightLoc(row, col); + board.highlightBreadboardPin(rowCol); if (cmpInst.visual === "buttonpair") { //TODO: don't specialize this - let [row2, col2]: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${cmpInst.breadboardStartColumn + 3}`]; - board.breadboard.highlightLoc(row2, col2); + let rowCol2: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${cmpInst.breadboardStartColumn + 3}`]; + board.highlightBreadboardPin(rowCol2); } svg.addClass(cmp.element, "notgrayed"); } @@ -498,9 +481,9 @@ namespace pxsim.instructions { let panel = mkPanel(); //board - let board = mkBoard(props.boardDef, props.cmpDefs, BOARD_WIDTH, true) + let board = mkBlankBoardAndBreadboard(props.boardDef, props.cmpDefs, props.fnArgs, BOARD_WIDTH, true) drawSteps(board, step, props); - panel.appendChild(board.hostElement); + panel.appendChild(board.getView()); //number let numDiv = document.createElement("div"); @@ -568,9 +551,9 @@ namespace pxsim.instructions { function updateFrontPanel(props: BoardProps): [HTMLElement, BoardProps] { let panel = document.getElementById("front-panel"); - let board = mkBoard(props.boardDef, props.cmpDefs, FRONT_PAGE_BOARD_WIDTH, false); + let board = mkBlankBoardAndBreadboard(props.boardDef, props.cmpDefs, props.fnArgs, FRONT_PAGE_BOARD_WIDTH, false); board.addAll(props.allAlloc); - panel.appendChild(board.hostElement); + panel.appendChild(board.getView()); return [panel, props]; } @@ -579,9 +562,9 @@ namespace pxsim.instructions { let panel = mkPanel(); addClass(panel, "back-panel"); - let board = mkBoard(props.boardDef, props.cmpDefs, BACK_PAGE_BOARD_WIDTH, false) + let board = mkBlankBoardAndBreadboard(props.boardDef, props.cmpDefs, props.fnArgs, BACK_PAGE_BOARD_WIDTH, false) board.addAll(props.allAlloc); - panel.appendChild(board.hostElement); + panel.appendChild(board.getView()); return panel; } @@ -646,11 +629,14 @@ namespace pxsim.instructions { const cmpDefs = PART_DEFINITIONS; //props - let dummyBreadboard = new visuals.Breadboard(); + let dummyBreadboard = new visuals.Breadboard({}); + let onboardCmps = boardDef.onboardComponents || []; + let activeComponents = (parts || []).filter(c => onboardCmps.indexOf(c) < 0); + activeComponents.sort(); let props = mkBoardProps({ boardDef: boardDef, cmpDefs: cmpDefs, - cmpList: parts, + cmpList: activeComponents, fnArgs: fnArgs, getBBCoord: dummyBreadboard.getCoord.bind(dummyBreadboard) }); @@ -669,7 +655,7 @@ namespace pxsim.instructions { } //final - let finalPanel = mkFinalPanel(props); - document.body.appendChild(finalPanel); + // let finalPanel = mkFinalPanel(props); + // document.body.appendChild(finalPanel); } } \ No newline at end of file diff --git a/sim/microbit.ts b/sim/microbit.ts index 94d1a73a..9354fc30 100644 --- a/sim/microbit.ts +++ b/sim/microbit.ts @@ -1,4 +1,121 @@ namespace pxsim.visuals { + const MB_STYLE = ` + svg.sim { + margin-bottom:1em; + } + svg.sim.grayscale { + -moz-filter: grayscale(1); + -webkit-filter: grayscale(1); + filter: grayscale(1); + } + .sim-button { + pointer-events: none; + } + + .sim-button-outer:hover { + stroke:grey; + stroke-width: 3px; + } + .sim-button-nut { + fill:#704A4A; + pointer-events:none; + } + .sim-button-nut:hover { + stroke:1px solid #704A4A; + } + .sim-pin:hover { + stroke:#D4AF37; + stroke-width:2px; + } + + .sim-pin-touch.touched:hover { + stroke:darkorange; + } + + .sim-led-back:hover { + stroke:#a0a0a0; + stroke-width:3px; + } + .sim-led:hover { + stroke:#ff7f7f; + stroke-width:3px; + } + + .sim-systemled { + fill:#333; + stroke:#555; + stroke-width: 1px; + } + + .sim-light-level-button { + stroke:#fff; + stroke-width: 3px; + } + + .sim-antenna { + stroke:#555; + stroke-width: 2px; + } + + .sim-text { + font-family:"Lucida Console", Monaco, monospace; + font-size:25px; + fill:#fff; + pointer-events: none; + } + + .sim-text-pin { + font-family:"Lucida Console", Monaco, monospace; + font-size:20px; + fill:#fff; + pointer-events: none; + } + + .sim-thermometer { + stroke:#aaa; + stroke-width: 3px; + } + + /* 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; } + } + + /* wireframe */ + .sim-wireframe * { + fill: none; + stroke: black; + } + `; const pins4onXs = [66.7, 79.1, 91.4, 103.7, 164.3, 176.6, 188.9, 201.3, 213.6, 275.2, 287.5, 299.8, 312.1, 324.5, 385.1, 397.4, 409.7, 422]; const pins4onMids = pins4onXs.map(x => x + 5); const littlePinDist = pins4onMids[1] - pins4onMids[0]; @@ -83,9 +200,10 @@ namespace pxsim.visuals { } export interface IBoardProps { - runtime: pxsim.Runtime; + runtime?: pxsim.Runtime; theme?: IBoardTheme; disableTilt?: boolean; + wireframe?: boolean; } const pointerEvents = !!(window as any).PointerEvent ? { @@ -132,15 +250,20 @@ namespace pxsim.visuals { private pinNmToCoord: Map = {}; constructor(public props: IBoardProps) { - this.board = this.props.runtime.board as pxsim.DalBoard; - this.board.updateSubscribers.push(() => this.updateState()); - this.recordPinCoords(); this.buildDom(); + if (props && props.wireframe) + svg.addClass(this.element, "sim-wireframe"); - this.updateTheme(); - this.updateState(); - this.attachEvents(); + if (props && props.theme) + this.updateTheme(); + + if (props && props.runtime) { + this.board = this.props.runtime.board as pxsim.DalBoard; + this.board.updateSubscribers.push(() => this.updateState()); + this.updateState(); + this.attachEvents(); + } } public getView(): SVGAndSize { @@ -157,6 +280,10 @@ namespace pxsim.visuals { return this.pinNmToCoord[pinNm]; } + public highlightPin(pinNm: string): void { + //TODO: for instructions + } + public getPinDist(): number { return littlePinDist * 1.7; } @@ -438,118 +565,7 @@ namespace pxsim.visuals { "height": MB_HEIGHT + "px", }); this.style = svg.child(this.element, "style", {}); - this.style.textContent = ` - svg.sim { - margin-bottom:1em; - } - svg.sim.grayscale { - -moz-filter: grayscale(1); - -webkit-filter: grayscale(1); - filter: grayscale(1); - } - .sim-button { - pointer-events: none; - } - - .sim-button-outer:hover { - stroke:grey; - stroke-width: 3px; - } - .sim-button-nut { - fill:#704A4A; - pointer-events:none; - } - .sim-button-nut:hover { - stroke:1px solid #704A4A; - } - .sim-pin:hover { - stroke:#D4AF37; - stroke-width:2px; - } - - .sim-pin-touch.touched:hover { - stroke:darkorange; - } - - .sim-led-back:hover { - stroke:#a0a0a0; - stroke-width:3px; - } - .sim-led:hover { - stroke:#ff7f7f; - stroke-width:3px; - } - - .sim-systemled { - fill:#333; - stroke:#555; - stroke-width: 1px; - } - - .sim-light-level-button { - stroke:#fff; - stroke-width: 3px; - } - - .sim-antenna { - stroke:#555; - stroke-width: 2px; - } - - .sim-text { - font-family:"Lucida Console", Monaco, monospace; - font-size:25px; - fill:#fff; - pointer-events: none; - } - - .sim-text-pin { - font-family:"Lucida Console", Monaco, monospace; - font-size:20px; - fill:#fff; - pointer-events: none; - } - - .sim-thermometer { - stroke:#aaa; - stroke-width: 3px; - } - - /* 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; } - } - - `; + this.style.textContent = MB_STYLE; this.defs = svg.child(this.element, "defs", {}); this.g = svg.elt("g"); diff --git a/sim/simlib.ts b/sim/simlib.ts index 40572920..b7b40a46 100644 --- a/sim/simlib.ts +++ b/sim/simlib.ts @@ -209,5 +209,6 @@ namespace pxsim.visuals { getView(): SVGAndSize; getCoord(pinNm: string): Coord; getPinDist(): number; + highlightPin(pinNm: string): void; } } \ No newline at end of file diff --git a/sim/visuals/boardhost.ts b/sim/visuals/boardhost.ts index 1f47ee39..9ce15be8 100644 --- a/sim/visuals/boardhost.ts +++ b/sim/visuals/boardhost.ts @@ -1,4 +1,15 @@ namespace pxsim.visuals { + export interface BoardHostOpts { + state: DalBoard, + boardDef: BoardDefinition, + cmpsList?: string[], + cmpDefs: Map, + fnArgs: any, + forceBreadboard?: boolean, + maxWidth?: string, + maxHeight?: string + wireframe?: boolean + } export class BoardHost { private components: IBoardComponent[] = []; private wireFactory: WireFactory; @@ -11,33 +22,27 @@ namespace pxsim.visuals { private defs: SVGDefsElement; private state: DalBoard; - constructor(state: DalBoard, boardDef: BoardDefinition, cmpsList: string[], cmpDefs: Map, fnArgs: any) { - this.state = state; - let onboardCmps = boardDef.onboardComponents || []; - let activeComponents = (cmpsList || []).filter(c => onboardCmps.indexOf(c) < 0); + constructor(opts: BoardHostOpts) { + this.state = opts.state; + let onboardCmps = opts.boardDef.onboardComponents || []; + let activeComponents = (opts.cmpsList || []).filter(c => onboardCmps.indexOf(c) < 0); activeComponents.sort(); - // boardDef.visual === "microbit" this.boardView = new visuals.MicrobitBoardSvg({ runtime: runtime, theme: visuals.randomTheme(), - disableTilt: false + disableTilt: false, + wireframe: opts.wireframe, }); - //TODO: port Arduino/generic board - // this.boardView = new visuals.GenericBoardSvg({ - // boardDef: boardDef, - // activeComponents: activeComponents, - // componentDefinitions: cmpDefs, - // runtime: runtime, - // fnArgs: fnArgs - // }) - // } - const VIEW_WIDTH = "100%"; - const VIEW_HEIGHT = "100%"; + let maxWidth = opts.maxWidth || "100%"; + let maxHeight = opts.maxHeight || "100%"; - if (0 < activeComponents.length) { - this.breadboard = new Breadboard(); + let useBreadboard = 0 < activeComponents.length || opts.forceBreadboard; + if (useBreadboard) { + this.breadboard = new Breadboard({ + wireframe: opts.wireframe, + }); let composition = composeSVG({ el1: this.boardView.getView(), scaleUnit1: this.boardView.getPinDist(), @@ -45,8 +50,8 @@ namespace pxsim.visuals { scaleUnit2: this.breadboard.getPinDist(), margin: [0, 0, 10, 0], middleMargin: 80, - maxWidth: VIEW_WIDTH, - maxHeight: VIEW_HEIGHT, + maxWidth: maxWidth, + maxHeight: maxHeight, }); let under = composition.under; let over = composition.over; @@ -62,9 +67,9 @@ namespace pxsim.visuals { this.wireFactory = new WireFactory(under, over, edges, this.style, this.getLocCoord.bind(this)); let allocRes = allocateDefinitions({ - boardDef: boardDef, - cmpDefs: cmpDefs, - fnArgs: fnArgs, + boardDef: opts.boardDef, + cmpDefs: opts.cmpDefs, + fnArgs: opts.fnArgs, getBBCoord: this.breadboard.getCoord.bind(this.breadboard), cmpList: activeComponents, }); @@ -74,14 +79,37 @@ namespace pxsim.visuals { let el = this.boardView.getView().el; this.view = el; svg.hydrate(this.view, { - width: VIEW_WIDTH, - height: VIEW_HEIGHT, + width: maxWidth, + height: maxHeight, }); } this.state.updateSubscribers.push(() => this.updateState()); } + public highlightBoardPin(pinNm: string) { + this.boardView.highlightPin(pinNm); + } + + public highlightBreadboardPin(rowCol: BBRowCol) { + this.breadboard.highlightLoc(rowCol); + } + + public highlightWire(wire: Wire) { + //underboard wires + wire.wires.forEach(e => { + (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"); + }); + } + public getView(): SVGElement { return this.view; } @@ -120,7 +148,7 @@ namespace pxsim.visuals { let builtinVisual = cmpDesc.visual as string; let cnstr = builtinComponentSimVisual[builtinVisual]; let stateFn = builtinComponentSimState[builtinVisual]; - let cmp = cnstr(); + cmp = cnstr(); cmp.init(this.state.bus, stateFn(this.state), this.view, cmpDesc.microbitPins, cmpDesc.otherArgs); this.components.push(cmp); this.view.appendChild(cmp.element); diff --git a/sim/visuals/breadboard.ts b/sim/visuals/breadboard.ts index ec36a6aa..f213cf39 100644 --- a/sim/visuals/breadboard.ts +++ b/sim/visuals/breadboard.ts @@ -280,6 +280,10 @@ namespace pxsim.visuals { el: SVGRectElement, group?: string }; + + export interface BreadboardOpts { + wireframe?: boolean, + } export class Breadboard { public bb: SVGSVGElement; private styleEl: SVGStyleElement; @@ -293,8 +297,11 @@ namespace pxsim.visuals { private rowColToPin: Map> = {}; private rowColToLbls: Map> = {}; - constructor() { + constructor(opts: BreadboardOpts) { this.buildDom(); + + if (opts.wireframe) + svg.addClass(this.bb, "sim-bb-outline"); } public updateLocation(x: number, y: number) { @@ -627,7 +634,8 @@ namespace pxsim.visuals { return {el: this.bb, y: 0, x: 0, w: WIDTH, h: HEIGHT}; } - public highlightLoc(row: string, col: string) { + public highlightLoc(rowCol: BBRowCol) { + let [row, col] = rowCol; let pin = this.rowColToPin[row][col]; let {cx, cy} = pin; let lbls = this.rowColToLbls[row][col]; diff --git a/sim/visuals/genericboard.ts b/sim/visuals/genericboard.ts index dabda29e..2194537f 100644 --- a/sim/visuals/genericboard.ts +++ b/sim/visuals/genericboard.ts @@ -232,7 +232,7 @@ namespace pxsim.visuals { this.componentDefs = props.componentDefinitions; // breadboard - this.breadboard = new Breadboard() + this.breadboard = new Breadboard({}) this.g.appendChild(this.breadboard.bb); let bbSize = this.breadboard.getSVGAndSize(); let [bbWidth, bbHeight] = [bbSize.w, bbSize.h]; diff --git a/sim/visuals/wiring.ts b/sim/visuals/wiring.ts index 9303962e..5575231b 100644 --- a/sim/visuals/wiring.ts +++ b/sim/visuals/wiring.ts @@ -67,14 +67,18 @@ namespace pxsim.visuals { colorClass?: string, bendFactor?: number, } - export function mkWirePart(cp: [number, number], clr: string): visuals.SVGAndSize { + export function mkWirePart(cp: [number, number], clr: string, croc: boolean = false): visuals.SVGAndSize { let g = svg.elt("g"); let [cx, cy] = cp; let offset = WIRE_PART_CURVE_OFF; let p1: visuals.Coord = [cx - offset, cy - WIRE_PART_LENGTH / 2]; let p2: visuals.Coord = [cx + offset, cy + WIRE_PART_LENGTH / 2]; clr = visuals.mapWireColor(clr); - let e1 = mkOpenJumperEnd(p1, true, clr); + let e1: SVGElAndSize; + if (croc) + e1 = mkCrocEnd(p1, true, clr); + else + e1 = mkOpenJumperEnd(p1, true, clr); let s = mkWirePartSeg(p1, p2, clr); let e2 = mkOpenJumperEnd(p2, false, clr); g.appendChild(s.el);