export function patchBlocks(pkgTargetVersion: string, dom: Element) { // is this a old script? if (pxt.semver.majorCmp(pkgTargetVersion || "0.0.0", "4.0.20") >= 0) return; // button and pin pressed/released blocks /* Button.A TouchPin.P0 TouchPin.P1 converts to Button.B ButtonEvent.Click TouchPin.P2 ButtonEvent.Up */ const inputNodes = pxt.U.toArray(dom.querySelectorAll("block[type=device_button_event]")) .concat(pxt.U.toArray(dom.querySelectorAll("block[type=device_pin_event]"))) .concat(pxt.U.toArray(dom.querySelectorAll("block[type=device_pin_released]"))) inputNodes.forEach(node => { const nodeType = node.getAttribute("type"); if(nodeType === "device_button_event") { node.setAttribute("type", "device_button_selected_event"); } else { node.setAttribute("type", "device_pin_custom_event"); } // fix lowercase 'name'in device_pin_event if(nodeType === "device_pin_event") { node.querySelectorAll("field[name=name]")[0].setAttribute("name", "NAME") } const valueNode = node.ownerDocument.createElement("value"); valueNode.setAttribute("name", "eventType") const shadowNode = node.ownerDocument.createElement("shadow"); shadowNode.setAttribute("type", "control_button_event_value_id") const fieldNode = node.ownerDocument.createElement("field"); fieldNode.setAttribute("name", "id") if(nodeType === "device_button_event") { fieldNode.textContent = "ButtonEvent.Click"; } else if(nodeType === "device_pin_released") { fieldNode.textContent = "ButtonEvent.Up"; } else { fieldNode.textContent = "ButtonEvent.Down"; } shadowNode.prepend(fieldNode) valueNode.prepend(shadowNode) node.prepend(valueNode) }); // loudness /* converts to */ const loudnessNodes = pxt.U.toArray(dom.querySelectorAll("block[type=loudness]")) loudnessNodes.forEach(node => { node.setAttribute("type", "soundLevel"); }); // rgbw to rgb block const rgbwNodes = pxt.U.toArray(dom.querySelectorAll("block[type=core_rgbw]")) rgbwNodes.forEach(node => { node.setAttribute("type", "core_rgb"); node.querySelectorAll("value[name=white]")[0].remove(); }); // arrow blocks /* ArrowNames.North converts to IconNames.ArrowNorth */ const arrowNodes = pxt.U.toArray(dom.querySelectorAll("block[type=basic_show_arrow]")) arrowNodes.forEach(node => { node.setAttribute("type", "basic_show_icon"); const arrowNode = node.querySelectorAll("value[name=i]")[0] const iconName = "IconNames.Arrow" + arrowNode.querySelectorAll("field[name=arrow]")[0].textContent.split('.')[1]; const iconNode = node.ownerDocument.createElement("field"); iconNode.setAttribute("name", "i") iconNode.textContent = iconName; const mutationNode = node.ownerDocument.createElement("mutation"); // mutationNode.setAttribute("xmlns", "http://www.w3.org/1999/xhtml") mutationNode.setAttribute("_expanded", "0") mutationNode.setAttribute("_input_init", "false") node.prepend(iconNode) node.prepend(mutationNode) node.removeChild(arrowNode); }); // arrow icons /* ArrowNames.East converts to IconNames.ArrowEast */ const arrowImageNodes = pxt.U.toArray(dom.querySelectorAll("block[type=builtin_arrow_image]")) arrowImageNodes.forEach(node => { node.setAttribute("type", "builtin_image"); const arrowNode = node.querySelectorAll("field[name=i]")[0]; arrowNode.textContent = "IconNames.Arrow" + arrowNode.textContent.split('.')[1]; }); // is this a very old script? if (pxt.semver.majorCmp(pkgTargetVersion || "0.0.0", "1.0.0") >= 0) return; // LEDs /** * FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE to ` # # # # # . . . . # . . . . . . . . . # . . . . # ` */ const nodes = pxt.U.toArray(dom.querySelectorAll("block[type=device_show_leds]")) .concat(pxt.U.toArray(dom.querySelectorAll("block[type=device_build_image]"))) .concat(pxt.U.toArray(dom.querySelectorAll("shadow[type=device_build_image]"))) .concat(pxt.U.toArray(dom.querySelectorAll("block[type=device_build_big_image]"))) .concat(pxt.U.toArray(dom.querySelectorAll("shadow[type=device_build_big_image]"))); nodes.forEach(node => { // don't rewrite if already upgraded, eg. field LEDS already present if (pxt.U.toArray(node.children).filter(child => child.tagName == "field" && "LEDS" == child.getAttribute("name"))[0]) return; // read LEDxx value and assmebly into a new field const leds: string[][] = [[], [], [], [], []]; pxt.U.toArray(node.children) .filter(child => child.tagName == "field" && /^LED\d+$/.test(child.getAttribute("name"))) .forEach(lednode => { let n = lednode.getAttribute("name"); let col = parseInt(n[3]); let row = parseInt(n[4]); leds[row][col] = lednode.textContent == "TRUE" ? "#" : "."; // remove node node.removeChild(lednode); }); // add new field const f = node.ownerDocument.createElement("field"); f.setAttribute("name", "LEDS"); const s = '`\n' + leds.map(row => row.join('')).join('\n') + '\n`'; f.appendChild(node.ownerDocument.createTextNode(s)); node.insertBefore(f, null); }); // radio /* receivedNumber name value receivedString converts to receivedNumber name value receivedString */ const varids: pxt.Map = {}; function addField(node: Element, renameMap: pxt.Map, name: string) { const f = node.ownerDocument.createElement("field"); f.setAttribute("name", "HANDLER_" + name) f.setAttribute("id", varids[renameMap[name] || name]); f.appendChild(node.ownerDocument.createTextNode(name)); node.appendChild(f); } pxt.U.toArray(dom.querySelectorAll("variable")).forEach(node => varids[node.textContent] = node.getAttribute("id")); pxt.U.toArray(dom.querySelectorAll("block[type=radio_on_packet]")) .forEach(node => { const mutation = node.querySelector("mutation"); if (!mutation) return; const renameMap = JSON.parse(node.getAttribute("renamemap") || "{}"); const props = mutation.getAttribute("callbackproperties"); if (props) { const parts = props.split(","); // It's tempting to generate radio_on_number if parts.length === 0 but // that would create a variable named "receivedNumber" and possibly shadow // an existing variable in the user's program. It's safer to stick to the // old block. if (parts.length === 1) { if (parts[0] === "receivedNumber") { node.setAttribute("type", "radio_on_number"); node.removeChild(node.querySelector("field[name=receivedNumber]")); addField(node, renameMap, "receivedNumber"); } else if (parts[0] === "receivedString") { node.setAttribute("type", "radio_on_string"); node.removeChild(node.querySelector("field[name=receivedString]")); addField(node, renameMap, "receivedString"); } else { return; } node.removeChild(mutation); } else if (parts.length === 2 && parts.indexOf("receivedNumber") !== -1 && parts.indexOf("receivedString") !== -1) { node.setAttribute("type", "radio_on_value"); node.removeChild(node.querySelector("field[name=receivedNumber]")); node.removeChild(node.querySelector("field[name=receivedString]")); addField(node, renameMap, "name"); addField(node, renameMap, "value"); node.removeChild(mutation); } } }) // device_random now refers to randomRange() so we need to add the missing lower bound argument pxt.U.toArray(dom.querySelectorAll("block[type=device_random]")) .concat(pxt.U.toArray(dom.querySelectorAll("shadow[type=device_random]"))) .forEach(node => { if (getValue(node, "min")) return; const v = node.ownerDocument.createElement("value"); v.setAttribute("name", "min"); addNumberShadow(v); node.appendChild(v); }); /* DIVIDE 0 2 1 3 */ pxt.U.toArray(dom.querySelectorAll("block[type=math_arithmetic]")) .concat(pxt.U.toArray(dom.querySelectorAll("shadow[type=math_arithmetic]"))) .forEach(node => { const op = getField(node, "OP"); if (!op || op.textContent.trim() !== "DIVIDE") return; // Convert to integer division /* idiv 0 0 */ node.setAttribute("type", "math_js_op"); op.textContent = "idiv"; const mutation = node.ownerDocument.createElement("mutation"); mutation.setAttribute("op-type", "infix"); // mutation has to be first or Blockly will drop the second argument node.insertBefore(mutation, node.firstChild); const a = getValue(node, "A"); if (a) a.setAttribute("name", "ARG0"); const b = getValue(node, "B"); if (b) b.setAttribute("name", "ARG1"); }); renameField(dom, "math_number_minmax", "NUM", "SLIDER"); renameField(dom, "device_note", "note", "name"); } function renameField(dom: Element, blockType: string, oldName: string, newName: string) { pxt.U.toArray(dom.querySelectorAll(`block[type=${blockType}]`)) .concat(pxt.U.toArray(dom.querySelectorAll(`shadow[type=${blockType}]`))) .forEach(node => { const thefield = getField(node, oldName); if (thefield) { thefield.setAttribute("name", newName); } }); } function getField(parent: Element, name: string) { return getFieldOrValue(parent, name, true); } function getValue(parent: Element, name: string) { return getFieldOrValue(parent, name, false); } function getFieldOrValue(parent: Element, name: string, isField: boolean) { const nodeType = isField ? "field" : "value"; for (let i = 0; i < parent.children.length; i++) { const child = parent.children.item(i); if (child.tagName === nodeType && child.getAttribute("name") === name) { return child; } } return undefined; } function addNumberShadow(valueNode: Element) { const s = valueNode.ownerDocument.createElement("shadow"); s.setAttribute("type", "math_number"); const f = valueNode.ownerDocument.createElement("field"); f.setAttribute("name", "NUM"); f.textContent = "0"; s.appendChild(f); valueNode.appendChild(s); }