blockly.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. // Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
  2. // Secretary of Defense (Personnel & Readiness).
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  5. // in compliance with the License. You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software distributed under the License
  10. // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  11. // or implied. See the License for the specific language governing permissions and limitations under
  12. // the License.
  13. /// vwf/model/blockly.js is the driver for the Google blockly visual programming language.
  14. ///
  15. /// @module vwf/model/blockly
  16. /// @requires vwf/model ... and others
  17. define( [ "module", "vwf/model", "vwf/utility",
  18. "vwf/model/blockly/JS-Interpreter/acorn",
  19. "vwf/model/blockly/blockly_compressed", "vwf/model/blockly/blocks_compressed",
  20. "vwf/model/blockly/javascript_compressed", "vwf/model/blockly/msg/js/en"
  21. ],
  22. function( module, model, utility, acorn, Blockly ) {
  23. var self;
  24. return model.load( module, {
  25. // == Module Definition ====================================================================
  26. // -- initialize ---------------------------------------------------------------------------
  27. initialize: function( options ) {
  28. self = this;
  29. this.arguments = Array.prototype.slice.call( arguments );
  30. if ( options === undefined ) {
  31. options = {};
  32. }
  33. this.state = {
  34. "nodes": {},
  35. "scenes": {},
  36. "prototypes": {},
  37. "blockly": { "node": undefined },
  38. "executingBlocks": {},
  39. "executionHalted": false,
  40. "createNode": function( nodeID, childID, childExtendsID, childImplementsIDs,
  41. childSource, childType, childIndex, childName, callback ) {
  42. return {
  43. "parentID": nodeID,
  44. "ID": childID,
  45. "extendsID": childExtendsID,
  46. "implementsIDs": childImplementsIDs,
  47. "source": childSource,
  48. "type": childType,
  49. "name": childName,
  50. "blocks": "<xml></xml>",
  51. "toolbox": undefined,
  52. "defaultXml": undefined,
  53. "code": undefined,
  54. "lastLineExeTime": undefined,
  55. "timeBetweenLines": 1,
  56. "interpreter": undefined,
  57. "interpreterStatus": ""
  58. };
  59. }
  60. };
  61. // turns on logger debugger console messages
  62. this.debug = {
  63. "creation": false,
  64. "initializing": false,
  65. "parenting": false,
  66. "deleting": false,
  67. "properties": false,
  68. "setting": false,
  69. "getting": false,
  70. "methods": false,
  71. "prototypes": false
  72. };
  73. // interpreter documentation
  74. // https://neil.fraser.name/software/JS-Interpreter/docs.html
  75. },
  76. creatingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
  77. childSource, childType, childIndex, childName, callback ) {
  78. // If the parent nodeID is 0, this node is attached directly to the root and is therefore either
  79. // the scene or a prototype. In either of those cases, save the uri of the new node
  80. var childURI = ( nodeID === 0 ? childIndex : undefined );
  81. var appID = this.kernel.application();
  82. if ( this.debug.creation ) {
  83. this.logger.infox( "creatingNode", nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childName );
  84. }
  85. // If the node being created is a prototype, construct it and add it to the array of prototypes,
  86. // and then return
  87. var prototypeID = utility.ifPrototypeGetId( appID, this.state.prototypes, nodeID, childID );
  88. if ( prototypeID !== undefined ) {
  89. if ( this.debug.prototypes ) {
  90. this.logger.infox( "prototype: ", prototypeID );
  91. }
  92. this.state.prototypes[ prototypeID ] = {
  93. parentID: nodeID,
  94. ID: childID,
  95. extendsID: childExtendsID,
  96. implementsID: childImplementsIDs,
  97. source: childSource,
  98. type: childType,
  99. uri: childURI,
  100. name: childName
  101. };
  102. return;
  103. }
  104. var node = this.state.nodes[ childID ];
  105. if ( node === undefined && isBlockly3Node( childID ) ) {
  106. this.state.nodes[ childID ] = node = this.state.createNode( nodeID, childID, childExtendsID, childImplementsIDs,
  107. childSource, childType, childIndex, childName, callback );
  108. }
  109. },
  110. initializingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
  111. childSource, childType, childIndex, childName ) {
  112. if ( this.debug.initializing ) {
  113. this.logger.infox( "initializingNode", nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childName );
  114. }
  115. },
  116. deletingNode: function( nodeID ) {
  117. if ( this.debug.deleting ) {
  118. this.logger.infox( "deletingNode", nodeID );
  119. }
  120. if ( this.state.nodes[ nodeID ] !== undefined ) {
  121. delete this.state.nodes[ nodeID ];
  122. }
  123. },
  124. addingChild: function( nodeID, childID, childName ) {
  125. if ( this.debug.parenting ) {
  126. this.logger.infox( "addingChild", nodeID, childID, childName );
  127. }
  128. },
  129. movingChild: function( nodeID, childID, childName ) {
  130. if ( this.debug.parenting ) {
  131. this.logger.infox( "movingChild", nodeID, childID, childName );
  132. }
  133. },
  134. removingChild: function( nodeID, childID, childName ) {
  135. if ( this.debug.parenting ) {
  136. this.logger.infox( "removingChild", nodeID, childID, childName );
  137. }
  138. },
  139. // -- creatingProperty ---------------------------------------------------------------------
  140. creatingProperty: function( nodeID, propertyName, propertyValue ) {
  141. var value = undefined;
  142. if ( this.debug.properties ) {
  143. this.logger.infox( "C === creatingProperty ", nodeID, propertyName, propertyValue );
  144. }
  145. var node = this.state.nodes[ nodeID ];
  146. if ( node !== undefined ) {
  147. value = this.settingProperty( nodeID, propertyName, propertyValue );
  148. }
  149. return value;
  150. },
  151. // -- initializingProperty -----------------------------------------------------------------
  152. initializingProperty: function( nodeID, propertyName, propertyValue ) {
  153. var value = undefined;
  154. if ( this.debug.properties ) {
  155. this.logger.infox( " I === initializingProperty ", nodeID, propertyName, propertyValue );
  156. }
  157. var node = this.state.nodes[ nodeID ];
  158. if ( node !== undefined ) {
  159. value = this.settingProperty( nodeID, propertyName, propertyValue );
  160. }
  161. return value;
  162. },
  163. // -- settingProperty ----------------------------------------------------------------------
  164. settingProperty: function( nodeID, propertyName, propertyValue ) {
  165. if ( this.debug.properties || this.debug.setting ) {
  166. this.logger.infox( " S === settingProperty ", nodeID, propertyName, propertyValue );
  167. }
  168. var node = this.state.nodes[ nodeID ]; // { name: childName, glgeObject: undefined }
  169. var value = undefined;
  170. if ( ( node !== undefined ) && ( utility.validObject( propertyValue ) ) ) {
  171. switch ( propertyName ) {
  172. case "blockly_code":
  173. value = node.code = propertyValue;
  174. break;
  175. case "blockly_xml":
  176. value = node.blocks = propertyValue;
  177. break;
  178. case "blockly_executing":
  179. var exe = Boolean( propertyValue );
  180. if ( exe ) {
  181. if ( this.state.executingBlocks === undefined ) {
  182. this.state.executingBlocks = {};
  183. }
  184. if ( this.state.executingBlocks[ nodeID ] === undefined ) {
  185. getJavaScript( node );
  186. this.state.executingBlocks[ nodeID ] = node;
  187. }
  188. setToolboxBlockEnable( false );
  189. } else {
  190. if ( this.state.executingBlocks && this.state.executingBlocks[ nodeID ] !== undefined ) {
  191. delete this.state.executingBlocks[ nodeID ];
  192. var count = Object.keys( this.state.executingBlocks ).length;
  193. if ( count === 0 ) {
  194. this.state.executingBlocks = {};
  195. setToolboxBlockEnable( true );
  196. }
  197. }
  198. }
  199. break;
  200. case "blockly_toolbox":
  201. node.toolbox = propertyValue;
  202. break;
  203. case "blockly_defaultXml":
  204. node.defaultXml = propertyValue;
  205. break;
  206. default:
  207. break;
  208. }
  209. }
  210. return value;
  211. },
  212. // -- gettingProperty ----------------------------------------------------------------------
  213. gettingProperty: function( nodeID, propertyName ) {
  214. if ( this.debug.properties || this.debug.getting ) {
  215. this.logger.infox( " G === gettingProperty ", nodeID, propertyName );
  216. }
  217. var node = this.state.nodes[ nodeID ];
  218. var value = undefined;
  219. if ( node !== undefined ) {
  220. switch ( propertyName ) {
  221. case "blockly_executing":
  222. value = ( this.state.executingBlocks && this.state.executingBlocks[ nodeID ] !== undefined );
  223. break;
  224. case "blockly_code":
  225. value = node.code;
  226. break;
  227. case "blockly_xml":
  228. value = node.blocks;
  229. break;
  230. }
  231. }
  232. return value;
  233. },
  234. // TODO: deletingMethod
  235. // -- callingMethod --------------------------------------------------------------------------
  236. callingMethod: function( nodeID, methodName /* [, parameter1, parameter2, ... ] */ ) { // TODO: parameters
  237. var node = this.state.nodes[ nodeID ];
  238. if ( this.debug.methods ) {
  239. this.logger.infox( " M === callingMethod ", nodeID, methodName );
  240. }
  241. if ( nodeID == this.kernel.application() ) {
  242. switch ( methodName ) {
  243. case "stopAllExecution":
  244. for ( var id in this.state.executingBlocks ) {
  245. this.state.executingBlocks[ id ].interpreterStatus = "completed";
  246. this.kernel.setProperty( id, 'blockly_executing', false );
  247. this.kernel.fireEvent( id, "blocklyStopped", [ true ] );
  248. }
  249. break;
  250. case "startAllExecution":
  251. for ( var id in this.state.nodes ) {
  252. this.kernel.setProperty( id, 'blockly_executing', true );
  253. this.kernel.fireEvent( id, "blocklyStarted", [ true ] );
  254. }
  255. break;
  256. }
  257. } else if ( node !== undefined ) {
  258. switch ( methodName ) {
  259. case "blocklyClear":
  260. if ( Blockly.mainWorkspace ) {
  261. Blockly.mainWorkspace.clear();
  262. this.kernel.setProperty( nodeID, "blockly_xml", '<xml></xml>' );
  263. }
  264. break;
  265. }
  266. }
  267. },
  268. // TODO: creatingEvent, deltetingEvent, firingEvent
  269. // -- executing ------------------------------------------------------------------------------
  270. //executing: function( nodeID, scriptText, scriptType ) {
  271. // return undefined;
  272. //},
  273. // == ticking =============================================================================
  274. ticking: function( vwfTime ) {
  275. if ( this.state.executingBlocks !== undefined ) {
  276. var blocklyNode = undefined;
  277. for ( var nodeID in this.state.executingBlocks ) {
  278. blocklyNode = this.state.executingBlocks[ nodeID ];
  279. var executeNextLine = false;
  280. if ( blocklyNode.interpreter === undefined ||
  281. blocklyNode.interpreterStatus === "completed" ) {
  282. blocklyNode.interpreter = createInterpreter( acorn, blocklyNode.code );
  283. blocklyNode.interpreterStatus = "created";
  284. blocklyNode.lastLineExeTime = vwfTime;
  285. executeNextLine = true;
  286. } else {
  287. var elaspedTime = vwfTime - blocklyNode.lastLineExeTime;
  288. if ( elaspedTime >= blocklyNode.timeBetweenLines ) {
  289. executeNextLine = true;
  290. blocklyNode.lastLineExeTime = vwfTime;
  291. }
  292. }
  293. if ( executeNextLine ) {
  294. self.state.executionHalted = false;
  295. nextStep( blocklyNode );
  296. this.kernel.fireEvent( nodeID, "blocklyExecuted", [ blocklyNode.interpreter.value ] );
  297. }
  298. }
  299. }
  300. }
  301. } );
  302. function getPrototypes( extendsID ) {
  303. var prototypes = [];
  304. var id = extendsID;
  305. while ( id !== undefined ) {
  306. prototypes.push( id );
  307. id = self.kernel.prototype( id );
  308. }
  309. return prototypes;
  310. }
  311. function isBlockly3Node( nodeID ) {
  312. return self.kernel.test( nodeID,
  313. "self::element(*,'http://vwf.example.com/blockly/controller.vwf')",
  314. nodeID );
  315. }
  316. function isBlocklyNode( implementsIDs ) {
  317. var found = false;
  318. if ( implementsIDs ) {
  319. for ( var i = 0; i < implementsIDs.length && !found; i++ ) {
  320. found = ( implementsIDs[i] == "http://vwf.example.com/blockly/controller.vwf" );
  321. }
  322. }
  323. return found;
  324. }
  325. function getJavaScript( node ) {
  326. var xml = Blockly.Xml.workspaceToDom( Blockly.getMainWorkspace() );
  327. Blockly.JavaScript.vwfID = node.ID;
  328. if ( xml ) {
  329. node.blocks = Blockly.Xml.domToText( xml );
  330. }
  331. node.code = Blockly.JavaScript.workspaceToCode();
  332. }
  333. function setToolboxBlockEnable( enable ) {
  334. if ( Blockly.Toolbox.flyout_ !== undefined && Blockly.Toolbox.flyout_.workspace_ !== undefined ) {
  335. var blocks = Blockly.Toolbox.flyout_.workspace_.getTopBlocks( false );
  336. if ( blocks ) {
  337. for ( var i = 0; i < blocks.length; i++ ) {
  338. blocks[ i ].setDisabled( !enable );
  339. }
  340. }
  341. } else if ( Blockly.mainWorkspace && Blockly.mainWorkspace.flyout_ && Blockly.mainWorkspace.flyout_.workspace_ ){
  342. var blocks = Blockly.mainWorkspace.flyout_.workspace_.getTopBlocks( false );
  343. if ( blocks ) {
  344. for ( var i = 0; i < blocks.length; i++ ) {
  345. blocks[ i ].setDisabled( !enable );
  346. }
  347. }
  348. }
  349. }
  350. function nextStep( node ) {
  351. if ( node.interpreter !== undefined ) {
  352. var stepType = node.interpreter.step();
  353. while ( stepType && !self.state.executionHalted ) {
  354. if ( stepType === "stepProgram" ) {
  355. if ( node.interpreterStatus === "created" ) {
  356. self.kernel.fireEvent( node.ID, "blocklyStarted", [ true ] );
  357. node.interpreterStatus = "started";
  358. }
  359. }
  360. stepType = node.interpreter.step();
  361. }
  362. if ( stepType === false ) {
  363. if ( node.interpreterStatus === "started" ) {
  364. self.kernel.setProperty( node.ID, "blockly_executing", false );
  365. self.kernel.fireEvent( node.ID, "blocklyStopped", [ true ] );
  366. node.interpreterStatus = "completed";
  367. }
  368. }
  369. }
  370. }
  371. function createInterpreter( acorn, code ) {
  372. var initFunc = function( interpreter, scope ) {
  373. var vwfKernelFunctions, i;
  374. var myVwf = interpreter.createObject( interpreter.OBJECT );
  375. interpreter.setProperty( scope, 'vwf', myVwf );
  376. vwfKernelFunctions = [ 'setProperty', 'getProperty' ];
  377. for ( i = 0; i < vwfKernelFunctions.length; i++ ) {
  378. var wrapper = ( function( nativeFunc ) {
  379. return function() {
  380. var parms = [];
  381. for ( var j = 0; j < arguments.length; j++) {
  382. parms.push( arguments[ j ].toString() );
  383. }
  384. self.state.executionHalted = true;
  385. return interpreter.createPrimitive( nativeFunc.apply( vwf, parms ) );
  386. };
  387. } )( vwf[ vwfKernelFunctions[ i ] ] );
  388. interpreter.setProperty( myVwf, vwfKernelFunctions[ i ], interpreter.createNativeFunction( wrapper ) );
  389. }
  390. vwfKernelFunctions = [ 'callMethod', 'fireEvent' ];
  391. for ( i = 0; i < vwfKernelFunctions.length; i++ ) {
  392. var wrapper = ( function( nativeFunc ) {
  393. return function() {
  394. var parms = [];
  395. for ( var j = 0; j < arguments.length; j++) {
  396. if ( j >= 2 ) {
  397. if ( arguments[ j ].type === "object" ) {
  398. parms.push( setArgsFromObj( arguments[ j ] ) );
  399. }
  400. } else {
  401. parms.push( arguments[ j ].toString() );
  402. }
  403. }
  404. self.state.executionHalted = true;
  405. return interpreter.createPrimitive( nativeFunc.apply( vwf, parms ) );
  406. };
  407. } )( vwf[ vwfKernelFunctions[ i ] ] );
  408. interpreter.setProperty( myVwf, vwfKernelFunctions[ i ], interpreter.createNativeFunction( wrapper ) );
  409. }
  410. };
  411. return new Interpreter( acorn, code, initFunc );
  412. }
  413. function setArgsFromObj( object ) {
  414. var args = [];
  415. for ( var i in object.properties ) {
  416. if ( object.properties[ i ].type === "object" ) {
  417. args.push( setArgsFromObj( object.properties[ i ] ) );
  418. } else {
  419. args.push( object.properties[ i ].data );
  420. }
  421. }
  422. return args;
  423. }
  424. } );