Split editor extensions into multiple module loaded files. Re-using editor/deploy.ts for cmds. Fixing field ports
This commit is contained in:
parent
d6d8b0655b
commit
4445acce7a
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
|
||||||
require("./editor")
|
require("./editor/deploy")
|
||||||
|
|
||||||
declare namespace pxt.editor {
|
declare namespace pxt.editor {
|
||||||
function deployCoreAsync(resp: pxtc.CompileResult, disconnect?: boolean): Promise<void>;
|
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,246 +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
|
import { deployCoreAsync, initAsync } from "./deploy";
|
||||||
namespace pxt {
|
import { FieldPorts } from "./field_ports";
|
||||||
export const dummyExport = 1;
|
|
||||||
|
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;
|
* Update the shape of Blockly blocks with square corners
|
||||||
|
*/
|
||||||
export let ev3: Ev3Wrapper
|
function updateBlocklyShape() {
|
||||||
|
|
||||||
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
|
* Rounded corner radius.
|
||||||
|
* @const
|
||||||
*/
|
*/
|
||||||
function updateBlocklyShape() {
|
(Blockly.BlockSvg as any).CORNER_RADIUS = 0 * (Blockly.BlockSvg as any).GRID_UNIT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounded corner radius.
|
* Inner space between edge of statement input and notch.
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
(Blockly.BlockSvg as any).CORNER_RADIUS = 0 * (Blockly.BlockSvg as any).GRID_UNIT;
|
(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.
|
* SVG path for drawing next/previous notch from right to left.
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
(Blockly.BlockSvg as any).STATEMENT_INPUT_INNER_SPACE = 3 * (Blockly.BlockSvg as any).GRID_UNIT;
|
(Blockly.BlockSvg as any).NOTCH_PATH_RIGHT = (
|
||||||
/**
|
'l -8,8 ' +
|
||||||
* SVG path for drawing next/previous notch from left to right.
|
'h -16 ' +
|
||||||
* @const
|
'l -8,-8 '
|
||||||
*/
|
);
|
||||||
(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.
|
* SVG start point for drawing the top-left corner.
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
(Blockly.BlockSvg as any).NOTCH_PATH_RIGHT = (
|
(Blockly.BlockSvg as any).TOP_LEFT_CORNER_START =
|
||||||
'l -8,8 ' +
|
'm 0,' + 0;
|
||||||
'h -16 ' +
|
|
||||||
'l -8,-8 '
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SVG start point for drawing the top-left corner.
|
* SVG path for drawing the rounded top-left corner.
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
(Blockly.BlockSvg as any).TOP_LEFT_CORNER_START =
|
(Blockly.BlockSvg as any).TOP_LEFT_CORNER =
|
||||||
'm 0,' + 0;
|
'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0 ';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SVG path for drawing the rounded top-left corner.
|
* SVG path for drawing the rounded top-right corner.
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
(Blockly.BlockSvg as any).TOP_LEFT_CORNER =
|
(Blockly.BlockSvg as any).TOP_RIGHT_CORNER =
|
||||||
'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0 ';
|
'l ' + 0 + ',' + (Blockly.BlockSvg as any).CORNER_RADIUS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SVG path for drawing the rounded top-right corner.
|
* SVG path for drawing the rounded bottom-right corner.
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
(Blockly.BlockSvg as any).TOP_RIGHT_CORNER =
|
(Blockly.BlockSvg as any).BOTTOM_RIGHT_CORNER =
|
||||||
'l ' + 0 + ',' + (Blockly.BlockSvg as any).CORNER_RADIUS;
|
'l 0,' + (Blockly.BlockSvg as any).CORNER_RADIUS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SVG path for drawing the rounded bottom-right corner.
|
* SVG path for drawing the rounded bottom-left corner.
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
(Blockly.BlockSvg as any).BOTTOM_RIGHT_CORNER =
|
(Blockly.BlockSvg as any).BOTTOM_LEFT_CORNER =
|
||||||
'l 0,' + (Blockly.BlockSvg as any).CORNER_RADIUS;
|
'l -' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SVG path for drawing the rounded bottom-left corner.
|
* SVG path for drawing the top-left corner of a statement input.
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
(Blockly.BlockSvg as any).BOTTOM_LEFT_CORNER =
|
(Blockly.BlockSvg as any).INNER_TOP_LEFT_CORNER =
|
||||||
'l -' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',0';
|
'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',-' + 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SVG path for drawing the top-left corner of a statement input.
|
* SVG path for drawing the bottom-left corner of a statement input.
|
||||||
* @const
|
* Includes the rounded inside corner.
|
||||||
*/
|
* @const
|
||||||
(Blockly.BlockSvg as any).INNER_TOP_LEFT_CORNER =
|
*/
|
||||||
'l ' + (Blockly.BlockSvg as any).CORNER_RADIUS + ',-' + 0;
|
(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 = {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
@ -1,150 +1,147 @@
|
|||||||
/// <reference path="../node_modules/pxt-core/localtypings/blockly.d.ts" />
|
/// <reference path="../node_modules/pxt-core/localtypings/blockly.d.ts"/>
|
||||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||||
|
|
||||||
namespace pxt.editor {
|
export interface FieldPortsOptions extends Blockly.FieldCustomDropdownOptions {
|
||||||
|
columns?: string;
|
||||||
|
width?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface FieldPortsOptions extends Blockly.FieldCustomDropdownOptions {
|
export class FieldPorts extends Blockly.FieldDropdown implements Blockly.FieldCustom {
|
||||||
columns?: string;
|
public isFieldCustom_ = true;
|
||||||
width?: string;
|
|
||||||
|
// 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;
|
* Create a dropdown menu under the text.
|
||||||
|
* @private
|
||||||
// Width in pixels
|
*/
|
||||||
private width_: number;
|
public showEditor_() {
|
||||||
|
// If there is an existing drop-down we own, this is a request to hide the drop-down.
|
||||||
// Columns in grid
|
if (Blockly.DropDownDiv.hideIfOwner(this)) {
|
||||||
private columns_: number;
|
return;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
|
||||||
/**
|
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||||
* Create a dropdown menu under the text.
|
Blockly.DropDownDiv.clearContent();
|
||||||
* @private
|
// Populate the drop-down with the icons for this field.
|
||||||
*/
|
let dropdownDiv = Blockly.DropDownDiv.getContentDiv();
|
||||||
public showEditor_() {
|
let contentDiv = document.createElement('div');
|
||||||
// If there is an existing drop-down we own, this is a request to hide the drop-down.
|
// Accessibility properties
|
||||||
if (Blockly.DropDownDiv.hideIfOwner(this)) {
|
contentDiv.setAttribute('role', 'menu');
|
||||||
return;
|
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.
|
let button = document.createElement('button');
|
||||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
button.setAttribute('id', ':' + i); // For aria-activedescendant
|
||||||
Blockly.DropDownDiv.clearContent();
|
button.setAttribute('role', 'menuitem');
|
||||||
// Populate the drop-down with the icons for this field.
|
button.setAttribute('class', 'blocklyDropDownButton');
|
||||||
let dropdownDiv = Blockly.DropDownDiv.getContentDiv();
|
button.title = content.alt;
|
||||||
let contentDiv = document.createElement('div');
|
if (this.columns_) {
|
||||||
// Accessibility properties
|
button.style.width = ((this.width_ / this.columns_) - 8) + 'px';
|
||||||
contentDiv.setAttribute('role', 'menu');
|
button.style.height = ((this.width_ / this.columns_) - 8) + 'px';
|
||||||
contentDiv.setAttribute('aria-haspopup', 'true');
|
} else {
|
||||||
const options = this.getOptions();
|
button.style.width = content.width + 'px';
|
||||||
for (let i = 0, option: any; option = options[i]; i++) {
|
button.style.height = content.height + 'px';
|
||||||
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';
|
let backgroundColor = this.sourceBlock_.getColour();
|
||||||
dropdownDiv.appendChild(contentDiv);
|
if (value == this.getValue()) {
|
||||||
|
// This icon is selected, show it in a different colour
|
||||||
Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), this.sourceBlock_.getColourTertiary());
|
backgroundColor = this.sourceBlock_.getColourTertiary();
|
||||||
|
button.setAttribute('aria-selected', 'true');
|
||||||
// Calculate positioning based on the field position.
|
}
|
||||||
var scale = this.sourceBlock_.workspace.scale;
|
button.style.backgroundColor = backgroundColor;
|
||||||
var bBox = { width: this.size_.width, height: this.size_.height };
|
button.style.borderColor = this.sourceBlock_.getColourTertiary();
|
||||||
bBox.width *= scale;
|
Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
|
||||||
bBox.height *= scale;
|
Blockly.bindEvent_(button, 'mouseup', this, this.buttonClick_);
|
||||||
var position = this.fieldGroup_.getBoundingClientRect();
|
// These are applied manually instead of using the :hover pseudoclass
|
||||||
var primaryX = position.left + bBox.width / 2;
|
// because Android has a bad long press "helper" menu and green highlight
|
||||||
var primaryY = position.top + bBox.height;
|
// that we must prevent with ontouchstart preventDefault
|
||||||
var secondaryX = primaryX;
|
Blockly.bindEvent_(button, 'mousedown', button, function (e) {
|
||||||
var secondaryY = position.top;
|
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
|
||||||
// Set bounds to workspace; show the drop-down.
|
e.preventDefault();
|
||||||
(Blockly.DropDownDiv as any).setBoundsElement(this.sourceBlock_.workspace.getParentSvg().parentNode);
|
});
|
||||||
(Blockly.DropDownDiv as any).show(this, primaryX, primaryY, secondaryX, secondaryY,
|
Blockly.bindEvent_(button, 'mouseover', button, function () {
|
||||||
this.onHide_.bind(this));
|
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());
|
||||||
* 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();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
// Calculate positioning based on the field position.
|
||||||
* Callback for when the drop-down is hidden.
|
var scale = this.sourceBlock_.workspace.scale;
|
||||||
*/
|
var bBox = { width: this.size_.width, height: this.size_.height };
|
||||||
private onHide_ = function () {
|
bBox.width *= scale;
|
||||||
Blockly.DropDownDiv.content_.removeAttribute('role');
|
bBox.height *= scale;
|
||||||
Blockly.DropDownDiv.content_.removeAttribute('aria-haspopup');
|
var position = this.fieldGroup_.getBoundingClientRect();
|
||||||
Blockly.DropDownDiv.content_.removeAttribute('aria-activedescendant');
|
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": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": false,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"declaration": true,
|
"module": "commonjs",
|
||||||
"out": "../built/editor.js",
|
"outDir": "../built/editor",
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"newLine": "LF",
|
"newLine": "LF",
|
||||||
"sourceMap": false
|
"sourceMap": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"declaration": true
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user