diff --git a/cmds/cmds.ts b/cmds/cmds.ts index b999bf60..25adc56c 100644 --- a/cmds/cmds.ts +++ b/cmds/cmds.ts @@ -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; diff --git a/editor/deploy.ts b/editor/deploy.ts new file mode 100644 index 00000000..4a3f20b7 --- /dev/null +++ b/editor/deploy.ts @@ -0,0 +1,133 @@ +/// +/// + +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 +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) + }) +} diff --git a/editor/extension.ts b/editor/extension.ts index de1279e9..473cea7d 100644 --- a/editor/extension.ts +++ b/editor/extension.ts @@ -1,246 +1,116 @@ -/// +/// +/// -// 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.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(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 - 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.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(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") diff --git a/editor/field_ports.ts b/editor/field_ports.ts index 12f3bb15..58f00c1d 100644 --- a/editor/field_ports.ts +++ b/editor/field_ports.ts @@ -1,150 +1,147 @@ -/// +/// /// -namespace pxt.editor { +export interface FieldPortsOptions extends Blockly.FieldCustomDropdownOptions { + columns?: string; + width?: string; +} - 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; } - 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; } - - /** - * 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; } - // 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); + 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'; } - 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)); + 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); - /** - * 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(); - }; + Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), this.sourceBlock_.getColourTertiary()); - /** - * 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'); - }; + // 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'); + }; } \ No newline at end of file diff --git a/editor/tsconfig.json b/editor/tsconfig.json index de974d4b..35979843 100644 --- a/editor/tsconfig.json +++ b/editor/tsconfig.json @@ -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 } } \ No newline at end of file