Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "every component" block #2452

Merged
merged 18 commits into from Feb 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a4edc37
Update components.js
Vishwas-Adiga Mar 28, 2021
b47e787
blocklyeditor/component_database: Add function to get all component t…
Vishwas-Adiga Mar 28, 2021
740f8ba
blocklyeditor/components: Add drop down for component types
Vishwas-Adiga Mar 28, 2021
f6396a8
blocklyeditor/drawer: Add all components block to generic drawer
Vishwas-Adiga Mar 28, 2021
debd15f
blocklyeditor/yail.js: Add all components macro call
Vishwas-Adiga Mar 28, 2021
d0966eb
blocklyeditor: Add generator for all components block
Vishwas-Adiga Mar 28, 2021
119d944
blocklyeditor: Create warning handler to check if block type exists
Vishwas-Adiga Mar 28, 2021
cfd8957
components/util: Add util class to filter components
Vishwas-Adiga Mar 28, 2021
24c7e64
buildserver/runtime: Define syntax to get all components of a type in…
Vishwas-Adiga Mar 28, 2021
21446c1
blocklyeditor/messages: Add block title
Vishwas-Adiga Apr 5, 2021
a7d83a8
YaVersion: Increment YaVersion and BLOCKS_LANGUAGE_VERSION
Vishwas-Adiga Apr 5, 2021
f223595
versioning.js: Increment language version
Vishwas-Adiga Apr 5, 2021
fa14583
Merge remote-tracking branch 'upstream/ucr' into feature/every-block
Vishwas-Adiga Jun 13, 2021
7c6b212
Add "every block" to messages
Vishwas-Adiga Jun 13, 2021
a75782b
Move "every block" below helper blocks
Vishwas-Adiga Jun 13, 2021
d3c1894
Make requested code style changes
Vishwas-Adiga Jun 13, 2021
a707668
Merge remote-tracking branch 'github/ucr' into HEAD
ewpatton Feb 3, 2023
7981f12
Fix versioning comment for 'every component' block
ewpatton Feb 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
77 changes: 77 additions & 0 deletions appinventor/blocklyeditor/src/blocks/components.js
Expand Up @@ -38,6 +38,7 @@ Blockly.ComponentBlock.COLOUR_SET = '#266643'; // [38, 102, 67]
Blockly.ComponentBlock.COLOUR_COMPONENT = '#439970'; // [67, 153, 112]

Blockly.ComponentBlock.COMPONENT_SELECTOR = "COMPONENT_SELECTOR";
Blockly.ComponentBlock.COMPONENT_TYPE_SELECTOR = "COMPONENT_TYPE_SELECTOR";

/**
* Add a menu option to the context menu for {@code block} to swap between
Expand Down Expand Up @@ -1493,6 +1494,76 @@ Blockly.Blocks.component_component_block = {

};

/**
* Create a component list block for a given component type.
* @lends {Blockly.BlockSvg}
* @lends {Blockly.Block}
*/
Blockly.Blocks['component_all_component_block'] = {
category : 'Component',

helpUrl : function() {
var mode = this.typeName === "Form" ? "Screen" : this.typeName;
return Blockly.ComponentBlock.HELPURLS[mode];
},

mutationToDom : function() {
var container = document.createElement('mutation');
container.setAttribute('component_type', this.typeName);
return container;
},

domToMutation : function(xmlElement) {

this.typeName = xmlElement.getAttribute('component_type');
this.setColour(Blockly.ComponentBlock.COLOUR_COMPONENT);
this.componentTypeDropDown = Blockly.ComponentBlock.createComponentTypeDropDown(this);
this.componentTypeDropDown.setValue(this.typeName);

this.appendDummyInput()
.appendField(Blockly.Msg.LANG_COMPONENT_BLOCK_EVERY_COMPONENT_TITLE_EVERY)
.appendField(this.componentTypeDropDown, Blockly.ComponentBlock.COMPONENT_TYPE_SELECTOR);
this.setOutput(true, Blockly.Blocks.Utilities.YailTypeToBlocklyType("list", Blockly.Blocks.Utilities.OUTPUT));
this.errors = [{name:"checkIfUndefinedBlock"}, {name:"checkComponentTypeNotExistsError"}];
},
// Renames the block's instanceName, type, and reset its title
rename : function(oldname, newname) {
return true;
},

typeblock : function() {
var componentDb = Blockly.mainWorkspace.getComponentDatabase();
var tb = [];

componentDb.forEachInstance(function(instance) {
if (instance.typeName != "Form") {
tb.push({
translatedName: Blockly.Msg.LANG_COMPONENT_BLOCK_EVERY_COMPONENT_TITLE_EVERY +
" " + componentDb.getInternationalizedComponentType(instance.typeName),
mutatorAttributes: {
component_type: instance.typeName,
}
});
}
});

goog.array.removeDuplicates(tb, null, function(t) {
return t.mutatorAttributes.component_type;
});
return tb;
},

verify : function() {
// TODO(ewpatton): Logic assumes that components cannot be removed (e.g., editing AIA)
if (this.getTopWorkspace().getComponentDatabase().hasType(this.typeName)) {
this.notBadBlock();
} else {
this.badBlock();
}
}

};

Blockly.ComponentBlock.timeUnits = ["Years", "Months", "Weeks", "Days", "Hours", "Minutes", "Seconds", "Duration"];
Blockly.ComponentBlock.timeUnitsMenu =
[[ Blockly.Msg.TIME_YEARS, "Years"],
Expand All @@ -1517,6 +1588,12 @@ Blockly.ComponentBlock.createComponentDropDown = function(block){
return componentDropDown;
};

Blockly.ComponentBlock.createComponentTypeDropDown = function(block) {
var componentDropDown = new Blockly.FieldDropdown([["",""]]);
componentDropDown.menuGenerator_ = function() { return block.getTopWorkspace().getComponentDatabase().getComponentTypes(); };
return componentDropDown;
};

Blockly.ComponentBlock.createClockAddDropDown = function(/*block*/){
var componentDropDown = new Blockly.FieldDropdown([["",""]]);
componentDropDown.menuGenerator_ = function(){ return Blockly.ComponentBlock.timeUnitsMenu; };
Expand Down
40 changes: 38 additions & 2 deletions appinventor/blocklyeditor/src/component_database.js
Expand Up @@ -146,7 +146,7 @@ Blockly.ComponentDatabase = function() {

/**
* Maps names of option lists to OptionLists.
*
*
* This map is used to de-duplicate duplicated optionList info provided via
* the simple_components.json file, so that every option is represented
* exactly once in the component database. Everything that needs to reference
Expand Down Expand Up @@ -374,6 +374,42 @@ Blockly.ComponentDatabase.prototype.getComponentNamesByType = function(component
}
};

/**
* Obtain type names of added components for presentation in a drop-down field.
*
* @returns {Array.<Array.<string>>} An array of 2-tuples containing the type of each component.
* If no components are declared, a single element list is returned with the pair
* (' ', 'none').
*/
Blockly.ComponentDatabase.prototype.getComponentTypes = function() {
var componentTypeArray = [];
for (var uid in this.instances_) {
var typeName = this.instances_[uid].typeName;
if (typeName != "Form")
componentTypeArray.push([this.i18nComponentTypes_[typeName], typeName]);
}

goog.array.removeDuplicates(componentTypeArray, null, function(type) {
return type[1];
});

if (componentTypeArray.length == 0) {
return [[' ', 'none']]
} else {
// Sort the components by type
componentTypeArray.sort(function(a, b) {
if (a[0] < b[0]) {
return -1;
} else if (a[0] > b[0]) {
return 1;
} else {
return 0;
}
});
return componentTypeArray;
}
};

/**
* Populate the types database.
*
Expand Down Expand Up @@ -490,7 +526,7 @@ Blockly.ComponentDatabase.prototype.processHelper = function(helper) {
/**
* Processes data defining an OptionList (from simple_components.json) and
* returns a HelperKey associated with the OptionList.
*
*
* This function is used in conjunction with the optionLists_ field to remove
* duplicate optionList data provided via the simple_components.json file.
* @param {!Object} data The data defining the OptionList.
Expand Down
7 changes: 6 additions & 1 deletion appinventor/blocklyeditor/src/drawer.js
Expand Up @@ -235,7 +235,7 @@ Blockly.Drawer.prototype.instanceRecordToXMLArray = function(instanceRecord) {
if (subsetJsonString.length > 0) {
// TODO: All of this code should be cleaned up and moved into a separate
// function. Logs should be removed.

var subsetArray = [];
var subsetBlocks = [];
subsetArray = JSON.parse(subsetJsonString);
Expand Down Expand Up @@ -528,6 +528,11 @@ Blockly.Drawer.prototype.componentTypeToXMLArray = function(typeName) {
Array.prototype.push.apply(xmlArray, xmlUtils.XMLToArray(xml));
}.bind(this));

// add the all components getter
Array.prototype.push.apply(xmlArray, this.blockTypeToXMLArray('component_all_component_block', {
component_type: typeName
}));

return xmlArray;
};

Expand Down
1 change: 1 addition & 0 deletions appinventor/blocklyeditor/src/generators/yail.js
Expand Up @@ -68,6 +68,7 @@ Blockly.Yail.YAIL_FILTER = "(filter_nondest ";
Blockly.Yail.YAIL_FOREACH = "(foreach ";
Blockly.Yail.YAIL_FORRANGE = "(forrange ";
Blockly.Yail.YAIL_GET_COMPONENT = "(get-component ";
Blockly.Yail.YAIL_GET_ALL_COMPONENT = "(get-all-components ";
Blockly.Yail.YAIL_GET_PROPERTY = "(get-property ";
Blockly.Yail.YAIL_GET_COMPONENT_TYPE_PROPERTY = "(get-property-and-check ";
Blockly.Yail.YAIL_GET_VARIABLE = "(get-var ";
Expand Down
14 changes: 14 additions & 0 deletions appinventor/blocklyeditor/src/generators/yail/componentblock.js
Expand Up @@ -334,3 +334,17 @@ Blockly.Yail.component_component_block = function() {
return [Blockly.Yail.YAIL_GET_COMPONENT + this.getFieldValue("COMPONENT_SELECTOR") + Blockly.Yail.YAIL_CLOSE_COMBINATION,
Blockly.Yail.ORDER_ATOMIC];
};

/**
* Returns a function that takes no arguments, generates Yail to get a list of all
* components of a type, and returns a 2-element array containing the component
* getter code and the operation order Blockly.Yail.ORDER_ATOMIC.
*
* @param {String} instanceName
* @returns {Function} component getter code generation function with instanceName bound in
*/
Blockly.Yail['component_all_component_block'] = function() {
var fqcn = this.workspace.getComponentDatabase().getType(this.getFieldValue("COMPONENT_TYPE_SELECTOR")).componentInfo.type;
return [Blockly.Yail.YAIL_GET_ALL_COMPONENT + fqcn + Blockly.Yail.YAIL_CLOSE_COMBINATION,
Blockly.Yail.ORDER_ATOMIC];
};
3 changes: 2 additions & 1 deletion appinventor/blocklyeditor/src/msg/ai_blockly/messages.json
Expand Up @@ -142,7 +142,7 @@
"Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_INPUT_VALUE": "value",
"Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_TITLE": "for each in dictionary",
"Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_TOOLTIP": "Runs the blocks in the 'do' section for each key-value entry in the dictionary. Use the given variable names to refer to the key/value of the current dictionary item.",

"Blockly.Msg.LANG_CONTROLS_FLOW_STATEMENTS_INPUT_OFLOOP": "of loop",
"Blockly.Msg.LANG_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK": "break out",
"Blockly.Msg.LANG_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE": "continue with next iteration",
Expand Down Expand Up @@ -797,6 +797,7 @@
"Blockly.Msg.LANG_COMPONENT_BLOCK_GENERIC_SETTER_TITLE_TO": " to",
"Blockly.Msg.LANG_COMPONENT_BLOCK_GENERIC_SETTER_TITLE_OF_COMPONENT": "of component",
"Blockly.Msg.LANG_COMPONENT_BLOCK_GENERIC_SETTER_HELPURL": "",
"Blockly.Msg.LANG_COMPONENT_BLOCK_EVERY_COMPONENT_TITLE_EVERY": "every",
"Blockly.Msg.LANG_SCREENS_TITLE": "Screen Name: ",
"Blockly.Msg.LANG_NO_ASSETS": "No available assets",
"Blockly.Msg.SHOW_WARNINGS": "Show Warnings",
Expand Down
7 changes: 4 additions & 3 deletions appinventor/blocklyeditor/src/versioning.js
Expand Up @@ -1002,7 +1002,7 @@ Blockly.Versioning.makeMethodUseHelper =
.findAllMethodCalls(dom, componentType, methodName);
for (var i = 0, method; method = methodNodes[i]; i++) {
for (var j = 0, child; child = method.children[j]; j++) {
if (child.tagName == 'value' &&
if (child.tagName == 'value' &&
child.getAttribute('name') == 'ARG' + argNum) {
replaceFunc(child, workspace);
break;
Expand Down Expand Up @@ -1177,7 +1177,7 @@ Blockly.Versioning.tryReplaceBlockWithDropdown =
/**
* Replaces the block currently attached to the passed value input with a screen
* names block. The current block is replaced iff it is a constant (eg a text or
* number block).
* number block).
* @param {Element} valueNode The node to modify.
*/
Blockly.Versioning.tryReplaceBlockWithScreen = function(valueNode) {
Expand Down Expand Up @@ -2269,6 +2269,7 @@ Blockly.Versioning.AllUpgradeMaps =
35: "noUpgrade",

// AI2: Added mode on List Mathematical Operations
// AI2: Added "every component" block.
36: [Blockly.Versioning.renameBlockType('lists_minimum_number', 'lists_minimum_value'),
Blockly.Versioning.renameBlockType('lists_maximum_number', 'lists_maximum_value')]

Expand Down Expand Up @@ -3265,7 +3266,7 @@ Blockly.Versioning.AllUpgradeMaps =

// AI2: Added methods JsonTextDecodeWithDictionaries and XMLTextDecodeAsDictionary
7: "noUpgrade",

// AI2: Added methods PatchText, PatchTextWithEncoding, and PatchFile
8: "noUpgrade"

Expand Down
12 changes: 12 additions & 0 deletions appinventor/blocklyeditor/src/warningHandler.js
Expand Up @@ -529,6 +529,18 @@ Blockly.WarningHandler.prototype['checkComponentNotExistsError'] = function(bloc
return false;
};

// check if there exists at least one component of the type of this block's value

Blockly.WarningHandler.prototype['checkComponentTypeNotExistsError'] = function(block) {
var type = this.workspace.componentDb_.getType(block.typeName);
if (type == undefined) {
var errorMessage = Blockly.Msg.ERROR_COMPONENT_DOES_NOT_EXIST;
block.setErrorIconText(errorMessage);
return true;
}
return false;
};

// [lyn, 12/31/2013] Function that determines which component event handlers
// in the main workspace are duplicates. Sets the IAmADuplicate property of each
// duplicate event handler block to true; otherwise sets it to false.
Expand Down
Expand Up @@ -136,6 +136,13 @@
((_ component-name)
(lookup-in-current-form-environment 'component-name))))

;;; (get-all-components comptype)
;;; ==> (filter-type-in-current-form-environment 'comptype)
(define-syntax get-all-components
(syntax-rules ()
((_ component-type)
(filter-type-in-current-form-environment 'component-type))))

;; We'd like to do something like the following which could re-use existing components
;; and thereore avoid overriding property changes that the user might have made via
;; the REPL but it just didn't work. Some components just wouldn't show up. It seemed
Expand Down Expand Up @@ -815,6 +822,14 @@
(gnu.mapping.Environment:get env name)
default-value)))

(define (filter-type-in-current-form-environment type :: gnu.mapping.Symbol)
(define-alias ComponentUtil <com.google.appinventor.components.runtime.util.ComponentUtil>)
(let ((env (if (not (eq? *this-form* #!null))
(*:.form-environment *this-form*)
;; The following is just for testing. In normal situations *this-form* should be non-null
*test-environment*)))
(sanitize-component-data (ComponentUtil:filterComponentsOfType env type))))

(define (delete-from-current-form-environment name :: gnu.mapping.Symbol)
(if (not (eq? *this-form* #!null))
(gnu.mapping.Environment:remove (*:.form-environment *this-form*) name)
Expand Down
Expand Up @@ -659,6 +659,7 @@ private YaVersion() {
// For BLOCKS_LANGUAGE_VERSION 36
// - Add stats blocks
// - Rename lists_*_number to lists_*_value
// - Added "every component" block.
public static final int BLOCKS_LANGUAGE_VERSION = 36;

// ................................. Target SDK Version Number ..................................
Expand Down Expand Up @@ -829,7 +830,7 @@ private YaVersion() {
// For CANVAS_COMPONENT_VERSION 13
// - BackgroundImageinBase64 was added
// For CANVAS_PROPERTY_VERSION 14
// -TAP_THRESHOLD was changed from being constant to user settable
// -TAP_THRESHOLD was changed from being constant to user settable
// -TAP_THRESHOLD renamed to tapThreshold
// -TapThreshold was added
// For CANVAS_COMPONENT_VERSION 15
Expand Down
@@ -0,0 +1,45 @@
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2020 MIT, All rights reserve
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.components.runtime.util;

import com.google.appinventor.components.runtime.Component;

import gnu.mapping.Environment;
import gnu.mapping.LocationEnumeration;
import gnu.mapping.Location;

import java.util.ArrayList;
import java.util.List;

/**
* Util class that handles component filtering from the form environment.
* Java implementation used over Scheme for ease of working with iterators.
* See runtime.scm
*/
public final class ComponentUtil {

private ComponentUtil() {
}

/**
* Filters the form environment and returns all components of a specified
* type.
* @param env the current form environment
* @param type of the component (eg: com.google.appinventor.components.runtime.Label)
* @returns list of components that match the filter
*/
public static List<Object> filterComponentsOfType(Environment env, String type) {
List<Object> components = new ArrayList<>();
LocationEnumeration iterator = env.enumerateAllLocations();
while (iterator.hasNext()) {
Location loc = iterator.next();
Object maybeComponent = loc.get();
if (maybeComponent.getClass().getName().equals(type)) {
components.add(maybeComponent);
}
}
return components;
}
}