diff --git a/editor/deploy.ts b/editor/deploy.ts
index 8695e340..c2ebf646 100644
--- a/editor/deploy.ts
+++ b/editor/deploy.ts
@@ -1,4 +1,4 @@
-///
+///
///
import HF2 = pxt.HF2;
@@ -14,6 +14,7 @@ export function debug() {
}
// Web Serial API https://wicg.github.io/serial/
+// https://www.npmjs.com/package/@types/web-bluetooth
// chromium bug https://bugs.chromium.org/p/chromium/issues/detail?id=884928
// Under experimental features in Chrome Desktop 77+
enum ParityType {
@@ -23,6 +24,7 @@ enum ParityType {
"mark",
"space"
}
+
declare interface SerialOptions {
baudRate?: number;
databits?: number;
@@ -34,15 +36,18 @@ declare interface SerialOptions {
xoff?: boolean;
xany?: boolean;
}
+
type SerialPortInfo = pxt.Map;
type SerialPortRequestOptions = any;
+
declare class SerialPort {
open(options?: SerialOptions): Promise;
close(): void;
readonly readable: any;
readonly writable: any;
- //getInfo(): SerialPortInfo;
+ // getInfo(): SerialPortInfo;
}
+
declare interface Serial extends EventTarget {
onconnect: any;
ondisconnect: any;
@@ -60,7 +65,11 @@ class WebSerialPackageIO implements pxt.packetio.PacketIO {
private _writer: any;
constructor(private port: SerialPort, private options: SerialOptions) {
- console.log(`serial: new io`)
+ console.log(`serial: New io`)
+ }
+
+ bufferSize(buffer: Uint8Array) {
+ return HF2.read16(buffer, 0) + 2;
}
async readSerialAsync() {
@@ -68,21 +77,28 @@ class WebSerialPackageIO implements pxt.packetio.PacketIO {
let buffer: Uint8Array;
const reader = this._reader;
while (reader === this._reader) { // will change if we recycle the connection
- const { done, value } = await this._reader.read()
+ const { done, value } = await this._reader.read();
if (!buffer) buffer = value;
else { // concat
- let tmp = new Uint8Array(buffer.length + value.byteLength)
- tmp.set(buffer, 0)
- tmp.set(value, buffer.length)
+ let tmp = new Uint8Array(buffer.length + value.byteLength);
+ tmp.set(buffer, 0);
+ tmp.set(value, buffer.length);
buffer = tmp;
}
if (buffer) {
- let size = HF2.read16(buffer, 0);
- if (buffer.length == size + 2) {
+ let size = this.bufferSize(buffer);
+ if (buffer.length == size) {
this.onData(new Uint8Array(buffer));
buffer = undefined;
+ } else if (buffer.length > size) {
+ console.warn(`Received larger bufer then command command: ${buffer.length} recieved but waiting for ${size}`);
+ let tmp = buffer.slice(0, size - 1);
+ this.onData(new Uint8Array(tmp));
+ tmp = buffer.slice(size, buffer.length - 1);
+ buffer = tmp;
+ console.debug(`Next buffer size: ${this.bufferSize(buffer)}`);
} else {
- console.warn("Incomplete command " + size);
+ console.warn(`Incomplete command: ${buffer.length} recieved but waiting for ${size}. Keep waiting...`);
}
}
}
@@ -93,11 +109,14 @@ class WebSerialPackageIO implements pxt.packetio.PacketIO {
}
static portIos: WebSerialPackageIO[] = [];
+
static async mkPacketIOAsync(): Promise {
const serial = (navigator).serial;
if (serial) {
try {
- const requestOptions: SerialPortRequestOptions = {};
+ const requestOptions: SerialPortRequestOptions = {
+ // filters: [{ usbVendorId: 0x0694, usbProductId: 0x0005 }],
+ };
const port = await serial.requestPort(requestOptions);
let io = WebSerialPackageIO.portIos.filter(i => i.port == port)[0];
@@ -111,7 +130,7 @@ class WebSerialPackageIO implements pxt.packetio.PacketIO {
}
return io;
} catch (e) {
- console.log(`connection error`, e)
+ console.log(`Connection error`, e)
}
}
throw new Error("could not open serial port");
@@ -123,7 +142,8 @@ class WebSerialPackageIO implements pxt.packetio.PacketIO {
}
private openAsync() {
- console.log(`serial: opening port`)
+ console.log(`serial: Opening port`);
+ // this.io.onConnectionChanged();
if (!!this._reader) return Promise.resolve();
this._reader = undefined;
this._writer = undefined;
@@ -189,6 +209,7 @@ function hf2Async() {
let useHID = false;
let useWebSerial = false;
+
export function initAsync(): Promise {
if (pxt.U.isNodeJS) {
// doesn't seem to work ATM
@@ -234,6 +255,7 @@ async function cleanupAsync() {
}
let initPromise: Promise
+
function initHidAsync() { // needs to run within a click handler
if (initPromise)
return initPromise
@@ -261,6 +283,7 @@ const rbfTemplate = `
4c45474f580000006d000100000000001c000000000000000e000000821b038405018130813e8053
74617274696e672e2e2e0084006080XX00448581644886488405018130813e80427965210084000a
`
+
export function deployCoreAsync(resp: pxtc.CompileResult) {
let filename = resp.downloadFileBaseName || "pxt"
filename = filename.replace(/^lego-/, "")
diff --git a/editor/extension.ts b/editor/extension.ts
index 866a173e..2fcfc1a2 100644
--- a/editor/extension.ts
+++ b/editor/extension.ts
@@ -1,8 +1,7 @@
///
-///
///
///
-///
+///
///
import { deployCoreAsync, initAsync } from "./deploy";
diff --git a/editor/wrap.ts b/editor/wrap.ts
index 630ddaa9..1702d589 100644
--- a/editor/wrap.ts
+++ b/editor/wrap.ts
@@ -110,14 +110,112 @@ export class Ev3Wrapper {
})
}
+ dumpInputCmd(buf : Uint8Array) {
+ log(`Reply size: ${HF2.read16(buf, 0)}, Message counter: ${HF2.read16(buf, 2)}, Reply type: ${buf[4]} (${buf[4] === 0x03 ? "System command reply OK" : buf[4] === 0x05 ? "System command reply ERROR" : "Unknown"})`);
+ switch (buf[6]) {
+ case 0x00:
+ log("Reply Status SUCCESS");
+ break;
+ case 0x01:
+ log("Reply Status UNKNOWN_HANDLE");
+ break;
+ case 0x02:
+ log("Reply Status HANDLE_NOT_READY");
+ break;
+ case 0x03:
+ log("Reply Status CORRUPT_FILE");
+ break;
+ case 0x04:
+ log("Reply Status NO_HANDLES_AVAILABLE");
+ break;
+ case 0x05:
+ log("Reply Status NO_PERMISSION");
+ break;
+ case 0x06:
+ log("Reply Status ILLEGAL_PATH");
+ break;
+ case 0x07:
+ log("Reply Status FILE_EXITS");
+ break;
+ case 0x08:
+ log("Reply Status END_OF_FILE");
+ break;
+ case 0x09:
+ log("Reply Status SIZE_ERROR");
+ break;
+ case 0x0A:
+ log("Reply Status UNKNOWN_ERROR");
+ break;
+ case 0x0B:
+ log("Reply Status ILLEGAL_FILENAME");
+ break;
+ case 0x0C:
+ log("Reply Status ILLEGAL_CONNECTION");
+ break;
+ }
+ }
+
+ dumpOutputCmd(buf: Uint8Array) {
+ log(`Command size: ${HF2.read16(buf, 0)}, Message counter: ${HF2.read16(buf, 2)}, Command type: ${buf[4]} (${buf[4] === 0x01 ? "System command, reply required" : buf[4] === 0x81 ? "System command, reply not required" : "Unknown"})`);
+ switch (buf[5]) {
+ case 0x92:
+ log("System command: Begin file download");
+ break;
+ case 0x93:
+ log("System command: Continue file download");
+ break;
+ case 0x94:
+ log("System command: Begin file upload");
+ break;
+ case 0x95:
+ log("System command: Continue file upload");
+ break;
+ case 0x96:
+ log("System command: Begin get bytes from a file (while writing to the file)");
+ break;
+ case 0x97:
+ log("System command: Continue get byte from a file (while writing to the file)");
+ break;
+ case 0x98:
+ log("System command: Close file handle");
+ break;
+ case 0x99:
+ log("System command: List files");
+ break;
+ case 0x9A:
+ log("System command: Continue list files");
+ break;
+ case 0x9B:
+ log("System command: Create directory");
+ break;
+ case 0x9C:
+ log("System command: Delete");
+ break;
+ case 0x9D:
+ log("System command: List handles");
+ break;
+ case 0x9E:
+ log("System command: Write to mailbox");
+ break;
+ case 0x9F:
+ log("System command: Transfer trusted pin code to brick");
+ break;
+ case 0xA0:
+ log("System command: Restart the brick in Firmware update mode");
+ break;
+ }
+ }
+
talkAsync(buf: Uint8Array, altResponse = 0) {
return this.lock.enqueue("talk", () => {
this.msgs.drain()
if (this.dataDump)
log("TALK: " + U.toHex(buf))
+ this.dumpOutputCmd(buf)
return this.io.sendPacketAsync(buf)
.then(() => this.msgs.shiftAsync(5000))
.then(resp => {
+ this.dumpInputCmd(resp)
if (resp[2] != buf[2] || resp[3] != buf[3])
U.userError("msg count de-sync")
if (buf[4] == 1) {
@@ -132,7 +230,7 @@ export class Ev3Wrapper {
}
flashAsync(path: string, file: Uint8Array) {
- log(`write ${file.length} bytes to ${path}`)
+ log(`Write ${file.length} bytes to ${path}`)
let handle = -1
@@ -274,13 +372,13 @@ export class Ev3Wrapper {
reconnectAsync(first = false): Promise {
this.resetState()
if (first) return this.initAsync()
- log(`reconnect`);
+ log(`Reconnect`);
return this.io.reconnectAsync()
.then(() => this.initAsync())
}
disconnectAsync() {
- log(`disconnect`);
+ log(`Disconnect`);
return this.io.disconnectAsync()
}
}
diff --git a/fieldeditors/extension.ts b/fieldeditors/extension.ts
index 10944a73..11c4dda5 100644
--- a/fieldeditors/extension.ts
+++ b/fieldeditors/extension.ts
@@ -1,20 +1,18 @@
-///
+///
///
-import { FieldPorts } from "./field_ports";
+const Blockly = pxt.blocks.requireBlockly();
+
import { FieldMotors } from "./field_motors";
import { FieldBrickButtons } from "./field_brickbuttons";
import { FieldColorEnum } from "./field_color";
import { FieldMusic } from "./field_music";
pxt.editor.initFieldExtensionsAsync = function (opts: pxt.editor.FieldExtensionOptions): Promise {
- pxt.debug('loading pxt-ev3 target extensions...')
+ pxt.debug('loading pxt-ev3 target extensions...');
updateBlocklyShape();
const res: pxt.editor.FieldExtensionResult = {
fieldEditors: [{
- selector: "ports",
- editor: FieldPorts
- }, {
selector: "motors",
editor: FieldMotors
}, {
@@ -132,10 +130,4 @@ function updateBlocklyShape() {
*/
(Blockly as any).Flyout.prototype.MARGIN = 8;
-}
-
-// 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")
+}
\ No newline at end of file
diff --git a/fieldeditors/field_brickbuttons.ts b/fieldeditors/field_brickbuttons.ts
index 647beaff..2d8c8108 100644
--- a/fieldeditors/field_brickbuttons.ts
+++ b/fieldeditors/field_brickbuttons.ts
@@ -1,26 +1,26 @@
-///
-///
+///
-export interface FieldPortsOptions extends Blockly.FieldCustomDropdownOptions {
+const Blockly = pxt.blocks.requireBlockly();
+
+export interface FieldBrickButtonsOptions {
+ data: string;
columns?: string;
width?: string;
}
-export class FieldBrickButtons extends Blockly.FieldDropdown implements Blockly.FieldCustom {
- public isFieldCustom_ = true;
+export class FieldBrickButtons extends Blockly.FieldDropdown {
- // Width in pixels
- private width_: number;
+ public isFieldCustom_ = true;
- // Columns in grid
- private columns_: number;
+ private width_: number; // Width in pixels
+ private columns_: number; // Columns in grid
private savedPrimary_: string;
- constructor(text: string, options: FieldPortsOptions, validator?: Function) {
- super(options.data);
-
- this.columns_ = parseInt(options.columns) || 4;
+ constructor(text: string, options: FieldBrickButtonsOptions, validator?: Function) {
+ super(options.data as any);
+
+ this.columns_ = parseInt(options.columns) || 1;
this.width_ = parseInt(options.width) || 150;
}
@@ -84,7 +84,7 @@ export class FieldBrickButtons extends Blockly.FieldDropdown implements Blockly.
d: 'M 157.78865,189.01028 108.18908,189.38233 70.654464,150.794 86.323259,135.4895 v -28.08625 h 14.101921 v 16.11144 a 12.006218,12.006218 0 0 0 11.85346,11.9788 c 11.59882,0.1841 43.13227,0 43.13227,0 a 10.18472,10.18472 0 0 0 10.38059,-10.38058 v -17.70966 h 14.39179 v 28.08632 l 15.3045,15.3045 z'
})
- const buttons = [buttonEnter, buttonLeft, buttonRight, buttonTop, buttonBottom];
+ const buttons = [buttonEnter, buttonLeft, buttonTop, buttonBottom, buttonRight];
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.
@@ -96,20 +96,24 @@ export class FieldBrickButtons extends Blockly.FieldDropdown implements Blockly.
const title = pxsim.svg.child(button, 'title');
title.textContent = content;
- Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
- Blockly.bindEvent_(button, 'mouseup', this, this.buttonClick_);
+ button.addEventListener("click", (event) => {
+ this.buttonClick_(event);
+ });
+ button.addEventListener("mouseup", (event) => {
+ this.buttonClick_(event);
+ });
// 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('stroke', '#ffffff');
- e.preventDefault();
+ button.addEventListener("mousedown", (event) => {
+ button.setAttribute('stroke', '#ffffff');
+ event.preventDefault();
});
- Blockly.bindEvent_(button, 'mouseover', button, function () {
- this.setAttribute('stroke', '#ffffff');
+ button.addEventListener("mouseover", (event) => {
+ button.setAttribute('stroke', '#ffffff');
});
- Blockly.bindEvent_(button, 'mouseout', button, function () {
- this.setAttribute('stroke', 'transparent');
+ button.addEventListener("mouseout", (event) => {
+ button.setAttribute('stroke', 'transparent');
});
button.setAttribute('data-value', value);
@@ -124,12 +128,12 @@ export class FieldBrickButtons extends Blockly.FieldDropdown implements Blockly.
Blockly.DropDownDiv.showPositionedByField(this, this.onHide_.bind(this));
// Update colour to look selected.
- let source = this.sourceBlock_ as Blockly.BlockSvg;
+ let source = this.sourceBlock_ as any;
this.savedPrimary_ = source?.getColour();
if (source?.isShadow()) {
source.setColour(source.getColourTertiary());
} else if (this.borderRect_) {
- this.borderRect_.setAttribute('fill', (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary());
+ this.borderRect_.setAttribute('fill', (this.sourceBlock_ as any).getColourTertiary());
}
}
@@ -155,7 +159,7 @@ export class FieldBrickButtons extends Blockly.FieldDropdown implements Blockly.
content.removeAttribute('aria-activedescendant');
(content as HTMLElement).style.width = '';
// Update color (deselect) on dropdown hide
- let source = this.sourceBlock_ as Blockly.BlockSvg;
+ let source = this.sourceBlock_ as any;
if (source?.isShadow()) {
source.setColour(this.savedPrimary_);
} else if (this.borderRect_) {
diff --git a/fieldeditors/field_color.ts b/fieldeditors/field_color.ts
index a7330a19..e29e105e 100644
--- a/fieldeditors/field_color.ts
+++ b/fieldeditors/field_color.ts
@@ -1,18 +1,24 @@
-///
-///
+///
-export interface FieldColorEnumOptions extends pxtblockly.FieldColourNumberOptions {
+const pxtblockly = pxt.blocks.requirePxtBlockly()
+const Blockly = pxt.blocks.requireBlockly();
+
+export interface FieldColorEnumOptions {
+ className?: string;
}
-export class FieldColorEnum extends pxtblockly.FieldColorNumber implements Blockly.FieldCustom {
+export class FieldColorEnum extends pxtblockly.FieldColorNumber {
public isFieldCustom_ = true;
+
private paramsData: any[];
+ private className_: string;
- constructor(text: string, params: FieldColorEnumOptions, opt_validator?: Function) {
- super(text, params, opt_validator);
+ constructor(text: string, params: FieldColorEnumOptions) {
+ super(text, params as any);
this.paramsData = params["data"];
+ this.className_ = params.className;
}
mapColour(enumString: string) {
@@ -44,6 +50,10 @@ export class FieldColorEnum extends pxtblockly.FieldColorNumber implements Block
showEditor_() {
super.showEditor_();
+ const picker = Blockly.DropDownDiv.getContentDiv().childNodes[0] as HTMLElement;
+ if (this.className_ && picker) {
+ pxt.BrowserUtils.addClass(picker as HTMLElement, this.className_);
+ }
const colorCells = document.querySelectorAll('.legoColorPicker td');
colorCells.forEach((cell) => {
const titleName = this.mapColour(cell.getAttribute("title"));
@@ -52,6 +62,20 @@ export class FieldColorEnum extends pxtblockly.FieldColorNumber implements Block
});
}
+ doValueUpdate_(colour: string) {
+ super.doValueUpdate_(colour);
+ this.applyColour();
+ }
+
+ applyColour() {
+ if (this.borderRect_) {
+ this.borderRect_.style.fill = this.value_;
+ } else if (this.sourceBlock_) {
+ (this.sourceBlock_ as any)?.pathObject?.svgPath?.setAttribute('fill', this.value_);
+ (this.sourceBlock_ as any)?.pathObject?.svgPath?.setAttribute('stroke', '#fff');
+ }
+ };
+
/**
* Return the current colour.
* @param {boolean} opt_asHex optional field if the returned value should be a hex
@@ -81,4 +105,5 @@ export class FieldColorEnum extends pxtblockly.FieldColorNumber implements Block
this.sourceBlock_.setColour(colour);
}
}
+
}
\ No newline at end of file
diff --git a/fieldeditors/field_motors.ts b/fieldeditors/field_motors.ts
index 069d7c30..f5170a19 100644
--- a/fieldeditors/field_motors.ts
+++ b/fieldeditors/field_motors.ts
@@ -1,14 +1,16 @@
-///
-///
-///
+///
-export interface FieldMotorsOptions extends pxtblockly.FieldImagesOptions {
+const pxtblockly = pxt.blocks.requirePxtBlockly()
+const Blockly = pxt.blocks.requireBlockly();
+
+export interface FieldMotorsOptions {
+ blocksInfo: any;
columns?: string;
width?: string;
//sort?: boolean;
}
-export class FieldMotors extends pxtblockly.FieldImages implements Blockly.FieldCustom {
+export class FieldMotors extends pxtblockly.FieldImages {
public isFieldCustom_ = true;
//public shouldSort_: boolean;
@@ -19,21 +21,105 @@ export class FieldMotors extends pxtblockly.FieldImages implements Blockly.Field
this.width_ = parseInt(options.width) || 400;
//this.shouldSort_ = options.sort;
this.addLabel_ = true;
+ }
- this.renderSelectedImage_ = Blockly.FieldDropdown.prototype.renderSelectedText_;
- this.updateSize_ = (Blockly.Field as any).prototype.updateSize_;
+ protected render_(): void {
+ if (this.addLabel_) {
+ this.renderSelectedText_()
+ this.positionBorderRect_();
+ }
+ else {
+ super.render_();
+ }
+ }
+
+ /** Renders the selected option, which must be text. */
+ protected renderSelectedText_() {
+ // Retrieves the selected option to display through getText_.
+ this.getTextContent().nodeValue = this.getDisplayText_();
+ const textElement = this.getTextElement();
+ Blockly.utils.dom.addClass(textElement, 'blocklyDropdownText');
+ textElement.setAttribute('text-anchor', 'start');
+
+ // Height and width include the border rect.
+ const hasBorder = !!this.borderRect_;
+ const height = Math.max(
+ hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
+ this.getConstants()!.FIELD_TEXT_HEIGHT,
+ );
+ const textWidth = Blockly.utils.dom.getFastTextWidth(
+ this.getTextElement(),
+ this.getConstants()!.FIELD_TEXT_FONTSIZE,
+ this.getConstants()!.FIELD_TEXT_FONTWEIGHT,
+ this.getConstants()!.FIELD_TEXT_FONTFAMILY,
+ );
+ const xPadding = hasBorder
+ ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
+ : 0;
+ let arrowWidth = 0;
+ if (this.getSvgArrow()) {
+ arrowWidth = this.positionSVGArrow_(
+ textWidth + xPadding,
+ height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2,
+ );
+ }
+ this.size_.width = textWidth + arrowWidth + xPadding * 2;
+ this.size_.height = height;
+
+ this.positionTextElement_(xPadding, textWidth);
+ }
+
+ positionSVGArrow_(x: number, y: number): number {
+ const svgArrow = this.getSvgArrow();
+ if (!svgArrow) {
+ return 0;
+ }
+ const block = this.getSourceBlock();
+ const hasBorder = !!this.borderRect_;
+ const xPadding = hasBorder
+ ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
+ : 0;
+ const textPadding = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_PADDING;
+ const svgArrowSize = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE;
+ const arrowX = block.RTL ? xPadding : x + textPadding;
+ svgArrow.setAttribute(
+ 'transform',
+ 'translate(' + arrowX + ',' + y + ')',
+ );
+ return svgArrowSize + textPadding;
+ }
+
+ // This hack exists because svgArrow is private in Blockly's field dropdown.
+ // It should always be the last image element in the field group
+ protected getSvgArrow() {
+ if (this.fieldGroup_) {
+ const children = this.fieldGroup_.children;
+
+ let lastImage: SVGImageElement;
+
+ for (let i = 0; i < children.length; i++) {
+ if (children.item(i).tagName.toLowerCase() === "image") {
+ lastImage = children.item(i) as SVGImageElement;
+ }
+ }
+
+ return lastImage;
+ }
+
+ return undefined;
}
/**
* Create a dropdown menu under the text.
* @private
*/
- public showEditor_() {
+ public showEditor_(e?: Event) {
+ this.setOpeningPointerCoords(e);
// If there is an existing drop-down we own, this is a request to hide the drop-down.
if (Blockly.DropDownDiv.hideIfOwner(this)) {
return;
}
- let sourceBlock = this.sourceBlock_ as Blockly.BlockSvg;
+ let sourceBlock = this.sourceBlock_ as any;
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
Blockly.DropDownDiv.hideWithoutAnimation();
Blockly.DropDownDiv.clearContent();
@@ -41,10 +127,15 @@ export class FieldMotors extends pxtblockly.FieldImages implements Blockly.Field
let dropdownDiv = Blockly.DropDownDiv.getContentDiv();
let contentDiv = document.createElement('div');
// Accessibility properties
+ dropdownDiv.setAttribute('class', 'blocklyDropDownMotorsContent');
contentDiv.setAttribute('role', 'menu');
contentDiv.setAttribute('aria-haspopup', 'true');
+ contentDiv.setAttribute('class', 'blocklyMotorsFieldOptions');
+ this.addPointerListener(dropdownDiv);
+ this.addKeyDownHandler(contentDiv);
const options = this.getOptions();
- //if (this.shouldSort_) options.sort();
+ // if (this.shouldSort_) options.sort();
+ let row = this.createRow();
for (let i = 0; i < options.length; i++) {
const content = (options[i] as any)[0]; // Human-readable text or image.
const value = (options[i] as any)[1]; // Language-neutral value.
@@ -58,9 +149,12 @@ export class FieldMotors extends pxtblockly.FieldImages implements Blockly.Field
contentDiv.appendChild(placeholder);
continue;
}
- let button = document.createElement('button');
+ const buttonContainer = document.createElement('div');
+ buttonContainer.setAttribute('class', 'blocklyDropDownButtonContainer');
+ let button = document.createElement('div');
button.setAttribute('id', ':' + i); // For aria-activedescendant
button.setAttribute('role', 'menuitem');
+ button.setAttribute('aria-selected', 'false');
button.setAttribute('class', 'blocklyDropDownButton');
button.title = content.alt;
if ((this as any).columns_) {
@@ -75,17 +169,27 @@ export class FieldMotors extends pxtblockly.FieldImages implements Blockly.Field
// This icon is selected, show it in a different colour
backgroundColor = sourceBlock.getColourTertiary();
button.setAttribute('aria-selected', 'true');
+ this.activeDescendantIndex = i;
+ contentDiv.setAttribute('aria-activedescendant', button.id);
+ button.setAttribute('class', `blocklyDropDownButton ${this.openingPointerCoords ? "blocklyDropDownButtonHover" : "blocklyDropDownButtonFocus"}`);
}
button.style.backgroundColor = backgroundColor;
button.style.borderColor = sourceBlock.getColourTertiary();
- Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
- Blockly.bindEvent_(button, 'mouseover', button, function () {
- this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
- contentDiv.setAttribute('aria-activedescendant', this.id);
+ Blockly.browserEvents.bind(button, 'click', this, () => this.buttonClickAndClose_(value));
+ Blockly.browserEvents.bind(button, 'pointermove', this, () => {
+ if (this.pointerMoveTriggeredByUser()) {
+ this.gridItems.forEach(button => button.setAttribute('class', 'blocklyDropDownButton'));
+ this.activeDescendantIndex = i;
+ button.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
+ contentDiv.setAttribute('aria-activedescendant', button.id);
+ }
});
- Blockly.bindEvent_(button, 'mouseout', button, function () {
- this.setAttribute('class', 'blocklyDropDownButton');
- contentDiv.removeAttribute('aria-activedescendant');
+ Blockly.browserEvents.bind(button, 'pointerout', this, () => {
+ if (this.pointerOutTriggeredByUser()) {
+ button.setAttribute('class', 'blocklyDropDownButton');
+ contentDiv.removeAttribute('aria-activedescendant');
+ this.activeDescendantIndex = undefined;
+ }
});
let buttonImg = document.createElement('img');
buttonImg.src = content.src;
@@ -94,21 +198,24 @@ export class FieldMotors extends pxtblockly.FieldImages implements Blockly.Field
// 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);
- buttonImg.style.height = 'auto';
button.appendChild(buttonImg);
if (this.addLabel_) {
const buttonText = this.createTextNode_(content.alt);
buttonText.setAttribute('data-value', value);
- buttonText.style.whiteSpace = 'inherit';
- buttonText.style.width = 'auto';
- buttonText.style.padding = '0 10px';
button.appendChild(buttonText);
}
- contentDiv.appendChild(button);
+ this.gridItems.push(button);
+ buttonContainer.appendChild(button);
+ row.append(buttonContainer);
+ if (row.childElementCount === this.columns_) {
+ contentDiv.appendChild(row);
+ row = this.createRow();
+ }
+ }
+ if (row.childElementCount) {
+ contentDiv.appendChild(row);
}
contentDiv.style.width = (this as any).width_ + 'px';
- contentDiv.style.display = 'flex';
- contentDiv.style.alignItems = 'stretch';
dropdownDiv.appendChild(contentDiv);
Blockly.DropDownDiv.setColour(sourceBlock.getColour(), sourceBlock.getColourTertiary());
@@ -116,6 +223,8 @@ export class FieldMotors extends pxtblockly.FieldImages implements Blockly.Field
// Position based on the field position.
Blockly.DropDownDiv.showPositionedByField(this, this.onHideCallback.bind(this));
+ contentDiv.focus();
+
// Update colour to look selected.
this.savedPrimary_ = sourceBlock?.getColour();
if (sourceBlock?.isShadow()) {
@@ -125,12 +234,4 @@ export class FieldMotors extends pxtblockly.FieldImages implements Blockly.Field
}
}
- trimOptions_() {
- }
-
- protected buttonClick_ = function (e: any) {
- let value = e.target.getAttribute('data-value');
- this.setValue(value);
- Blockly.DropDownDiv.hide();
- };
}
\ No newline at end of file
diff --git a/fieldeditors/field_music.ts b/fieldeditors/field_music.ts
index 2d98c61e..7eb62122 100644
--- a/fieldeditors/field_music.ts
+++ b/fieldeditors/field_music.ts
@@ -1,10 +1,12 @@
-///
-///
-///
+///
-export interface FieldMusicOptions extends pxtblockly.FieldImagesOptions {
+const pxtblockly = pxt.blocks.requirePxtBlockly()
+const Blockly = pxt.blocks.requireBlockly();
+
+export interface FieldMusicOptions {
columns?: string;
width?: string;
+ addLabel?: string;
}
declare const pxtTargetBundle: any;
@@ -13,20 +15,21 @@ let soundCache: any;
let soundIconCache: any;
let soundIconCacheArray: any;
-export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldCustom {
+export class FieldMusic extends pxtblockly.FieldImages {
+
public isFieldCustom_ = true;
private selectedCategory_: string;
-
private categoriesCache_: string[];
constructor(text: string, options: FieldMusicOptions, validator?: Function) {
- super(text, { blocksInfo: options.blocksInfo, sort: true, data: options.data }, validator);
+ super(text, options as any, validator);
this.columns_ = parseInt(options.columns) || 4;
this.width_ = parseInt(options.width) || 450;
- //this.setText = Blockly.FieldDropdown.prototype.setText;
+ this.addLabel_ = true;
+
this.updateSize_ = (Blockly.Field as any).prototype.updateSize_;
if (!pxt.BrowserUtils.isIE() && !soundCache) {
@@ -42,11 +45,13 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
* Create a dropdown menu under the text.
* @private
*/
- public showEditor_() {
+ public showEditor_(e?: Event) {
+ this.setOpeningPointerCoords(e);
// If there is an existing drop-down we own, this is a request to hide the drop-down.
if (Blockly.DropDownDiv.hideIfOwner(this)) {
return;
}
+ let sourceBlock = this.sourceBlock_ as any;
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
Blockly.DropDownDiv.hideWithoutAnimation();
Blockly.DropDownDiv.clearContent();
@@ -54,15 +59,15 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
let dropdownDiv = Blockly.DropDownDiv.getContentDiv() as HTMLElement;
let contentDiv = document.createElement('div');
// Accessibility properties
+ dropdownDiv.setAttribute('class', 'blocklyDropDownMusicContent');
contentDiv.setAttribute('role', 'menu');
contentDiv.setAttribute('aria-haspopup', 'true');
- contentDiv.className = 'blocklyMusicFieldOptions';
- contentDiv.style.display = "flex";
- contentDiv.style.flexWrap = "wrap";
- contentDiv.style.float = "none";
+ contentDiv.setAttribute('class', 'blocklyMusicFieldOptions');
+ this.addPointerListener(dropdownDiv);
+ this.addKeyDownHandler(contentDiv);
const options = this.getOptions();
- //options.sort(); // Do not need to use to not apply sorting in different languages
-
+ //if (this.shouldSort_) options.sort(); // Do not need to use to not apply sorting in different languages
+
// Create categoies
const categories = this.getCategories(options);
const selectedCategory = this.parseCategory(this.getText());
@@ -72,32 +77,29 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
// Accessibility properties
categoriesDiv.setAttribute('role', 'menu');
categoriesDiv.setAttribute('aria-haspopup', 'true');
- categoriesDiv.style.backgroundColor = (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary();
categoriesDiv.className = 'blocklyMusicFieldCategories';
+ categoriesDiv.style.backgroundColor = sourceBlock.getColourTertiary();
+ categoriesDiv.style.width = (this as any).width_ + 'px';
this.refreshCategories(categoriesDiv, categories);
-
this.refreshOptions(contentDiv, options);
- contentDiv.style.width = (this as any).width_ + 'px';
- contentDiv.style.cssFloat = 'left';
-
- (dropdownDiv as HTMLElement).style.maxHeight = `410px`;
dropdownDiv.appendChild(categoriesDiv);
dropdownDiv.appendChild(contentDiv);
- Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary());
+ Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), sourceBlock.getColourTertiary());
// Position based on the field position.
- Blockly.DropDownDiv.showPositionedByField(this, this.onHide_.bind(this));
+ Blockly.DropDownDiv.showPositionedByField(this, this.onHideCallback.bind(this));
+
+ contentDiv.focus();
// Update colour to look selected.
- let source = this.sourceBlock_ as Blockly.BlockSvg;
- this.savedPrimary_ = source?.getColour();
- if (source?.isShadow()) {
- source.setColour(source.getColourTertiary());
+ this.savedPrimary_ = sourceBlock?.getColour();
+ if (sourceBlock?.isShadow()) {
+ sourceBlock.setColour(sourceBlock.style.colourTertiary);
} else if (this.borderRect_) {
- this.borderRect_.setAttribute('fill', (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary());
+ this.borderRect_.setAttribute('fill', sourceBlock.style.colourTertiary);
}
}
@@ -133,8 +135,8 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
button.style.padding = "2px 6px";
button.style.backgroundColor = backgroundColor;
button.style.borderColor = backgroundColor;
- Blockly.bindEvent_(button, 'click', this, this.categoryClick_);
- Blockly.bindEvent_(button, 'mouseup', this, this.categoryClick_);
+ Blockly.browserEvents.bind(button, 'click', this, this.categoryClick_);
+ Blockly.browserEvents.bind(button, 'mouseup', this, this.categoryClick_);
const textNode = this.createTextNode_(category);
textNode.setAttribute('data-value', category);
@@ -144,10 +146,12 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
}
refreshOptions(contentDiv: Element, options: any) {
+ let sourceBlock = this.sourceBlock_ as any;
const categories = this.getCategories(options);
+ let row = this.createRow();
// Show options
for (let i = 0, option: any; option = options[i]; i++) {
- let content = (options[i] as any)[0]; // Human-readable text or image.
+ const content = (options[i] as any)[0]; // Human-readable text or image.
const value = (options[i] as any)[1]; // Language-neutral value.
// Filter for options in selected category
@@ -164,9 +168,12 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
contentDiv.appendChild(placeholder);
continue;
}
- let button = document.createElement('button');
+ const buttonContainer = document.createElement('div');
+ buttonContainer.setAttribute('class', 'blocklyDropDownButtonContainer')
+ let button = document.createElement('div');
button.setAttribute('id', ':' + i); // For aria-activedescendant
- button.setAttribute('role', 'menuitem');
+ button.setAttribute('role', 'gridcell');
+ button.setAttribute('aria-selected', 'false');
button.setAttribute('class', 'blocklyDropDownButton');
button.title = content;
if ((this as any).columns_) {
@@ -176,37 +183,34 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
button.style.width = content.width + 'px';
button.style.height = content.height + 'px';
}
- let backgroundColor = this.savedPrimary_ || this.sourceBlock_.getColour();
+ let backgroundColor = this.savedPrimary_ || sourceBlock.getColour();
if (value == this.getValue()) {
// This icon is selected, show it in a different colour
- backgroundColor = (this.sourceBlock_ as Blockly.BlockSvg).getColourTertiary();
+ backgroundColor = sourceBlock.getColourTertiary();
button.setAttribute('aria-selected', 'true');
+ this.activeDescendantIndex = i;
+ contentDiv.setAttribute('aria-activedescendant', button.id);
+ button.setAttribute('class', `blocklyDropDownButton ${this.openingPointerCoords ? "blocklyDropDownButtonHover" : "blocklyDropDownButtonFocus"}`);
}
button.style.backgroundColor = backgroundColor;
- button.style.borderColor = (this.sourceBlock_ as Blockly.BlockSvg).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
- let that = this;
- Blockly.bindEvent_(button, 'mousedown', button, function (e) {
- this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
- e.preventDefault();
- });
- Blockly.bindEvent_(button, 'mouseenter', button, function () {
- that.buttonEnter_(value);
- });
- Blockly.bindEvent_(button, 'mouseleave', button, function () {
- that.buttonLeave_();
- });
- Blockly.bindEvent_(button, 'mouseover', button, function () {
- this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
- contentDiv.setAttribute('aria-activedescendant', this.id);
+ button.style.borderColor = sourceBlock.getColourTertiary();
+ Blockly.browserEvents.bind(button, 'click', this, () => this.buttonClickAndClose_(value));
+ Blockly.browserEvents.bind(button, 'mouseenter', button, () => this.buttonEnter_(value));
+ Blockly.browserEvents.bind(button, 'mouseleave', button, () => this.buttonLeave_());
+ Blockly.browserEvents.bind(button, 'pointermove', this, () => {
+ if (this.pointerMoveTriggeredByUser()) {
+ this.gridItems.forEach(button => button.setAttribute('class', 'blocklyDropDownButton'));
+ this.activeDescendantIndex = i;
+ button.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
+ contentDiv.setAttribute('aria-activedescendant', button.id);
+ }
});
- Blockly.bindEvent_(button, 'mouseout', button, function () {
- this.setAttribute('class', 'blocklyDropDownButton');
- contentDiv.removeAttribute('aria-activedescendant');
+ Blockly.browserEvents.bind(button, 'pointerout', this, () => {
+ if (this.pointerOutTriggeredByUser()) {
+ button.setAttribute('class', 'blocklyDropDownButton');
+ contentDiv.removeAttribute('aria-activedescendant');
+ this.activeDescendantIndex = undefined;
+ }
});
// Find index in array by category name
@@ -214,29 +218,33 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
let buttonImg = document.createElement('img');
buttonImg.src = this.getSoundIcon(categoryIndex);
-
+ //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.
- const textNode = this.createTextNode_(content);
button.setAttribute('data-value', value);
buttonImg.setAttribute('data-value', value);
- buttonImg.style.height = "auto";
- textNode.setAttribute('data-value', value);
- if (pxt.Util.userLanguage() !== "en") textNode.setAttribute('lang', pxt.Util.userLanguage()); // for hyphens, here you need to set the correct abbreviation of the selected language
- textNode.style.display = "block";
- textNode.style.lineHeight = "1rem";
- textNode.style.marginBottom = "5%";
- textNode.style.padding = "0px 8px";
- textNode.style.wordBreak = "break-word";
- textNode.style.hyphens = "auto";
-
button.appendChild(buttonImg);
- button.appendChild(textNode);
- contentDiv.appendChild(button);
- }
- }
- trimOptions_() {
+ if (this.addLabel_) {
+ const buttonText = this.createTextNode_(content);
+ buttonText.setAttribute('data-value', value);
+ if (pxt.Util.userLanguage() !== "en") {
+ buttonText.setAttribute('lang', pxt.Util.userLanguage()); // for hyphens, here you need to set the correct abbreviation of the selected language
+ }
+ button.appendChild(buttonText);
+ }
+
+ this.gridItems.push(button);
+ buttonContainer.appendChild(button);
+ row.append(buttonContainer);
+ if (row.childElementCount === this.columns_) {
+ contentDiv.appendChild(row);
+ row = this.createRow();
+ }
+ }
+ if (row.childElementCount) {
+ contentDiv.appendChild(row);
+ }
}
protected onHide_() {
@@ -244,7 +252,7 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
(Blockly.DropDownDiv.getContentDiv() as HTMLElement).style.maxHeight = '';
this.stopSounds();
// Update color (deselect) on dropdown hide
- let source = this.sourceBlock_ as Blockly.BlockSvg;
+ let source = this.sourceBlock_ as any;
if (source?.isShadow()) {
source.setColour(this.savedPrimary_);
} else if (this.borderRect_) {
@@ -254,7 +262,7 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
protected createTextNode_(content: string) {
const category = this.parseCategory(content);
- let text = content.substr(content.indexOf(' ') + 1);
+ let text = content.slice(content.indexOf(' ') + 1);
const textSpan = document.createElement('span');
textSpan.setAttribute('class', 'blocklyDropdownText');
@@ -263,20 +271,14 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
}
private parseCategory(content: string) {
- return content.substr(0, content.indexOf(' '));
+ return content.slice(0, content.indexOf(' '));
}
- protected buttonClick_ = function (e: any) {
- let value = e.target.getAttribute('data-value');
- this.setValue(value);
- Blockly.DropDownDiv.hide();
- };
-
private setSelectedCategory(value: string) {
this.selectedCategory_ = value;
}
- protected categoryClick_ = function (e: any) {
+ protected categoryClick_(e: any) {
let value = e.target.getAttribute('data-value');
this.setSelectedCategory(value);
@@ -312,7 +314,7 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
}
pxsim.AudioContextManager.playBufferAsync(refBuf as any);
}
- }
+ }
};
protected buttonLeave_ = function () {
@@ -329,4 +331,4 @@ export class FieldMusic extends pxtblockly.FieldImages implements Blockly.FieldC
}
return undefined;
}
-}
+}
\ No newline at end of file
diff --git a/fieldeditors/field_ports.ts b/fieldeditors/field_ports.ts
deleted file mode 100644
index f3035dc0..00000000
--- a/fieldeditors/field_ports.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-///
-///
-///
-
-export interface FieldPortsOptions extends pxtblockly.FieldImagesOptions {
- columns?: string;
- width?: string;
-}
-
-export class FieldPorts extends pxtblockly.FieldImages implements Blockly.FieldCustom {
- public isFieldCustom_ = true;
-
- constructor(text: string, options: FieldPortsOptions, validator?: Function) {
- super(text, { blocksInfo: options.blocksInfo, sort: true, data: options.data }, validator);
-
- this.columns_ = parseInt(options.columns) || 4;
- this.width_ = parseInt(options.width) || 300;
-
- //this.setText = Blockly.FieldDropdown.prototype.setText;
- this.updateSize_ = (Blockly.Field as any).prototype.updateSize_;
- }
-
- trimOptions_() {
- }
-
- protected buttonClick_ = function (e: any) {
- let value = e.target.getAttribute('data-value');
- this.setValue(value);
- Blockly.DropDownDiv.hide();
- };
-}
\ No newline at end of file
diff --git a/libs/color-sensor/color.ts b/libs/color-sensor/color.ts
index 6894e349..ff25629f 100644
--- a/libs/color-sensor/color.ts
+++ b/libs/color-sensor/color.ts
@@ -146,7 +146,9 @@ namespace sensors {
//% blockId=colorOnColorDetected
//% parts="colorsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=100 blockGap=8
//% group="Color Sensor"
onColorDetected(color: number, handler: () => void) {
@@ -166,7 +168,9 @@ namespace sensors {
//% blockId=colorpauseUntilColorDetectedDetected
//% parts="colorsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=98 blockGap=8
//% group="Color Sensor"
pauseUntilColorDetected(color: number) {
@@ -186,7 +190,9 @@ namespace sensors {
//% blockId=colorGetColor
//% parts="colorsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=97 blockGap=8
//% group="Color Sensor"
color(): ColorSensorColor {
@@ -204,7 +210,9 @@ namespace sensors {
//% blockId=colorisColorDetectedDetected
//% parts="colorsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=99 blockGap=8
//% group="Color Sensor"
isColorDetected(color: number) {
@@ -219,7 +227,9 @@ namespace sensors {
//% blockId=colorRgbRaw block="**color sensor** %this| RGB raw"
//% parts="colorsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=89
//% group="Color Sensor"
rgbRaw(): number[] {
@@ -238,7 +248,9 @@ namespace sensors {
//% blockId=colorOnLightDetected
//% parts="colorsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=79 blockGap=8
//% group="Color Sensor"
onLightDetected(mode: LightIntensityMode, condition: Light, handler: () => void) {
@@ -255,7 +267,9 @@ namespace sensors {
//% blockId=colorPauseUntilLightDetected
//% parts="colorsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=78 blockGap=8
//% group="Color Sensor"
pauseUntilLightDetected(mode: LightIntensityMode, condition: Light) {
@@ -273,7 +287,9 @@ namespace sensors {
//% blockId=colorLight
//% parts="colorsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=77
//% group="Color Sensor"
light(mode: LightIntensityMode) {
@@ -319,7 +335,9 @@ namespace sensors {
//% help=sensors/color-sensor/set-threshold
//% blockId=colorSetThreshold block="set **color sensor** %this|%condition|to %value"
//% value.min=0 value.max=100
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=67 blockGap=8
//% group="Color Sensor"
setThreshold(condition: Light, value: number) {
@@ -340,7 +358,9 @@ namespace sensors {
*/
//% help=sensors/color-sensor/threshold
//% blockId=colorGetThreshold block="**color sensor** %this|%condition"
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=68 blockGap=8
//% group="Color Sensor"
threshold(condition: Light): number {
@@ -357,7 +377,9 @@ namespace sensors {
*/
//% help=sensors/color-sensor/calibrate-light
//% blockId=colorCalibrateLight block="calibrate **color sensor** %this|for %mode"
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=69 blockGap=8
//% group="Color Sensor"
calibrateLight(mode: LightIntensityMode, deviation: number = 8) {
diff --git a/libs/color-sensor/ns.ts b/libs/color-sensor/ns.ts
index 7bf4f847..b9323843 100644
--- a/libs/color-sensor/ns.ts
+++ b/libs/color-sensor/ns.ts
@@ -5,11 +5,12 @@ namespace sensors {
* @param color to use, eg: ColorSensorColor.Blue
*/
//% blockId=colorEnumPicker block="%color" shim=TD_ID
- //% weight=0 blockHidden=1 turnRatio.fieldOptions.decompileLiterals=1
+ //% weight=0 blockHidden=1
//% color.fieldEditor="colorenum"
//% color.fieldOptions.colours='["#f12a21", "#ffd01b", "#006db3", "#00934b", "#000000", "#6c2d00", "#ffffff", "#dfe6e9"]'
- //% color.fieldOptions.columns=2 color.fieldOptions.className='legoColorPicker'
+ //% color.fieldOptions.columns=2
+ //% color.fieldOptions.className='legoColorPicker'
export function __colorEnumPicker(color: ColorSensorColor): number {
return color;
}
-}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/libs/core/input.ts b/libs/core/input.ts
index b25abe34..ed02c7c3 100644
--- a/libs/core/input.ts
+++ b/libs/core/input.ts
@@ -359,11 +359,13 @@ namespace sensors.internal {
protected mode: number; // the mode user asked for
protected realmode: number;
+ protected undetectable: boolean; // not all NXT analog sensors can be detected
constructor(port: number) {
super(port);
this.mode = 0;
this.realmode = 0;
+ this.undetectable = false;
}
_activated() {
@@ -376,14 +378,14 @@ namespace sensors.internal {
this.mode = v;
if (!this.isActive()) return;
if (this.realmode != this.mode) {
- control.dmesg(`_setMode p=${this._port} m: ${this.realmode} -> ${v}`);
+ // control.dmesg(`_setMode p=${this._port} m: ${this.realmode} -> ${v}`);
this.realmode = v;
setAnalogMode(this._port, this._deviceType(), this.mode);
}
}
_readPin1() {
- if (!this.isActive()) return 0;
+ if (!this.undetectable && !this.isActive()) return 0;
return analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.InPin1 + 2 * this._port);
}
diff --git a/libs/core/output.ts b/libs/core/output.ts
index c313db4b..c7190452 100644
--- a/libs/core/output.ts
+++ b/libs/core/output.ts
@@ -194,10 +194,11 @@ namespace motors {
* Sets the automatic brake on or off when the motor is off
* @param brake a value indicating if the motor should break when off
*/
- //% blockId=outputMotorSetBrakeMode block="set %motor|brake %brake=toggleOnOff"
+ //% blockId=outputMotorSetBrakeMode block="set %motor|brake $brake"
//% motor.fieldEditor="motors"
//% motor.fieldOptions.decompileLiterals=1
//% weight=60 blockGap=8
+ //% brake.shadow="toggleOnOff"
//% group="Properties"
//% help=motors/motor/set-brake
setBrake(brake: boolean) {
@@ -207,30 +208,32 @@ namespace motors {
/**
* Indicates to pause while a motor moves for a given distance or duration.
- * @param value true to pause; false to continue the program execution
+ * @param brake true to pause; false to continue the program execution
*/
- //% blockId=outputMotorSetPauseMode block="set %motor|pause on run %brake=toggleOnOff"
+ //% blockId=outputMotorSetPauseMode block="set %motor|pause on run $brake"
//% motor.fieldEditor="motors"
//% motor.fieldOptions.decompileLiterals=1
+ //% brake.shadow="toggleOnOff"
//% weight=60 blockGap=8
//% group="Properties"
- setPauseOnRun(value: boolean) {
+ setPauseOnRun(brake: boolean) {
this.init();
- this._pauseOnRun = value;
+ this._pauseOnRun = brake;
}
/**
* Inverts the motor polarity
*/
- //% blockId=motorSetInverted block="set %motor|inverted %reversed=toggleOnOff"
+ //% blockId=motorSetInverted block="set %motor|inverted $reversed"
//% motor.fieldEditor="motors"
//% motor.fieldOptions.decompileLiterals=1
//% weight=59 blockGap=8
+ //% reversed.shadow="toggleOnOff"
//% group="Properties"
//% help=motors/motor/set-inverted
- setInverted(inverted: boolean) {
+ setInverted(reversed: boolean) {
this.init();
- this._inverted = inverted;
+ this._inverted = reversed;
}
protected invertedFactor(): number {
@@ -240,7 +243,7 @@ namespace motors {
/**
* Set the settle time after braking in milliseconds (default is 10ms).
*/
- //% blockId=motorSetBrakeSettleTime block="set %motor|brake settle time %millis|ms"
+ //% blockId=motorSetBrakeSettleTime block="set %motor|brake settle time $millis|ms"
//% motor.fieldEditor="motors"
//% motor.fieldOptions.decompileLiterals=1
//% weight=1 blockGap=8
@@ -344,11 +347,12 @@ namespace motors {
* @param value (optional) measured distance or rotation
* @param unit (optional) unit of the value
*/
- //% blockId=motorRun block="run %motor at %speed=motorSpeedPicker|\\%||for %value %unit"
+ //% blockId=motorRun block="run %motor at $speed|\\%||for $value $unit"
//% weight=100 blockGap=8
//% group="Move"
//% motor.fieldEditor="motors"
//% motor.fieldOptions.decompileLiterals=1
+ //% speed.shadow="motorSpeedPicker"
//% expandableArgumentMode=toggle
//% help=motors/motor/run
run(speed: number, value: number = 0, unit: MoveUnit = MoveUnit.MilliSeconds) {
@@ -394,11 +398,12 @@ namespace motors {
* @param acceleration acceleration phase measured distance or rotation, eg: 500
* @param deceleration deceleration phase measured distance or rotation, eg: 500
*/
- //% blockId=motorSchedule block="ramp %motor at %speed=motorSpeedPicker|\\%|for %value|%unit||accelerate %acceleration|decelerate %deceleration"
+ //% blockId=motorSchedule block="ramp %motor at $speed|\\%|for $value|$unit||accelerate $acceleration|decelerate $deceleration"
//% weight=99 blockGap=8
//% group="Move"
//% motor.fieldEditor="motors"
//% motor.fieldOptions.decompileLiterals=1
+ //% speed.shadow="motorSpeedPicker"
//% help=motors/motor/ramp
//% inlineInputMode=inline
//% expandableArgumentMode=toggle
@@ -498,9 +503,10 @@ namespace motors {
* Indicates if the motor(s) speed should be regulated. Default is true.
* @param value true for regulated motor
*/
- //% blockId=outputMotorSetRegulated block="set %motor|regulated %value=toggleOnOff"
+ //% blockId=outputMotorSetRegulated block="set %motor|regulated $value"
//% motor.fieldEditor="motors"
//% motor.fieldOptions.decompileLiterals=1
+ //% value.shadow="toggleOnOff"
//% weight=58 blockGap=8
//% group="Properties"
//% help=motors/motor/set-regulated
@@ -524,11 +530,12 @@ namespace motors {
* Pauses the execution until the previous command finished.
* @param timeOut optional maximum pausing time in milliseconds
*/
- //% blockId=motorPauseUntilRead block="pause until %motor|ready"
+ //% blockId=motorPauseUntilRead block="pause until %motor|ready||timeout $timeOut"
//% motor.fieldEditor="motors"
//% motor.fieldOptions.decompileLiterals=1
//% weight=90 blockGap=8
//% group="Move"
+ //% expandableArgumentMode="toggle"
pauseUntilReady(timeOut?: number) {
pauseUntil(() => this.isReady(), timeOut);
}
@@ -642,12 +649,14 @@ namespace motors {
/**
* Pauses the program until the motor is stalled.
+ * @param timeOut optional maximum pausing time in milliseconds
*/
- //% blockId=motorPauseUntilStall block="pause until %motor|stalled"
+ //% blockId=motorPauseUntilStall block="pause until %motor|stalled||timeout $timeOut"
//% motor.fieldEditor="motors"
//% motor.fieldOptions.decompileLiterals=1
//% weight=89
//% group="Move"
+ //% expandableArgumentMode="toggle"
//% help=motors/motor/pause-until-stalled
pauseUntilStalled(timeOut?: number): void {
// let it start
@@ -720,10 +729,12 @@ namespace motors {
* @param value (optional) move duration or rotation
* @param unit (optional) unit of the value
*/
- //% blockId=motorPairTank block="tank **motors** %motors %speedLeft=motorSpeedPicker|\\% %speedRight=motorSpeedPicker|\\%||for %value %unit"
+ //% blockId=motorPairTank block="tank **motors** %motors $speedLeft|\\% $speedRight|\\%||for $value $unit"
//% motors.fieldEditor="motors"
//% weight=96 blockGap=8
//% inlineInputMode=inline
+ //% speedLeft.shadow="motorSpeedPicker"
+ //% speedRight.shadow="motorSpeedPicker"
//% group="Move"
//% expandableArgumentMode=toggle
//% help=motors/synced/tank
@@ -750,10 +761,12 @@ namespace motors {
* @param value (optional) move duration or rotation
* @param unit (optional) unit of the value
*/
- //% blockId=motorPairSteer block="steer **motors** %chassis turn ratio %turnRatio=motorTurnRatioPicker speed %speed=motorSpeedPicker|\\%||for %value %unit"
+ //% blockId=motorPairSteer block="steer **motors** %chassis turn ratio $turnRatio speed $speed|\\%||for $value $unit"
//% chassis.fieldEditor="motors"
//% weight=95
+ //% turnRatio.shadow="motorTurnRatioPicker"
//% turnRatio.min=-200 turnRatio=200
+ //% speed.shadow="motorSpeedPicker"
//% inlineInputMode=inline
//% group="Move"
//% expandableArgumentMode=toggle
diff --git a/libs/gyro-sensor/gyro.ts b/libs/gyro-sensor/gyro.ts
index 0ee4628b..bc3c9691 100644
--- a/libs/gyro-sensor/gyro.ts
+++ b/libs/gyro-sensor/gyro.ts
@@ -46,7 +46,9 @@ namespace sensors {
//% blockId=gyroGetAngle
//% parts="gyroscope"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=100 blockGap=8
//% group="Gyro Sensor"
angle(): number {
@@ -66,7 +68,9 @@ namespace sensors {
//% blockId=gyroGetRate
//% parts="gyroscope"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=99 blockGap=8
//% group="Gyro Sensor"
rate(): number {
@@ -85,7 +89,9 @@ namespace sensors {
//% blockId=gyroCalibrate
//% parts="gyroscope"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=89 blockGap=8
//% group="Gyro Sensor"
calibrate(): void {
@@ -156,7 +162,9 @@ namespace sensors {
//% blockId=gyroReset
//% parts="gyroscope"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=88
//% group="Gyro Sensor"
reset(): void {
@@ -201,7 +209,9 @@ namespace sensors {
//% blockId=gyroDrift
//% parts="gyroscope"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=78 blockGap=8
//% group="Gyro Sensor"
drift(): number {
@@ -216,7 +226,9 @@ namespace sensors {
//% blockId=gyroComputeDrift
//% parts="gyroscope"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=79 blockGap=8
//% group="Gyro Sensor"
computeDrift() {
@@ -236,7 +248,9 @@ namespace sensors {
//% blockId=gyroPauseUntilRotated
//% parts="gyroscope"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% degrees.defl=90
//% weight=98
//% group="Gyro Sensor"
diff --git a/libs/infrared-sensor/ir.ts b/libs/infrared-sensor/ir.ts
index d69ce106..ea2b0691 100644
--- a/libs/infrared-sensor/ir.ts
+++ b/libs/infrared-sensor/ir.ts
@@ -200,7 +200,9 @@ namespace sensors {
//% blockNamespace=sensors
//% weight=100 blockGap=8
//% group="Infrared Sensor"
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
onEvent(event: InfraredSensorEvent, handler: () => void) {
this._setMode(InfraredSensorMode.Proximity)
control.onEvent(this._id, event, handler);
@@ -216,7 +218,9 @@ namespace sensors {
//% blockNamespace=sensors
//% weight=99 blockGap=8
//% group="Infrared Sensor"
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
pauseUntil(event: InfraredSensorEvent) {
this._setMode(InfraredSensorMode.Proximity)
control.waitForEvent(this._id, event);
@@ -233,7 +237,9 @@ namespace sensors {
//% blockNamespace=sensors
//% weight=98
//% group="Infrared Sensor"
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
proximity(): number {
this.poke();
this._setMode(InfraredSensorMode.Proximity)
@@ -248,7 +254,9 @@ namespace sensors {
//% blockId=irSetRemoteChannel block="set **infrared** %this|remote channel to %channel"
//% weight=99
//% group="Remote Infrared Beacon"
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% help=sensors/beacon/set-remote-channel
setRemoteChannel(channel: InfraredRemoteChannel) {
this.setMode(InfraredSensorMode.RemoteControl)
@@ -265,7 +273,9 @@ namespace sensors {
//% blockGap=8 weight=49
//% group="Infrared Sensor"
//% value.min=0 value.max=100
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
setPromixityThreshold(condition: InfraredSensorEvent, value: number) {
if (condition == InfraredSensorEvent.ObjectNear)
this._proximityThreshold.setLowThreshold(value)
@@ -280,7 +290,9 @@ namespace sensors {
//% blockId=irGetThreshold block="**infrared** %this|%condition"
//% weight=48
//% group="Infrared Sensor"
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
proximityThreshold(condition: InfraredSensorEvent): number {
return this._proximityThreshold.threshold(condition);
}
diff --git a/libs/nxt-light-sensor/light.ts b/libs/nxt-light-sensor/light.ts
index 6f177ac8..a9fd314f 100644
--- a/libs/nxt-light-sensor/light.ts
+++ b/libs/nxt-light-sensor/light.ts
@@ -94,7 +94,9 @@ namespace sensors {
//% blockId=nxtLight
//% parts="nxtlightsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=99 blockGap=8
//% subcategory="NXT"
//% group="Light Sensor"
@@ -126,7 +128,9 @@ namespace sensors {
//% blockId=setReflectedLightRange
//% parts="nxtlightsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=89 blockGap=8
//% subcategory="NXT"
//% group="Light Sensor"
@@ -147,7 +151,9 @@ namespace sensors {
//% blockId=setAmbientLightRange
//% parts="nxtlightsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=88 blockGap=8
//% subcategory="NXT"
//% group="Light Sensor"
@@ -189,6 +195,7 @@ namespace sensors {
*/
//%
reflectetLight() {
+ if (!this.isActive()) return 0;
let reflectedVal = Math.map(this.readValue(), this.darkReflectedLight, this.brightReflectedLight, 0, 100);
reflectedVal = Math.round(Math.constrain(reflectedVal, 0, 100));
return reflectedVal;
@@ -199,6 +206,7 @@ namespace sensors {
*/
//%
ambientLight() {
+ if (!this.isActive()) return 0;
let ambientVal = Math.map(this.readValue(), this.darkAmbientLight, this.brightAmbientLight, 0, 100);
ambientVal = Math.round(Math.constrain(ambientVal, 0, 100));
return ambientVal;
diff --git a/libs/storage/storage-core.ts b/libs/storage/storage-core.ts
index 539e5c9f..8356b028 100644
--- a/libs/storage/storage-core.ts
+++ b/libs/storage/storage-core.ts
@@ -1,11 +1,11 @@
-namespace storage {
+enum Separators {
+ //% block="comma"
+ Comma,
+ //% block="semicolon"
+ Semicolon
+}
- export enum Separators {
- //% block="comma"
- Comma,
- //% block="semicolon"
- Semicolon
- }
+namespace storage {
//% shim=storage::__unlink
function __unlink(filename: string): void { }
diff --git a/libs/touch-sensor/touch.ts b/libs/touch-sensor/touch.ts
index 53958e8f..1c63037d 100644
--- a/libs/touch-sensor/touch.ts
+++ b/libs/touch-sensor/touch.ts
@@ -37,7 +37,9 @@ namespace sensors {
//% blockId=touchEvent block="on **touch** %this|%event"
//% parts="touch"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=99 blockGap=8
//% group="Touch Sensor"
onEvent(ev: ButtonEvent, body: () => void) {
@@ -53,7 +55,9 @@ namespace sensors {
//% blockId=touchWaitUntil block="pause until **touch** %this|%event"
//% parts="touch"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=98 blockGap=8
//% group="Touch Sensor"
pauseUntil(ev: ButtonEvent) {
@@ -68,7 +72,9 @@ namespace sensors {
//% blockId=touchIsPressed block="**touch** %this|is pressed"
//% parts="touch"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=81 blockGap=8
//% group="Touch Sensor"
isPressed() {
@@ -85,7 +91,9 @@ namespace sensors {
//% blockHidden=true
//% parts="touch"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=80
//% group="Touch Sensor"
wasPressed() {
diff --git a/libs/ultrasonic-sensor/ultrasonic.ts b/libs/ultrasonic-sensor/ultrasonic.ts
index 3f683502..dd018bfc 100644
--- a/libs/ultrasonic-sensor/ultrasonic.ts
+++ b/libs/ultrasonic-sensor/ultrasonic.ts
@@ -49,7 +49,9 @@ namespace sensors {
//% block="on **ultrasonic** %this|%event"
//% parts="ultrasonicsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=100 blockGap=8
//% group="Ultrasonic Sensor"
onEvent(event: UltrasonicSensorEvent, handler: () => void) {
@@ -64,7 +66,9 @@ namespace sensors {
//% blockId=ultrasonicWait
//% parts="ultrasonicsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=99 blockGap=8
//% group="Ultrasonic Sensor"
pauseUntil(event: UltrasonicSensorEvent) {
@@ -80,7 +84,9 @@ namespace sensors {
//% blockId=sonarGetDistance
//% parts="ultrasonicsensor"
//% blockNamespace=sensors
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
//% weight=98
//% group="Ultrasonic Sensor"
distance(): number {
@@ -100,7 +106,9 @@ namespace sensors {
//% weight=89 blockGap=8
//% group="Ultrasonic Sensor"
//% value.min=0 value.max=255
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
setThreshold(condition: UltrasonicSensorEvent, value: number) {
switch (condition) {
case UltrasonicSensorEvent.ObjectNear: this.promixityThreshold.setLowThreshold(value); break;
@@ -115,7 +123,9 @@ namespace sensors {
//% blockId=ultrasonicGetThreshold block="**ultrasonic** %this|%condition"
//% weight=88
//% group="Ultrasonic Sensor"
- //% this.fieldEditor="ports"
+ //% this.fieldEditor="images"
+ //% this.fieldOptions.columns="4"
+ //% this.fieldOptions.width="300"
threshold(condition: UltrasonicSensorEvent): number {
switch (condition) {
case UltrasonicSensorEvent.ObjectNear: return this.promixityThreshold.threshold(ThresholdState.Low);
diff --git a/package.json b/package.json
index 16afb0cd..f2bca04c 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
"@types/node": "^9.3.0",
"@types/react": "16.8.25",
"@types/react-dom": "16.0.3",
- "@types/web-bluetooth": "0.0.4",
+ "@types/web-bluetooth": "0.0.21",
"@vusion/webfonts-generator": "^0.7.1",
"less": "3.13.1",
"react": "16.8.3",
@@ -45,8 +45,8 @@
"typescript": "4.8.3"
},
"dependencies": {
- "pxt-common-packages": "11.1.6",
- "pxt-core": "9.3.13"
+ "pxt-common-packages": "13.1.4",
+ "pxt-core": "12.2.8"
},
"scripts": {
"test": "node node_modules/pxt-core/built/pxt.js travis"
diff --git a/pxtarget.json b/pxtarget.json
index e78b80a6..5f6a7a51 100644
--- a/pxtarget.json
+++ b/pxtarget.json
@@ -80,7 +80,7 @@
},
"breakBlock": true,
"continueBlock": true,
- "bannedCategories": []
+ "bannedCategories": ["images"]
},
"compileService": {
"buildEngine": "dockermake",
@@ -110,6 +110,8 @@
"copyrightText": "LEGO, the LEGO logo, MINDSTORMS and the MINDSTORMS EV3 logo are trademarks and/ or copyrights of the LEGO Group. ©2018 The LEGO Group. All rights reserved.",
"crowdinProject": "makecode",
"selectLanguage": true,
+ "defaultColorTheme": "ev3-light",
+ "highContrastColorTheme": "pxt-high-contrast",
"blocksCollapsing": true,
"highContrast": true,
"greenScreen": true,
@@ -150,7 +152,7 @@
"print": true,
"showHomeScreen": true,
"homeScreenHero": "./static/hero.png",
- "invertedMenu": false,
+ "invertedMenu": true,
"invertedMonaco": false,
"monacoToolbox": true,
"invertedToolbox": false,
@@ -205,7 +207,7 @@
"monacoColors": {
"editor.background": "#f9f9f9"
},
- "fileNameExclusiveFilter": "[^a-zA-Z0-9]",
+ "fileNameExclusiveFilter": "[^a-zA-Z0-9_]",
"qrCode": true,
"shareFinishedTutorials": true,
"nameProjectFirst": true,
@@ -213,7 +215,10 @@
"githubEditor": true,
"chooseLanguageRestrictionOnNewProject": true,
"openProjectNewTab": true,
- "python": true
+ "python": true,
+ "timeMachine": true,
+ "timeMachineDiffInterval": 600000,
+ "timeMachineSnapshotInterval": 1800000
},
"ignoreDocsErrors": true,
"uploadDocs": true
diff --git a/sim/state/control.ts b/sim/state/control.ts
index e3618b35..3ff2bee8 100644
--- a/sim/state/control.ts
+++ b/sim/state/control.ts
@@ -29,7 +29,9 @@ namespace pxsim.MMapMethods {
destroy() {
}
buf(): Buffer {
- return { data: this.impl.data } as any
+ const b = pxsim.BufferMethods.createBuffer(this.impl.data.length);
+ b.data.set(this.impl.data);
+ return b;
}
}
@@ -77,11 +79,10 @@ namespace pxsim.MMapMethods {
}
namespace pxsim.control {
-
export function mmap(filename: string, size: number, offset: number): MMapMethods.MMap {
- let impl = MMapMethods.mmapRegistry[filename]
- if (!impl) impl = {}
- return new MMapMethods.MMap(impl, size)
+ let impl = MMapMethods.mmapRegistry[filename] || {};
+ const m = new MMapMethods.MMap(impl, size);
+ return m;
}
}
diff --git a/theme/blockly.less b/theme/blockly.less
index 658ce7ff..62e24ea3 100644
--- a/theme/blockly.less
+++ b/theme/blockly.less
@@ -28,22 +28,49 @@
ry: 0 !important;
}
-.blocklyDropDownButton {
- border-radius: 0px !important;
+/* Field editor */
+.blocklyMotorsFieldOptions .blocklyDropDownButton, .blocklyMusicFieldOptions .blocklyDropDownButton {
+ height: 100%;
+}
+
+.blocklyDropDownMotorsContent .blocklyMotorsFieldOptions .blocklyDropdownTextLabel {
+ display: inline-block;
+ width: auto;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: inherit;
+ padding: 0 10px;
+ line-height: 1rem;
+ color: #fff;
+ font-size: 13px;
}
-/* Music field editor */
+.blocklyDropDownMusicContent {
+ max-height: 450px;
+ overflow-y: auto;
+}
-.blocklyMusicFieldOptions .blocklyDropdownText, .blocklyMusicFieldCategories .blocklyDropdownText {
+.blocklyMotorsFieldOptions div[role="row"], .blocklyMusicFieldOptions div[role="row"] {
display: flex;
- justify-content: center;
- line-height: 1.5rem;
+}
+
+.blocklyMusicFieldCategories .blocklyDropdownText, .blocklyMusicFieldOptions .blocklyDropdownText {
+ // display: flex;
+ // justify-content: center;
+ line-height: 1rem;
color: #fff;
font-size: 13px;
}
+.blocklyMusicFieldOptions .blocklyDropdownText {
+ display: block;
+ margin-bottom: 5%;
+ padding: 0px 8px;
+ word-break: break-word;
+ hyphens: auto;
+}
+
.blocklyMusicFieldCategories {
- position: absolute;
text-align: center;
left: 0;
right: 0;
@@ -51,9 +78,6 @@
width: 100%;
font-size: 13px;
}
-.blocklyMusicFieldOptions {
- margin-top: 80px;
-}
.blocklyDropdownTag {
padding: 2px;
@@ -64,4 +88,17 @@
border: 1px solid;
transition: box-shadow .1s;
cursor: pointer;
+}
+
+.blocklyMotorsFieldOptions .blocklyDropDownButton > img, .blocklyMusicFieldOptions .blocklyDropDownButton > img {
+ position: relative;
+ width: 80%;
+ height: auto;
+ transform: unset;
+ top: unset;
+}
+
+// Default DropDownContent
+.blocklyDropDownContent {
+ overflow-y: auto; // Return scroll y
}
\ No newline at end of file
diff --git a/theme/color-themes/ev3-light.json b/theme/color-themes/ev3-light.json
new file mode 100644
index 00000000..de875a32
--- /dev/null
+++ b/theme/color-themes/ev3-light.json
@@ -0,0 +1,83 @@
+{
+ "id": "ev3-light",
+ "name": "EV3 Light",
+ "weight": 20,
+ "overrideFiles": [
+ "/overrides/ev3-light-overrides.css"
+ ],
+ "colors": {
+ "pxt-header-background": "#F2F2F2",
+ "pxt-header-foreground": "#FFFFFF",
+ "pxt-header-background-hover": "#1d3282",
+ "pxt-header-foreground-hover": "#FFFFFF",
+ "pxt-header-stencil": "#2742ab",
+
+ "pxt-header-secondary-background": "#999",
+ "pxt-header-secondary-foreground": "#FFFFFF",
+
+ "pxt-primary-background": "#1AA5C6",
+ "pxt-primary-foreground": "#FFFFFF",
+ "pxt-primary-background-hover": "#0F97b7",
+ "pxt-primary-foreground-hover": "#FFFFFF",
+ "pxt-primary-accent": "#0f97b7",
+
+ "pxt-secondary-background": "#fa7f2a",
+ "pxt-secondary-foreground": "#FFFFFF",
+ "pxt-secondary-background-hover": "#2742ab",
+ "pxt-secondary-foreground-hover": "#FFFFFF",
+ "pxt-secondary-accent": "#eb6306",
+
+ "pxt-tertiary-background": "#3454D1",
+ "pxt-tertiary-foreground": "#FFFFFF",
+ "pxt-tertiary-background-hover": "#2742ab",
+ "pxt-tertiary-foreground-hover": "#FFFFFF",
+ "pxt-tertiary-accent": "#1d3282",
+
+ "pxt-target-background1": "#ECF0F1",
+ "pxt-target-foreground1": "#000000",
+ "pxt-target-background1-hover": "#cfd9db",
+ "pxt-target-foreground1-hover": "#000000",
+ "pxt-target-stencil1": "#e1e1e1",
+
+ "pxt-target-background2": "#FDFDFF",
+ "pxt-target-foreground2": "#000000",
+ "pxt-target-background2-hover": "#cacaff",
+ "pxt-target-foreground2-hover": "#000000",
+ "pxt-target-stencil2": "#e1e1e1",
+
+ "pxt-target-background3": "#F2F2F2",
+ "pxt-target-foreground3": "#000000",
+ "pxt-target-background3-hover": "#e6e6e6",
+ "pxt-target-foreground3-hover": "#000000",
+ "pxt-target-stencil3": "#d9d9d9",
+
+ "pxt-neutral-background1": "#FFFFFF",
+ "pxt-neutral-foreground1": "#5d5d5d",
+ "pxt-neutral-background1-hover": "#e6e6e6",
+ "pxt-neutral-foreground1-hover": "rgba(0,0,0,.85)",
+ "pxt-neutral-stencil1": "rgba(34, 74, 114, 0.15)",
+
+ "pxt-neutral-background2": "#F8F8F8",
+ "pxt-neutral-foreground2": "rgba(0,0,0,.85)",
+ "pxt-neutral-background2-hover": "#DFDFDF",
+ "pxt-neutral-foreground2-hover": "rgba(0,0,0,.85)",
+ "pxt-neutral-stencil2": "#e9eef2",
+
+ "pxt-neutral-background3": "#3b3c3d",
+ "pxt-neutral-foreground3": "#FFFFFF",
+ "pxt-neutral-background3-hover": "#363c3d",
+ "pxt-neutral-foreground3-hover": "#FFFFFF",
+ "pxt-neutral-stencil3": "#FFFFFF",
+ "pxt-neutral-background3-alpha90": "#617374E5",
+
+ "pxt-neutral-base": "rgba(0, 0, 0, 1)",
+ "pxt-neutral-alpha0": "rgba(0, 0, 0, 0)",
+ "pxt-neutral-alpha10": "rgba(0, 0, 0, 0.1)",
+ "pxt-neutral-alpha20": "rgba(0, 0, 0, 0.2)",
+ "pxt-neutral-alpha50": "rgba(0, 0, 0, 0.5)",
+
+ "pxt-link": "#3977B4",
+ "pxt-link-hover": "#204467",
+ "pxt-focus-border": "#169CDF"
+ }
+}
\ No newline at end of file
diff --git a/theme/color-themes/overrides/ev3-light-overrides.css b/theme/color-themes/overrides/ev3-light-overrides.css
new file mode 100644
index 00000000..d87c4cba
--- /dev/null
+++ b/theme/color-themes/overrides/ev3-light-overrides.css
@@ -0,0 +1,71 @@
+/* Lots of specificity to override another !important rule */
+
+/* #simulator #editorSidebar .simtoolbar .ui.icon.tiny.buttons .ui.button.play-button.play .icon.play {
+ color: var(--pxt-colors-green-background) !important;
+}
+
+.theme-preview-ev3-light .theme-preview-sim-button {
+ background-color: var(--pxt-neutral-background2) !important;
+} */
+
+#mainmenu {
+ /* Some parts of the app use the same color as a background. This keeps the menu from blending in */
+ border-bottom: 5px solid #00a5c8;
+}
+
+#computertogglesim, #mobiletogglesim, #sidedocstoggle {
+ background: #95a5a6 !important;
+}
+
+.blocklyToolboxDiv, .monacoToolboxDiv {
+ background: var(--pxt-neutral-background1) !important;
+ color: var(--pxt-neutral-foreground1) !important;
+}
+
+/* Фон панели панели выбранной категории с блоками */
+.blocklyFlyoutBackground {
+ fill: var(--pxt-neutral-background3) !important;
+ fill-opacity: .9 !important;
+}
+
+/* Шапка в расширениях */
+.fullscreen.extensions-browser .common-modal .common-modal-header {
+ background-color: var(--pxt-header-secondary-background) !important;
+}
+
+/* Лейбл бета в расширениях */
+.common-card-label {
+ color: var(--pxt-secondary-foreground);
+ background-color: var(--pxt-secondary-background);
+ border-color: var(--pxt-secondary-accent);
+}
+
+/* Кнопка Понятно в окне загрузки */
+.ui.button.positive {
+ background-color: var(--pxt-primary-background) !important;
+ color: var(--pxt-primary-foreground) !important;
+ border: 1px solid var(--pxt-primary-background) !important;
+}
+
+/* Не работает, кнопка троеточия */
+.ui.button.editortools-btn.hw-button:focus, .ui.button.editortools-btn.hw-button:hover {
+ filter: grayscale(.15) brightness(.85) contrast(1.3) !important;
+ background-color: var(--pxt-neutral-background1) !important;
+ color: var(--pxt-neutral-foreground1) !important;
+}
+
+/* Кнопки переключения editortoggle */
+.menubar .ui.menu.fixed .ui.item.editor-menuitem .item.link:hover, .menubar .ui.inverted.menu .link.item:active, .ui.inverted.menu .dropdown.item:hover {
+ background: var(--pxt-neutral-alpha10);
+ color: #000;
+}
+
+/* Кнопка переключения языков JS на Python editortoggle */
+#root:not(.hc) #editordropdown.active {
+ background-color: var(--pxt-neutral-alpha20) !important;
+ color: #000 !important;
+}
+
+.ui.red.ribbon.label {
+ border-color: #760007 !important;
+}
\ No newline at end of file
diff --git a/theme/style.less b/theme/style.less
index 47092260..ae2a19a2 100644
--- a/theme/style.less
+++ b/theme/style.less
@@ -137,6 +137,10 @@
border-radius: 0;
}
+.collapsedEditorTools #downloadArea {
+ background: none;
+}
+
/* Mobile */
@media only screen and (max-width: @largestMobileScreen) {
#filelist {