Bump V3.0.22 (#110)

* change simulator svg

* change radio image

* Remove google fonts cdn

* change color of 'advanced' button

* font fix

* font fix 2

* display fix

* change fullsceen simulator bg

* Continuous servo

* handle continuous state

* adding shims

* update rendering for continuous servos

* fixing sim

* fix sig

* typo

* fix sim

* bump pxt

* bump pxt

* rerun travis

* Input blocks revision

- add Button and Pin event types
- merge onPinPressed & onPinReleased in new onPinEvent function
- create new onButtonEvent function

* update input blocks in docs and tests

* remove device_pin_release block

* Hide DAL.x behind Enum

* bring back deprecated blocks, but hide them

* shims and locales files

* fix input.input. typing

* remove buildpr

* bump V3

* update simulator aspect ratio

* add Loudness Block

* revoke loudness block

* Adds soundLevel

To be replaced by pxt-common-packages when DAL is updated.

* Remove P0 & P3 from AnalogPin

Co-authored-by: Juri <gitkraken@juriwolf.de>
This commit is contained in:
Amerlander
2020-09-08 11:04:25 +02:00
committed by GitHub
parent 98d8b2977b
commit 918af4f3ac
233 changed files with 9391 additions and 2739 deletions

123
editor/dialogs.tsx Normal file
View File

@ -0,0 +1,123 @@
import * as React from "react";
export function renderUsbPairDialog(firmwareUrl?: string, failedOnce?: boolean): JSX.Element {
const boardName = pxt.appTarget.appTheme.boardName || "???";
const helpUrl = pxt.appTarget.appTheme.usbDocs;
firmwareUrl = failedOnce && `${helpUrl}/webusb/troubleshoot`; // todo mo
const instructions = <div className="ui grid">
<div className="row">
<div className="column">
<div className="ui two column grid padded">
<div className="column">
<div className="ui">
<div className="image">
<img alt={lf("Comic connecting micro:bit to computer")} className="ui medium rounded image" src="./static/download/connect.png" />
</div>
<div className="content">
<div className="description">
<span className="ui purple circular label">1</span>
<strong>{lf("Connect the {0} to your computer with a USB cable", boardName)}</strong>
<br />
<span className="ui small">{lf("Use the microUSB port on the top of the {0}", boardName)}</span>
</div>
</div>
</div>
</div>
<div className="column">
<div className="ui">
<div className="image">
<img alt={lf("Comic of successful micro:bit connection")} className="ui medium rounded image" src="./static/download/pair.png" />
</div>
<div className="content">
<div className="description">
<span className="ui purple circular label">2</span>
<strong>{lf("Pair your {0}", boardName)}</strong>
<br />
<span className="ui small">{lf("Click 'Pair device' below and select 'Calliope mini' or 'DAPLink CMSIS-DAP' from the list")}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>;
if (!firmwareUrl) return instructions;
return <div className="ui grid stackable">
<div className="column five wide firmware orange">
<div className="ui header inverted">{lf("Update Firmware")}</div>
<strong className="ui small">{lf("You must have version 0249 or above of the firmware")}</strong>
<div className="image">
<img alt={lf("Comic rainbow updating micro:bit firmware")} className="ui image" src="./static/download/firmware.png" />
</div>
<a className="ui button" role="button" href={firmwareUrl} target="_blank">{lf("Check Firmware")}</a>
</div>
<div className="column eleven wide instructions">
{instructions}
</div>
</div>;
}
export function renderBrowserDownloadInstructions(): JSX.Element {
const boardName = pxt.appTarget.appTheme.boardName || lf("device");
const boardDriveName = pxt.appTarget.appTheme.driveDisplayName || pxt.appTarget.compile.driveName || "???";
return <div className="ui grid stackable upload">
<div className="column sixteen wide instructions">
<div className="ui grid">
<div className="row">
<div className="column">
<div className="ui two column grid padded">
<div className="column">
<div className="ui">
<div className="image">
<img alt={lf("Comic connecting micro:bit to computer")} className="ui medium rounded image" src="./static/download/connect.png" />
</div>
<div className="content">
<div className="description">
<span className="ui purple circular label">1</span>
<strong>{lf("Connect the {0} to your computer with a USB cable", boardName)}</strong>
<br />
<span className="ui small">{lf("Use the microUSB port on the top of the {0}", boardName)}</span>
</div>
</div>
</div>
</div>
<div className="column">
<div className="ui">
<div className="image">
<img alt={lf("Comic moving hex file to micro:bit")} className="ui medium rounded image" src="./static/download/transfer.png" />
</div>
<div className="content">
<div className="description">
<span className="ui purple circular label">2</span>
<strong>{lf("Move the .hex file to the {0}", boardName)}</strong>
<br />
<span className="ui small">{lf("Locate the downloaded .hex file and drag it to the {0} drive", boardDriveName)}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>;
}
export function cantImportAsync(project: pxt.editor.IProjectView) {
// this feature is support in v0 only
return project.showModalDialogAsync({
header: lf("Can't import microbit.co.uk scripts..."),
body: lf("Importing microbit.co.uk programs is not supported in this editor anymore. Please open this script in the https://makecode.microbit.org/v0 editor."),
buttons: [
{
label: lf("Go to the old editor"),
url: `https://makecode.microbit.org/v0`
}
]
}).then(() => project.openHome())
}

File diff suppressed because it is too large Load Diff

400
editor/flash.ts Normal file
View File

@ -0,0 +1,400 @@
const imul = (Math as any).imul;
const timeoutMessage = "timeout"
const membase = 0x20000000
const loadAddr = membase
const dataAddr = 0x20002000
const stackAddr = 0x20001000
const flashPageBIN = new Uint32Array([
0xbe00be00, // bkpt - LR is set to this
0x2502b5f0, 0x4c204b1f, 0xf3bf511d, 0xf3bf8f6f, 0x25808f4f, 0x002e00ed,
0x2f00595f, 0x25a1d0fc, 0x515800ed, 0x2d00599d, 0x2500d0fc, 0xf3bf511d,
0xf3bf8f6f, 0x25808f4f, 0x002e00ed, 0x2f00595f, 0x2501d0fc, 0xf3bf511d,
0xf3bf8f6f, 0x599d8f4f, 0xd0fc2d00, 0x25002680, 0x00f60092, 0xd1094295,
0x511a2200, 0x8f6ff3bf, 0x8f4ff3bf, 0x2a00599a, 0xbdf0d0fc, 0x5147594f,
0x2f00599f, 0x3504d0fc, 0x46c0e7ec, 0x4001e000, 0x00000504,
])
// void computeHashes(uint32_t *dst, uint8_t *ptr, uint32_t pageSize, uint32_t numPages)
const computeChecksums2 = new Uint32Array([
0x4c27b5f0, 0x44a52680, 0x22009201, 0x91004f25, 0x00769303, 0x24080013,
0x25010019, 0x40eb4029, 0xd0002900, 0x3c01407b, 0xd1f52c00, 0x468c0091,
0xa9044665, 0x506b3201, 0xd1eb42b2, 0x089b9b01, 0x23139302, 0x9b03469c,
0xd104429c, 0x2000be2a, 0x449d4b15, 0x9f00bdf0, 0x4d149e02, 0x49154a14,
0x3e01cf08, 0x2111434b, 0x491341cb, 0x405a434b, 0x4663405d, 0x230541da,
0x4b10435a, 0x466318d2, 0x230541dd, 0x4b0d435d, 0x2e0018ed, 0x6002d1e7,
0x9a009b01, 0x18d36045, 0x93003008, 0xe7d23401, 0xfffffbec, 0xedb88320,
0x00000414, 0x1ec3a6c8, 0x2f9be6cc, 0xcc9e2d51, 0x1b873593, 0xe6546b64,
])
let startTime = 0
function log(msg: string) {
let now = Date.now()
if (!startTime) startTime = now
now -= startTime
let ts = ("00000" + now).slice(-5)
pxt.debug(`dap ${ts}: ${msg}`)
}
function murmur3_core(data: Uint8Array) {
let h0 = 0x2F9BE6CC;
let h1 = 0x1EC3A6C8;
for (let i = 0; i < data.length; i += 4) {
let k = pxt.HF2.read32(data, i) >>> 0
k = imul(k, 0xcc9e2d51);
k = (k << 15) | (k >>> 17);
k = imul(k, 0x1b873593);
h0 ^= k;
h1 ^= k;
h0 = (h0 << 13) | (h0 >>> 19);
h1 = (h1 << 13) | (h1 >>> 19);
h0 = (imul(h0, 5) + 0xe6546b64) >>> 0;
h1 = (imul(h1, 5) + 0xe6546b64) >>> 0;
}
return [h0, h1]
}
class DAPWrapper implements pxt.packetio.PacketIOWrapper {
familyID: number;
private dap: DapJS.DAP;
private cortexM: DapJS.CortexM
private cmsisdap: any;
private flashing = false;
private readSerialId = 0;
private pbuf = new pxt.U.PromiseBuffer<Uint8Array>();
private pageSize = 1024;
private numPages = 256;
private binName = pxtc.BINARY_HEX;
constructor(public readonly io: pxt.packetio.PacketIO) {
this.familyID = 0x1366; // this is the microbit vendor id, not quite UF2 family id
this.io.onDeviceConnectionChanged = (connect) =>
this.disconnectAsync()
.then(() => connect && this.reconnectAsync());
this.io.onData = buf => {
// console.log("RD: " + pxt.Util.toHex(buf))
this.pbuf.push(buf);
}
this.allocDAP();
}
icon = "usb";
private startReadSerial() {
log(`start read serial`)
const rid = this.readSerialId;
const readSerial = () => {
if (rid != this.readSerialId) {
log(`stopped read serial ${rid}`)
return;
}
if (this.flashing) {
setTimeout(readSerial, 500);
return;
}
// done
this.cmsisdap.cmdNums(0x83, [])
.then((r: number[]) => {
const len = r[1]
let str = ""
for (let i = 2; i < len + 2; ++i) {
str += String.fromCharCode(r[i])
}
if (str.length > 0) {
pxt.U.nextTick(readSerial)
if (this.onSerial) {
const utf8Str = pxt.U.toUTF8(str);
this.onSerial(pxt.U.stringToUint8Array(utf8Str), false)
}
} else
setTimeout(readSerial, 50)
}, (err: any) => {
log(`read error: ` + err.message);
this.disconnectAsync(); // force disconnect
});
}
readSerial();
}
private stopSerialAsync() {
log(`stopping serial reader`)
this.readSerialId++;
return Promise.delay(200);
}
onSerial: (buf: Uint8Array, isStderr: boolean) => void;
private allocDAP() {
log(`alloc dap`);
this.dap = new DapJS.DAP({
write: writeAsync,
close: this.disconnectAsync,
read: readAsync,
//sendMany: sendMany
});
this.cmsisdap = (this.dap as any).dap;
this.cortexM = new DapJS.CortexM(this.dap);
const h = this.io;
const pbuf = this.pbuf;
function writeAsync(data: ArrayBuffer) {
//console.log("WR: " + pxt.Util.toHex(new Uint8Array(data)));
return h.sendPacketAsync(new Uint8Array(data));
}
function readAsync() {
return pbuf.shiftAsync();
}
}
reconnectAsync(): Promise<void> {
log(`reconnect`)
// configure serial at 115200
return this.stopSerialAsync()
.then(() => this.io.reconnectAsync())
.then(() => this.cortexM.init())
.then(() => this.cmsisdap.cmdNums(0x80, []))
.then(r => {
this.binName = (r[2] == 57 && r[3] == 57 && r[5] >= 51 ? "mbcodal-" : "") + pxtc.BINARY_HEX
})
.then(() => this.cortexM.memory.readBlock(0x10000010, 2, this.pageSize))
.then(res => {
this.pageSize = pxt.HF2.read32(res, 0)
this.numPages = pxt.HF2.read32(res, 4)
})
.then(() => this.cmsisdap.cmdNums(0x82, [0x00, 0xC2, 0x01, 0x00]))
.then(() => this.startReadSerial());
}
disconnectAsync() {
log(`disconnect`)
return this.stopSerialAsync()
.then(() => this.io.disconnectAsync());
}
reflashAsync(resp: pxtc.CompileResult): Promise<void> {
log("reflash")
startTime = 0
pxt.tickEvent("hid.flash.start");
this.flashing = true;
return (this.io.isConnected() ? Promise.resolve() : this.io.reconnectAsync())
.then(() => this.cortexM.init())
.then(() => this.cortexM.reset(true))
.then(() => this.cortexM.memory.readBlock(0x10001014, 1, this.pageSize))
.then(v => {
if ((pxt.HF2.read32(v, 0) & 0xff) != 0) {
pxt.tickEvent("hid.flash.uicrfail");
return this.fullVendorCommandFlashAsync(resp);
}
return this.quickHidFlashAsync(resp);
})
.finally(() => { this.flashing = false })
.then(() => Promise.delay(100))
.then(() => this.disconnectAsync())
}
private fullVendorCommandFlashAsync(resp: pxtc.CompileResult): Promise<void> {
log("full flash")
const chunkSize = 62;
let aborted = false;
return Promise.resolve()
.then(() => {
return this.cmsisdap.cmdNums(0x8A /* DAPLinkFlash.OPEN */, [1]);
})
.then((res) => {
const hexUint8 = pxt.U.stringToUint8Array(resp.outfiles[this.binName]);
const hexArray: number[] = Array.prototype.slice.call(hexUint8);
const sendPages = (offset: number = 0): Promise<void> => {
const end = Math.min(hexArray.length, offset + chunkSize);
const nextPage = hexArray.slice(offset, end);
nextPage.unshift(nextPage.length);
return this.cmsisdap.cmdNums(0x8C /* DAPLinkFlash.WRITE */, nextPage)
.then(() => {
if (!aborted && end < hexArray.length) {
return sendPages(end);
}
return Promise.resolve();
});
}
return sendPages();
})
.then((res) => {
return this.cmsisdap.cmdNums(0x8B /* DAPLinkFlash.CLOSE */, []);
})
.timeout(60000, timeoutMessage)
.catch((e) => {
aborted = true;
return this.cmsisdap.cmdNums(0x89 /* DAPLinkFlash.RESET */, [])
.catch((e2: any) => {
// Best effort reset, no-op if there's an error
})
.then(() => {
return Promise.reject(e);
});
});
}
private quickHidFlashAsync(resp: pxtc.CompileResult): Promise<void> {
log("quick flash")
let logV = (msg: string) => { }
//let logV = log
let aborted = false;
const runFlash = (b: ts.pxtc.UF2.Block, dataAddr: number) => {
const cmd = this.cortexM.prepareCommand();
cmd.halt();
cmd.writeCoreRegister(DapJS.CortexReg.PC, loadAddr + 4 + 1);
cmd.writeCoreRegister(DapJS.CortexReg.LR, loadAddr + 1);
cmd.writeCoreRegister(DapJS.CortexReg.SP, stackAddr);
cmd.writeCoreRegister(0, b.targetAddr);
cmd.writeCoreRegister(1, dataAddr);
cmd.writeCoreRegister(2, this.pageSize >> 2);
return Promise.resolve()
.then(() => {
logV("setregs")
return cmd.go()
})
.then(() => {
logV("dbg en")
// starts the program
return this.cortexM.debug.enable()
})
}
let checksums: Uint8Array
return this.getFlashChecksumsAsync()
.then(buf => {
checksums = buf;
log("write code");
return this.cortexM.memory.writeBlock(loadAddr, flashPageBIN);
})
.then(() => {
log("convert");
// TODO this is seriously inefficient (130ms on a fast machine)
let uf2 = ts.pxtc.UF2.newBlockFile();
ts.pxtc.UF2.writeHex(uf2, resp.outfiles[this.binName].split(/\r?\n/));
let bytes = pxt.U.stringToUint8Array(ts.pxtc.UF2.serializeFile(uf2));
let parsed = ts.pxtc.UF2.parseFile(bytes);
let aligned = DAPWrapper.pageAlignBlocks(parsed, this.pageSize);
log(`initial: ${aligned.length} pages`);
aligned = DAPWrapper.onlyChanged(aligned, checksums, this.pageSize);
log(`incremental: ${aligned.length} pages`);
return Promise.mapSeries(pxt.U.range(aligned.length),
i => {
if (aborted) return Promise.resolve();
let b = aligned[i];
if (b.targetAddr >= 0x10000000)
return Promise.resolve();
logV("about to write at 0x" + b.targetAddr.toString(16));
let writeBl = Promise.resolve();
let thisAddr = (i & 1) ? dataAddr : dataAddr + this.pageSize;
let nextAddr = (i & 1) ? dataAddr + this.pageSize : dataAddr;
if (i == 0) {
let u32data = new Uint32Array(b.data.length / 4);
for (let i = 0; i < b.data.length; i += 4)
u32data[i >> 2] = pxt.HF2.read32(b.data, i);
writeBl = this.cortexM.memory.writeBlock(thisAddr, u32data);
}
return writeBl
.then(() => runFlash(b, thisAddr))
.then(() => {
let next = aligned[i + 1];
if (!next)
return Promise.resolve();
logV("write next");
let buf = new Uint32Array(next.data.buffer);
return this.cortexM.memory.writeBlock(nextAddr, buf);
})
.then(() => {
logV("wait");
return this.cortexM.waitForHalt(500);
})
.then(() => {
logV("done block");
});
})
.then(() => {
log("flash done");
pxt.tickEvent("hid.flash.done");
return this.cortexM.reset(false);
});
})
.timeout(25000, timeoutMessage)
.catch((e) => {
aborted = true;
return Promise.reject(e);
});
}
private getFlashChecksumsAsync() {
log("flash checksums")
let pages = this.numPages
return this.cortexM.runCode(computeChecksums2, loadAddr, loadAddr + 1, 0xffffffff, stackAddr, true,
dataAddr, 0, this.pageSize, pages)
.then(() => this.cortexM.memory.readBlock(dataAddr, pages * 2, this.pageSize))
}
static onlyChanged(blocks: ts.pxtc.UF2.Block[], checksums: Uint8Array, pageSize: number) {
return blocks.filter(b => {
let idx = b.targetAddr / pageSize
pxt.U.assert((idx | 0) == idx)
pxt.U.assert(b.data.length == pageSize)
if (idx * 8 + 8 > checksums.length)
return true // out of range?
let c0 = pxt.HF2.read32(checksums, idx * 8)
let c1 = pxt.HF2.read32(checksums, idx * 8 + 4)
let ch = murmur3_core(b.data)
if (c0 == ch[0] && c1 == ch[1])
return false
return true
})
}
static pageAlignBlocks(blocks: ts.pxtc.UF2.Block[], pageSize: number) {
pxt.U.assert(pageSize % 256 == 0)
let res: ts.pxtc.UF2.Block[] = []
for (let i = 0; i < blocks.length;) {
let b0 = blocks[i]
let newbuf = new Uint8Array(pageSize)
for (let i = 0; i < newbuf.length; ++i)
newbuf[i] = 0xff
let startPad = b0.targetAddr & (pageSize - 1)
let newAddr = b0.targetAddr - startPad
for (; i < blocks.length; ++i) {
let b = blocks[i]
if (b.targetAddr + b.payloadSize > newAddr + pageSize)
break
pxt.U.memcpy(newbuf, b.targetAddr - newAddr, b.data, 0, b.payloadSize)
}
let bb = pxt.U.flatClone(b0)
bb.data = newbuf
bb.targetAddr = newAddr
bb.payloadSize = pageSize
res.push(bb)
}
return res
}
}
export function mkDAPLinkPacketIOWrapper(io: pxt.packetio.PacketIO): pxt.packetio.PacketIOWrapper {
pxt.log(`packetio: mk wrapper dap wrapper`)
return new DAPWrapper(io);
}

263
editor/patch.ts Normal file
View File

@ -0,0 +1,263 @@
/**
* <block type="device_show_leds">
<field name="LED00">FALSE</field>
<field name="LED10">FALSE</field>
<field name="LED20">FALSE</field>
<field name="LED30">FALSE</field>
<field name="LED40">FALSE</field>
<field name="LED01">FALSE</field>
<field name="LED11">FALSE</field>
<field name="LED21">FALSE</field>
<field name="LED31">TRUE</field>
<field name="LED41">FALSE</field>
<field name="LED02">FALSE</field>
<field name="LED12">FALSE</field>
<field name="LED22">FALSE</field>
<field name="LED32">FALSE</field>
<field name="LED42">FALSE</field>
<field name="LED03">FALSE</field>
<field name="LED13">TRUE</field>
<field name="LED23">FALSE</field>
<field name="LED33">FALSE</field>
<field name="LED43">FALSE</field>
<field name="LED04">FALSE</field>
<field name="LED14">FALSE</field>
<field name="LED24">FALSE</field>
<field name="LED34">FALSE</field>
<field name="LED44">FALSE</field>
</block>
to
<block type="device_show_leds">
<field name="LEDS">`
# # # # #
. . . . #
. . . . .
. . . . #
. . . . #
`
</field>
</block>
*/
export function patchBlocks(pkgTargetVersion: string, dom: Element) {
// is this a old script?
if (pxt.semver.majorCmp(pkgTargetVersion || "0.0.0", "1.0.0") >= 0) return;
// showleds
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.innerHTML == "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
/*
<block type="radio_on_packet" x="174" y="120">
<mutation callbackproperties="receivedNumber" renamemap="{}"></mutation>
<field name="receivedNumber">receivedNumber</field>
</block>
<block type="radio_on_packet" disabled="true" x="127" y="263">
<mutation callbackproperties="receivedString,receivedNumber" renamemap="{&quot;receivedString&quot;:&quot;name&quot;,&quot;receivedNumber&quot;:&quot;value&quot;}"></mutation>
<field name="receivedString">name</field>
<field name="receivedNumber">value</field>
</block>
<block type="radio_on_packet" disabled="true" x="162" y="420">
<mutation callbackproperties="receivedString" renamemap="{}"></mutation>
<field name="receivedString">receivedString</field>
</block>
converts to
<block type="radio_on_number" x="196" y="208">
<field name="HANDLER_receivedNumber" id="DCy(W;1)*jLWQUpoy4Mm" variabletype="">receivedNumber</field>
</block>
<block type="radio_on_value" x="134" y="408">
<field name="HANDLER_name" id="*d-Jm^MJXO]Djs(dTR*?" variabletype="">name</field>
<field name="HANDLER_value" id="A6HQjH[k^X43o3h775+G" variabletype="">value</field>
</block>
<block type="radio_on_string" x="165" y="583">
<field name="HANDLER_receivedString" id="V9KsE!h$(iO?%W:[32CV" variabletype="">receivedString</field>
</block>
*/
const varids: pxt.Map<string> = {};
function addField(node: Element, renameMap: pxt.Map<string>, 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.innerHTML] = 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);
});
/*
<block type="math_arithmetic">
<field name="OP">DIVIDE</field>
<value name="A">
<shadow type="math_number"><field name="NUM">0</field></shadow>
<block type="math_number"><field name="NUM">2</field></block>
</value>
<value name="B">
<shadow type="math_number"><field name="NUM">1</field></shadow>
<block type="math_number"><field name="NUM">3</field></block>
</value>
</block>
*/
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
/*
<block type="math_js_op">
<mutation op-type="infix"></mutation>
<field name="OP">idiv</field>
<value name="ARG0">
<shadow type="math_number"><field name="NUM">0</field></shadow>
</value>
<value name="ARG1">
<shadow type="math_number"><field name="NUM">0</field></shadow>
</value>
</block>
*/
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);
}