Add a output port field editor
This commit is contained in:
parent
afaedaa0b2
commit
a1b059171b
@ -142,7 +142,11 @@ namespace pxt.editor {
|
|||||||
initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
|
initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): Promise<pxt.editor.ExtensionResult> {
|
||||||
pxt.debug('loading pxt-ev3 target extensions...')
|
pxt.debug('loading pxt-ev3 target extensions...')
|
||||||
const res: pxt.editor.ExtensionResult = {
|
const res: pxt.editor.ExtensionResult = {
|
||||||
deployCoreAsync,
|
fieldEditors: [{
|
||||||
|
selector: "ports",
|
||||||
|
editor: FieldPorts
|
||||||
|
}],
|
||||||
|
deployCoreAsync
|
||||||
};
|
};
|
||||||
initAsync().catch(e => {
|
initAsync().catch(e => {
|
||||||
// probably no HID - we'll try this again upon deployment
|
// probably no HID - we'll try this again upon deployment
|
||||||
|
150
editor/field_ports.ts
Normal file
150
editor/field_ports.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/// <reference path="../node_modules/pxt-core/localtypings/blockly.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 class FieldPorts extends Blockly.FieldDropdown implements Blockly.FieldCustom {
|
||||||
|
public isFieldCustom_ = true;
|
||||||
|
|
||||||
|
// Width in pixels
|
||||||
|
private width_: number;
|
||||||
|
|
||||||
|
// Columns in grid
|
||||||
|
private columns_: number;
|
||||||
|
|
||||||
|
private savedPrimary_: string;
|
||||||
|
|
||||||
|
constructor(text: string, options: FieldPortsOptions, validator?: Function) {
|
||||||
|
super(options.data);
|
||||||
|
|
||||||
|
this.columns_ = parseInt(options.columns) || 4;
|
||||||
|
this.width_ = parseInt(options.width) || 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a dropdown menu under the text.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
public showEditor_() {
|
||||||
|
// If there is an existing drop-down we own, this is a request to hide the drop-down.
|
||||||
|
if (Blockly.DropDownDiv.hideIfOwner(this)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
|
||||||
|
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||||
|
Blockly.DropDownDiv.clearContent();
|
||||||
|
// Populate the drop-down with the icons for this field.
|
||||||
|
let dropdownDiv = Blockly.DropDownDiv.getContentDiv();
|
||||||
|
let contentDiv = document.createElement('div');
|
||||||
|
// Accessibility properties
|
||||||
|
contentDiv.setAttribute('role', 'menu');
|
||||||
|
contentDiv.setAttribute('aria-haspopup', 'true');
|
||||||
|
const options = this.getOptions();
|
||||||
|
for (let i = 0, option: any; option = options[i]; i++) {
|
||||||
|
let content = (options[i] as any)[0]; // Human-readable text or image.
|
||||||
|
const value = (options[i] as any)[1]; // Language-neutral value.
|
||||||
|
// Icons with the type property placeholder take up space but don't have any functionality
|
||||||
|
// Use for special-case layouts
|
||||||
|
if (content.type == 'placeholder') {
|
||||||
|
let placeholder = document.createElement('span');
|
||||||
|
placeholder.setAttribute('class', 'blocklyDropDownPlaceholder');
|
||||||
|
placeholder.style.width = content.width + 'px';
|
||||||
|
placeholder.style.height = content.height + 'px';
|
||||||
|
contentDiv.appendChild(placeholder);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let button = document.createElement('button');
|
||||||
|
button.setAttribute('id', ':' + i); // For aria-activedescendant
|
||||||
|
button.setAttribute('role', 'menuitem');
|
||||||
|
button.setAttribute('class', 'blocklyDropDownButton');
|
||||||
|
button.title = content.alt;
|
||||||
|
if (this.columns_) {
|
||||||
|
button.style.width = ((this.width_ / this.columns_) - 8) + 'px';
|
||||||
|
button.style.height = ((this.width_ / this.columns_) - 8) + 'px';
|
||||||
|
} else {
|
||||||
|
button.style.width = content.width + 'px';
|
||||||
|
button.style.height = content.height + 'px';
|
||||||
|
}
|
||||||
|
let backgroundColor = this.sourceBlock_.getColour();
|
||||||
|
if (value == this.getValue()) {
|
||||||
|
// This icon is selected, show it in a different colour
|
||||||
|
backgroundColor = this.sourceBlock_.getColourTertiary();
|
||||||
|
button.setAttribute('aria-selected', 'true');
|
||||||
|
}
|
||||||
|
button.style.backgroundColor = backgroundColor;
|
||||||
|
button.style.borderColor = this.sourceBlock_.getColourTertiary();
|
||||||
|
Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
|
||||||
|
Blockly.bindEvent_(button, 'mouseup', this, this.buttonClick_);
|
||||||
|
// These are applied manually instead of using the :hover pseudoclass
|
||||||
|
// because Android has a bad long press "helper" menu and green highlight
|
||||||
|
// that we must prevent with ontouchstart preventDefault
|
||||||
|
Blockly.bindEvent_(button, 'mousedown', button, function (e) {
|
||||||
|
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
Blockly.bindEvent_(button, 'mouseover', button, function () {
|
||||||
|
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
|
||||||
|
contentDiv.setAttribute('aria-activedescendant', this.id);
|
||||||
|
});
|
||||||
|
Blockly.bindEvent_(button, 'mouseout', button, function () {
|
||||||
|
this.setAttribute('class', 'blocklyDropDownButton');
|
||||||
|
contentDiv.removeAttribute('aria-activedescendant');
|
||||||
|
});
|
||||||
|
let buttonImg = document.createElement('img');
|
||||||
|
buttonImg.src = content.src;
|
||||||
|
//buttonImg.alt = icon.alt;
|
||||||
|
// Upon click/touch, we will be able to get the clicked element as e.target
|
||||||
|
// Store a data attribute on all possible click targets so we can match it to the icon.
|
||||||
|
button.setAttribute('data-value', value);
|
||||||
|
buttonImg.setAttribute('data-value', value);
|
||||||
|
button.appendChild(buttonImg);
|
||||||
|
contentDiv.appendChild(button);
|
||||||
|
}
|
||||||
|
contentDiv.style.width = this.width_ + 'px';
|
||||||
|
dropdownDiv.appendChild(contentDiv);
|
||||||
|
|
||||||
|
Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), this.sourceBlock_.getColourTertiary());
|
||||||
|
|
||||||
|
// Calculate positioning based on the field position.
|
||||||
|
var scale = this.sourceBlock_.workspace.scale;
|
||||||
|
var bBox = { width: this.size_.width, height: this.size_.height };
|
||||||
|
bBox.width *= scale;
|
||||||
|
bBox.height *= scale;
|
||||||
|
var position = this.fieldGroup_.getBoundingClientRect();
|
||||||
|
var primaryX = position.left + bBox.width / 2;
|
||||||
|
var primaryY = position.top + bBox.height;
|
||||||
|
var secondaryX = primaryX;
|
||||||
|
var secondaryY = position.top;
|
||||||
|
// Set bounds to workspace; show the drop-down.
|
||||||
|
(Blockly.DropDownDiv as any).setBoundsElement(this.sourceBlock_.workspace.getParentSvg().parentNode);
|
||||||
|
(Blockly.DropDownDiv as any).show(this, primaryX, primaryY, secondaryX, secondaryY,
|
||||||
|
this.onHide_.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when a button is clicked inside the drop-down.
|
||||||
|
* Should be bound to the FieldIconMenu.
|
||||||
|
* @param {Event} e DOM event for the click/touch
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private buttonClick_ = function (e: any) {
|
||||||
|
let value = e.target.getAttribute('data-value');
|
||||||
|
this.setValue(value);
|
||||||
|
Blockly.DropDownDiv.hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when the drop-down is hidden.
|
||||||
|
*/
|
||||||
|
private onHide_ = function () {
|
||||||
|
Blockly.DropDownDiv.content_.removeAttribute('role');
|
||||||
|
Blockly.DropDownDiv.content_.removeAttribute('aria-haspopup');
|
||||||
|
Blockly.DropDownDiv.content_.removeAttribute('aria-activedescendant');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -110,6 +110,7 @@ namespace sensors {
|
|||||||
//% blockId=colorOnColorDetected
|
//% blockId=colorOnColorDetected
|
||||||
//% parts="colorsensor"
|
//% parts="colorsensor"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=100 blockGap=8
|
//% weight=100 blockGap=8
|
||||||
//% group="Color Sensor"
|
//% group="Color Sensor"
|
||||||
onColorDetected(color: ColorSensorColor, handler: () => void) {
|
onColorDetected(color: ColorSensorColor, handler: () => void) {
|
||||||
@ -129,6 +130,7 @@ namespace sensors {
|
|||||||
//% blockId=colorPauseForColorDetected
|
//% blockId=colorPauseForColorDetected
|
||||||
//% parts="colorsensor"
|
//% parts="colorsensor"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=99 blockGap=8
|
//% weight=99 blockGap=8
|
||||||
//% group="Color Sensor"
|
//% group="Color Sensor"
|
||||||
pauseForColor(color: ColorSensorColor) {
|
pauseForColor(color: ColorSensorColor) {
|
||||||
@ -148,6 +150,7 @@ namespace sensors {
|
|||||||
//% blockId=colorGetColor
|
//% blockId=colorGetColor
|
||||||
//% parts="colorsensor"
|
//% parts="colorsensor"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=99
|
//% weight=99
|
||||||
//% group="Color Sensor"
|
//% group="Color Sensor"
|
||||||
color(): ColorSensorColor {
|
color(): ColorSensorColor {
|
||||||
@ -165,6 +168,7 @@ namespace sensors {
|
|||||||
//% blockId=colorOnLightChanged
|
//% blockId=colorOnLightChanged
|
||||||
//% parts="colorsensor"
|
//% parts="colorsensor"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=89 blockGap=8
|
//% weight=89 blockGap=8
|
||||||
//% group="Color Sensor"
|
//% group="Color Sensor"
|
||||||
onLightChanged(mode: LightIntensityMode, condition: LightCondition, handler: () => void) {
|
onLightChanged(mode: LightIntensityMode, condition: LightCondition, handler: () => void) {
|
||||||
@ -181,6 +185,7 @@ namespace sensors {
|
|||||||
//% blockId=colorPauseForLight
|
//% blockId=colorPauseForLight
|
||||||
//% parts="colorsensor"
|
//% parts="colorsensor"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=88 blockGap=8
|
//% weight=88 blockGap=8
|
||||||
//% group="Color Sensor"
|
//% group="Color Sensor"
|
||||||
pauseForLight(mode: LightIntensityMode, condition: LightCondition) {
|
pauseForLight(mode: LightIntensityMode, condition: LightCondition) {
|
||||||
@ -198,6 +203,7 @@ namespace sensors {
|
|||||||
//% blockId=colorLight
|
//% blockId=colorLight
|
||||||
//% parts="colorsensor"
|
//% parts="colorsensor"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=87
|
//% weight=87
|
||||||
//% group="Color Sensor"
|
//% group="Color Sensor"
|
||||||
light(mode: LightIntensityMode) {
|
light(mode: LightIntensityMode) {
|
||||||
|
@ -28,6 +28,7 @@ namespace sensors {
|
|||||||
//% blockId=gyroGetAngle
|
//% blockId=gyroGetAngle
|
||||||
//% parts="gyroscope"
|
//% parts="gyroscope"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=65 blockGap=8
|
//% weight=65 blockGap=8
|
||||||
//% group="Gyro Sensor"
|
//% group="Gyro Sensor"
|
||||||
angle(): number {
|
angle(): number {
|
||||||
@ -44,6 +45,7 @@ namespace sensors {
|
|||||||
//% blockId=gyroGetRate
|
//% blockId=gyroGetRate
|
||||||
//% parts="gyroscope"
|
//% parts="gyroscope"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=65 blockGap=8
|
//% weight=65 blockGap=8
|
||||||
//% group="Gyro Sensor"
|
//% group="Gyro Sensor"
|
||||||
rate(): number {
|
rate(): number {
|
||||||
|
@ -45,6 +45,7 @@ namespace sensors {
|
|||||||
//% blockId=touchEvent block="on %sensor|%event"
|
//% blockId=touchEvent block="on %sensor|%event"
|
||||||
//% parts="touch"
|
//% parts="touch"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=99 blockGap=8
|
//% weight=99 blockGap=8
|
||||||
//% group="Touch Sensor"
|
//% group="Touch Sensor"
|
||||||
onEvent(ev: TouchSensorEvent, body: () => void) {
|
onEvent(ev: TouchSensorEvent, body: () => void) {
|
||||||
@ -60,6 +61,7 @@ namespace sensors {
|
|||||||
//% blockId=touchWaitUntil block="pause until %sensor|%event"
|
//% blockId=touchWaitUntil block="pause until %sensor|%event"
|
||||||
//% parts="touch"
|
//% parts="touch"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=98 blockGap=8
|
//% weight=98 blockGap=8
|
||||||
//% group="Touch Sensor"
|
//% group="Touch Sensor"
|
||||||
pauseUntil(ev: TouchSensorEvent) {
|
pauseUntil(ev: TouchSensorEvent) {
|
||||||
@ -75,6 +77,7 @@ namespace sensors {
|
|||||||
//% blockId=touchIsPressed
|
//% blockId=touchIsPressed
|
||||||
//% parts="touch"
|
//% parts="touch"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=81 blockGap=8
|
//% weight=81 blockGap=8
|
||||||
//% group="Touch Sensor"
|
//% group="Touch Sensor"
|
||||||
isPressed() {
|
isPressed() {
|
||||||
@ -90,6 +93,7 @@ namespace sensors {
|
|||||||
//% blockId=touchWasPressed
|
//% blockId=touchWasPressed
|
||||||
//% parts="touch"
|
//% parts="touch"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=81 blockGap=8
|
//% weight=81 blockGap=8
|
||||||
//% group="Touch Sensor"
|
//% group="Touch Sensor"
|
||||||
wasPressed() {
|
wasPressed() {
|
||||||
|
@ -46,6 +46,7 @@ namespace sensors {
|
|||||||
//% block="on %sensor|%event"
|
//% block="on %sensor|%event"
|
||||||
//% parts="ultrasonicsensor"
|
//% parts="ultrasonicsensor"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=100 blockGap=8
|
//% weight=100 blockGap=8
|
||||||
//% group="Ultrasonic Sensor"
|
//% group="Ultrasonic Sensor"
|
||||||
onEvent(event: UltrasonicSensorEvent, handler: () => void) {
|
onEvent(event: UltrasonicSensorEvent, handler: () => void) {
|
||||||
@ -60,6 +61,7 @@ namespace sensors {
|
|||||||
//% blockId=ultrasonicWait
|
//% blockId=ultrasonicWait
|
||||||
//% parts="ultrasonicsensor"
|
//% parts="ultrasonicsensor"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=99 blockGap=8
|
//% weight=99 blockGap=8
|
||||||
//% group="Ultrasonic Sensor"
|
//% group="Ultrasonic Sensor"
|
||||||
pauseUntil(event: UltrasonicSensorEvent) {
|
pauseUntil(event: UltrasonicSensorEvent) {
|
||||||
@ -75,6 +77,7 @@ namespace sensors {
|
|||||||
//% blockId=sonarGetDistance
|
//% blockId=sonarGetDistance
|
||||||
//% parts="ultrasonicsensor"
|
//% parts="ultrasonicsensor"
|
||||||
//% blockNamespace=sensors
|
//% blockNamespace=sensors
|
||||||
|
//% sensor.fieldEditor="ports"
|
||||||
//% weight=65 blockGap=8
|
//% weight=65 blockGap=8
|
||||||
//% group="Ultrasonic Sensor"
|
//% group="Ultrasonic Sensor"
|
||||||
distance(): number {
|
distance(): number {
|
||||||
|
Loading…
Reference in New Issue
Block a user