factory.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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 JavaScript for Blockly's Block Factory application through
  22. * which users can build blocks using a visual interface and dynamically
  23. * generate a preview block and starter code for the block (block definition and
  24. * generator stub. Uses the Block Factory namespace. Depends on the FactoryUtils
  25. * for its code generation functions.
  26. *
  27. * @author fraser@google.com (Neil Fraser), quachtina96 (Tina Quach)
  28. */
  29. 'use strict';
  30. /**
  31. * Namespace for Block Factory.
  32. */
  33. goog.provide('BlockFactory');
  34. goog.require('FactoryUtils');
  35. goog.require('StandardCategories');
  36. /**
  37. * Workspace for user to build block.
  38. * @type {Blockly.Workspace}
  39. */
  40. BlockFactory.mainWorkspace = null;
  41. /**
  42. * Workspace for preview of block.
  43. * @type {Blockly.Workspace}
  44. */
  45. BlockFactory.previewWorkspace = null;
  46. /**
  47. * Name of block if not named.
  48. */
  49. BlockFactory.UNNAMED = 'unnamed';
  50. /**
  51. * Existing direction ('ltr' vs 'rtl') of preview.
  52. */
  53. BlockFactory.oldDir = null;
  54. /*
  55. * The starting XML for the Block Factory main workspace. Contains the
  56. * unmovable, undeletable factory_base block.
  57. */
  58. BlockFactory.STARTER_BLOCK_XML_TEXT = '<xml><block type="factory_base" ' +
  59. 'deletable="false" movable="false"></block></xml>';
  60. /**
  61. * Change the language code format.
  62. */
  63. BlockFactory.formatChange = function() {
  64. var mask = document.getElementById('blocklyMask');
  65. var languagePre = document.getElementById('languagePre');
  66. var languageTA = document.getElementById('languageTA');
  67. if (document.getElementById('format').value == 'Manual') {
  68. Blockly.hideChaff();
  69. mask.style.display = 'block';
  70. languagePre.style.display = 'none';
  71. languageTA.style.display = 'block';
  72. var code = languagePre.textContent.trim();
  73. languageTA.value = code;
  74. languageTA.focus();
  75. BlockFactory.updatePreview();
  76. } else {
  77. mask.style.display = 'none';
  78. languageTA.style.display = 'none';
  79. languagePre.style.display = 'block';
  80. BlockFactory.updateLanguage();
  81. }
  82. BlockFactory.disableEnableLink();
  83. };
  84. /**
  85. * Update the language code based on constructs made in Blockly.
  86. */
  87. BlockFactory.updateLanguage = function() {
  88. var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace);
  89. if (!rootBlock) {
  90. return;
  91. }
  92. var blockType = rootBlock.getFieldValue('NAME').trim().toLowerCase();
  93. if (!blockType) {
  94. blockType = BlockFactory.UNNAMED;
  95. }
  96. var format = document.getElementById('format').value;
  97. var code = FactoryUtils.getBlockDefinition(blockType, rootBlock, format,
  98. BlockFactory.mainWorkspace);
  99. FactoryUtils.injectCode(code, 'languagePre');
  100. BlockFactory.updatePreview();
  101. };
  102. /**
  103. * Update the generator code.
  104. * @param {!Blockly.Block} block Rendered block in preview workspace.
  105. */
  106. BlockFactory.updateGenerator = function(block) {
  107. var language = document.getElementById('language').value;
  108. var generatorStub = FactoryUtils.getGeneratorStub(block, language);
  109. FactoryUtils.injectCode(generatorStub, 'generatorPre');
  110. };
  111. /**
  112. * Update the preview display.
  113. */
  114. BlockFactory.updatePreview = function() {
  115. // Toggle between LTR/RTL if needed (also used in first display).
  116. var newDir = document.getElementById('direction').value;
  117. if (BlockFactory.oldDir != newDir) {
  118. if (BlockFactory.previewWorkspace) {
  119. BlockFactory.previewWorkspace.dispose();
  120. }
  121. var rtl = newDir == 'rtl';
  122. BlockFactory.previewWorkspace = Blockly.inject('preview',
  123. {rtl: rtl,
  124. media: '../../media/',
  125. scrollbars: true});
  126. BlockFactory.oldDir = newDir;
  127. }
  128. BlockFactory.previewWorkspace.clear();
  129. // Fetch the code and determine its format (JSON or JavaScript).
  130. var format = document.getElementById('format').value;
  131. if (format == 'Manual') {
  132. var code = document.getElementById('languageTA').value;
  133. // If the code is JSON, it will parse, otherwise treat as JS.
  134. try {
  135. JSON.parse(code);
  136. format = 'JSON';
  137. } catch (e) {
  138. format = 'JavaScript';
  139. }
  140. } else {
  141. var code = document.getElementById('languagePre').textContent;
  142. }
  143. if (!code.trim()) {
  144. // Nothing to render. Happens while cloud storage is loading.
  145. return;
  146. }
  147. // Backup Blockly.Blocks object so that main workspace and preview don't
  148. // collide if user creates a 'factory_base' block, for instance.
  149. var backupBlocks = Blockly.Blocks;
  150. try {
  151. // Make a shallow copy.
  152. Blockly.Blocks = Object.create(null);
  153. for (var prop in backupBlocks) {
  154. Blockly.Blocks[prop] = backupBlocks[prop];
  155. }
  156. if (format == 'JSON') {
  157. var json = JSON.parse(code);
  158. Blockly.Blocks[json.type || BlockFactory.UNNAMED] = {
  159. init: function() {
  160. this.jsonInit(json);
  161. }
  162. };
  163. } else if (format == 'JavaScript') {
  164. eval(code);
  165. } else {
  166. throw 'Unknown format: ' + format;
  167. }
  168. // Look for a block on Blockly.Blocks that does not match the backup.
  169. var blockType = null;
  170. for (var type in Blockly.Blocks) {
  171. if (typeof Blockly.Blocks[type].init == 'function' &&
  172. Blockly.Blocks[type] != backupBlocks[type]) {
  173. blockType = type;
  174. break;
  175. }
  176. }
  177. if (!blockType) {
  178. return;
  179. }
  180. // Create the preview block.
  181. var previewBlock = BlockFactory.previewWorkspace.newBlock(blockType);
  182. previewBlock.initSvg();
  183. previewBlock.render();
  184. previewBlock.setMovable(false);
  185. previewBlock.setDeletable(false);
  186. previewBlock.moveBy(15, 10);
  187. BlockFactory.previewWorkspace.clearUndo();
  188. BlockFactory.updateGenerator(previewBlock);
  189. // Warn user only if their block type is already exists in Blockly's
  190. // standard library.
  191. var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace);
  192. if (StandardCategories.coreBlockTypes.indexOf(blockType) != -1) {
  193. rootBlock.setWarningText('A core Blockly block already exists ' +
  194. 'under this name.');
  195. } else if (blockType == 'block_type') {
  196. // Warn user to let them know they can't save a block under the default
  197. // name 'block_type'
  198. rootBlock.setWarningText('You cannot save a block with the default ' +
  199. 'name, "block_type"');
  200. } else {
  201. rootBlock.setWarningText(null);
  202. }
  203. } finally {
  204. Blockly.Blocks = backupBlocks;
  205. }
  206. };
  207. /**
  208. * Disable link and save buttons if the format is 'Manual', enable otherwise.
  209. */
  210. BlockFactory.disableEnableLink = function() {
  211. var linkButton = document.getElementById('linkButton');
  212. var saveBlockButton = document.getElementById('localSaveButton');
  213. var saveToLibButton = document.getElementById('saveToBlockLibraryButton');
  214. var disabled = document.getElementById('format').value == 'Manual';
  215. linkButton.disabled = disabled;
  216. saveBlockButton.disabled = disabled;
  217. saveToLibButton.disabled = disabled;
  218. };
  219. /**
  220. * Render starter block (factory_base).
  221. */
  222. BlockFactory.showStarterBlock = function() {
  223. BlockFactory.mainWorkspace.clear();
  224. var xml = Blockly.Xml.textToDom(BlockFactory.STARTER_BLOCK_XML_TEXT);
  225. Blockly.Xml.domToWorkspace(xml, BlockFactory.mainWorkspace);
  226. };
  227. /**
  228. * Returns whether or not the current block open is the starter block.
  229. */
  230. BlockFactory.isStarterBlock = function() {
  231. var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace);
  232. // The starter block does not have blocks nested into the factory_base block.
  233. return !(rootBlock.getChildren().length > 0 ||
  234. // The starter block's name is the default, 'block_type'.
  235. rootBlock.getFieldValue('NAME').trim().toLowerCase() != 'block_type' ||
  236. // The starter block has no connections.
  237. rootBlock.getFieldValue('CONNECTIONS') != 'NONE' ||
  238. // The starter block has automatic inputs.
  239. rootBlock.getFieldValue('INLINE') != 'AUTO');
  240. };