diff --git a/README.md b/README.md index 72fc4a5..0ece7a6 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ A Node Red node that offers a visual programming interface, to make programming Moreover the generated Javascript code can be displayed, so you can learn Javascript coding step by step... Thanks to lots of people, for their assistance during the development of this node: -* On the Google Blockly [forum](https://groups.google.com/forum/#!forum/blockly): Andrew Marshall, Mark Friedman, Erik Pasternak, Timo Herngreen, ... -* On the Node-RED [forum](https://discourse.nodered.org/): Julian Knight for thinking out loud, Simon Walters for lots of stuff, ... +* A lot of folks on the Google Blockly [forum](https://groups.google.com/forum/#!forum/blockly) ... +* [Simon Walters](https://github.com/cymplecy) as analyst and righteous judge of this repository. +* [Jeff](https://github.com/jsccjj) for contributing to features like full-screen mode. ## Install Run the following npm command in your Node-RED user directory (typically ~/.node-red): @@ -34,7 +35,7 @@ As soon as an input message arrives, we will set the *'payload'* property of tha The following animation shows how this example flow can be constructed using the Blockly node: -![Config screen](https://raw.githubusercontent.com/bartbutenaers/node-red-contrib-blockly/master/images/blockly_hello_world.gif) +![blockly_2_0_demo](https://user-images.githubusercontent.com/14224149/126551137-5c99d6d9-7097-47c1-8d0b-ea71d27ec7f7.gif) Lots of other examples can be found in the [wiki](https://github.com/bartbutenaers/node-red-contrib-blockly/wiki) pages ... @@ -90,15 +91,17 @@ Lots of other examples can be found in the [wiki](https://github.com/bartbutenae ## Config screen The node's config screen consists out of a series of elements: -![Config screen](https://raw.githubusercontent.com/bartbutenaers/node-red-contrib-blockly/master/images/blockly_screen.png) +![Config screen](https://user-images.githubusercontent.com/14224149/126552977-4b008412-81a3-491e-b74d-d5a32e484bb4.png) 1. The **library** button is similar to the standard *function node*: the code from this Blockly node can be made globally available by adding it to a libary. That library will be stored withing the 'libs' directory in your node-red user directory. -2. The **editor** tabsheet displays a Blockly workspace editor. Here blocks can be added, which will be converted afterwards to Javascript code. -3. The **generated Javascript** tabsheets will display the Javascript code, which is generated based on the blocks in the Blockly editor. This code is readonly, which means you cannot change it! Reason is that it is *not possible* to generate Blockly blocks, starting from Javascript code ... -4. The **language** dropdown offers all available languages. The texts in the blocks will be translated in the specified language. Starting from version 1.1.0 following languages will be available: English, French, Japanese, Dutch. Please see below if you want to add another language to this selection. -5. The Blockly **toolbox** shows all available blocks, grouped by category. -6. The Blockly **editable area** shows all the blocks representing your custom logic. Blocks can be dragged from the toolbox into the editable area. A lot of user interaction is offered in this area: - + When pressing the delete button, the selected blocks will be removed. +2. The **config node** that contains the settings used in this Blockly node. If no config node has been selected, then the default settings will be used. +3. The **editor** tabsheet displays a Blockly workspace editor. Here blocks can be added, which will be converted afterwards to Javascript code. Note the fullscreen icon beside the toolbar label: see [wiki](https://github.com/bartbutenaers/node-red-contrib-blockly/wiki/Use-fullscreen-mode). +4. The **generated Javascript** tabsheet will display the Javascript code, which is generated based on the blocks in the Blockly editor. This code is readonly, which means you cannot change it! Reason is that it is *not possible* to generate Blockly blocks, starting from Javascript code ... +5. The **generated XML** tabsheet will display the XML, that represents the blocks in the Blockly editor. +6. The Blockly **toolbox** shows all available blocks, grouped by category. +7. The Blockly **editable area** shows all the blocks representing your custom logic. Blocks can be dragged from the toolbox into the editable area. A lot of user interaction is offered in this area: + + When pressing the delete button, the selected blocks will be removed. + + When pressing ctrl-f, a search field will be displayed (containing a next and previous button). See [wiki](https://github.com/bartbutenaers/node-red-contrib-blockly/wiki/Search-for-Blockly-nodes). + By clicking and dragging with the mouse, the user can start panning to alter which part of the area is being visualised. + By clicking on a block and dragging with the mouse, the block (and all of its's chained next blocks) will be moved. + By rotating the mouse wheel, you can zoom in/out. @@ -108,30 +111,49 @@ The node's config screen consists out of a series of elements: + ... Remark: the toolbox and the editable area together are called a *'workspace'. -7. The **center* button allows the available blocks to be centered in the middle of visible workspace area. -8. The **zoom in** button. -9. The **zoom out** button. -10. The number of **output ports** for the Blockly node in the Node-Red flow editor. Make sure that this number doesn't exceed the output port number of the *'send'* block (see below). +8. The **backpack** button allows to create a custom list of your favorite blocks. See [wiki](https://github.com/bartbutenaers/node-red-contrib-blockly/wiki/Add-your-favorite-blocks-to-a-backpack). +9. The **zoom to fit** button will automatically zoom the workspace, so all the blocks will become visible at once. +10. The **center* button allows the available blocks to be centered in the middle of visible workspace area. +11. The **zoom in** button. +12. The **zoom out** button. +13. The **thrash can** button. Click on the bin to show the removed blocks, and optionally restore them. +14. The number of **output ports** for the Blockly node in the Node-Red flow editor. Make sure that this number doesn't exceed the output port number of the *'send'* block (see below). ## Node-Red blocks When writing Javascript code in a standard *function node*, some Node-Red functionality can be called from within that code (see function node [API](https://nodered.org/docs/writing-functions#api-reference)). To offer the same functionality in the Blockly editor, a series of extra blocks have been added. These blocks are availble in the 'Node-Red' category in the toolbox: -![Function API](https://raw.githubusercontent.com/bartbutenaers/node-red-contrib-blockly/master/images/blockly_api.png) +![Function API](https://user-images.githubusercontent.com/14224149/126556426-e629916e-5739-482c-84f4-20bd14712a1b.png) 1. **Get** the value of some property in an object. -1. **Set** some property in an object to a specified value. -1. **Send** to the specified output port the message, which is specified on the block input. Make sure that the specified output number doesn't exceed the number of outputs on the Blockly node! -1. The **input message (msg)** exposes the input message that arrive's in the Node-Red flow, on the Blockly node input port. -1. The Node-Red **flow** memory can be used to store data that needs to be shared by all nodes in a flow. -1. The Node-Red **global** memory can be used to store data that needs to be shared by all nodes. -1. The Node-Red **(node)context** memory can be used to store data that needs to be shared by all blockly nodes. -1. **Return** the specified message. This means that we stop processing (i.e. the next blocks will not be executed), and the message will be send on the output port. -1. Show the specified text in the **node status**. Both the status icon shape and color can be specified. -1. **Remove** the node status from the node. -1. **Log** the specified text in the console. Multiple log levels are available (log, error, warning, trace, debug). The warnings and errors will also be displayed in the flow editor debug tab. The trace and debug levels are only used to display details, and are not visible if no logger for those levels is specified in Node-Red. -1. **Clone message** can be used to create a new separate copy of the specified message. -1. Get the specified **node property**. Starting from Node-Red ***version 0.19*** both the Blockly node identifier, and the Blockly node name can be retrieved. -1. Some things can be don when the **node is closed**, most of the time to cleanup stuff. +2. **Set** some property in an object to a specified value. +3. **Send** to the specified output port the message, which is specified on the block input. Make sure that the specified output number doesn't exceed the number of outputs on the Blockly node! +4. The **input message (msg)** exposes the input message that arrive's in the Node-Red flow, on the Blockly node input port. +5. The Node-Red **flow** memory can be used to store data that needs to be shared by all nodes in a flow. +6. The Node-Red **global** memory can be used to store data that needs to be shared by all nodes. +7. The Node-Red **(node)context** memory can be used to store data that needs to be shared by all blockly nodes. +8. **Return** the specified message. This means that we stop processing (i.e. the next blocks will not be executed), and the message will be send on the output port. +9. Show the specified text in the **node status**. Both the status icon shape and color can be specified. +10. **Remove** the node status from the node. +11. **Log** the specified text in the console. Multiple log levels are available (log, error, warning, trace, debug). The warnings and errors will also be displayed in the flow editor debug tab. The trace and debug levels are only used to display details, and are not visible if no logger for those levels is specified in Node-Red. +12. **Clone message** can be used to create a new separate copy of the specified message. +13. Get the specified **node property**. The node identifier, node name and node output count can be get. +14. Specify that the processing of the message has been **completed**, so it can be handled by the Complete-node in the flow. See [wiki](https://github.com/bartbutenaers/node-red-contrib-blockly/wiki/Trigger-a-Complete-node-via-a-Done-block). +15. Some things can be don when the **node is closed**, most of the time to cleanup stuff. + +## Config node settings +The Blocky workspace can be configured via the settings in the selected config node: + +![Config node](https://user-images.githubusercontent.com/14224149/126557196-7d6f06c5-be68-43c9-8686-f707e8f94977.png) + +These settings will be applied to every Blockly node where this config node has been selected: ++ Specify whether a **trash can** icon needs to be displayed in the workspace. ++ Specify whether **comments** can be added to blocks in the workspace. ++ Specify whether the 4 **zoom** icons need to to be displayed in the workspace. ++ Specify whether a **backpack** icon need to be be displayed in the workspace. ++ Customize the toolbox **categories** to fit your needs. CAUTION: this is only for advanced users and need to be done with care!! When this checkbox is activated, the "Categories" tabsheet will become enabled. See [wiki](https://github.com/bartbutenaers/node-red-contrib-blockly/wiki/Customizing-the-toolbox). ++ Specify the **language** that needs to be used in the blocks in the workspace. ++ Specify the location of the **toolbox**, relative to the workspace. ++ Specify the **renderer**, which determines how the blocks need to be drawn. ## Need a change ... When you need a change in this node, you can create a new Github issue. A couple of remarks about this: diff --git a/blockly.html b/blockly.html index 14a873f..1cdea29 100644 --- a/blockly.html +++ b/blockly.html @@ -10,139 +10,398 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - + + + + \ No newline at end of file diff --git a/blockly_config.js b/blockly_config.js new file mode 100644 index 0000000..3655bfd --- /dev/null +++ b/blockly_config.js @@ -0,0 +1,26 @@ +/** + * Copyright 2021 Bart Butenaers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + module.exports = function(RED) { + var settings = RED.settings; + + function BlocklyConfigNode(config) { + RED.nodes.createNode(this, config); + //this.showTrashcan = config.showTrashcan; + //this.renderer = config.renderer; + } + + RED.nodes.registerType("blockly-config", BlocklyConfigNode); +} \ No newline at end of file diff --git a/icons/puzzle.png b/icons/puzzle.png deleted file mode 100644 index b8cbc1c..0000000 Binary files a/icons/puzzle.png and /dev/null differ diff --git a/lib/basic/blockly_accessible_compressed.js b/lib/basic/blockly_accessible_compressed.js deleted file mode 100644 index ec05725..0000000 --- a/lib/basic/blockly_accessible_compressed.js +++ /dev/null @@ -1,1735 +0,0 @@ -// Do not edit this file; automatically generated by build.py. -'use strict'; - -var $jscomp=$jscomp||{};$jscomp.scope={};var COMPILED=!0,goog=goog||{};goog.global=this;goog.isDef=function(a){return void 0!==a};goog.isString=function(a){return"string"==typeof a};goog.isBoolean=function(a){return"boolean"==typeof a};goog.isNumber=function(a){return"number"==typeof a}; -goog.exportPath_=function(a,b,c){a=a.split(".");c=c||goog.global;a[0]in c||"undefined"==typeof c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)!a.length&&goog.isDef(b)?c[d]=b:c=c[d]&&c[d]!==Object.prototype[d]?c[d]:c[d]={}}; -goog.define=function(a,b){if(!COMPILED){var c=goog.global.CLOSURE_UNCOMPILED_DEFINES,d=goog.global.CLOSURE_DEFINES;c&&void 0===c.nodeType&&Object.prototype.hasOwnProperty.call(c,a)?b=c[a]:d&&void 0===d.nodeType&&Object.prototype.hasOwnProperty.call(d,a)&&(b=d[a])}goog.exportPath_(a,b)};goog.DEBUG=!1;goog.LOCALE="en";goog.TRUSTED_SITE=!0;goog.STRICT_MODE_COMPATIBLE=!1;goog.DISALLOW_TEST_ONLY_CODE=COMPILED&&!goog.DEBUG;goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING=!1; -goog.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide can not be used within a module.");if(!COMPILED&&goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');goog.constructNamespace_(a)};goog.constructNamespace_=function(a,b){if(!COMPILED){delete goog.implicitNamespaces_[a];for(var c=a;(c=c.substring(0,c.lastIndexOf(".")))&&!goog.getObjectByName(c);)goog.implicitNamespaces_[c]=!0}goog.exportPath_(a,b)};goog.VALID_MODULE_RE_=/^[a-zA-Z_$][a-zA-Z0-9._$]*$/; -goog.module=function(a){if(!goog.isString(a)||!a||-1==a.search(goog.VALID_MODULE_RE_))throw Error("Invalid module identifier");if(!goog.isInGoogModuleLoader_())throw Error("Module "+a+" has been loaded incorrectly. Note, modules cannot be loaded as normal scripts. They require some kind of pre-processing step. You're likely trying to load a module via a script tag or as a part of a concatenated bundle without rewriting the module. For more info see: https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide."); -if(goog.moduleLoaderState_.moduleName)throw Error("goog.module may only be called once per module.");goog.moduleLoaderState_.moduleName=a;if(!COMPILED){if(goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');delete goog.implicitNamespaces_[a]}}; -goog.module.get=function(a){if(!COMPILED&&a in goog.loadedModules_){if(goog.loadedModules_[a].type!=goog.ModuleType.GOOG)throw Error("Can only goog.module.get for goog.modules.");if(goog.loadedModules_[a].moduleId!=a)throw Error("Cannot goog.module.get by path.");}return goog.module.getInternal_(a)};goog.module.getInternal_=function(a){if(!COMPILED){if(a in goog.loadedModules_)return goog.loadedModules_[a].exports;if(!goog.implicitNamespaces_[a])return a=goog.getObjectByName(a),null!=a?a:null}return null}; -goog.ModuleType={ES6:"es6",GOOG:"goog"};goog.moduleLoaderState_=null;goog.isInModuleLoader_=function(){return goog.isInGoogModuleLoader_()||goog.isInEs6ModuleLoader_()};goog.isInGoogModuleLoader_=function(){return!!goog.moduleLoaderState_&&goog.moduleLoaderState_.type==goog.ModuleType.GOOG};goog.isInEs6ModuleLoader_=function(){return!!goog.moduleLoaderState_&&goog.moduleLoaderState_.type==goog.ModuleType.ES6};goog.getModulePath_=function(){return goog.moduleLoaderState_&&goog.moduleLoaderState_.path}; -goog.module.declareLegacyNamespace=function(){if(!COMPILED&&!goog.isInGoogModuleLoader_())throw Error("goog.module.declareLegacyNamespace must be called from within a goog.module");if(!COMPILED&&!goog.moduleLoaderState_.moduleName)throw Error("goog.module must be called prior to goog.module.declareLegacyNamespace.");goog.moduleLoaderState_.declareLegacyNamespace=!0}; -goog.setTestOnly=function(a){if(goog.DISALLOW_TEST_ONLY_CODE)throw a=a||"",Error("Importing test-only code into non-debug environment"+(a?": "+a:"."));};goog.forwardDeclare=function(a){};COMPILED||(goog.isProvided_=function(a){return a in goog.loadedModules_||!goog.implicitNamespaces_[a]&&goog.isDefAndNotNull(goog.getObjectByName(a))},goog.implicitNamespaces_={"goog.module":!0}); -goog.getObjectByName=function(a,b){a=a.split(".");b=b||goog.global;for(var c=0;c>>0);goog.uidCounter_=0;goog.getHashCode=goog.getUid; -goog.removeHashCode=goog.removeUid;goog.cloneObject=function(a){var b=goog.typeOf(a);if("object"==b||"array"==b){if("function"===typeof a.clone)return a.clone();b="array"==b?[]:{};for(var c in a)b[c]=goog.cloneObject(a[c]);return b}return a};goog.bindNative_=function(a,b,c){return a.call.apply(a.bind,arguments)}; -goog.bindJs_=function(a,b,c){if(!a)throw Error();if(2Number(a[1])?!1:b('(()=>{"use strict";class X{constructor(){if(new.target!=String)throw 1;this.x=42}}let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof String))throw 1;for(const a of[2,3]){if(a==2)continue;function f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()==3}})()')});a("es6-impl",function(){return!0});a("es7",function(){return b("2 ** 2 == 4")}); -a("es8",function(){return b("async () => 1, true")});a("es9",function(){return b("({...rest} = {}), true")});a("es_next",function(){return b("({...rest} = {}), true")});return c},goog.Transpiler.prototype.needsTranspile=function(a,b){if("always"==goog.TRANSPILE)return!0;if("never"==goog.TRANSPILE)return!1;this.requiresTranspilation_||(this.requiresTranspilation_=this.createRequiresTranspilation_());if(a in this.requiresTranspilation_)return this.requiresTranspilation_[a];throw Error("Unknown language mode: "+ -a);},goog.Transpiler.prototype.transpile=function(a,b){return goog.transpile_(a,b)},goog.transpiler_=new goog.Transpiler,goog.protectScriptTag_=function(a){return a.replace(/<\/(SCRIPT)/ig,"\\x3c/$1")},goog.DebugLoader_=function(){this.dependencies_={};this.idToPath_={};this.written_={};this.loadingDeps_=[];this.depsToLoad_=[];this.paused_=!1;this.factory_=new goog.DependencyFactory(goog.transpiler_);this.deferredCallbacks_={};this.deferredQueue_=[]},goog.DebugLoader_.prototype.bootstrap=function(a, -b){function c(){d&&(goog.global.setTimeout(d,0),d=null)}var d=b;if(a.length){b=[];for(var e=0;e\x3c/script>")}else{var d=b.createElement("script");d.defer=goog.Dependency.defer_;d.async=!1;d.type="text/javascript";goog.DebugLoader_.IS_OLD_IE_?(a.pause(),d.onreadystatechange=function(){if("loaded"==d.readyState||"complete"==d.readyState)a.loaded(),a.resume()}):d.onload=function(){d.onload= -null;a.loaded()};d.src=this.path;b.head.appendChild(d)}}else goog.logToConsole_("Cannot use default debug loader outside of HTML documents."),"deps.js"==this.relativePath?(goog.logToConsole_("Consider setting CLOSURE_IMPORT_SCRIPT before loading base.js, or seting CLOSURE_NO_DEPS to true."),a.loaded()):a.pause()},goog.Es6ModuleDependency=function(a,b,c,d){goog.Dependency.call(this,a,b,[],c,d)},goog.inherits(goog.Es6ModuleDependency,goog.Dependency),goog.Es6ModuleDependency.prototype.load=function(a){function b(a, -b){b?d.write('