682 lines
25 KiB
TypeScript
682 lines
25 KiB
TypeScript
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
|
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
|
/// <reference path="../../node_modules/pxt-core/built/pxtrunner.d.ts"/>
|
|
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
|
/// <reference path="../visuals/genericboard.ts"/>
|
|
/// <reference path="../visuals/wiring.ts"/>
|
|
|
|
//HACK: allows instructions.html to access pxtblocks without requiring simulator.html to import blocks as well
|
|
if (!(<any>window).pxt) (<any>window).pxt = {};
|
|
import pxtrunner = pxt.runner;
|
|
import pxtdocs = pxt.docs;
|
|
|
|
namespace pxsim.instructions {
|
|
const LOC_LBL_SIZE = 10;
|
|
const QUANT_LBL_SIZE = 30;
|
|
const QUANT_LBL = (q: number) => `${q}x`;
|
|
const WIRE_QUANT_LBL_SIZE = 20;
|
|
const LBL_VERT_PAD = 3;
|
|
const LBL_RIGHT_PAD = 5;
|
|
const LBL_LEFT_PAD = 5;
|
|
const REQ_WIRE_HEIGHT = 45;
|
|
const REQ_CMP_HEIGHT = 55;
|
|
const REQ_CMP_SCALE = 0.5 * 4;
|
|
type Orientation = "landscape" | "portrait";
|
|
const ORIENTATION: Orientation = "portrait";
|
|
const PPI = 96.0;
|
|
const PAGE_SCALAR = 0.95;
|
|
const [FULL_PAGE_WIDTH, FULL_PAGE_HEIGHT]
|
|
= (ORIENTATION == "portrait" ? [PPI * 8.5 * PAGE_SCALAR, PPI * 11.0 * PAGE_SCALAR] : [PPI * 11.0 * PAGE_SCALAR, PPI * 8.5 * PAGE_SCALAR]);
|
|
const PAGE_MARGIN = PPI * 0.45;
|
|
const PAGE_WIDTH = FULL_PAGE_WIDTH - PAGE_MARGIN * 2;
|
|
const PAGE_HEIGHT = FULL_PAGE_HEIGHT - PAGE_MARGIN * 2;
|
|
const BORDER_COLOR = "gray";
|
|
const BORDER_RADIUS = 5 * 4;
|
|
const BORDER_WIDTH = 2 * 2;
|
|
const [PANEL_ROWS, PANEL_COLS] = [1, 1];
|
|
const PANEL_MARGIN = 20;
|
|
const PANEL_PADDING = 8 * 3;
|
|
const PANEL_WIDTH = PAGE_WIDTH / PANEL_COLS - (PANEL_MARGIN + PANEL_PADDING + BORDER_WIDTH) * PANEL_COLS;
|
|
const PANEL_HEIGHT = PAGE_HEIGHT / PANEL_ROWS - (PANEL_MARGIN + PANEL_PADDING + BORDER_WIDTH) * PANEL_ROWS;
|
|
const BOARD_WIDTH = 465;
|
|
const BOARD_LEFT = (PANEL_WIDTH - BOARD_WIDTH) / 2.0 + PANEL_PADDING;
|
|
const BOARD_BOT = PANEL_PADDING;
|
|
const NUM_BOX_SIZE = 120;
|
|
const NUM_FONT = 80;
|
|
const NUM_MARGIN = 10;
|
|
const FRONT_PAGE_BOARD_WIDTH = 400;
|
|
const PART_SCALAR = 2.3
|
|
const PARTS_BOARD_SCALE = 0.17;
|
|
const PARTS_BB_SCALE = 0.25;
|
|
const PARTS_CMP_SCALE = 0.3;
|
|
const PARTS_WIRE_SCALE = 0.23;
|
|
const BACK_PAGE_BOARD_WIDTH = PANEL_WIDTH - PANEL_PADDING * 1.5;
|
|
const STYLE = `
|
|
.instr-panel {
|
|
margin: ${PANEL_MARGIN}px;
|
|
padding: ${PANEL_PADDING}px;
|
|
border-width: ${BORDER_WIDTH}px;
|
|
border-color: ${BORDER_COLOR};
|
|
border-style: solid;
|
|
border-radius: ${BORDER_RADIUS}px;
|
|
display: inline-block;
|
|
width: ${PANEL_WIDTH}px;
|
|
height: ${PANEL_HEIGHT}px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
page-break-inside: avoid;
|
|
}
|
|
.board-svg {
|
|
margin: 0 auto;
|
|
display: block;
|
|
position: absolute;
|
|
bottom: ${BOARD_BOT}px;
|
|
left: ${BOARD_LEFT}px;
|
|
}
|
|
.panel-num-outer {
|
|
position: absolute;
|
|
left: ${-BORDER_WIDTH}px;
|
|
top: ${-BORDER_WIDTH}px;
|
|
width: ${NUM_BOX_SIZE}px;
|
|
height: ${NUM_BOX_SIZE}px;
|
|
border-width: ${BORDER_WIDTH}px;
|
|
border-style: solid;
|
|
border-color: ${BORDER_COLOR};
|
|
border-radius: ${BORDER_RADIUS}px 0 ${BORDER_RADIUS}px 0;
|
|
}
|
|
.panel-num {
|
|
margin: ${NUM_MARGIN}px 0;
|
|
text-align: center;
|
|
font-size: ${NUM_FONT}px;
|
|
}
|
|
.cmp-div {
|
|
display: inline-block;
|
|
}
|
|
.reqs-div {
|
|
margin-left: ${PANEL_PADDING + NUM_BOX_SIZE}px;
|
|
margin-top: 5px;
|
|
}
|
|
.partslist-wire,
|
|
.partslist-cmp {
|
|
margin: 10px;
|
|
}
|
|
.partslist-wire {
|
|
display: inline-block;
|
|
}
|
|
`;
|
|
|
|
function addClass(el: HTMLElement, cls: string) {
|
|
//TODO move to library
|
|
if (el.classList) el.classList.add(cls);
|
|
//BUG: won't work if element has class that is prefix of new class
|
|
//TODO: make github issue (same issue exists svg.addClass)
|
|
else if (!el.className.indexOf(cls)) el.className += " " + cls;
|
|
}
|
|
function mkTxt(p: [number, number], txt: string, size: number) {
|
|
let el = svg.elt("text")
|
|
let [x, y] = p;
|
|
svg.hydrate(el, { x: x, y: y, style: `font-size:${size}px;` });
|
|
el.textContent = txt;
|
|
return el;
|
|
}
|
|
type mkCmpDivOpts = {
|
|
top?: string,
|
|
topSize?: number,
|
|
right?: string,
|
|
rightSize?: number,
|
|
left?: string,
|
|
leftSize?: number,
|
|
bot?: string,
|
|
botSize?: number,
|
|
wireClr?: string,
|
|
cmpWidth?: number,
|
|
cmpHeight?: number,
|
|
cmpScale?: number,
|
|
crocClips?: boolean
|
|
};
|
|
function mkBoardImgSvg(def: string | BoardImageDefinition): visuals.SVGElAndSize {
|
|
let boardView: visuals.BoardView;
|
|
if (def === "microbit") {
|
|
boardView = new visuals.MicrobitBoardSvg({
|
|
theme: visuals.randomTheme()
|
|
})
|
|
} else {
|
|
boardView = new visuals.GenericBoardSvg({
|
|
visualDef: <BoardImageDefinition>def
|
|
})
|
|
}
|
|
return boardView.getView();
|
|
}
|
|
function mkBBSvg(): visuals.SVGElAndSize {
|
|
let bb = new visuals.Breadboard({});
|
|
return bb.getSVGAndSize();
|
|
}
|
|
function wrapSvg(el: visuals.SVGElAndSize, opts: mkCmpDivOpts): HTMLElement {
|
|
//TODO: Refactor this function; it is too complicated. There is a lot of error-prone math being done
|
|
// to scale and place all elements which could be simplified with more forethought.
|
|
let svgEl = <SVGSVGElement>document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
let dims = { l: 0, t: 0, w: 0, h: 0 };
|
|
|
|
let cmpSvgEl = <SVGSVGElement>document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
svgEl.appendChild(cmpSvgEl);
|
|
|
|
cmpSvgEl.appendChild(el.el);
|
|
let cmpSvgAtts = {
|
|
"viewBox": `${el.x} ${el.y} ${el.w} ${el.h}`,
|
|
"preserveAspectRatio": "xMidYMid",
|
|
};
|
|
dims.w = el.w;
|
|
dims.h = el.h;
|
|
let scale = (scaler: number) => {
|
|
dims.h *= scaler;
|
|
dims.w *= scaler;
|
|
(<any>cmpSvgAtts).width = dims.w;
|
|
(<any>cmpSvgAtts).height = dims.h;
|
|
}
|
|
if (opts.cmpScale) {
|
|
scale(opts.cmpScale)
|
|
}
|
|
if (opts.cmpWidth && opts.cmpWidth < dims.w) {
|
|
scale(opts.cmpWidth / dims.w);
|
|
} else if (opts.cmpHeight && opts.cmpHeight < dims.h) {
|
|
scale(opts.cmpHeight / dims.h)
|
|
}
|
|
svg.hydrate(cmpSvgEl, cmpSvgAtts);
|
|
let elDims = { l: dims.l, t: dims.t, w: dims.w, h: dims.h };
|
|
|
|
let updateL = (newL: number) => {
|
|
if (newL < dims.l) {
|
|
let extraW = dims.l - newL;
|
|
dims.l = newL;
|
|
dims.w += extraW;
|
|
}
|
|
}
|
|
let updateR = (newR: number) => {
|
|
let oldR = dims.l + dims.w;
|
|
if (oldR < newR) {
|
|
let extraW = newR - oldR;
|
|
dims.w += extraW;
|
|
}
|
|
}
|
|
let updateT = (newT: number) => {
|
|
if (newT < dims.t) {
|
|
let extraH = dims.t - newT;
|
|
dims.t = newT;
|
|
dims.h += extraH;
|
|
}
|
|
}
|
|
let updateB = (newB: number) => {
|
|
let oldB = dims.t + dims.h;
|
|
if (oldB < newB) {
|
|
let extraH = newB - oldB;
|
|
dims.h += extraH;
|
|
}
|
|
}
|
|
|
|
//labels
|
|
let [xOff, yOff] = [-0.3, 0.3]; //HACK: these constants tweak the way "mkTxt" knows how to center the text
|
|
const txtAspectRatio = [1.4, 1.0];
|
|
if (opts && opts.top) {
|
|
let size = opts.topSize;
|
|
let txtW = size / txtAspectRatio[0];
|
|
let txtH = size / txtAspectRatio[1];
|
|
let [cx, y] = [elDims.l + elDims.w / 2, elDims.t - LBL_VERT_PAD - txtH / 2];
|
|
let lbl = visuals.mkTxt(cx, y, size, 0, opts.top, xOff, yOff);
|
|
svg.addClass(lbl, "cmp-lbl");
|
|
svgEl.appendChild(lbl);
|
|
|
|
let len = txtW * opts.top.length;
|
|
updateT(y - txtH / 2);
|
|
updateL(cx - len / 2);
|
|
updateR(cx + len / 2);
|
|
}
|
|
if (opts && opts.bot) {
|
|
let size = opts.botSize;
|
|
let txtW = size / txtAspectRatio[0];
|
|
let txtH = size / txtAspectRatio[1];
|
|
let [cx, y] = [elDims.l + elDims.w / 2, elDims.t + elDims.h + LBL_VERT_PAD + txtH / 2];
|
|
let lbl = visuals.mkTxt(cx, y, size, 0, opts.bot, xOff, yOff);
|
|
svg.addClass(lbl, "cmp-lbl");
|
|
svgEl.appendChild(lbl);
|
|
|
|
let len = txtW * opts.bot.length;
|
|
updateB(y + txtH / 2);
|
|
updateL(cx - len / 2);
|
|
updateR(cx + len / 2);
|
|
}
|
|
if (opts && opts.right) {
|
|
let size = opts.rightSize;
|
|
let txtW = size / txtAspectRatio[0];
|
|
let txtH = size / txtAspectRatio[1];
|
|
let len = txtW * opts.right.length;
|
|
let [cx, cy] = [elDims.l + elDims.w + LBL_RIGHT_PAD + len / 2, elDims.t + elDims.h / 2];
|
|
let lbl = visuals.mkTxt(cx, cy, size, 0, opts.right, xOff, yOff);
|
|
svg.addClass(lbl, "cmp-lbl");
|
|
svgEl.appendChild(lbl);
|
|
|
|
updateT(cy - txtH / 2);
|
|
updateR(cx + len / 2);
|
|
updateB(cy + txtH / 2);
|
|
}
|
|
if (opts && opts.left) {
|
|
let size = opts.leftSize;
|
|
let txtW = size / txtAspectRatio[0];
|
|
let txtH = size / txtAspectRatio[1];
|
|
let len = txtW * opts.left.length;
|
|
let [cx, cy] = [elDims.l - LBL_LEFT_PAD - len / 2, elDims.t + elDims.h / 2];
|
|
let lbl = visuals.mkTxt(cx, cy, size, 0, opts.left, xOff, yOff);
|
|
svg.addClass(lbl, "cmp-lbl");
|
|
svgEl.appendChild(lbl);
|
|
|
|
updateT(cy - txtH / 2);
|
|
updateL(cx - len / 2);
|
|
updateB(cy + txtH / 2);
|
|
}
|
|
|
|
let svgAtts = {
|
|
"viewBox": `${dims.l} ${dims.t} ${dims.w} ${dims.h}`,
|
|
"width": dims.w * PART_SCALAR,
|
|
"height": dims.h * PART_SCALAR,
|
|
"preserveAspectRatio": "xMidYMid",
|
|
};
|
|
svg.hydrate(svgEl, svgAtts);
|
|
let div = document.createElement("div");
|
|
div.appendChild(svgEl);
|
|
return div;
|
|
}
|
|
function mkCmpDiv(cmp: "wire" | string | PartVisualDefinition, opts: mkCmpDivOpts): HTMLElement {
|
|
let el: visuals.SVGElAndSize;
|
|
if (cmp == "wire") {
|
|
//TODO: support non-croc wire parts
|
|
el = visuals.mkWirePart([0, 0], opts.wireClr || "red", opts.crocClips);
|
|
} else if (typeof cmp == "string") {
|
|
let builtinVis = <string>cmp;
|
|
let cnstr = builtinComponentPartVisual[builtinVis];
|
|
el = cnstr([0, 0]);
|
|
} else {
|
|
let partVis = <PartVisualDefinition>cmp;
|
|
el = visuals.mkGenericPartSVG(partVis);
|
|
}
|
|
return wrapSvg(el, opts);
|
|
}
|
|
type BoardProps = {
|
|
boardDef: BoardDefinition,
|
|
cmpDefs: Map<PartDefinition>,
|
|
fnArgs: any,
|
|
allAlloc: AllocatorResult,
|
|
stepToWires: WireInst[][],
|
|
stepToCmps: CmpInst[][]
|
|
allWires: WireInst[],
|
|
allCmps: CmpInst[],
|
|
lastStep: number,
|
|
colorToWires: Map<WireInst[]>,
|
|
allWireColors: string[],
|
|
};
|
|
function mkBoardProps(allocOpts: AllocatorOpts): BoardProps {
|
|
let allocRes = allocateDefinitions(allocOpts);
|
|
let {powerWires, components} = allocRes;
|
|
let stepToWires: WireInst[][] = [];
|
|
let stepToCmps: CmpInst[][] = [];
|
|
powerWires.forEach(w => {
|
|
let step = w.assemblyStep + 1;
|
|
(stepToWires[step] || (stepToWires[step] = [])).push(w)
|
|
});
|
|
let getMaxStep = (ns: { assemblyStep: number }[]) => ns.reduce((m, n) => Math.max(m, n.assemblyStep), 0);
|
|
let stepOffset = powerWires.length > 0 ? getMaxStep(powerWires) + 2 : 1;
|
|
components.forEach(cAndWs => {
|
|
let {component, wires} = cAndWs;
|
|
let cStep = component.assemblyStep + stepOffset;
|
|
let arr = stepToCmps[cStep] || (stepToCmps[cStep] = []);
|
|
arr.push(component);
|
|
let wSteps = wires.map(w => w.assemblyStep + stepOffset);
|
|
wires.forEach((w, i) => {
|
|
let wStep = wSteps[i];
|
|
let arr = stepToWires[wStep] || (stepToWires[wStep] = []);
|
|
arr.push(w);
|
|
})
|
|
stepOffset = Math.max(cStep, wSteps.reduce((m, n) => Math.max(m, n), 0)) + 1;
|
|
});
|
|
let lastStep = stepOffset - 1;
|
|
let allCmps = components.map(p => p.component);
|
|
let allWires = powerWires.concat(components.map(p => p.wires).reduce((p, n) => p.concat(n), []));
|
|
let colorToWires: Map<WireInst[]> = {}
|
|
let allWireColors: string[] = [];
|
|
allWires.forEach(w => {
|
|
if (!colorToWires[w.color]) {
|
|
colorToWires[w.color] = [];
|
|
allWireColors.push(w.color);
|
|
}
|
|
colorToWires[w.color].push(w);
|
|
});
|
|
return {
|
|
boardDef: allocOpts.boardDef,
|
|
cmpDefs: allocOpts.cmpDefs,
|
|
fnArgs: allocOpts.fnArgs,
|
|
allAlloc: allocRes,
|
|
stepToWires: stepToWires,
|
|
stepToCmps: stepToCmps,
|
|
allWires: allWires,
|
|
allCmps: allCmps,
|
|
lastStep: lastStep,
|
|
colorToWires: colorToWires,
|
|
allWireColors: allWireColors,
|
|
};
|
|
}
|
|
function mkBlankBoardAndBreadboard(boardDef: BoardDefinition, cmpDefs: Map<PartDefinition>, 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,
|
|
});
|
|
let view = boardHost.getView();
|
|
svg.addClass(view, "board-svg");
|
|
|
|
//set smiley
|
|
//HACK
|
|
// let img = board.board.displayCmp.image;
|
|
// img.set(1, 0, 255);
|
|
// img.set(3, 0, 255);
|
|
// img.set(0, 2, 255);
|
|
// img.set(1, 3, 255);
|
|
// img.set(2, 3, 255);
|
|
// img.set(3, 3, 255);
|
|
// img.set(4, 2, 255);
|
|
// board.updateState();
|
|
|
|
return boardHost;
|
|
}
|
|
function drawSteps(board: visuals.BoardHost, step: number, props: BoardProps) {
|
|
let view = board.getView();
|
|
if (step > 0) {
|
|
svg.addClass(view, "grayed");
|
|
}
|
|
|
|
for (let i = 0; i <= step; i++) {
|
|
let wires = props.stepToWires[i];
|
|
if (wires) {
|
|
wires.forEach(w => {
|
|
let wire = board.addWire(w)
|
|
//last step
|
|
if (i === step) {
|
|
//location highlights
|
|
if (w.start.type == "breadboard") {
|
|
let lbls = board.highlightBreadboardPin((<BBLoc>w.start).rowCol);
|
|
} else {
|
|
board.highlightBoardPin((<BoardLoc>w.start).pin);
|
|
}
|
|
if (w.end.type == "breadboard") {
|
|
let [row, col] = (<BBLoc>w.end).rowCol;
|
|
let lbls = board.highlightBreadboardPin((<BBLoc>w.end).rowCol);
|
|
} else {
|
|
board.highlightBoardPin((<BoardLoc>w.end).pin);
|
|
}
|
|
//highlight wire
|
|
board.highlightWire(wire);
|
|
}
|
|
});
|
|
}
|
|
let cmps = props.stepToCmps[i];
|
|
if (cmps) {
|
|
cmps.forEach(cmpInst => {
|
|
let cmp = board.addComponent(cmpInst)
|
|
let colOffset = (<any>cmpInst.visual).breadboardStartColIdx || 0;
|
|
let rowCol: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${colOffset + cmpInst.breadboardStartColumn}`];
|
|
//last step
|
|
if (i === step) {
|
|
board.highlightBreadboardPin(rowCol);
|
|
if (cmpInst.visual === "buttonpair") {
|
|
//TODO: don't specialize this
|
|
let rowCol2: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${cmpInst.breadboardStartColumn + 3}`];
|
|
board.highlightBreadboardPin(rowCol2);
|
|
}
|
|
svg.addClass(cmp.element, "notgrayed");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
function mkPanel() {
|
|
//panel
|
|
let panel = document.createElement("div");
|
|
addClass(panel, "instr-panel");
|
|
|
|
return panel;
|
|
}
|
|
function mkPartsPanel(props: BoardProps) {
|
|
let panel = mkPanel();
|
|
|
|
// board and breadboard
|
|
let boardImg = mkBoardImgSvg(props.boardDef.visual);
|
|
let board = wrapSvg(boardImg, { left: QUANT_LBL(1), leftSize: QUANT_LBL_SIZE, cmpScale: PARTS_BOARD_SCALE });
|
|
panel.appendChild(board);
|
|
let bbRaw = mkBBSvg();
|
|
let bb = wrapSvg(bbRaw, { left: QUANT_LBL(1), leftSize: QUANT_LBL_SIZE, cmpScale: PARTS_BB_SCALE });
|
|
panel.appendChild(bb);
|
|
|
|
// components
|
|
let cmps = props.allCmps;
|
|
cmps.forEach(c => {
|
|
let quant = 1;
|
|
// TODO: don't special case this
|
|
if (c.visual === "buttonpair") {
|
|
quant = 2;
|
|
}
|
|
let cmp = mkCmpDiv(c.visual, {
|
|
left: QUANT_LBL(quant),
|
|
leftSize: QUANT_LBL_SIZE,
|
|
cmpScale: PARTS_CMP_SCALE,
|
|
});
|
|
addClass(cmp, "partslist-cmp");
|
|
panel.appendChild(cmp);
|
|
});
|
|
|
|
// wires
|
|
props.allWireColors.forEach(clr => {
|
|
let quant = props.colorToWires[clr].length;
|
|
let cmp = mkCmpDiv("wire", {
|
|
left: QUANT_LBL(quant),
|
|
leftSize: WIRE_QUANT_LBL_SIZE,
|
|
wireClr: clr,
|
|
cmpScale: PARTS_WIRE_SCALE,
|
|
crocClips: props.boardDef.useCrocClips
|
|
})
|
|
addClass(cmp, "partslist-wire");
|
|
panel.appendChild(cmp);
|
|
})
|
|
|
|
return panel;
|
|
}
|
|
function mkStepPanel(step: number, props: BoardProps) {
|
|
let panel = mkPanel();
|
|
|
|
//board
|
|
let board = mkBlankBoardAndBreadboard(props.boardDef, props.cmpDefs, props.fnArgs, BOARD_WIDTH, true)
|
|
drawSteps(board, step, props);
|
|
panel.appendChild(board.getView());
|
|
|
|
//number
|
|
let numDiv = document.createElement("div");
|
|
addClass(numDiv, "panel-num-outer");
|
|
addClass(numDiv, "noselect");
|
|
panel.appendChild(numDiv)
|
|
let num = document.createElement("div");
|
|
addClass(num, "panel-num");
|
|
num.textContent = (step + 1) + "";
|
|
numDiv.appendChild(num)
|
|
|
|
// add requirements
|
|
let reqsDiv = document.createElement("div");
|
|
addClass(reqsDiv, "reqs-div")
|
|
panel.appendChild(reqsDiv);
|
|
let wires = (props.stepToWires[step] || []);
|
|
let mkLabel = (loc: Loc) => {
|
|
if (loc.type === "breadboard") {
|
|
let [row, col] = (<BBLoc>loc).rowCol;
|
|
return `(${row},${col})`
|
|
} else
|
|
return (<BoardLoc>loc).pin;
|
|
};
|
|
wires.forEach(w => {
|
|
let cmp = mkCmpDiv("wire", {
|
|
top: mkLabel(w.end),
|
|
topSize: LOC_LBL_SIZE,
|
|
bot: mkLabel(w.start),
|
|
botSize: LOC_LBL_SIZE,
|
|
wireClr: w.color,
|
|
cmpHeight: REQ_WIRE_HEIGHT,
|
|
crocClips: props.boardDef.useCrocClips
|
|
})
|
|
addClass(cmp, "cmp-div");
|
|
reqsDiv.appendChild(cmp);
|
|
});
|
|
let cmps = (props.stepToCmps[step] || []);
|
|
cmps.forEach(c => {
|
|
let l: BBRowCol = [`${c.breadboardStartRow}`, `${c.breadboardStartColumn}`];
|
|
let locs = [l];
|
|
if (c.visual === "buttonpair") {
|
|
//TODO: don't special case this
|
|
let l2: BBRowCol = [`${c.breadboardStartRow}`, `${c.breadboardStartColumn + 3}`];
|
|
locs.push(l2);
|
|
}
|
|
locs.forEach((l, i) => {
|
|
let [row, col] = l;
|
|
let cmp = mkCmpDiv(c.visual, {
|
|
top: `(${row},${col})`,
|
|
topSize: LOC_LBL_SIZE,
|
|
cmpHeight: REQ_CMP_HEIGHT,
|
|
cmpScale: REQ_CMP_SCALE
|
|
})
|
|
addClass(cmp, "cmp-div");
|
|
reqsDiv.appendChild(cmp);
|
|
});
|
|
});
|
|
|
|
return panel;
|
|
}
|
|
function updateFrontPanel(props: BoardProps): [HTMLElement, BoardProps] {
|
|
let panel = document.getElementById("front-panel");
|
|
|
|
let board = mkBlankBoardAndBreadboard(props.boardDef, props.cmpDefs, props.fnArgs, FRONT_PAGE_BOARD_WIDTH, false);
|
|
board.addAll(props.allAlloc);
|
|
panel.appendChild(board.getView());
|
|
|
|
return [panel, props];
|
|
}
|
|
function mkFinalPanel(props: BoardProps) {
|
|
|
|
let panel = mkPanel();
|
|
addClass(panel, "back-panel");
|
|
let board = mkBlankBoardAndBreadboard(props.boardDef, props.cmpDefs, props.fnArgs, BACK_PAGE_BOARD_WIDTH, false)
|
|
board.addAll(props.allAlloc);
|
|
panel.appendChild(board.getView());
|
|
|
|
return panel;
|
|
}
|
|
export function drawInstructions() {
|
|
let getQsVal = parseQueryString();
|
|
|
|
//project name
|
|
let name = getQsVal("name") || "Untitled";
|
|
if (name) {
|
|
$("#proj-title").text(name);
|
|
}
|
|
|
|
//project code
|
|
let tsCode = getQsVal("code");
|
|
let tsPackage = getQsVal("package") || "";
|
|
let codeSpinnerDiv = document.getElementById("proj-code-spinner");
|
|
let codeContainerDiv = document.getElementById("proj-code-container");
|
|
if (tsCode) {
|
|
//we use the docs renderer to decompile the code to blocks and render it
|
|
//TODO: render the blocks code directly
|
|
let md =
|
|
`\`\`\`blocks
|
|
${tsCode}
|
|
\`\`\`
|
|
\`\`\`package
|
|
${tsPackage}
|
|
\`\`\`
|
|
`
|
|
|
|
pxtdocs.requireMarked = function () { return (<any>window).marked; }
|
|
pxtrunner.renderMarkdownAsync(codeContainerDiv, md)
|
|
.done(function () {
|
|
let codeSvg = $("#proj-code-container svg");
|
|
if (codeSvg.length > 0) {
|
|
//code rendered successfully as blocks
|
|
codeSvg.css("width", "inherit");
|
|
codeSvg.css("height", "inherit");
|
|
//takes the svg out of the wrapper markdown
|
|
codeContainerDiv.innerHTML = "";
|
|
codeContainerDiv.appendChild(codeSvg[0]);
|
|
} else {
|
|
//code failed to convert to blocks, display as typescript instead
|
|
codeContainerDiv.innerText = tsCode;
|
|
}
|
|
$(codeContainerDiv).show();
|
|
$(codeSpinnerDiv).hide();
|
|
});
|
|
}
|
|
|
|
//parts list
|
|
let parts = (getQsVal("parts") || "").split(" ");
|
|
parts.sort();
|
|
|
|
// parts definitions
|
|
let partDefinitions = JSON.parse(getQsVal("partdefs") || "{}") as pxsim.Map<PartDefinition>
|
|
|
|
//fn args
|
|
let fnArgs = JSON.parse((getQsVal("fnArgs") || "{}"));
|
|
|
|
//init runtime
|
|
const COMP_CODE = "";
|
|
if (!pxsim.initCurrentRuntime)
|
|
pxsim.initCurrentRuntime = initRuntimeWithDalBoard;
|
|
pxsim.runtime = new Runtime(COMP_CODE);
|
|
pxsim.runtime.board = null;
|
|
pxsim.initCurrentRuntime();
|
|
|
|
let style = document.createElement("style");
|
|
document.head.appendChild(style);
|
|
|
|
style.textContent += STYLE;
|
|
|
|
const boardDef = CURRENT_BOARD;
|
|
const cmpDefs = partDefinitions;
|
|
|
|
//props
|
|
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: activeComponents,
|
|
fnArgs: fnArgs,
|
|
getBBCoord: dummyBreadboard.getCoord.bind(dummyBreadboard)
|
|
});
|
|
|
|
//front page
|
|
let frontPanel = updateFrontPanel(props);
|
|
|
|
//all required parts
|
|
let partsPanel = mkPartsPanel(props);
|
|
document.body.appendChild(partsPanel);
|
|
|
|
//steps
|
|
for (let s = 0; s <= props.lastStep; s++) {
|
|
let p = mkStepPanel(s, props);
|
|
document.body.appendChild(p);
|
|
}
|
|
|
|
//final
|
|
let finalPanel = mkFinalPanel(props);
|
|
document.body.appendChild(finalPanel);
|
|
}
|
|
} |