factory.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. /**
  2. * Blockly Demos: Block Factory
  3. *
  4. * Copyright 2012 Google Inc.
  5. * https://developers.google.com/blockly/
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. */
  19. /**
  20. * @fileoverview JavaScript for Blockly's Block Factory application.
  21. * @author fraser@google.com (Neil Fraser)
  22. */
  23. 'use strict';
  24. /**
  25. * Workspace for user to build block.
  26. * @type {Blockly.Workspace}
  27. */
  28. var mainWorkspace = null;
  29. /**
  30. * Workspace for preview of block.
  31. * @type {Blockly.Workspace}
  32. */
  33. var previewWorkspace = null;
  34. /**
  35. * Name of block if not named.
  36. */
  37. var UNNAMED = 'unnamed';
  38. /**
  39. * Change the language code format.
  40. */
  41. function formatChange() {
  42. var mask = document.getElementById('blocklyMask');
  43. var languagePre = document.getElementById('languagePre');
  44. var languageTA = document.getElementById('languageTA');
  45. if (document.getElementById('format').value == 'Manual') {
  46. Blockly.hideChaff();
  47. mask.style.display = 'block';
  48. languagePre.style.display = 'none';
  49. languageTA.style.display = 'block';
  50. var code = languagePre.textContent.trim();
  51. languageTA.value = code;
  52. languageTA.focus();
  53. updatePreview();
  54. } else {
  55. mask.style.display = 'none';
  56. languageTA.style.display = 'none';
  57. languagePre.style.display = 'block';
  58. updateLanguage();
  59. }
  60. disableEnableLink();
  61. }
  62. /**
  63. * Update the language code based on constructs made in Blockly.
  64. */
  65. function updateLanguage() {
  66. var rootBlock = getRootBlock();
  67. if (!rootBlock) {
  68. return;
  69. }
  70. var blockType = rootBlock.getFieldValue('NAME').trim().toLowerCase();
  71. if (!blockType) {
  72. blockType = UNNAMED;
  73. }
  74. blockType = blockType.replace(/\W/g, '_').replace(/^(\d)/, '_\\1');
  75. switch (document.getElementById('format').value) {
  76. case 'JSON':
  77. var code = formatJson_(blockType, rootBlock);
  78. break;
  79. case 'JavaScript':
  80. var code = formatJavaScript_(blockType, rootBlock);
  81. break;
  82. }
  83. injectCode(code, 'languagePre');
  84. updatePreview();
  85. }
  86. /**
  87. * Update the language code as JSON.
  88. * @param {string} blockType Name of block.
  89. * @param {!Blockly.Block} rootBlock Factory_base block.
  90. * @return {string} Generanted language code.
  91. * @private
  92. */
  93. function formatJson_(blockType, rootBlock) {
  94. var JS = {};
  95. // Type is not used by Blockly, but may be used by a loader.
  96. JS.type = blockType;
  97. // Generate inputs.
  98. var message = [];
  99. var args = [];
  100. var contentsBlock = rootBlock.getInputTargetBlock('INPUTS');
  101. var lastInput = null;
  102. while (contentsBlock) {
  103. if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) {
  104. var fields = getFieldsJson_(contentsBlock.getInputTargetBlock('FIELDS'));
  105. for (var i = 0; i < fields.length; i++) {
  106. if (typeof fields[i] == 'string') {
  107. message.push(fields[i].replace(/%/g, '%%'));
  108. } else {
  109. args.push(fields[i]);
  110. message.push('%' + args.length);
  111. }
  112. }
  113. var input = {type: contentsBlock.type};
  114. // Dummy inputs don't have names. Other inputs do.
  115. if (contentsBlock.type != 'input_dummy') {
  116. input.name = contentsBlock.getFieldValue('INPUTNAME');
  117. }
  118. var check = JSON.parse(getOptTypesFrom(contentsBlock, 'TYPE') || 'null');
  119. if (check) {
  120. input.check = check;
  121. }
  122. var align = contentsBlock.getFieldValue('ALIGN');
  123. if (align != 'LEFT') {
  124. input.align = align;
  125. }
  126. args.push(input);
  127. message.push('%' + args.length);
  128. lastInput = contentsBlock;
  129. }
  130. contentsBlock = contentsBlock.nextConnection &&
  131. contentsBlock.nextConnection.targetBlock();
  132. }
  133. // Remove last input if dummy and not empty.
  134. if (lastInput && lastInput.type == 'input_dummy') {
  135. var fields = lastInput.getInputTargetBlock('FIELDS');
  136. if (fields && getFieldsJson_(fields).join('').trim() != '') {
  137. var align = lastInput.getFieldValue('ALIGN');
  138. if (align != 'LEFT') {
  139. JS.lastDummyAlign0 = align;
  140. }
  141. args.pop();
  142. message.pop();
  143. }
  144. }
  145. JS.message0 = message.join(' ');
  146. if (args.length) {
  147. JS.args0 = args;
  148. }
  149. // Generate inline/external switch.
  150. if (rootBlock.getFieldValue('INLINE') == 'EXT') {
  151. JS.inputsInline = false;
  152. } else if (rootBlock.getFieldValue('INLINE') == 'INT') {
  153. JS.inputsInline = true;
  154. }
  155. // Generate output, or next/previous connections.
  156. switch (rootBlock.getFieldValue('CONNECTIONS')) {
  157. case 'LEFT':
  158. JS.output =
  159. JSON.parse(getOptTypesFrom(rootBlock, 'OUTPUTTYPE') || 'null');
  160. break;
  161. case 'BOTH':
  162. JS.previousStatement =
  163. JSON.parse(getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null');
  164. JS.nextStatement =
  165. JSON.parse(getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null');
  166. break;
  167. case 'TOP':
  168. JS.previousStatement =
  169. JSON.parse(getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null');
  170. break;
  171. case 'BOTTOM':
  172. JS.nextStatement =
  173. JSON.parse(getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null');
  174. break;
  175. }
  176. // Generate colour.
  177. var colourBlock = rootBlock.getInputTargetBlock('COLOUR');
  178. if (colourBlock && !colourBlock.disabled) {
  179. var hue = parseInt(colourBlock.getFieldValue('HUE'), 10);
  180. JS.colour = hue;
  181. }
  182. JS.tooltip = '';
  183. JS.helpUrl = 'http://www.example.com/';
  184. return JSON.stringify(JS, null, ' ');
  185. }
  186. /**
  187. * Update the language code as JavaScript.
  188. * @param {string} blockType Name of block.
  189. * @param {!Blockly.Block} rootBlock Factory_base block.
  190. * @return {string} Generanted language code.
  191. * @private
  192. */
  193. function formatJavaScript_(blockType, rootBlock) {
  194. var code = [];
  195. code.push("Blockly.Blocks['" + blockType + "'] = {");
  196. code.push(" init: function() {");
  197. // Generate inputs.
  198. var TYPES = {'input_value': 'appendValueInput',
  199. 'input_statement': 'appendStatementInput',
  200. 'input_dummy': 'appendDummyInput'};
  201. var contentsBlock = rootBlock.getInputTargetBlock('INPUTS');
  202. while (contentsBlock) {
  203. if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) {
  204. var name = '';
  205. // Dummy inputs don't have names. Other inputs do.
  206. if (contentsBlock.type != 'input_dummy') {
  207. name = escapeString(contentsBlock.getFieldValue('INPUTNAME'));
  208. }
  209. code.push(' this.' + TYPES[contentsBlock.type] + '(' + name + ')');
  210. var check = getOptTypesFrom(contentsBlock, 'TYPE');
  211. if (check) {
  212. code.push(' .setCheck(' + check + ')');
  213. }
  214. var align = contentsBlock.getFieldValue('ALIGN');
  215. if (align != 'LEFT') {
  216. code.push(' .setAlign(Blockly.ALIGN_' + align + ')');
  217. }
  218. var fields = getFieldsJs_(contentsBlock.getInputTargetBlock('FIELDS'));
  219. for (var i = 0; i < fields.length; i++) {
  220. code.push(' .appendField(' + fields[i] + ')');
  221. }
  222. // Add semicolon to last line to finish the statement.
  223. code[code.length - 1] += ';';
  224. }
  225. contentsBlock = contentsBlock.nextConnection &&
  226. contentsBlock.nextConnection.targetBlock();
  227. }
  228. // Generate inline/external switch.
  229. if (rootBlock.getFieldValue('INLINE') == 'EXT') {
  230. code.push(' this.setInputsInline(false);');
  231. } else if (rootBlock.getFieldValue('INLINE') == 'INT') {
  232. code.push(' this.setInputsInline(true);');
  233. }
  234. // Generate output, or next/previous connections.
  235. switch (rootBlock.getFieldValue('CONNECTIONS')) {
  236. case 'LEFT':
  237. code.push(connectionLineJs_('setOutput', 'OUTPUTTYPE'));
  238. break;
  239. case 'BOTH':
  240. code.push(connectionLineJs_('setPreviousStatement', 'TOPTYPE'));
  241. code.push(connectionLineJs_('setNextStatement', 'BOTTOMTYPE'));
  242. break;
  243. case 'TOP':
  244. code.push(connectionLineJs_('setPreviousStatement', 'TOPTYPE'));
  245. break;
  246. case 'BOTTOM':
  247. code.push(connectionLineJs_('setNextStatement', 'BOTTOMTYPE'));
  248. break;
  249. }
  250. // Generate colour.
  251. var colourBlock = rootBlock.getInputTargetBlock('COLOUR');
  252. if (colourBlock && !colourBlock.disabled) {
  253. var hue = parseInt(colourBlock.getFieldValue('HUE'), 10);
  254. if (!isNaN(hue)) {
  255. code.push(' this.setColour(' + hue + ');');
  256. }
  257. }
  258. code.push(" this.setTooltip('');");
  259. code.push(" this.setHelpUrl('http://www.example.com/');");
  260. code.push(' }');
  261. code.push('};');
  262. return code.join('\n');
  263. }
  264. /**
  265. * Create JS code required to create a top, bottom, or value connection.
  266. * @param {string} functionName JavaScript function name.
  267. * @param {string} typeName Name of type input.
  268. * @return {string} Line of JavaScript code to create connection.
  269. * @private
  270. */
  271. function connectionLineJs_(functionName, typeName) {
  272. var type = getOptTypesFrom(getRootBlock(), typeName);
  273. if (type) {
  274. type = ', ' + type;
  275. } else {
  276. type = '';
  277. }
  278. return ' this.' + functionName + '(true' + type + ');';
  279. }
  280. /**
  281. * Returns field strings and any config.
  282. * @param {!Blockly.Block} block Input block.
  283. * @return {!Array.<string>} Field strings.
  284. * @private
  285. */
  286. function getFieldsJs_(block) {
  287. var fields = [];
  288. while (block) {
  289. if (!block.disabled && !block.getInheritedDisabled()) {
  290. switch (block.type) {
  291. case 'field_static':
  292. // Result: 'hello'
  293. fields.push(escapeString(block.getFieldValue('TEXT')));
  294. break;
  295. case 'field_input':
  296. // Result: new Blockly.FieldTextInput('Hello'), 'GREET'
  297. fields.push('new Blockly.FieldTextInput(' +
  298. escapeString(block.getFieldValue('TEXT')) + '), ' +
  299. escapeString(block.getFieldValue('FIELDNAME')));
  300. break;
  301. case 'field_number':
  302. // Result: new Blockly.FieldNumber(10, 0, 100, 1), 'NUMBER'
  303. var args = [
  304. Number(block.getFieldValue('VALUE')),
  305. Number(block.getFieldValue('MIN')),
  306. Number(block.getFieldValue('MAX')),
  307. Number(block.getFieldValue('PRECISION'))
  308. ];
  309. // Remove any trailing arguments that aren't needed.
  310. if (args[3] == 0) {
  311. args.pop();
  312. if (args[2] == Infinity) {
  313. args.pop();
  314. if (args[1] == -Infinity) {
  315. args.pop();
  316. }
  317. }
  318. }
  319. fields.push('new Blockly.FieldNumber(' + args.join(', ') + '), ' +
  320. escapeString(block.getFieldValue('FIELDNAME')));
  321. break;
  322. case 'field_angle':
  323. // Result: new Blockly.FieldAngle(90), 'ANGLE'
  324. fields.push('new Blockly.FieldAngle(' +
  325. Number(block.getFieldValue('ANGLE')) + '), ' +
  326. escapeString(block.getFieldValue('FIELDNAME')));
  327. break;
  328. case 'field_checkbox':
  329. // Result: new Blockly.FieldCheckbox('TRUE'), 'CHECK'
  330. fields.push('new Blockly.FieldCheckbox(' +
  331. escapeString(block.getFieldValue('CHECKED')) + '), ' +
  332. escapeString(block.getFieldValue('FIELDNAME')));
  333. break;
  334. case 'field_colour':
  335. // Result: new Blockly.FieldColour('#ff0000'), 'COLOUR'
  336. fields.push('new Blockly.FieldColour(' +
  337. escapeString(block.getFieldValue('COLOUR')) + '), ' +
  338. escapeString(block.getFieldValue('FIELDNAME')));
  339. break;
  340. case 'field_date':
  341. // Result: new Blockly.FieldDate('2015-02-04'), 'DATE'
  342. fields.push('new Blockly.FieldDate(' +
  343. escapeString(block.getFieldValue('DATE')) + '), ' +
  344. escapeString(block.getFieldValue('FIELDNAME')));
  345. break;
  346. case 'field_variable':
  347. // Result: new Blockly.FieldVariable('item'), 'VAR'
  348. var varname = escapeString(block.getFieldValue('TEXT') || null);
  349. fields.push('new Blockly.FieldVariable(' + varname + '), ' +
  350. escapeString(block.getFieldValue('FIELDNAME')));
  351. break;
  352. case 'field_dropdown':
  353. // Result:
  354. // new Blockly.FieldDropdown([['yes', '1'], ['no', '0']]), 'TOGGLE'
  355. var options = [];
  356. for (var i = 0; i < block.optionCount_; i++) {
  357. options[i] = '[' + escapeString(block.getFieldValue('USER' + i)) +
  358. ', ' + escapeString(block.getFieldValue('CPU' + i)) + ']';
  359. }
  360. if (options.length) {
  361. fields.push('new Blockly.FieldDropdown([' +
  362. options.join(', ') + ']), ' +
  363. escapeString(block.getFieldValue('FIELDNAME')));
  364. }
  365. break;
  366. case 'field_image':
  367. // Result: new Blockly.FieldImage('http://...', 80, 60, '*')
  368. var src = escapeString(block.getFieldValue('SRC'));
  369. var width = Number(block.getFieldValue('WIDTH'));
  370. var height = Number(block.getFieldValue('HEIGHT'));
  371. var alt = escapeString(block.getFieldValue('ALT'));
  372. fields.push('new Blockly.FieldImage(' +
  373. src + ', ' + width + ', ' + height + ', ' + alt + ')');
  374. break;
  375. }
  376. }
  377. block = block.nextConnection && block.nextConnection.targetBlock();
  378. }
  379. return fields;
  380. }
  381. /**
  382. * Returns field strings and any config.
  383. * @param {!Blockly.Block} block Input block.
  384. * @return {!Array.<string|!Object>} Array of static text and field configs.
  385. * @private
  386. */
  387. function getFieldsJson_(block) {
  388. var fields = [];
  389. while (block) {
  390. if (!block.disabled && !block.getInheritedDisabled()) {
  391. switch (block.type) {
  392. case 'field_static':
  393. // Result: 'hello'
  394. fields.push(block.getFieldValue('TEXT'));
  395. break;
  396. case 'field_input':
  397. fields.push({
  398. type: block.type,
  399. name: block.getFieldValue('FIELDNAME'),
  400. text: block.getFieldValue('TEXT')
  401. });
  402. break;
  403. case 'field_number':
  404. var obj = {
  405. type: block.type,
  406. name: block.getFieldValue('FIELDNAME'),
  407. value: parseFloat(block.getFieldValue('VALUE'))
  408. };
  409. var min = parseFloat(block.getFieldValue('MIN'));
  410. if (min > -Infinity) {
  411. obj.min = min;
  412. }
  413. var max = parseFloat(block.getFieldValue('MAX'));
  414. if (max < Infinity) {
  415. obj.max = max;
  416. }
  417. var precision = parseFloat(block.getFieldValue('PRECISION'));
  418. if (precision) {
  419. obj.precision = precision;
  420. }
  421. fields.push(obj);
  422. break;
  423. case 'field_angle':
  424. fields.push({
  425. type: block.type,
  426. name: block.getFieldValue('FIELDNAME'),
  427. angle: Number(block.getFieldValue('ANGLE'))
  428. });
  429. break;
  430. case 'field_checkbox':
  431. fields.push({
  432. type: block.type,
  433. name: block.getFieldValue('FIELDNAME'),
  434. checked: block.getFieldValue('CHECKED') == 'TRUE'
  435. });
  436. break;
  437. case 'field_colour':
  438. fields.push({
  439. type: block.type,
  440. name: block.getFieldValue('FIELDNAME'),
  441. colour: block.getFieldValue('COLOUR')
  442. });
  443. break;
  444. case 'field_date':
  445. fields.push({
  446. type: block.type,
  447. name: block.getFieldValue('FIELDNAME'),
  448. date: block.getFieldValue('DATE')
  449. });
  450. break;
  451. case 'field_variable':
  452. fields.push({
  453. type: block.type,
  454. name: block.getFieldValue('FIELDNAME'),
  455. variable: block.getFieldValue('TEXT') || null
  456. });
  457. break;
  458. case 'field_dropdown':
  459. var options = [];
  460. for (var i = 0; i < block.optionCount_; i++) {
  461. options[i] = [block.getFieldValue('USER' + i),
  462. block.getFieldValue('CPU' + i)];
  463. }
  464. if (options.length) {
  465. fields.push({
  466. type: block.type,
  467. name: block.getFieldValue('FIELDNAME'),
  468. options: options
  469. });
  470. }
  471. break;
  472. case 'field_image':
  473. fields.push({
  474. type: block.type,
  475. src: block.getFieldValue('SRC'),
  476. width: Number(block.getFieldValue('WIDTH')),
  477. height: Number(block.getFieldValue('HEIGHT')),
  478. alt: block.getFieldValue('ALT')
  479. });
  480. break;
  481. }
  482. }
  483. block = block.nextConnection && block.nextConnection.targetBlock();
  484. }
  485. return fields;
  486. }
  487. /**
  488. * Escape a string.
  489. * @param {string} string String to escape.
  490. * @return {string} Escaped string surrouned by quotes.
  491. */
  492. function escapeString(string) {
  493. return JSON.stringify(string);
  494. }
  495. /**
  496. * Fetch the type(s) defined in the given input.
  497. * Format as a string for appending to the generated code.
  498. * @param {!Blockly.Block} block Block with input.
  499. * @param {string} name Name of the input.
  500. * @return {?string} String defining the types.
  501. */
  502. function getOptTypesFrom(block, name) {
  503. var types = getTypesFrom_(block, name);
  504. if (types.length == 0) {
  505. return undefined;
  506. } else if (types.indexOf('null') != -1) {
  507. return 'null';
  508. } else if (types.length == 1) {
  509. return types[0];
  510. } else {
  511. return '[' + types.join(', ') + ']';
  512. }
  513. }
  514. /**
  515. * Fetch the type(s) defined in the given input.
  516. * @param {!Blockly.Block} block Block with input.
  517. * @param {string} name Name of the input.
  518. * @return {!Array.<string>} List of types.
  519. * @private
  520. */
  521. function getTypesFrom_(block, name) {
  522. var typeBlock = block.getInputTargetBlock(name);
  523. var types;
  524. if (!typeBlock || typeBlock.disabled) {
  525. types = [];
  526. } else if (typeBlock.type == 'type_other') {
  527. types = [escapeString(typeBlock.getFieldValue('TYPE'))];
  528. } else if (typeBlock.type == 'type_group') {
  529. types = [];
  530. for (var i = 0; i < typeBlock.typeCount_; i++) {
  531. types = types.concat(getTypesFrom_(typeBlock, 'TYPE' + i));
  532. }
  533. // Remove duplicates.
  534. var hash = Object.create(null);
  535. for (var n = types.length - 1; n >= 0; n--) {
  536. if (hash[types[n]]) {
  537. types.splice(n, 1);
  538. }
  539. hash[types[n]] = true;
  540. }
  541. } else {
  542. types = [escapeString(typeBlock.valueType)];
  543. }
  544. return types;
  545. }
  546. /**
  547. * Update the generator code.
  548. * @param {!Blockly.Block} block Rendered block in preview workspace.
  549. */
  550. function updateGenerator(block) {
  551. function makeVar(root, name) {
  552. name = name.toLowerCase().replace(/\W/g, '_');
  553. return ' var ' + root + '_' + name;
  554. }
  555. var language = document.getElementById('language').value;
  556. var code = [];
  557. code.push("Blockly." + language + "['" + block.type +
  558. "'] = function(block) {");
  559. // Generate getters for any fields or inputs.
  560. for (var i = 0, input; input = block.inputList[i]; i++) {
  561. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  562. var name = field.name;
  563. if (!name) {
  564. continue;
  565. }
  566. if (field instanceof Blockly.FieldVariable) {
  567. // Subclass of Blockly.FieldDropdown, must test first.
  568. code.push(makeVar('variable', name) +
  569. " = Blockly." + language +
  570. ".variableDB_.getName(block.getFieldValue('" + name +
  571. "'), Blockly.Variables.NAME_TYPE);");
  572. } else if (field instanceof Blockly.FieldAngle) {
  573. // Subclass of Blockly.FieldTextInput, must test first.
  574. code.push(makeVar('angle', name) +
  575. " = block.getFieldValue('" + name + "');");
  576. } else if (Blockly.FieldDate && field instanceof Blockly.FieldDate) {
  577. // Blockly.FieldDate may not be compiled into Blockly.
  578. code.push(makeVar('date', name) +
  579. " = block.getFieldValue('" + name + "');");
  580. } else if (field instanceof Blockly.FieldColour) {
  581. code.push(makeVar('colour', name) +
  582. " = block.getFieldValue('" + name + "');");
  583. } else if (field instanceof Blockly.FieldCheckbox) {
  584. code.push(makeVar('checkbox', name) +
  585. " = block.getFieldValue('" + name + "') == 'TRUE';");
  586. } else if (field instanceof Blockly.FieldDropdown) {
  587. code.push(makeVar('dropdown', name) +
  588. " = block.getFieldValue('" + name + "');");
  589. } else if (field instanceof Blockly.FieldNumber) {
  590. code.push(makeVar('number', name) +
  591. " = block.getFieldValue('" + name + "');");
  592. } else if (field instanceof Blockly.FieldTextInput) {
  593. code.push(makeVar('text', name) +
  594. " = block.getFieldValue('" + name + "');");
  595. }
  596. }
  597. var name = input.name;
  598. if (name) {
  599. if (input.type == Blockly.INPUT_VALUE) {
  600. code.push(makeVar('value', name) +
  601. " = Blockly." + language + ".valueToCode(block, '" + name +
  602. "', Blockly." + language + ".ORDER_ATOMIC);");
  603. } else if (input.type == Blockly.NEXT_STATEMENT) {
  604. code.push(makeVar('statements', name) +
  605. " = Blockly." + language + ".statementToCode(block, '" +
  606. name + "');");
  607. }
  608. }
  609. }
  610. // Most languages end lines with a semicolon. Python does not.
  611. var lineEnd = {
  612. 'JavaScript': ';',
  613. 'Python': '',
  614. 'PHP': ';',
  615. 'Dart': ';'
  616. };
  617. code.push(" // TODO: Assemble " + language + " into code variable.");
  618. if (block.outputConnection) {
  619. code.push(" var code = '...';");
  620. code.push(" // TODO: Change ORDER_NONE to the correct strength.");
  621. code.push(" return [code, Blockly." + language + ".ORDER_NONE];");
  622. } else {
  623. code.push(" var code = '..." + (lineEnd[language] || '') + "\\n';");
  624. code.push(" return code;");
  625. }
  626. code.push("};");
  627. injectCode(code.join('\n'), 'generatorPre');
  628. }
  629. /**
  630. * Existing direction ('ltr' vs 'rtl') of preview.
  631. */
  632. var oldDir = null;
  633. /**
  634. * Update the preview display.
  635. */
  636. function updatePreview() {
  637. // Toggle between LTR/RTL if needed (also used in first display).
  638. var newDir = document.getElementById('direction').value;
  639. if (oldDir != newDir) {
  640. if (previewWorkspace) {
  641. previewWorkspace.dispose();
  642. }
  643. var rtl = newDir == 'rtl';
  644. previewWorkspace = Blockly.inject('preview',
  645. {rtl: rtl,
  646. media: '../../media/',
  647. scrollbars: true});
  648. oldDir = newDir;
  649. }
  650. previewWorkspace.clear();
  651. // Fetch the code and determine its format (JSON or JavaScript).
  652. var format = document.getElementById('format').value;
  653. if (format == 'Manual') {
  654. var code = document.getElementById('languageTA').value;
  655. // If the code is JSON, it will parse, otherwise treat as JS.
  656. try {
  657. JSON.parse(code);
  658. format = 'JSON';
  659. } catch (e) {
  660. format = 'JavaScript';
  661. }
  662. } else {
  663. var code = document.getElementById('languagePre').textContent;
  664. }
  665. if (!code.trim()) {
  666. // Nothing to render. Happens while cloud storage is loading.
  667. return;
  668. }
  669. // Backup Blockly.Blocks object so that main workspace and preview don't
  670. // collide if user creates a 'factory_base' block, for instance.
  671. var backupBlocks = Blockly.Blocks;
  672. try {
  673. // Make a shallow copy.
  674. Blockly.Blocks = {};
  675. for (var prop in backupBlocks) {
  676. Blockly.Blocks[prop] = backupBlocks[prop];
  677. }
  678. if (format == 'JSON') {
  679. var json = JSON.parse(code);
  680. Blockly.Blocks[json.type || UNNAMED] = {
  681. init: function() {
  682. this.jsonInit(json);
  683. }
  684. };
  685. } else if (format == 'JavaScript') {
  686. eval(code);
  687. } else {
  688. throw 'Unknown format: ' + format;
  689. }
  690. // Look for a block on Blockly.Blocks that does not match the backup.
  691. var blockType = null;
  692. for (var type in Blockly.Blocks) {
  693. if (typeof Blockly.Blocks[type].init == 'function' &&
  694. Blockly.Blocks[type] != backupBlocks[type]) {
  695. blockType = type;
  696. break;
  697. }
  698. }
  699. if (!blockType) {
  700. return;
  701. }
  702. // Create the preview block.
  703. var previewBlock = previewWorkspace.newBlock(blockType);
  704. previewBlock.initSvg();
  705. previewBlock.render();
  706. previewBlock.setMovable(false);
  707. previewBlock.setDeletable(false);
  708. previewBlock.moveBy(15, 10);
  709. previewWorkspace.clearUndo();
  710. updateGenerator(previewBlock);
  711. } finally {
  712. Blockly.Blocks = backupBlocks;
  713. }
  714. }
  715. /**
  716. * Inject code into a pre tag, with syntax highlighting.
  717. * Safe from HTML/script injection.
  718. * @param {string} code Lines of code.
  719. * @param {string} id ID of <pre> element to inject into.
  720. */
  721. function injectCode(code, id) {
  722. var pre = document.getElementById(id);
  723. pre.textContent = code;
  724. code = pre.textContent;
  725. code = prettyPrintOne(code, 'js');
  726. pre.innerHTML = code;
  727. }
  728. /**
  729. * Return the uneditable container block that everything else attaches to.
  730. * @return {Blockly.Block}
  731. */
  732. function getRootBlock() {
  733. var blocks = mainWorkspace.getTopBlocks(false);
  734. for (var i = 0, block; block = blocks[i]; i++) {
  735. if (block.type == 'factory_base') {
  736. return block;
  737. }
  738. }
  739. return null;
  740. }
  741. /**
  742. * Disable the link button if the format is 'Manual', enable otherwise.
  743. */
  744. function disableEnableLink() {
  745. var linkButton = document.getElementById('linkButton');
  746. linkButton.disabled = document.getElementById('format').value == 'Manual';
  747. }
  748. /**
  749. * Initialize Blockly and layout. Called on page load.
  750. */
  751. function init() {
  752. if ('BlocklyStorage' in window) {
  753. BlocklyStorage.HTTPREQUEST_ERROR =
  754. 'There was a problem with the request.\n';
  755. BlocklyStorage.LINK_ALERT =
  756. 'Share your blocks with this link:\n\n%1';
  757. BlocklyStorage.HASH_ERROR =
  758. 'Sorry, "%1" doesn\'t correspond with any saved Blockly file.';
  759. BlocklyStorage.XML_ERROR = 'Could not load your saved file.\n'+
  760. 'Perhaps it was created with a different version of Blockly?';
  761. var linkButton = document.getElementById('linkButton');
  762. linkButton.style.display = 'inline-block';
  763. linkButton.addEventListener('click',
  764. function() {BlocklyStorage.link(mainWorkspace);});
  765. disableEnableLink();
  766. }
  767. document.getElementById('helpButton').addEventListener('click',
  768. function() {
  769. open('https://developers.google.com/blockly/guides/create-custom-blocks/block-factory',
  770. 'BlockFactoryHelp');
  771. });
  772. var expandList = [
  773. document.getElementById('blockly'),
  774. document.getElementById('blocklyMask'),
  775. document.getElementById('preview'),
  776. document.getElementById('languagePre'),
  777. document.getElementById('languageTA'),
  778. document.getElementById('generatorPre')
  779. ];
  780. var onresize = function(e) {
  781. for (var i = 0, expand; expand = expandList[i]; i++) {
  782. expand.style.width = (expand.parentNode.offsetWidth - 2) + 'px';
  783. expand.style.height = (expand.parentNode.offsetHeight - 2) + 'px';
  784. }
  785. };
  786. onresize();
  787. window.addEventListener('resize', onresize);
  788. var toolbox = document.getElementById('toolbox');
  789. mainWorkspace = Blockly.inject('blockly',
  790. {collapse: false,
  791. toolbox: toolbox,
  792. media: '../../media/'});
  793. // Create the root block.
  794. if ('BlocklyStorage' in window && window.location.hash.length > 1) {
  795. BlocklyStorage.retrieveXml(window.location.hash.substring(1),
  796. mainWorkspace);
  797. } else {
  798. var xml = '<xml><block type="factory_base" deletable="false" movable="false"></block></xml>';
  799. Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), mainWorkspace);
  800. }
  801. mainWorkspace.clearUndo();
  802. mainWorkspace.addChangeListener(Blockly.Events.disableOrphans);
  803. mainWorkspace.addChangeListener(updateLanguage);
  804. document.getElementById('direction')
  805. .addEventListener('change', updatePreview);
  806. document.getElementById('languageTA')
  807. .addEventListener('change', updatePreview);
  808. document.getElementById('languageTA')
  809. .addEventListener('keyup', updatePreview);
  810. document.getElementById('format')
  811. .addEventListener('change', formatChange);
  812. document.getElementById('language')
  813. .addEventListener('change', updatePreview);
  814. }
  815. window.addEventListener('load', init);