New motor field editor (#470)

* New motor field editor showing two dropdown, first to select the type and the second to select the port.
This commit is contained in:
Sam El-Husseini
2018-04-09 15:59:28 -07:00
committed by GitHub
parent d837a515dc
commit a433988929
20 changed files with 1998 additions and 310 deletions

View File

@ -3,7 +3,7 @@
import { deployCoreAsync, initAsync } from "./deploy";
import { FieldPorts } from "./field_ports";
import { FieldImages } from "./field_images";
import { FieldMotors } from "./field_motors";
import { FieldSpeed } from "./field_speed";
import { FieldBrickButtons } from "./field_brickbuttons";
import { FieldTurnRatio } from "./field_turnratio";
@ -17,8 +17,8 @@ pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): P
selector: "ports",
editor: FieldPorts
}, {
selector: "images",
editor: FieldImages
selector: "motors",
editor: FieldMotors
}, {
selector: "speed",
editor: FieldSpeed

View File

@ -155,5 +155,6 @@ export class FieldBrickButtons extends Blockly.FieldDropdown implements Blockly.
Blockly.DropDownDiv.content_.removeAttribute('role');
Blockly.DropDownDiv.content_.removeAttribute('aria-haspopup');
Blockly.DropDownDiv.content_.removeAttribute('aria-activedescendant');
Blockly.DropDownDiv.getContentDiv().style.width = '';
};
}

View File

@ -1,114 +0,0 @@
/// <reference path="../node_modules/pxt-core/localtypings/blockly.d.ts"/>
/// <reference path="../node_modules/pxt-core/built/pxtblocks.d.ts"/>
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
export interface FieldImagesOptions extends pxtblockly.FieldImageDropdownOptions {
}
export class FieldImages extends pxtblockly.FieldImageDropdown implements Blockly.FieldCustom {
public isFieldCustom_ = true;
constructor(text: string, options: FieldImagesOptions, validator?: Function) {
super(text, options, validator);
}
/**
* 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 as any).columns_) {
button.style.width = (((this as any).width_ / (this as any).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 as any).buttonClick_);
Blockly.bindEvent_(button, 'mouseup', this, (this as any).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 as any).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 as any).onHide_.bind(this));
}
}

565
editor/field_motors.ts Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
/// <reference path="../node_modules/pxt-core/localtypings/blockly.d.ts"/>
/// <reference path="../node_modules/pxt-core/built/pxtblocks.d.ts"/>
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
export interface FieldPortsOptions extends Blockly.FieldCustomDropdownOptions {
@ -6,146 +7,31 @@ export interface FieldPortsOptions extends Blockly.FieldCustomDropdownOptions {
width?: string;
}
export class FieldPorts extends Blockly.FieldDropdown implements Blockly.FieldCustom {
export class FieldPorts extends pxtblockly.FieldImages 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);
super(text, options, validator);
this.columns_ = parseInt(options.columns) || 4;
this.width_ = parseInt(options.width) || 300;
this.setText = Blockly.FieldDropdown.prototype.setText;
this.updateWidth = (Blockly.Field as any).prototype.updateWidth;
this.updateTextNode_ = Blockly.Field.prototype.updateTextNode_;
}
trimOptions_() {
}
/**
* 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');
let options = this.getOptions();
options = options.sort();
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));
getOptions() {
const options = super.getOptions();
return options ? options.sort() : undefined;
}
/**
* 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) {
protected 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');
};
}