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:
		
							
								
								
									
										123
									
								
								editor/dialogs.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								editor/dialogs.tsx
									
									
									
									
									
										Normal 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())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1029
									
								
								editor/extension.tsx
									
									
									
									
									
								
							
							
						
						
									
										1029
									
								
								editor/extension.tsx
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										400
									
								
								editor/flash.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										400
									
								
								editor/flash.ts
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										263
									
								
								editor/patch.ts
									
									
									
									
									
										Normal 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="{"receivedString":"name","receivedNumber":"value"}"></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);
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user