factory_utils.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. /**
  2. * @license
  3. * Blockly Demos: Block Factory
  4. *
  5. * Copyright 2016 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview FactoryUtils is a namespace that holds block starter code
  22. * generation functions shared by the Block Factory, Workspace Factory, and
  23. * Exporter applications within Blockly Factory. Holds functions to generate
  24. * block definitions and generator stubs and to create and download files.
  25. *
  26. * @author fraser@google.com (Neil Fraser), quachtina96 (Tina Quach)
  27. */
  28. 'use strict';
  29. /**
  30. * Namespace for FactoryUtils.
  31. */
  32. goog.provide('FactoryUtils');
  33. /**
  34. * Get block definition code for the current block.
  35. * @param {string} blockType Type of block.
  36. * @param {!Blockly.Block} rootBlock RootBlock from main workspace in which
  37. * user uses Block Factory Blocks to create a custom block.
  38. * @param {string} format 'JSON' or 'JavaScript'.
  39. * @param {!Blockly.Workspace} workspace Where the root block lives.
  40. * @return {string} Block definition.
  41. */
  42. FactoryUtils.getBlockDefinition = function(blockType, rootBlock, format, workspace) {
  43. blockType = blockType.replace(/\W/g, '_').replace(/^(\d)/, '_\\1');
  44. switch (format) {
  45. case 'JSON':
  46. var code = FactoryUtils.formatJson_(blockType, rootBlock);
  47. break;
  48. case 'JavaScript':
  49. var code = FactoryUtils.formatJavaScript_(blockType, rootBlock, workspace);
  50. break;
  51. }
  52. return code;
  53. };
  54. /**
  55. * Get the generator code for a given block.
  56. * @param {!Blockly.Block} block Rendered block in preview workspace.
  57. * @param {string} generatorLanguage 'JavaScript', 'Python', 'PHP', 'Lua',
  58. * 'Dart'.
  59. * @return {string} Generator code for multiple blocks.
  60. */
  61. FactoryUtils.getGeneratorStub = function(block, generatorLanguage) {
  62. function makeVar(root, name) {
  63. name = name.toLowerCase().replace(/\W/g, '_');
  64. return ' var ' + root + '_' + name;
  65. }
  66. // The makevar function lives in the original update generator.
  67. var language = generatorLanguage;
  68. var code = [];
  69. code.push("Blockly." + language + "['" + block.type +
  70. "'] = function(block) {");
  71. // Generate getters for any fields or inputs.
  72. for (var i = 0, input; input = block.inputList[i]; i++) {
  73. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  74. var name = field.name;
  75. if (!name) {
  76. continue;
  77. }
  78. if (field instanceof Blockly.FieldVariable) {
  79. // Subclass of Blockly.FieldDropdown, must test first.
  80. code.push(makeVar('variable', name) +
  81. " = Blockly." + language +
  82. ".variableDB_.getName(block.getFieldValue('" + name +
  83. "'), Blockly.Variables.NAME_TYPE);");
  84. } else if (field instanceof Blockly.FieldAngle) {
  85. // Subclass of Blockly.FieldTextInput, must test first.
  86. code.push(makeVar('angle', name) +
  87. " = block.getFieldValue('" + name + "');");
  88. } else if (Blockly.FieldDate && field instanceof Blockly.FieldDate) {
  89. // Blockly.FieldDate may not be compiled into Blockly.
  90. code.push(makeVar('date', name) +
  91. " = block.getFieldValue('" + name + "');");
  92. } else if (field instanceof Blockly.FieldColour) {
  93. code.push(makeVar('colour', name) +
  94. " = block.getFieldValue('" + name + "');");
  95. } else if (field instanceof Blockly.FieldCheckbox) {
  96. code.push(makeVar('checkbox', name) +
  97. " = block.getFieldValue('" + name + "') == 'TRUE';");
  98. } else if (field instanceof Blockly.FieldDropdown) {
  99. code.push(makeVar('dropdown', name) +
  100. " = block.getFieldValue('" + name + "');");
  101. } else if (field instanceof Blockly.FieldNumber) {
  102. code.push(makeVar('number', name) +
  103. " = block.getFieldValue('" + name + "');");
  104. } else if (field instanceof Blockly.FieldTextInput) {
  105. code.push(makeVar('text', name) +
  106. " = block.getFieldValue('" + name + "');");
  107. }
  108. }
  109. var name = input.name;
  110. if (name) {
  111. if (input.type == Blockly.INPUT_VALUE) {
  112. code.push(makeVar('value', name) +
  113. " = Blockly." + language + ".valueToCode(block, '" + name +
  114. "', Blockly." + language + ".ORDER_ATOMIC);");
  115. } else if (input.type == Blockly.NEXT_STATEMENT) {
  116. code.push(makeVar('statements', name) +
  117. " = Blockly." + language + ".statementToCode(block, '" +
  118. name + "');");
  119. }
  120. }
  121. }
  122. // Most languages end lines with a semicolon. Python does not.
  123. var lineEnd = {
  124. 'JavaScript': ';',
  125. 'Python': '',
  126. 'PHP': ';',
  127. 'Dart': ';'
  128. };
  129. code.push(" // TODO: Assemble " + language + " into code variable.");
  130. if (block.outputConnection) {
  131. code.push(" var code = '...';");
  132. code.push(" // TODO: Change ORDER_NONE to the correct strength.");
  133. code.push(" return [code, Blockly." + language + ".ORDER_NONE];");
  134. } else {
  135. code.push(" var code = '..." + (lineEnd[language] || '') + "\\n';");
  136. code.push(" return code;");
  137. }
  138. code.push("};");
  139. return code.join('\n');
  140. };
  141. /**
  142. * Update the language code as JSON.
  143. * @param {string} blockType Name of block.
  144. * @param {!Blockly.Block} rootBlock Factory_base block.
  145. * @return {string} Generanted language code.
  146. * @private
  147. */
  148. FactoryUtils.formatJson_ = function(blockType, rootBlock) {
  149. var JS = {};
  150. // Type is not used by Blockly, but may be used by a loader.
  151. JS.type = blockType;
  152. // Generate inputs.
  153. var message = [];
  154. var args = [];
  155. var contentsBlock = rootBlock.getInputTargetBlock('INPUTS');
  156. var lastInput = null;
  157. while (contentsBlock) {
  158. if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) {
  159. var fields = FactoryUtils.getFieldsJson_(
  160. contentsBlock.getInputTargetBlock('FIELDS'));
  161. for (var i = 0; i < fields.length; i++) {
  162. if (typeof fields[i] == 'string') {
  163. message.push(fields[i].replace(/%/g, '%%'));
  164. } else {
  165. args.push(fields[i]);
  166. message.push('%' + args.length);
  167. }
  168. }
  169. var input = {type: contentsBlock.type};
  170. // Dummy inputs don't have names. Other inputs do.
  171. if (contentsBlock.type != 'input_dummy') {
  172. input.name = contentsBlock.getFieldValue('INPUTNAME');
  173. }
  174. var check = JSON.parse(
  175. FactoryUtils.getOptTypesFrom(contentsBlock, 'TYPE') || 'null');
  176. if (check) {
  177. input.check = check;
  178. }
  179. var align = contentsBlock.getFieldValue('ALIGN');
  180. if (align != 'LEFT') {
  181. input.align = align;
  182. }
  183. args.push(input);
  184. message.push('%' + args.length);
  185. lastInput = contentsBlock;
  186. }
  187. contentsBlock = contentsBlock.nextConnection &&
  188. contentsBlock.nextConnection.targetBlock();
  189. }
  190. // Remove last input if dummy and not empty.
  191. if (lastInput && lastInput.type == 'input_dummy') {
  192. var fields = lastInput.getInputTargetBlock('FIELDS');
  193. if (fields && FactoryUtils.getFieldsJson_(fields).join('').trim() != '') {
  194. var align = lastInput.getFieldValue('ALIGN');
  195. if (align != 'LEFT') {
  196. JS.lastDummyAlign0 = align;
  197. }
  198. args.pop();
  199. message.pop();
  200. }
  201. }
  202. JS.message0 = message.join(' ');
  203. if (args.length) {
  204. JS.args0 = args;
  205. }
  206. // Generate inline/external switch.
  207. if (rootBlock.getFieldValue('INLINE') == 'EXT') {
  208. JS.inputsInline = false;
  209. } else if (rootBlock.getFieldValue('INLINE') == 'INT') {
  210. JS.inputsInline = true;
  211. }
  212. // Generate output, or next/previous connections.
  213. switch (rootBlock.getFieldValue('CONNECTIONS')) {
  214. case 'LEFT':
  215. JS.output =
  216. JSON.parse(
  217. FactoryUtils.getOptTypesFrom(rootBlock, 'OUTPUTTYPE') || 'null');
  218. break;
  219. case 'BOTH':
  220. JS.previousStatement =
  221. JSON.parse(
  222. FactoryUtils.getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null');
  223. JS.nextStatement =
  224. JSON.parse(
  225. FactoryUtils.getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null');
  226. break;
  227. case 'TOP':
  228. JS.previousStatement =
  229. JSON.parse(
  230. FactoryUtils.getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null');
  231. break;
  232. case 'BOTTOM':
  233. JS.nextStatement =
  234. JSON.parse(
  235. FactoryUtils.getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null');
  236. break;
  237. }
  238. // Generate colour.
  239. var colourBlock = rootBlock.getInputTargetBlock('COLOUR');
  240. if (colourBlock && !colourBlock.disabled) {
  241. var hue = parseInt(colourBlock.getFieldValue('HUE'), 10);
  242. JS.colour = hue;
  243. }
  244. JS.tooltip = '';
  245. JS.helpUrl = 'http://www.example.com/';
  246. return JSON.stringify(JS, null, ' ');
  247. };
  248. /**
  249. * Update the language code as JavaScript.
  250. * @param {string} blockType Name of block.
  251. * @param {!Blockly.Block} rootBlock Factory_base block.
  252. * @param {!Blockly.Workspace} workspace Where the root block lives.
  253. * @return {string} Generated language code.
  254. * @private
  255. */
  256. FactoryUtils.formatJavaScript_ = function(blockType, rootBlock, workspace) {
  257. var code = [];
  258. code.push("Blockly.Blocks['" + blockType + "'] = {");
  259. code.push(" init: function() {");
  260. // Generate inputs.
  261. var TYPES = {'input_value': 'appendValueInput',
  262. 'input_statement': 'appendStatementInput',
  263. 'input_dummy': 'appendDummyInput'};
  264. var contentsBlock = rootBlock.getInputTargetBlock('INPUTS');
  265. while (contentsBlock) {
  266. if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) {
  267. var name = '';
  268. // Dummy inputs don't have names. Other inputs do.
  269. if (contentsBlock.type != 'input_dummy') {
  270. name =
  271. FactoryUtils.escapeString(contentsBlock.getFieldValue('INPUTNAME'));
  272. }
  273. code.push(' this.' + TYPES[contentsBlock.type] + '(' + name + ')');
  274. var check = FactoryUtils.getOptTypesFrom(contentsBlock, 'TYPE');
  275. if (check) {
  276. code.push(' .setCheck(' + check + ')');
  277. }
  278. var align = contentsBlock.getFieldValue('ALIGN');
  279. if (align != 'LEFT') {
  280. code.push(' .setAlign(Blockly.ALIGN_' + align + ')');
  281. }
  282. var fields = FactoryUtils.getFieldsJs_(
  283. contentsBlock.getInputTargetBlock('FIELDS'));
  284. for (var i = 0; i < fields.length; i++) {
  285. code.push(' .appendField(' + fields[i] + ')');
  286. }
  287. // Add semicolon to last line to finish the statement.
  288. code[code.length - 1] += ';';
  289. }
  290. contentsBlock = contentsBlock.nextConnection &&
  291. contentsBlock.nextConnection.targetBlock();
  292. }
  293. // Generate inline/external switch.
  294. if (rootBlock.getFieldValue('INLINE') == 'EXT') {
  295. code.push(' this.setInputsInline(false);');
  296. } else if (rootBlock.getFieldValue('INLINE') == 'INT') {
  297. code.push(' this.setInputsInline(true);');
  298. }
  299. // Generate output, or next/previous connections.
  300. switch (rootBlock.getFieldValue('CONNECTIONS')) {
  301. case 'LEFT':
  302. code.push(FactoryUtils.connectionLineJs_('setOutput', 'OUTPUTTYPE', workspace));
  303. break;
  304. case 'BOTH':
  305. code.push(
  306. FactoryUtils.connectionLineJs_('setPreviousStatement', 'TOPTYPE', workspace));
  307. code.push(
  308. FactoryUtils.connectionLineJs_('setNextStatement', 'BOTTOMTYPE', workspace));
  309. break;
  310. case 'TOP':
  311. code.push(
  312. FactoryUtils.connectionLineJs_('setPreviousStatement', 'TOPTYPE', workspace));
  313. break;
  314. case 'BOTTOM':
  315. code.push(
  316. FactoryUtils.connectionLineJs_('setNextStatement', 'BOTTOMTYPE', workspace));
  317. break;
  318. }
  319. // Generate colour.
  320. var colourBlock = rootBlock.getInputTargetBlock('COLOUR');
  321. if (colourBlock && !colourBlock.disabled) {
  322. var hue = parseInt(colourBlock.getFieldValue('HUE'), 10);
  323. if (!isNaN(hue)) {
  324. code.push(' this.setColour(' + hue + ');');
  325. }
  326. }
  327. code.push(" this.setTooltip('');");
  328. code.push(" this.setHelpUrl('http://www.example.com/');");
  329. code.push(' }');
  330. code.push('};');
  331. return code.join('\n');
  332. };
  333. /**
  334. * Create JS code required to create a top, bottom, or value connection.
  335. * @param {string} functionName JavaScript function name.
  336. * @param {string} typeName Name of type input.
  337. * @param {!Blockly.Workspace} workspace Where the root block lives.
  338. * @return {string} Line of JavaScript code to create connection.
  339. * @private
  340. */
  341. FactoryUtils.connectionLineJs_ = function(functionName, typeName, workspace) {
  342. var type = FactoryUtils.getOptTypesFrom(
  343. FactoryUtils.getRootBlock(workspace), typeName);
  344. if (type) {
  345. type = ', ' + type;
  346. } else {
  347. type = '';
  348. }
  349. return ' this.' + functionName + '(true' + type + ');';
  350. };
  351. /**
  352. * Returns field strings and any config.
  353. * @param {!Blockly.Block} block Input block.
  354. * @return {!Array.<string>} Field strings.
  355. * @private
  356. */
  357. FactoryUtils.getFieldsJs_ = function(block) {
  358. var fields = [];
  359. while (block) {
  360. if (!block.disabled && !block.getInheritedDisabled()) {
  361. switch (block.type) {
  362. case 'field_static':
  363. // Result: 'hello'
  364. fields.push(FactoryUtils.escapeString(block.getFieldValue('TEXT')));
  365. break;
  366. case 'field_input':
  367. // Result: new Blockly.FieldTextInput('Hello'), 'GREET'
  368. fields.push('new Blockly.FieldTextInput(' +
  369. FactoryUtils.escapeString(block.getFieldValue('TEXT')) + '), ' +
  370. FactoryUtils.escapeString(block.getFieldValue('FIELDNAME')));
  371. break;
  372. case 'field_number':
  373. // Result: new Blockly.FieldNumber(10, 0, 100, 1), 'NUMBER'
  374. var args = [
  375. Number(block.getFieldValue('VALUE')),
  376. Number(block.getFieldValue('MIN')),
  377. Number(block.getFieldValue('MAX')),
  378. Number(block.getFieldValue('PRECISION'))
  379. ];
  380. // Remove any trailing arguments that aren't needed.
  381. if (args[3] == 0) {
  382. args.pop();
  383. if (args[2] == Infinity) {
  384. args.pop();
  385. if (args[1] == -Infinity) {
  386. args.pop();
  387. }
  388. }
  389. }
  390. fields.push('new Blockly.FieldNumber(' + args.join(', ') + '), ' +
  391. FactoryUtils.escapeString(block.getFieldValue('FIELDNAME')));
  392. break;
  393. case 'field_angle':
  394. // Result: new Blockly.FieldAngle(90), 'ANGLE'
  395. fields.push('new Blockly.FieldAngle(' +
  396. parseFloat(block.getFieldValue('ANGLE')) + '), ' +
  397. FactoryUtils.escapeString(block.getFieldValue('FIELDNAME')));
  398. break;
  399. case 'field_checkbox':
  400. // Result: new Blockly.FieldCheckbox('TRUE'), 'CHECK'
  401. fields.push('new Blockly.FieldCheckbox(' +
  402. FactoryUtils.escapeString(block.getFieldValue('CHECKED')) +
  403. '), ' +
  404. FactoryUtils.escapeString(block.getFieldValue('FIELDNAME')));
  405. break;
  406. case 'field_colour':
  407. // Result: new Blockly.FieldColour('#ff0000'), 'COLOUR'
  408. fields.push('new Blockly.FieldColour(' +
  409. FactoryUtils.escapeString(block.getFieldValue('COLOUR')) +
  410. '), ' +
  411. FactoryUtils.escapeString(block.getFieldValue('FIELDNAME')));
  412. break;
  413. case 'field_date':
  414. // Result: new Blockly.FieldDate('2015-02-04'), 'DATE'
  415. fields.push('new Blockly.FieldDate(' +
  416. FactoryUtils.escapeString(block.getFieldValue('DATE')) + '), ' +
  417. FactoryUtils.escapeString(block.getFieldValue('FIELDNAME')));
  418. break;
  419. case 'field_variable':
  420. // Result: new Blockly.FieldVariable('item'), 'VAR'
  421. var varname
  422. = FactoryUtils.escapeString(block.getFieldValue('TEXT') || null);
  423. fields.push('new Blockly.FieldVariable(' + varname + '), ' +
  424. FactoryUtils.escapeString(block.getFieldValue('FIELDNAME')));
  425. break;
  426. case 'field_dropdown':
  427. // Result:
  428. // new Blockly.FieldDropdown([['yes', '1'], ['no', '0']]), 'TOGGLE'
  429. var options = [];
  430. for (var i = 0; i < block.optionCount_; i++) {
  431. options[i] = '[' +
  432. FactoryUtils.escapeString(block.getFieldValue('USER' + i)) +
  433. ', ' +
  434. FactoryUtils.escapeString(block.getFieldValue('CPU' + i)) + ']';
  435. }
  436. if (options.length) {
  437. fields.push('new Blockly.FieldDropdown([' +
  438. options.join(', ') + ']), ' +
  439. FactoryUtils.escapeString(block.getFieldValue('FIELDNAME')));
  440. }
  441. break;
  442. case 'field_image':
  443. // Result: new Blockly.FieldImage('http://...', 80, 60)
  444. var src = FactoryUtils.escapeString(block.getFieldValue('SRC'));
  445. var width = Number(block.getFieldValue('WIDTH'));
  446. var height = Number(block.getFieldValue('HEIGHT'));
  447. var alt = FactoryUtils.escapeString(block.getFieldValue('ALT'));
  448. fields.push('new Blockly.FieldImage(' +
  449. src + ', ' + width + ', ' + height + ', ' + alt + ')');
  450. break;
  451. }
  452. }
  453. block = block.nextConnection && block.nextConnection.targetBlock();
  454. }
  455. return fields;
  456. };
  457. /**
  458. * Returns field strings and any config.
  459. * @param {!Blockly.Block} block Input block.
  460. * @return {!Array.<string|!Object>} Array of static text and field configs.
  461. * @private
  462. */
  463. FactoryUtils.getFieldsJson_ = function(block) {
  464. var fields = [];
  465. while (block) {
  466. if (!block.disabled && !block.getInheritedDisabled()) {
  467. switch (block.type) {
  468. case 'field_static':
  469. // Result: 'hello'
  470. fields.push(block.getFieldValue('TEXT'));
  471. break;
  472. case 'field_input':
  473. fields.push({
  474. type: block.type,
  475. name: block.getFieldValue('FIELDNAME'),
  476. text: block.getFieldValue('TEXT')
  477. });
  478. break;
  479. case 'field_number':
  480. var obj = {
  481. type: block.type,
  482. name: block.getFieldValue('FIELDNAME'),
  483. value: parseFloat(block.getFieldValue('VALUE'))
  484. };
  485. var min = parseFloat(block.getFieldValue('MIN'));
  486. if (min > -Infinity) {
  487. obj.min = min;
  488. }
  489. var max = parseFloat(block.getFieldValue('MAX'));
  490. if (max < Infinity) {
  491. obj.max = max;
  492. }
  493. var precision = parseFloat(block.getFieldValue('PRECISION'));
  494. if (precision) {
  495. obj.precision = precision;
  496. }
  497. fields.push(obj);
  498. break;
  499. case 'field_angle':
  500. fields.push({
  501. type: block.type,
  502. name: block.getFieldValue('FIELDNAME'),
  503. angle: Number(block.getFieldValue('ANGLE'))
  504. });
  505. break;
  506. case 'field_checkbox':
  507. fields.push({
  508. type: block.type,
  509. name: block.getFieldValue('FIELDNAME'),
  510. checked: block.getFieldValue('CHECKED') == 'TRUE'
  511. });
  512. break;
  513. case 'field_colour':
  514. fields.push({
  515. type: block.type,
  516. name: block.getFieldValue('FIELDNAME'),
  517. colour: block.getFieldValue('COLOUR')
  518. });
  519. break;
  520. case 'field_date':
  521. fields.push({
  522. type: block.type,
  523. name: block.getFieldValue('FIELDNAME'),
  524. date: block.getFieldValue('DATE')
  525. });
  526. break;
  527. case 'field_variable':
  528. fields.push({
  529. type: block.type,
  530. name: block.getFieldValue('FIELDNAME'),
  531. variable: block.getFieldValue('TEXT') || null
  532. });
  533. break;
  534. case 'field_dropdown':
  535. var options = [];
  536. for (var i = 0; i < block.optionCount_; i++) {
  537. options[i] = [block.getFieldValue('USER' + i),
  538. block.getFieldValue('CPU' + i)];
  539. }
  540. if (options.length) {
  541. fields.push({
  542. type: block.type,
  543. name: block.getFieldValue('FIELDNAME'),
  544. options: options
  545. });
  546. }
  547. break;
  548. case 'field_image':
  549. fields.push({
  550. type: block.type,
  551. src: block.getFieldValue('SRC'),
  552. width: Number(block.getFieldValue('WIDTH')),
  553. height: Number(block.getFieldValue('HEIGHT')),
  554. alt: block.getFieldValue('ALT')
  555. });
  556. break;
  557. }
  558. }
  559. block = block.nextConnection && block.nextConnection.targetBlock();
  560. }
  561. return fields;
  562. };
  563. /**
  564. * Fetch the type(s) defined in the given input.
  565. * Format as a string for appending to the generated code.
  566. * @param {!Blockly.Block} block Block with input.
  567. * @param {string} name Name of the input.
  568. * @return {?string} String defining the types.
  569. */
  570. FactoryUtils.getOptTypesFrom = function(block, name) {
  571. var types = FactoryUtils.getTypesFrom_(block, name);
  572. if (types.length == 0) {
  573. return undefined;
  574. } else if (types.indexOf('null') != -1) {
  575. return 'null';
  576. } else if (types.length == 1) {
  577. return types[0];
  578. } else {
  579. return '[' + types.join(', ') + ']';
  580. }
  581. };
  582. /**
  583. * Fetch the type(s) defined in the given input.
  584. * @param {!Blockly.Block} block Block with input.
  585. * @param {string} name Name of the input.
  586. * @return {!Array.<string>} List of types.
  587. * @private
  588. */
  589. FactoryUtils.getTypesFrom_ = function(block, name) {
  590. var typeBlock = block.getInputTargetBlock(name);
  591. var types;
  592. if (!typeBlock || typeBlock.disabled) {
  593. types = [];
  594. } else if (typeBlock.type == 'type_other') {
  595. types = [FactoryUtils.escapeString(typeBlock.getFieldValue('TYPE'))];
  596. } else if (typeBlock.type == 'type_group') {
  597. types = [];
  598. for (var n = 0; n < typeBlock.typeCount_; n++) {
  599. types = types.concat(FactoryUtils.getTypesFrom_(typeBlock, 'TYPE' + n));
  600. }
  601. // Remove duplicates.
  602. var hash = Object.create(null);
  603. for (var n = types.length - 1; n >= 0; n--) {
  604. if (hash[types[n]]) {
  605. types.splice(n, 1);
  606. }
  607. hash[types[n]] = true;
  608. }
  609. } else {
  610. types = [FactoryUtils.escapeString(typeBlock.valueType)];
  611. }
  612. return types;
  613. };
  614. /**
  615. * Escape a string.
  616. * @param {string} string String to escape.
  617. * @return {string} Escaped string surrouned by quotes.
  618. */
  619. FactoryUtils.escapeString = function(string) {
  620. return JSON.stringify(string);
  621. };
  622. /**
  623. * Return the uneditable container block that everything else attaches to in
  624. * given workspace.
  625. * @param {!Blockly.Workspace} workspace Where the root block lives.
  626. * @return {Blockly.Block} Root block.
  627. */
  628. FactoryUtils.getRootBlock = function(workspace) {
  629. var blocks = workspace.getTopBlocks(false);
  630. for (var i = 0, block; block = blocks[i]; i++) {
  631. if (block.type == 'factory_base') {
  632. return block;
  633. }
  634. }
  635. return null;
  636. };
  637. // TODO(quachtina96): Move hide, show, makeInvisible, and makeVisible to a new
  638. // AppView namespace.
  639. /**
  640. * Hides element so that it's invisible and doesn't take up space.
  641. * @param {string} elementID ID of element to hide.
  642. */
  643. FactoryUtils.hide = function(elementID) {
  644. document.getElementById(elementID).style.display = 'none';
  645. };
  646. /**
  647. * Un-hides an element.
  648. * @param {string} elementID ID of element to hide.
  649. */
  650. FactoryUtils.show = function(elementID) {
  651. document.getElementById(elementID).style.display = 'block';
  652. };
  653. /**
  654. * Hides element so that it's invisible but still takes up space.
  655. * @param {string} elementID ID of element to hide.
  656. */
  657. FactoryUtils.makeInvisible = function(elementID) {
  658. document.getElementById(elementID).visibility = 'hidden';
  659. };
  660. /**
  661. * Makes element visible.
  662. * @param {string} elementID ID of element to hide.
  663. */
  664. FactoryUtils.makeVisible = function(elementID) {
  665. document.getElementById(elementID).visibility = 'visible';
  666. };
  667. /**
  668. * Create a file with the given attributes and download it.
  669. * @param {string} contents The contents of the file.
  670. * @param {string} filename The name of the file to save to.
  671. * @param {string} fileType The type of the file to save.
  672. */
  673. FactoryUtils.createAndDownloadFile = function(contents, filename, fileType) {
  674. var data = new Blob([contents], {type: 'text/' + fileType});
  675. var clickEvent = new MouseEvent("click", {
  676. "view": window,
  677. "bubbles": true,
  678. "cancelable": false
  679. });
  680. var a = document.createElement('a');
  681. a.href = window.URL.createObjectURL(data);
  682. a.download = filename;
  683. a.textContent = 'Download file!';
  684. a.dispatchEvent(clickEvent);
  685. };
  686. /**
  687. * Get Blockly Block by rendering pre-defined block in workspace.
  688. * @param {!Element} blockType Type of block that has already been defined.
  689. * @param {!Blockly.Workspace} workspace Workspace on which to render
  690. * the block.
  691. * @return {!Blockly.Block} The Blockly.Block of desired type.
  692. */
  693. FactoryUtils.getDefinedBlock = function(blockType, workspace) {
  694. workspace.clear();
  695. return workspace.newBlock(blockType);
  696. };
  697. /**
  698. * Parses a block definition get the type of the block it defines.
  699. * @param {string} blockDef A single block definition.
  700. * @return {string} Type of block defined by the given definition.
  701. */
  702. FactoryUtils.getBlockTypeFromJsDefinition = function(blockDef) {
  703. var indexOfStartBracket = blockDef.indexOf('[\'');
  704. var indexOfEndBracket = blockDef.indexOf('\']');
  705. if (indexOfStartBracket != -1 && indexOfEndBracket != -1) {
  706. return blockDef.substring(indexOfStartBracket + 2, indexOfEndBracket);
  707. } else {
  708. throw new Error ('Could not parse block type out of JavaScript block ' +
  709. 'definition. Brackets normally enclosing block type not found.');
  710. }
  711. };
  712. /**
  713. * Generates a category containing blocks of the specified block types.
  714. * @param {!Array.<!Blockly.Block>} blocks Blocks to include in the category.
  715. * @param {string} categoryName Name to use for the generated category.
  716. * @return {!Element} Category XML containing the given block types.
  717. */
  718. FactoryUtils.generateCategoryXml = function(blocks, categoryName) {
  719. // Create category DOM element.
  720. var categoryElement = goog.dom.createDom('category');
  721. categoryElement.setAttribute('name', categoryName);
  722. // For each block, add block element to category.
  723. for (var i = 0, block; block = blocks[i]; i++) {
  724. // Get preview block XML.
  725. var blockXml = Blockly.Xml.blockToDom(block);
  726. blockXml.removeAttribute('id');
  727. // Add block to category and category to XML.
  728. categoryElement.appendChild(blockXml);
  729. }
  730. return categoryElement;
  731. };
  732. /**
  733. * Parses a string containing JavaScript block definition(s) to create an array
  734. * in which each element is a single block definition.
  735. * @param {string} blockDefsString JavaScript block definition(s).
  736. * @return {!Array.<string>} Array of block definitions.
  737. */
  738. FactoryUtils.parseJsBlockDefinitions = function(blockDefsString) {
  739. var blockDefArray = [];
  740. var defStart = blockDefsString.indexOf('Blockly.Blocks');
  741. while (blockDefsString.indexOf('Blockly.Blocks', defStart) != -1) {
  742. var nextStart = blockDefsString.indexOf('Blockly.Blocks', defStart + 1);
  743. if (nextStart == -1) {
  744. // This is the last block definition.
  745. nextStart = blockDefsString.length;
  746. }
  747. var blockDef = blockDefsString.substring(defStart, nextStart);
  748. blockDefArray.push(blockDef);
  749. defStart = nextStart;
  750. }
  751. return blockDefArray;
  752. };
  753. /**
  754. * Parses a string containing JSON block definition(s) to create an array
  755. * in which each element is a single block definition. Expected input is
  756. * one or more block definitions in the form of concatenated, stringified
  757. * JSON objects.
  758. * @param {string} blockDefsString String containing JSON block
  759. * definition(s).
  760. * @return {!Array.<string>} Array of block definitions.
  761. */
  762. FactoryUtils.parseJsonBlockDefinitions = function(blockDefsString) {
  763. var blockDefArray = [];
  764. var unbalancedBracketCount = 0;
  765. var defStart = 0;
  766. // Iterate through the blockDefs string. Keep track of whether brackets
  767. // are balanced.
  768. for (var i = 0; i < blockDefsString.length; i++) {
  769. var currentChar = blockDefsString[i];
  770. if (currentChar == '{') {
  771. unbalancedBracketCount++;
  772. }
  773. else if (currentChar == '}') {
  774. unbalancedBracketCount--;
  775. if (unbalancedBracketCount == 0 && i > 0) {
  776. // The brackets are balanced. We've got a complete block defintion.
  777. var blockDef = blockDefsString.substring(defStart, i + 1);
  778. blockDefArray.push(blockDef);
  779. defStart = i + 1;
  780. }
  781. }
  782. }
  783. return blockDefArray;
  784. };
  785. /**
  786. * Define blocks from imported block definitions.
  787. * @param {string} blockDefsString Block definition(s).
  788. * @param {string} format Block definition format ('JSON' or 'JavaScript').
  789. * @return {!Array.<!Element>} Array of block types defined.
  790. */
  791. FactoryUtils.defineAndGetBlockTypes = function(blockDefsString, format) {
  792. var blockTypes = [];
  793. // Define blocks and get block types.
  794. if (format == 'JSON') {
  795. var blockDefArray = FactoryUtils.parseJsonBlockDefinitions(blockDefsString);
  796. // Populate array of blocktypes and define each block.
  797. for (var i = 0, blockDef; blockDef = blockDefArray[i]; i++) {
  798. var json = JSON.parse(blockDef);
  799. blockTypes.push(json.type);
  800. // Define the block.
  801. Blockly.Blocks[json.type] = {
  802. init: function() {
  803. this.jsonInit(json);
  804. }
  805. };
  806. }
  807. } else if (format == 'JavaScript') {
  808. var blockDefArray = FactoryUtils.parseJsBlockDefinitions(blockDefsString);
  809. // Populate array of block types.
  810. for (var i = 0, blockDef; blockDef = blockDefArray[i]; i++) {
  811. var blockType = FactoryUtils.getBlockTypeFromJsDefinition(blockDef);
  812. blockTypes.push(blockType);
  813. }
  814. // Define all blocks.
  815. eval(blockDefsString);
  816. }
  817. return blockTypes;
  818. };
  819. /**
  820. * Inject code into a pre tag, with syntax highlighting.
  821. * Safe from HTML/script injection.
  822. * @param {string} code Lines of code.
  823. * @param {string} id ID of <pre> element to inject into.
  824. */
  825. FactoryUtils.injectCode = function(code, id) {
  826. var pre = document.getElementById(id);
  827. pre.textContent = code;
  828. code = pre.textContent;
  829. code = prettyPrintOne(code, 'js');
  830. pre.innerHTML = code;
  831. };
  832. /**
  833. * Returns whether or not two blocks are the same based on their XML. Expects
  834. * XML with a single child node that is a factory_base block, the XML found on
  835. * Block Factory's main workspace.
  836. * @param {!Element} blockXml1 An XML element with a single child node that
  837. * is a factory_base block.
  838. * @param {!Element} blockXml2 An XML element with a single child node that
  839. * is a factory_base block.
  840. * @return {boolean} Whether or not two blocks are the same based on their XML.
  841. */
  842. FactoryUtils.sameBlockXml = function(blockXml1, blockXml2) {
  843. // Each XML element should contain a single child element with a 'block' tag
  844. if (blockXml1.tagName.toLowerCase() != 'xml' ||
  845. blockXml2.tagName.toLowerCase() != 'xml') {
  846. throw new Error('Expected two XML elements, recieved elements with tag ' +
  847. 'names: ' + blockXml1.tagName + ' and ' + blockXml2.tagName + '.');
  848. }
  849. // Compare the block elements directly. The XML tags may include other meta
  850. // information we want to igrore.
  851. var blockElement1 = blockXml1.getElementsByTagName('block')[0];
  852. var blockElement2 = blockXml2.getElementsByTagName('block')[0];
  853. if (!(blockElement1 && blockElement2)) {
  854. throw new Error('Could not get find block element in XML.');
  855. }
  856. var blockXmlText1 = Blockly.Xml.domToText(blockElement1);
  857. var blockXmlText2 = Blockly.Xml.domToText(blockElement2);
  858. // Strip white space.
  859. blockXmlText1 = blockXmlText1.replace(/\s+/g, '');
  860. blockXmlText2 = blockXmlText2.replace(/\s+/g, '');
  861. // Return whether or not changes have been saved.
  862. return blockXmlText1 == blockXmlText2;
  863. };
  864. /*
  865. * Checks if a block has a variable field. Blocks with variable fields cannot
  866. * be shadow blocks.
  867. * @param {Blockly.Block} block The block to check if a variable field exists.
  868. * @return {boolean} True if the block has a variable field, false otherwise.
  869. */
  870. FactoryUtils.hasVariableField = function(block) {
  871. if (!block) {
  872. return false;
  873. }
  874. return block.getVars().length > 0;
  875. };
  876. /**
  877. * Checks if a block is a procedures block. If procedures block names are
  878. * ever updated or expanded, this function should be updated as well (no
  879. * other known markers for procedure blocks beyond name).
  880. * @param {Blockly.Block} block The block to check.
  881. * @return {boolean} True if the block is a procedure block, false otherwise.
  882. */
  883. FactoryUtils.isProcedureBlock = function(block) {
  884. return block &&
  885. (block.type == 'procedures_defnoreturn' ||
  886. block.type == 'procedures_defreturn' ||
  887. block.type == 'procedures_callnoreturn' ||
  888. block.type == 'procedures_callreturn' ||
  889. block.type == 'procedures_ifreturn');
  890. };
  891. /**
  892. * Returns whether or not a modified block's changes has been saved to the
  893. * Block Library.
  894. * TODO(quachtina96): move into the Block Factory Controller once made.
  895. * @param {!BlockLibraryController} blockLibraryController Block Library
  896. * Controller storing custom blocks.
  897. * @return {boolean} True if all changes made to the block have been saved to
  898. * the given Block Library.
  899. */
  900. FactoryUtils.savedBlockChanges = function(blockLibraryController) {
  901. if (BlockFactory.isStarterBlock()) {
  902. return true;
  903. }
  904. var blockType = blockLibraryController.getCurrentBlockType();
  905. var currentXml = Blockly.Xml.workspaceToDom(BlockFactory.mainWorkspace);
  906. if (blockLibraryController.has(blockType)) {
  907. // Block is saved in block library.
  908. var savedXml = blockLibraryController.getBlockXml(blockType);
  909. return FactoryUtils.sameBlockXml(savedXml, currentXml);
  910. }
  911. return false;
  912. };