Merge pull request #135 from Microsoft/field_ports
Add an output port field editor
This commit is contained in:
		@@ -2,7 +2,7 @@
 | 
			
		||||
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
 | 
			
		||||
require("./editor")
 | 
			
		||||
require("./editor/deploy")
 | 
			
		||||
 | 
			
		||||
declare namespace pxt.editor {
 | 
			
		||||
    function deployCoreAsync(resp: pxtc.CompileResult, disconnect?: boolean): Promise<void>;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										133
									
								
								editor/deploy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								editor/deploy.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
/// <reference path="../node_modules/pxt-core/built/pxteditor.d.ts"/>
 | 
			
		||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
 | 
			
		||||
 | 
			
		||||
import UF2 = pxtc.UF2;
 | 
			
		||||
 | 
			
		||||
export let ev3: pxt.editor.Ev3Wrapper
 | 
			
		||||
 | 
			
		||||
export function debug() {
 | 
			
		||||
    return initAsync()
 | 
			
		||||
        .then(w => w.downloadFileAsync("/tmp/dmesg.txt", v => console.log(pxt.Util.uint8ArrayToString(v))))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hf2Async() {
 | 
			
		||||
    return pxt.HF2.mkPacketIOAsync()
 | 
			
		||||
        .then(h => {
 | 
			
		||||
            let w = new pxt.editor.Ev3Wrapper(h)
 | 
			
		||||
            ev3 = w
 | 
			
		||||
            return w.reconnectAsync(true)
 | 
			
		||||
                .then(() => w)
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let noHID = false
 | 
			
		||||
 | 
			
		||||
let initPromise: Promise<pxt.editor.Ev3Wrapper>
 | 
			
		||||
export function initAsync() {
 | 
			
		||||
    if (initPromise)
 | 
			
		||||
        return initPromise
 | 
			
		||||
 | 
			
		||||
    let canHID = false
 | 
			
		||||
    if (pxt.U.isNodeJS) {
 | 
			
		||||
        canHID = true
 | 
			
		||||
    } else {
 | 
			
		||||
        const forceHexDownload = /forceHexDownload/i.test(window.location.href);
 | 
			
		||||
        if (pxt.Cloud.isLocalHost() && pxt.Cloud.localToken && !forceHexDownload)
 | 
			
		||||
            canHID = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (noHID)
 | 
			
		||||
        canHID = false
 | 
			
		||||
 | 
			
		||||
    if (canHID) {
 | 
			
		||||
        initPromise = hf2Async()
 | 
			
		||||
            .catch(err => {
 | 
			
		||||
                initPromise = null
 | 
			
		||||
                noHID = true
 | 
			
		||||
                return Promise.reject(err)
 | 
			
		||||
            })
 | 
			
		||||
    } else {
 | 
			
		||||
        noHID = true
 | 
			
		||||
        initPromise = Promise.reject(new Error("no HID"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return initPromise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// this comes from aux/pxt.lms
 | 
			
		||||
const rbfTemplate = `
 | 
			
		||||
4c45474f580000006d000100000000001c000000000000000e000000821b038405018130813e8053
 | 
			
		||||
74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a
 | 
			
		||||
`
 | 
			
		||||
export function deployCoreAsync(resp: pxtc.CompileResult, isCli = false) {
 | 
			
		||||
    let w: pxt.editor.Ev3Wrapper
 | 
			
		||||
 | 
			
		||||
    let filename = resp.downloadFileBaseName || "pxt"
 | 
			
		||||
    filename = filename.replace(/^lego-/, "")
 | 
			
		||||
 | 
			
		||||
    let fspath = "../prjs/BrkProg_SAVE/"
 | 
			
		||||
 | 
			
		||||
    let elfPath = fspath + filename + ".elf"
 | 
			
		||||
    let rbfPath = fspath + filename + ".rbf"
 | 
			
		||||
 | 
			
		||||
    let rbfHex = rbfTemplate
 | 
			
		||||
        .replace(/\s+/g, "")
 | 
			
		||||
        .replace("XX", pxt.U.toHex(pxt.U.stringToUint8Array(elfPath)))
 | 
			
		||||
    let rbfBIN = pxt.U.fromHex(rbfHex)
 | 
			
		||||
    pxt.HF2.write16(rbfBIN, 4, rbfBIN.length)
 | 
			
		||||
 | 
			
		||||
    let origElfUF2 = UF2.parseFile(pxt.U.stringToUint8Array(atob(resp.outfiles[pxt.outputName()])))
 | 
			
		||||
 | 
			
		||||
    let mkFile = (ext: string, data: Uint8Array = null) => {
 | 
			
		||||
        let f = UF2.newBlockFile()
 | 
			
		||||
        f.filename = "Projects/" + filename + ext
 | 
			
		||||
        if (data)
 | 
			
		||||
            UF2.writeBytes(f, 0, data)
 | 
			
		||||
        return f
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let elfUF2 = mkFile(".elf")
 | 
			
		||||
    for (let b of origElfUF2) {
 | 
			
		||||
        UF2.writeBytes(elfUF2, b.targetAddr, b.data)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let r = UF2.concatFiles([elfUF2, mkFile(".rbf", rbfBIN)])
 | 
			
		||||
    let data = UF2.serializeFile(r)
 | 
			
		||||
 | 
			
		||||
    resp.outfiles[pxtc.BINARY_UF2] = btoa(data)
 | 
			
		||||
 | 
			
		||||
    let saveUF2Async = () => {
 | 
			
		||||
        if (isCli || !pxt.commands.saveOnlyAsync) {
 | 
			
		||||
            return Promise.resolve()
 | 
			
		||||
        } else {
 | 
			
		||||
            return pxt.commands.saveOnlyAsync(resp)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (noHID) return saveUF2Async()
 | 
			
		||||
 | 
			
		||||
    return initAsync()
 | 
			
		||||
        .then(w_ => {
 | 
			
		||||
            w = w_
 | 
			
		||||
            if (w.isStreaming)
 | 
			
		||||
                pxt.U.userError("please stop the program first")
 | 
			
		||||
            return w.stopAsync()
 | 
			
		||||
        })
 | 
			
		||||
        .then(() => w.rmAsync(elfPath))
 | 
			
		||||
        .then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
 | 
			
		||||
        .then(() => w.flashAsync(rbfPath, rbfBIN))
 | 
			
		||||
        .then(() => w.runAsync(rbfPath))
 | 
			
		||||
        .then(() => {
 | 
			
		||||
            if (isCli)
 | 
			
		||||
                return w.disconnectAsync()
 | 
			
		||||
            else
 | 
			
		||||
                return Promise.resolve()
 | 
			
		||||
            //return Promise.delay(1000).then(() => w.dmesgAsync())
 | 
			
		||||
        }).catch(e => {
 | 
			
		||||
            // if we failed to initalize, retry
 | 
			
		||||
            if (noHID)
 | 
			
		||||
                return saveUF2Async()
 | 
			
		||||
            else
 | 
			
		||||
                return Promise.reject(e)
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
@@ -1,242 +1,116 @@
 | 
			
		||||
/// <reference path="../node_modules/pxt-core/built/pxteditor.d.ts" />
 | 
			
		||||
/// <reference path="../node_modules/pxt-core/built/pxteditor.d.ts"/>
 | 
			
		||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
 | 
			
		||||
 | 
			
		||||
// When require()d from node, bind the global pxt namespace
 | 
			
		||||
namespace pxt {
 | 
			
		||||
    export const dummyExport = 1;
 | 
			
		||||
import { deployCoreAsync, initAsync } from "./deploy";
 | 
			
		||||
import { FieldPorts } from "./field_ports";
 | 
			
		||||
 | 
			
		||||
pxt.editor.initExtensionsAsync = function(opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
 | 
			
		||||
    pxt.debug('loading pxt-ev3 target extensions...')
 | 
			
		||||
    updateBlocklyShape();
 | 
			
		||||
    const res: pxt.editor.ExtensionResult = {
 | 
			
		||||
        fieldEditors: [{
 | 
			
		||||
            selector: "ports",
 | 
			
		||||
            editor: FieldPorts
 | 
			
		||||
        }],
 | 
			
		||||
        deployCoreAsync
 | 
			
		||||
    };
 | 
			
		||||
    initAsync().catch(e => {
 | 
			
		||||
        // probably no HID - we'll try this again upon deployment
 | 
			
		||||
    })
 | 
			
		||||
    return Promise.resolve<pxt.editor.ExtensionResult>(res);
 | 
			
		||||
}
 | 
			
		||||
eval("if (typeof process === 'object' && process + '' === '[object process]') pxt = global.pxt")
 | 
			
		||||
 | 
			
		||||
namespace pxt.editor {
 | 
			
		||||
    import UF2 = pxtc.UF2;
 | 
			
		||||
 | 
			
		||||
    export let ev3: Ev3Wrapper
 | 
			
		||||
 | 
			
		||||
    export function debug() {
 | 
			
		||||
        return initAsync()
 | 
			
		||||
            .then(w => w.downloadFileAsync("/tmp/dmesg.txt", v => console.log(pxt.Util.uint8ArrayToString(v))))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // this comes from aux/pxt.lms
 | 
			
		||||
    const rbfTemplate = `
 | 
			
		||||
4c45474f580000006d000100000000001c000000000000000e000000821b038405018130813e8053
 | 
			
		||||
74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
    function hf2Async() {
 | 
			
		||||
        return pxt.HF2.mkPacketIOAsync()
 | 
			
		||||
            .then(h => {
 | 
			
		||||
                let w = new Ev3Wrapper(h)
 | 
			
		||||
                ev3 = w
 | 
			
		||||
                return w.reconnectAsync(true)
 | 
			
		||||
                    .then(() => w)
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let noHID = false
 | 
			
		||||
 | 
			
		||||
    let initPromise: Promise<Ev3Wrapper>
 | 
			
		||||
    function initAsync() {
 | 
			
		||||
        if (initPromise)
 | 
			
		||||
            return initPromise
 | 
			
		||||
 | 
			
		||||
        let canHID = false
 | 
			
		||||
        if (U.isNodeJS) {
 | 
			
		||||
            canHID = true
 | 
			
		||||
        } else {
 | 
			
		||||
            const forceHexDownload = /forceHexDownload/i.test(window.location.href);
 | 
			
		||||
            if (Cloud.isLocalHost() && Cloud.localToken && !forceHexDownload)
 | 
			
		||||
                canHID = true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (noHID)
 | 
			
		||||
            canHID = false
 | 
			
		||||
 | 
			
		||||
        if (canHID) {
 | 
			
		||||
            initPromise = hf2Async()
 | 
			
		||||
                .catch(err => {
 | 
			
		||||
                    initPromise = null
 | 
			
		||||
                    noHID = true
 | 
			
		||||
                    return Promise.reject(err)
 | 
			
		||||
                })
 | 
			
		||||
        } else {
 | 
			
		||||
            noHID = true
 | 
			
		||||
            initPromise = Promise.reject(new Error("no HID"))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return initPromise
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    export function deployCoreAsync(resp: pxtc.CompileResult, isCli = false) {
 | 
			
		||||
        let w: Ev3Wrapper
 | 
			
		||||
 | 
			
		||||
        let filename = resp.downloadFileBaseName || "pxt"
 | 
			
		||||
        filename = filename.replace(/^lego-/, "")
 | 
			
		||||
 | 
			
		||||
        let fspath = "../prjs/BrkProg_SAVE/"
 | 
			
		||||
 | 
			
		||||
        let elfPath = fspath + filename + ".elf"
 | 
			
		||||
        let rbfPath = fspath + filename + ".rbf"
 | 
			
		||||
 | 
			
		||||
        let rbfHex = rbfTemplate
 | 
			
		||||
            .replace(/\s+/g, "")
 | 
			
		||||
            .replace("XX", U.toHex(U.stringToUint8Array(elfPath)))
 | 
			
		||||
        let rbfBIN = U.fromHex(rbfHex)
 | 
			
		||||
        HF2.write16(rbfBIN, 4, rbfBIN.length)
 | 
			
		||||
 | 
			
		||||
        let origElfUF2 = UF2.parseFile(U.stringToUint8Array(atob(resp.outfiles[pxt.outputName()])))
 | 
			
		||||
 | 
			
		||||
        let mkFile = (ext: string, data: Uint8Array = null) => {
 | 
			
		||||
            let f = UF2.newBlockFile()
 | 
			
		||||
            f.filename = "Projects/" + filename + ext
 | 
			
		||||
            if (data)
 | 
			
		||||
                UF2.writeBytes(f, 0, data)
 | 
			
		||||
            return f
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let elfUF2 = mkFile(".elf")
 | 
			
		||||
        for (let b of origElfUF2) {
 | 
			
		||||
            UF2.writeBytes(elfUF2, b.targetAddr, b.data)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let r = UF2.concatFiles([elfUF2, mkFile(".rbf", rbfBIN)])
 | 
			
		||||
        let data = UF2.serializeFile(r)
 | 
			
		||||
 | 
			
		||||
        resp.outfiles[pxtc.BINARY_UF2] = btoa(data)
 | 
			
		||||
 | 
			
		||||
        let saveUF2Async = () => {
 | 
			
		||||
            if (isCli || !pxt.commands.saveOnlyAsync) {
 | 
			
		||||
                return Promise.resolve()
 | 
			
		||||
            } else {
 | 
			
		||||
                return pxt.commands.saveOnlyAsync(resp)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (noHID) return saveUF2Async()
 | 
			
		||||
 | 
			
		||||
        return initAsync()
 | 
			
		||||
            .then(w_ => {
 | 
			
		||||
                w = w_
 | 
			
		||||
                if (w.isStreaming)
 | 
			
		||||
                    U.userError("please stop the program first")
 | 
			
		||||
                return w.stopAsync()
 | 
			
		||||
            })
 | 
			
		||||
            .then(() => w.rmAsync(elfPath))
 | 
			
		||||
            .then(() => w.flashAsync(elfPath, UF2.readBytes(origElfUF2, 0, origElfUF2.length * 256)))
 | 
			
		||||
            .then(() => w.flashAsync(rbfPath, rbfBIN))
 | 
			
		||||
            .then(() => w.runAsync(rbfPath))
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                if (isCli)
 | 
			
		||||
                    return w.disconnectAsync()
 | 
			
		||||
                else
 | 
			
		||||
                    return Promise.resolve()
 | 
			
		||||
                //return Promise.delay(1000).then(() => w.dmesgAsync())
 | 
			
		||||
            }).catch(e => {
 | 
			
		||||
                // if we failed to initalize, retry
 | 
			
		||||
                if (noHID)
 | 
			
		||||
                    return saveUF2Async()
 | 
			
		||||
                else
 | 
			
		||||
                    return Promise.reject(e)
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
/**
 | 
			
		||||
 * Update the shape of Blockly blocks with square corners
 | 
			
		||||
 */
 | 
			
		||||
function updateBlocklyShape() {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the shape of Blockly blocks with square corners
 | 
			
		||||
     * Rounded corner radius.
 | 
			
		||||
     * @const
 | 
			
		||||
     */
 | 
			
		||||
    function updateBlocklyShape() {
 | 
			
		||||
    (Blockly.BlockSvg as any).CORNER_RADIUS = 0 * (Blockly.BlockSvg as any).GRID_UNIT;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Rounded corner radius.
 | 
			
		||||
         * @const
 | 
			
		||||
         */
 | 
			
		||||
        (Blockly.BlockSvg as any).CORNER_RADIUS = 0 * (Blockly.BlockSvg as any).GRID_UNIT;
 | 
			
		||||
    /**
 | 
			
		||||
     * Inner space between edge of statement input and notch.
 | 
			
		||||
     * @const
 | 
			
		||||
     */
 | 
			
		||||
    (Blockly.BlockSvg as any).STATEMENT_INPUT_INNER_SPACE = 3 * (Blockly.BlockSvg as any).GRID_UNIT;
 | 
			
		||||
    /**
 | 
			
		||||
     * SVG path for drawing next/previous notch from left to right.
 | 
			
		||||
     * @const
 | 
			
		||||
     */
 | 
			
		||||
    (Blockly.BlockSvg as any).NOTCH_PATH_LEFT = (
 | 
			
		||||
        'l 8,8 ' +
 | 
			
		||||
        'h 16 ' +
 | 
			
		||||
        'l 8,-8 '
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Inner space between edge of statement input and notch.
 | 
			
		||||
         * @const
 | 
			
		||||
         */
 | 
			
		||||
        (Blockly.BlockSvg as any).STATEMENT_INPUT_INNER_SPACE = 3 * (Blockly.BlockSvg as any).GRID_UNIT;
 | 
			
		||||
        /**
 | 
			
		||||
         * SVG path for drawing next/previous notch from left to right.
 | 
			
		||||
         * @const
 | 
			
		||||
         */
 | 
			
		||||
        (Blockly.BlockSvg as any).NOTCH_PATH_LEFT = (
 | 
			
		||||
            'l 8,8 ' +
 | 
			
		||||
            'h 16 ' +
 | 
			
		||||
            'l 8,-8 '
 | 
			
		||||
        );
 | 
			
		||||
    /**
 | 
			
		||||
     * SVG path for drawing next/previous notch from right to left.
 | 
			
		||||
     * @const
 | 
			
		||||
     */
 | 
			
		||||
    (Blockly.BlockSvg as any).NOTCH_PATH_RIGHT = (
 | 
			
		||||
        'l -8,8 ' +
 | 
			
		||||
        'h -16 ' +
 | 
			
		||||
        'l -8,-8 '
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * SVG path for drawing next/previous notch from right to left.
 | 
			
		||||
         * @const
 | 
			
		||||
         */
 | 
			
		||||
        (Blockly.BlockSvg as any).NOTCH_PATH_RIGHT = (
 | 
			
		||||
            'l -8,8 ' +
 | 
			
		||||
            'h -16 ' +
 | 
			
		||||
            'l -8,-8 '
 | 
			
		||||
        );
 | 
			
		||||
    /**
 | 
			
		||||
     * SVG start point for drawing the top-left corner.
 | 
			
		||||
     * @const
 | 
			
		||||
     */
 | 
			
		||||
    (Blockly.BlockSvg as any).TOP_LEFT_CORNER_START =
 | 
			
		||||
        'm 0,' + 0;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * SVG start point for drawing the top-left corner.
 | 
			
		||||
         * @const
 | 
			
		||||
         */
 | 
			
		||||
        (Blockly.BlockSvg as any).TOP_LEFT_CORNER_START =
 | 
			
		||||
            'm 0,' + 0;
 | 
			
		||||
    /**
 | 
			
		||||
     * SVG path for drawing the rounded top-left corner.
 | 
			
		||||
     * @const
 | 
			
		||||
     */
 | 
			
		||||
    (Blockly.BlockSvg as any).TOP_LEFT_CORNER =
 | 
			
		||||
        'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0 ';
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * SVG path for drawing the rounded top-left corner.
 | 
			
		||||
         * @const
 | 
			
		||||
         */
 | 
			
		||||
        (Blockly.BlockSvg as any).TOP_LEFT_CORNER =
 | 
			
		||||
            'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0 ';
 | 
			
		||||
    /**
 | 
			
		||||
     * SVG path for drawing the rounded top-right corner.
 | 
			
		||||
     * @const
 | 
			
		||||
     */
 | 
			
		||||
    (Blockly.BlockSvg as any).TOP_RIGHT_CORNER =
 | 
			
		||||
        'l ' + 0 + ',' + (Blockly.BlockSvg as any).CORNER_RADIUS;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * SVG path for drawing the rounded top-right corner.
 | 
			
		||||
         * @const
 | 
			
		||||
         */
 | 
			
		||||
        (Blockly.BlockSvg as any).TOP_RIGHT_CORNER =
 | 
			
		||||
            'l ' + 0 + ',' + (Blockly.BlockSvg as any).CORNER_RADIUS;
 | 
			
		||||
    /**
 | 
			
		||||
     * SVG path for drawing the rounded bottom-right corner.
 | 
			
		||||
     * @const
 | 
			
		||||
     */
 | 
			
		||||
    (Blockly.BlockSvg as any).BOTTOM_RIGHT_CORNER =
 | 
			
		||||
        'l 0,' + (Blockly.BlockSvg as any).CORNER_RADIUS;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * SVG path for drawing the rounded bottom-right corner.
 | 
			
		||||
         * @const
 | 
			
		||||
         */
 | 
			
		||||
        (Blockly.BlockSvg as any).BOTTOM_RIGHT_CORNER =
 | 
			
		||||
            'l 0,' + (Blockly.BlockSvg as any).CORNER_RADIUS;
 | 
			
		||||
    /**
 | 
			
		||||
     * SVG path for drawing the rounded bottom-left corner.
 | 
			
		||||
     * @const
 | 
			
		||||
     */
 | 
			
		||||
    (Blockly.BlockSvg as any).BOTTOM_LEFT_CORNER =
 | 
			
		||||
        'l -' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0';
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * SVG path for drawing the rounded bottom-left corner.
 | 
			
		||||
         * @const
 | 
			
		||||
         */
 | 
			
		||||
        (Blockly.BlockSvg as any).BOTTOM_LEFT_CORNER =
 | 
			
		||||
            'l -' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0';
 | 
			
		||||
    /**
 | 
			
		||||
     * SVG path for drawing the top-left corner of a statement input.
 | 
			
		||||
     * @const
 | 
			
		||||
     */
 | 
			
		||||
    (Blockly.BlockSvg as any).INNER_TOP_LEFT_CORNER =
 | 
			
		||||
        'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',-' + 0;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * SVG path for drawing the top-left corner of a statement input.
 | 
			
		||||
         * @const
 | 
			
		||||
         */
 | 
			
		||||
        (Blockly.BlockSvg as any).INNER_TOP_LEFT_CORNER =
 | 
			
		||||
            'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',-' + 0;
 | 
			
		||||
    /**
 | 
			
		||||
     * SVG path for drawing the bottom-left corner of a statement input.
 | 
			
		||||
     * Includes the rounded inside corner.
 | 
			
		||||
     * @const
 | 
			
		||||
     */
 | 
			
		||||
    (Blockly.BlockSvg as any).INNER_BOTTOM_LEFT_CORNER =
 | 
			
		||||
        'l ' + 0 + ',' + (Blockly.BlockSvg as any).CORNER_RADIUS * 2 +
 | 
			
		||||
        'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',' + 0;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * SVG path for drawing the bottom-left corner of a statement input.
 | 
			
		||||
         * Includes the rounded inside corner.
 | 
			
		||||
         * @const
 | 
			
		||||
         */
 | 
			
		||||
        (Blockly.BlockSvg as any).INNER_BOTTOM_LEFT_CORNER =
 | 
			
		||||
            'l ' + 0 + ',' + (Blockly.BlockSvg as any).CORNER_RADIUS * 2 +
 | 
			
		||||
            'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',' + 0;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
 | 
			
		||||
        pxt.debug('loading pxt-ev3 target extensions...')
 | 
			
		||||
        updateBlocklyShape();
 | 
			
		||||
        const res: pxt.editor.ExtensionResult = {
 | 
			
		||||
            deployCoreAsync,
 | 
			
		||||
        };
 | 
			
		||||
        initAsync().catch(e => {
 | 
			
		||||
            // probably no HID - we'll try this again upon deployment
 | 
			
		||||
        })
 | 
			
		||||
        return Promise.resolve<pxt.editor.ExtensionResult>(res);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// When require()d from node, bind the global pxt namespace
 | 
			
		||||
// namespace pxt {
 | 
			
		||||
//     export const dummyExport = 1;
 | 
			
		||||
// }
 | 
			
		||||
// eval("if (typeof process === 'object' && process + '' === '[object process]') pxt = global.pxt")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										147
									
								
								editor/field_ports.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								editor/field_ports.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
/// <reference path="../node_modules/pxt-core/localtypings/blockly.d.ts"/>
 | 
			
		||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
 | 
			
		||||
 | 
			
		||||
export interface FieldPortsOptions extends Blockly.FieldCustomDropdownOptions {
 | 
			
		||||
    columns?: string;
 | 
			
		||||
    width?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class FieldPorts extends Blockly.FieldDropdown implements Blockly.FieldCustom {
 | 
			
		||||
    public isFieldCustom_ = true;
 | 
			
		||||
 | 
			
		||||
    // Width in pixels
 | 
			
		||||
    private width_: number;
 | 
			
		||||
 | 
			
		||||
    // Columns in grid
 | 
			
		||||
    private columns_: number;
 | 
			
		||||
 | 
			
		||||
    private savedPrimary_: string;
 | 
			
		||||
 | 
			
		||||
    constructor(text: string, options: FieldPortsOptions, validator?: Function) {
 | 
			
		||||
        super(options.data);
 | 
			
		||||
 | 
			
		||||
        this.columns_ = parseInt(options.columns) || 4;
 | 
			
		||||
        this.width_ = parseInt(options.width) || 300;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a dropdown menu under the text.
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    public showEditor_() {
 | 
			
		||||
        // If there is an existing drop-down we own, this is a request to hide the drop-down.
 | 
			
		||||
        if (Blockly.DropDownDiv.hideIfOwner(this)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // If there is an existing drop-down someone else owns, hide it immediately and clear it.
 | 
			
		||||
        Blockly.DropDownDiv.hideWithoutAnimation();
 | 
			
		||||
        Blockly.DropDownDiv.clearContent();
 | 
			
		||||
        // Populate the drop-down with the icons for this field.
 | 
			
		||||
        let dropdownDiv = Blockly.DropDownDiv.getContentDiv();
 | 
			
		||||
        let contentDiv = document.createElement('div');
 | 
			
		||||
        // Accessibility properties
 | 
			
		||||
        contentDiv.setAttribute('role', 'menu');
 | 
			
		||||
        contentDiv.setAttribute('aria-haspopup', 'true');
 | 
			
		||||
        const options = this.getOptions();
 | 
			
		||||
        for (let i = 0, option: any; option = options[i]; i++) {
 | 
			
		||||
            let content = (options[i] as any)[0]; // Human-readable text or image.
 | 
			
		||||
            const value = (options[i] as any)[1]; // Language-neutral value.
 | 
			
		||||
            // Icons with the type property placeholder take up space but don't have any functionality
 | 
			
		||||
            // Use for special-case layouts
 | 
			
		||||
            if (content.type == 'placeholder') {
 | 
			
		||||
                let placeholder = document.createElement('span');
 | 
			
		||||
                placeholder.setAttribute('class', 'blocklyDropDownPlaceholder');
 | 
			
		||||
                placeholder.style.width = content.width + 'px';
 | 
			
		||||
                placeholder.style.height = content.height + 'px';
 | 
			
		||||
                contentDiv.appendChild(placeholder);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            let button = document.createElement('button');
 | 
			
		||||
            button.setAttribute('id', ':' + i); // For aria-activedescendant
 | 
			
		||||
            button.setAttribute('role', 'menuitem');
 | 
			
		||||
            button.setAttribute('class', 'blocklyDropDownButton');
 | 
			
		||||
            button.title = content.alt;
 | 
			
		||||
            if (this.columns_) {
 | 
			
		||||
                button.style.width = ((this.width_ / this.columns_) - 8) + 'px';
 | 
			
		||||
                button.style.height = ((this.width_ / this.columns_) - 8) + 'px';
 | 
			
		||||
            } else {
 | 
			
		||||
                button.style.width = content.width + 'px';
 | 
			
		||||
                button.style.height = content.height + 'px';
 | 
			
		||||
            }
 | 
			
		||||
            let backgroundColor = this.sourceBlock_.getColour();
 | 
			
		||||
            if (value == this.getValue()) {
 | 
			
		||||
                // This icon is selected, show it in a different colour
 | 
			
		||||
                backgroundColor = this.sourceBlock_.getColourTertiary();
 | 
			
		||||
                button.setAttribute('aria-selected', 'true');
 | 
			
		||||
            }
 | 
			
		||||
            button.style.backgroundColor = backgroundColor;
 | 
			
		||||
            button.style.borderColor = this.sourceBlock_.getColourTertiary();
 | 
			
		||||
            Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
 | 
			
		||||
            Blockly.bindEvent_(button, 'mouseup', this, this.buttonClick_);
 | 
			
		||||
            // These are applied manually instead of using the :hover pseudoclass
 | 
			
		||||
            // because Android has a bad long press "helper" menu and green highlight
 | 
			
		||||
            // that we must prevent with ontouchstart preventDefault
 | 
			
		||||
            Blockly.bindEvent_(button, 'mousedown', button, function (e) {
 | 
			
		||||
                this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
            });
 | 
			
		||||
            Blockly.bindEvent_(button, 'mouseover', button, function () {
 | 
			
		||||
                this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
 | 
			
		||||
                contentDiv.setAttribute('aria-activedescendant', this.id);
 | 
			
		||||
            });
 | 
			
		||||
            Blockly.bindEvent_(button, 'mouseout', button, function () {
 | 
			
		||||
                this.setAttribute('class', 'blocklyDropDownButton');
 | 
			
		||||
                contentDiv.removeAttribute('aria-activedescendant');
 | 
			
		||||
            });
 | 
			
		||||
            let buttonImg = document.createElement('img');
 | 
			
		||||
            buttonImg.src = content.src;
 | 
			
		||||
            //buttonImg.alt = icon.alt;
 | 
			
		||||
            // Upon click/touch, we will be able to get the clicked element as e.target
 | 
			
		||||
            // Store a data attribute on all possible click targets so we can match it to the icon.
 | 
			
		||||
            button.setAttribute('data-value', value);
 | 
			
		||||
            buttonImg.setAttribute('data-value', value);
 | 
			
		||||
            button.appendChild(buttonImg);
 | 
			
		||||
            contentDiv.appendChild(button);
 | 
			
		||||
        }
 | 
			
		||||
        contentDiv.style.width = this.width_ + 'px';
 | 
			
		||||
        dropdownDiv.appendChild(contentDiv);
 | 
			
		||||
 | 
			
		||||
        Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), this.sourceBlock_.getColourTertiary());
 | 
			
		||||
 | 
			
		||||
        // Calculate positioning based on the field position.
 | 
			
		||||
        var scale = this.sourceBlock_.workspace.scale;
 | 
			
		||||
        var bBox = { width: this.size_.width, height: this.size_.height };
 | 
			
		||||
        bBox.width *= scale;
 | 
			
		||||
        bBox.height *= scale;
 | 
			
		||||
        var position = this.fieldGroup_.getBoundingClientRect();
 | 
			
		||||
        var primaryX = position.left + bBox.width / 2;
 | 
			
		||||
        var primaryY = position.top + bBox.height;
 | 
			
		||||
        var secondaryX = primaryX;
 | 
			
		||||
        var secondaryY = position.top;
 | 
			
		||||
        // Set bounds to workspace; show the drop-down.
 | 
			
		||||
        (Blockly.DropDownDiv as any).setBoundsElement(this.sourceBlock_.workspace.getParentSvg().parentNode);
 | 
			
		||||
        (Blockly.DropDownDiv as any).show(this, primaryX, primaryY, secondaryX, secondaryY,
 | 
			
		||||
            this.onHide_.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Callback for when a button is clicked inside the drop-down.
 | 
			
		||||
     * Should be bound to the FieldIconMenu.
 | 
			
		||||
     * @param {Event} e DOM event for the click/touch
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private buttonClick_ = function (e: any) {
 | 
			
		||||
        let value = e.target.getAttribute('data-value');
 | 
			
		||||
        this.setValue(value);
 | 
			
		||||
        Blockly.DropDownDiv.hide();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Callback for when the drop-down is hidden.
 | 
			
		||||
     */
 | 
			
		||||
    private onHide_ = function () {
 | 
			
		||||
        Blockly.DropDownDiv.content_.removeAttribute('role');
 | 
			
		||||
        Blockly.DropDownDiv.content_.removeAttribute('aria-haspopup');
 | 
			
		||||
        Blockly.DropDownDiv.content_.removeAttribute('aria-activedescendant');
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
{
 | 
			
		||||
    "compilerOptions": {
 | 
			
		||||
        "target": "es5",
 | 
			
		||||
        "noImplicitAny": true,
 | 
			
		||||
        "noImplicitAny": false,
 | 
			
		||||
        "noImplicitReturns": true,
 | 
			
		||||
        "declaration": true,
 | 
			
		||||
        "out": "../built/editor.js",
 | 
			
		||||
        "module": "commonjs",
 | 
			
		||||
        "outDir": "../built/editor",
 | 
			
		||||
        "rootDir": ".",
 | 
			
		||||
        "newLine": "LF",
 | 
			
		||||
        "sourceMap": false
 | 
			
		||||
        "sourceMap": false,
 | 
			
		||||
        "allowSyntheticDefaultImports": true,
 | 
			
		||||
        "declaration": true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -111,6 +111,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=colorOnColorDetected
 | 
			
		||||
        //% parts="colorsensor"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=100 blockGap=8
 | 
			
		||||
        //% group="Color Sensor"
 | 
			
		||||
        onColorDetected(color: ColorSensorColor, handler: () => void) {
 | 
			
		||||
@@ -130,6 +131,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=colorPauseForColorDetected
 | 
			
		||||
        //% parts="colorsensor"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=99 blockGap=8
 | 
			
		||||
        //% group="Color Sensor"
 | 
			
		||||
        pauseForColor(color: ColorSensorColor) {
 | 
			
		||||
@@ -149,6 +151,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=colorGetColor
 | 
			
		||||
        //% parts="colorsensor"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=99
 | 
			
		||||
        //% group="Color Sensor"
 | 
			
		||||
        color(): ColorSensorColor {
 | 
			
		||||
@@ -166,6 +169,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=colorOnLightChanged
 | 
			
		||||
        //% parts="colorsensor"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=89 blockGap=8
 | 
			
		||||
        //% group="Color Sensor"
 | 
			
		||||
        onLightChanged(mode: LightIntensityMode, condition: LightCondition, handler: () => void) {
 | 
			
		||||
@@ -182,6 +186,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=colorPauseForLight
 | 
			
		||||
        //% parts="colorsensor"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=88 blockGap=8
 | 
			
		||||
        //% group="Color Sensor"
 | 
			
		||||
        pauseForLight(mode: LightIntensityMode, condition: LightCondition) {
 | 
			
		||||
@@ -199,6 +204,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=colorLight
 | 
			
		||||
        //% parts="colorsensor"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=87
 | 
			
		||||
        //% group="Color Sensor"
 | 
			
		||||
        light(mode: LightIntensityMode) {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=gyroGetAngle
 | 
			
		||||
        //% parts="gyroscope"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=65 blockGap=8
 | 
			
		||||
        //% group="Gyro Sensor"
 | 
			
		||||
        angle(): number {
 | 
			
		||||
@@ -44,6 +45,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=gyroGetRate
 | 
			
		||||
        //% parts="gyroscope"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=65 blockGap=8        
 | 
			
		||||
        //% group="Gyro Sensor"
 | 
			
		||||
        rate(): number {
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=touchEvent block="on %sensor|%event"
 | 
			
		||||
        //% parts="touch"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=99 blockGap=8
 | 
			
		||||
        //% group="Touch Sensor"
 | 
			
		||||
        onEvent(ev: TouchSensorEvent, body: () => void) {
 | 
			
		||||
@@ -60,6 +61,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=touchWaitUntil block="pause until %sensor|%event"
 | 
			
		||||
        //% parts="touch"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=98 blockGap=8
 | 
			
		||||
        //% group="Touch Sensor"
 | 
			
		||||
        pauseUntil(ev: TouchSensorEvent) {
 | 
			
		||||
@@ -75,6 +77,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=touchIsPressed
 | 
			
		||||
        //% parts="touch"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=81
 | 
			
		||||
        //% group="Touch Sensor"
 | 
			
		||||
        isPressed() {
 | 
			
		||||
@@ -90,6 +93,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=touchWasPressed
 | 
			
		||||
        //% parts="touch"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=81 blockGap=8
 | 
			
		||||
        //% group="Touch Sensor"
 | 
			
		||||
        wasPressed() {
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ namespace sensors {
 | 
			
		||||
        //% block="on %sensor|%event"
 | 
			
		||||
        //% parts="ultrasonicsensor"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=100 blockGap=8
 | 
			
		||||
        //% group="Ultrasonic Sensor"
 | 
			
		||||
        onEvent(event: UltrasonicSensorEvent, handler: () => void) {
 | 
			
		||||
@@ -60,6 +61,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=ultrasonicWait
 | 
			
		||||
        //% parts="ultrasonicsensor"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=99 blockGap=8
 | 
			
		||||
        //% group="Ultrasonic Sensor"        
 | 
			
		||||
        pauseUntil(event: UltrasonicSensorEvent) {
 | 
			
		||||
@@ -75,6 +77,7 @@ namespace sensors {
 | 
			
		||||
        //% blockId=sonarGetDistance
 | 
			
		||||
        //% parts="ultrasonicsensor"
 | 
			
		||||
        //% blockNamespace=sensors
 | 
			
		||||
        //% sensor.fieldEditor="ports"
 | 
			
		||||
        //% weight=65
 | 
			
		||||
        //% group="Ultrasonic Sensor"     
 | 
			
		||||
        distance(): number {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user