ardublockly_blockly.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. /**
  2. * @license Licensed under the Apache License, Version 2.0 (the "License"):
  3. * http://www.apache.org/licenses/LICENSE-2.0
  4. *
  5. * @fileoverview Ardublockly JavaScript for the Blockly resources and bindings.
  6. */
  7. 'use strict';
  8. /** Create a namespace for the application. */
  9. var Ardublockly = Ardublockly || {};
  10. /**
  11. * Blockly main workspace.
  12. * @type Blockly.WorkspaceSvg
  13. */
  14. Ardublockly.workspace = null;
  15. /**
  16. * Blockly workspace toolbox XML.
  17. * @type Element
  18. */
  19. Ardublockly.xmlTree = null;
  20. Ardublockly.injectBlockly = function (blocklyEl, toolboxXml, blocklyPath) {
  21. // Remove any trailing slashes in the blockly path
  22. if (blocklyPath.substr(-1) === '/') {
  23. blocklyPath = blocklyPath.slice(0, -1);
  24. }
  25. // Ardublockly.xmlTree = Blockly.Xml.textToDom(toolboxXml);
  26. // The Toolbox menu language is edited directly from the XML nodes.
  27. Ardublockly.updateToolboxLanguage(toolboxXml);
  28. Ardublockly.workspace = Blockly.inject(blocklyEl, {
  29. collapse: true,
  30. comments: true,
  31. css: true,
  32. disable: true,
  33. grid: false,
  34. maxBlocks: Infinity,
  35. media: blocklyPath + '/media/',
  36. rtl: false,
  37. scrollbars: true,
  38. sounds: true,
  39. toolbox: Ardublockly.xmlTree,
  40. trashcan: true,
  41. zoom: {
  42. controls: true,
  43. wheel: false,
  44. startScale: 1.0,
  45. maxScale: 2,
  46. minScale: 0.2,
  47. scaleSpeed: 1.2
  48. }
  49. });
  50. };
  51. /** Binds the event listeners relevant to Blockly. */
  52. Ardublockly.bindBlocklyEventListeners = function () {
  53. blockpy.components.editor.blockly.addChangeListener(Ardublockly.renderContent);
  54. // Ensure the Blockly workspace resizes accordingly
  55. window.addEventListener('resize',
  56. function () { Blockly.asyncSvgResize(blockpy.components.editor.blockly); }, false);
  57. };
  58. /** @return {!string} Generated Arduino code from the Blockly workspace. */
  59. Ardublockly.generateArduino = function () {
  60. return Blockly.Python.workspaceToCode(blockpy.components.editor.blockly);
  61. };
  62. /** @return {!string} Generated XML code from the Blockly workspace. */
  63. Ardublockly.generateXml = function () {
  64. var xmlDom = Blockly.Xml.workspaceToDom(blockpy.components.editor.blockly);
  65. //add tag for different mode
  66. var tag = CCB.mode ? "main" : "wifi"
  67. var xmlText = tag + "$" + Blockly.Xml.domToPrettyText(xmlDom);
  68. return xmlText;
  69. };
  70. Ardublockly.loadXmlBlockFile = function (xmlFile, cbSuccess, cbError) {
  71. var request = Ardublockly.ajaxRequest();
  72. var requestCb = function () {
  73. if (request.readyState == 4) {
  74. if (request.status == 200) {
  75. var result_xml = request.responseText;
  76. //if file has tag(main/wifi)
  77. if (request.responseText.indexOf('$') == 4) {
  78. result_xml = request.responseText.substr(5);
  79. }
  80. var success = Ardublockly.replaceBlocksfromXml(result_xml);
  81. cbSuccess(success);
  82. } else {
  83. cbError();
  84. }
  85. }
  86. };
  87. try {
  88. request.open('GET', xmlFile, true);
  89. request.onreadystatechange = requestCb;
  90. request.send(null);
  91. } catch (e) {
  92. cbError();
  93. }
  94. };
  95. Ardublockly.replaceBlocksfromXml = function (blocksXml) {
  96. var xmlDom = null;
  97. try {
  98. xmlDom = Blockly.Xml.textToDom(blocksXml);
  99. } catch (e) {
  100. return false;
  101. }
  102. blockpy.components.editor.blockly.clear();
  103. var sucess = false;
  104. if (xmlDom) {
  105. sucess = Ardublockly.loadBlocksfromXmlDom(xmlDom);
  106. }
  107. return sucess;
  108. };
  109. /**
  110. * Parses the XML from its argument input to generate and add blocks to the
  111. * Blockly workspace.
  112. * @param {!string} blocksXmlDom String of XML DOM code for the blocks.
  113. * @return {!boolean} Indicates if the XML into blocks parse was successful.
  114. */
  115. Ardublockly.loadBlocksfromXmlDom = function (blocksXmlDom) {
  116. // try {
  117. // Blockly.Xml.domToWorkspace(blocksXmlDom, blockpy.components.editor.blockly);
  118. // } catch (e) {
  119. // return false;
  120. // }
  121. // return true;
  122. Blockly.Xml.domToWorkspace(blocksXmlDom, blockpy.components.editor.blockly);
  123. return !Blockly.Xml.getLoadingXmlErr();
  124. };
  125. /**
  126. * Save blocks into session storage. Note that MSIE 11 does not support
  127. * sessionStorage on file:// URLs.
  128. */
  129. Ardublockly.saveSessionStorageBlocks = function () {
  130. if (window.sessionStorage) {
  131. var xml = Blockly.Xml.workspaceToDom(blockpy.components.editor.blockly);
  132. var text = Blockly.Xml.domToText(xml);
  133. window.sessionStorage.loadOnceBlocks = text;
  134. }
  135. };
  136. /** Load blocks saved on session storage and deletes them from storage. */
  137. Ardublockly.loadSessionStorageBlocks = function () {
  138. try {
  139. var loadOnce = window.sessionStorage.loadOnceBlocks;
  140. } catch (e) {
  141. // Firefox sometimes throws a SecurityError when accessing sessionStorage.
  142. // Restarting Firefox fixes this, so it looks like a bug.
  143. var loadOnce = null;
  144. }
  145. if (loadOnce) {
  146. delete window.sessionStorage.loadOnceBlocks;
  147. var xml = Blockly.Xml.textToDom(loadOnce);
  148. Blockly.Xml.domToWorkspace(xml, blockpy.components.editor.blockly);
  149. }
  150. };
  151. /**
  152. * Save blocks into local storage. Note that MSIE 11 does not support
  153. * LocalStorage on file:// URLs.
  154. */
  155. Ardublockly.saveLocalStorageBlocks = function () {
  156. if (window.sessionStorage) {
  157. var xml = Blockly.Xml.workspaceToDom(blockpy.components.editor.blockly);
  158. var text = Blockly.Xml.domToText(xml);
  159. window.localStorage.loadOnceBlocks = text;
  160. }
  161. };
  162. /** Load blocks saved on loacl storage and deletes them from storage. */
  163. Ardublockly.loadLocalStorageBlocks = function () {
  164. try {
  165. var loadOnce = window.localStorage.loadOnceBlocks;
  166. } catch (e) {
  167. // Firefox sometimes throws a SecurityError when accessing sessionStorage.
  168. // Restarting Firefox fixes this, so it looks like a bug.
  169. var loadOnce = null;
  170. }
  171. if (loadOnce) {
  172. delete window.localStorage.loadOnceBlocks;
  173. var xml = Blockly.Xml.textToDom(loadOnce);
  174. Blockly.Xml.domToWorkspace(xml, blockpy.components.editor.blockly);
  175. }
  176. };
  177. /** Discard all blocks from the workspace. */
  178. Ardublockly.discardAllBlocks = function () {
  179. var blockCount = blockpy.components.editor.blockly.getAllBlocks().length;
  180. var is_delete = false;
  181. if (blockCount >= 1) {
  182. Ardublockly.alertMessage(
  183. Ardublockly.getLocalStr('discardAllCode'),
  184. '',
  185. true,
  186. function () {
  187. blockpy.components.editor.blockly.clear();
  188. Ardublockly.renderContent();
  189. });
  190. }
  191. };
  192. /** @return {!boolean} Indicates if the Blockly workspace has blocks. */
  193. Ardublockly.isWorkspaceEmpty = function () {
  194. return blockpy.components.editor.blockly.getAllBlocks().length ? false : true;
  195. };
  196. /**
  197. * Changes the Arduino board profile if different from the currently set one.
  198. * @param {string} newBoard Name of the new profile to set.
  199. */
  200. Ardublockly.changeBlocklyArduinoBoard = function (newBoard) {
  201. if (Blockly.Python.Boards.selected !== Blockly.Python.Boards[newBoard]) {
  202. Blockly.Python.Boards.changeBoard(blockpy.components.editor.blockly, newBoard);
  203. }
  204. };
  205. /** Update the toolbox categories language. */
  206. Ardublockly.updateToolboxLanguage = function (data) {
  207. var categories = ['catLogic', 'catLoops', 'catMath', 'catText',
  208. 'catVariables', 'catDefine', 'catFunctions', 'catInputOutput',
  209. 'catTime', 'catAudio', 'catMotors', 'catComms', 'catFASTLED', 'catTouch', 'catHCI',
  210. 'catENV', 'catMOTOR', 'catROBOT', 'catLists', 'catMOTION', 'catMainWifi',
  211. 'catCloudData', 'catWifiClient', 'catWifiWebServices', 'catWifiComms', 'catNetwork', 'catEasyMode', 'catBlynk', "ExtendedFunction"
  212. ];
  213. Ardublockly.xmlTree = Blockly.Xml.textToDom(data)
  214. var categoryNodes = Ardublockly.xmlTree.getElementsByTagName('category');
  215. for (var i = 0, cat; cat = categoryNodes[i]; i++) {
  216. var catId = cat.getAttribute('id');
  217. var catText = Ardublockly.getLocalStr(catId);
  218. if (catText) {
  219. cat.setAttribute('name', catText);
  220. }
  221. }
  222. };
  223. /**
  224. * Adds a category to the current toolbox.
  225. * @param {!string} categoryTitle Toolbox category title.
  226. * @param {!Element} categoryDom Toolbox category to add add the end of tree.
  227. */
  228. Ardublockly.addToolboxCategory = function (categoryTitle, categoryDom) {
  229. categoryDom.id = 'cat' + categoryTitle.replace(/\s+/g, '');
  230. categoryDom.setAttribute('name', categoryTitle);
  231. Ardublockly.xmlTree.appendChild(document.createElement('sep'));
  232. Ardublockly.xmlTree.appendChild(categoryDom);
  233. blockpy.components.editor.blockly.updateToolbox(Ardublockly.xmlTree);
  234. };
  235. /**
  236. * Removes a category to the current toolbox.
  237. * @param {!String} categoryTitle Toolbox category name to remove from tree.
  238. */
  239. Ardublockly.removeToolboxCategory = function (categoryTitle) {
  240. var categoryId = 'cat' + categoryTitle.replace(/\s+/g, '');
  241. var categoryNodes = Ardublockly.xmlTree.getElementsByTagName('category');
  242. for (var i = 0; i < categoryNodes.length; i++) {
  243. if (categoryNodes[i].getAttribute('id') === categoryId) {
  244. var previousNode = categoryNodes[i].previousElementSibling;
  245. Ardublockly.xmlTree.removeChild(categoryNodes[i]);
  246. if (previousNode && previousNode.nodeName == 'sep') {
  247. Ardublockly.xmlTree.removeChild(previousNode);
  248. }
  249. }
  250. }
  251. blockpy.components.editor.blockly.updateToolbox(Ardublockly.xmlTree);
  252. };
  253. /** Closes the toolbox block container sub-menu. */
  254. Ardublockly.blocklyCloseToolbox = function () {
  255. blockpy.components.editor.blockly.toolbox_.flyout_.hide();
  256. };
  257. /** @return {!integer} The width of the blockly workspace toolbox. */
  258. Ardublockly.blocklyToolboxWidth = function () {
  259. return blockpy.components.editor.blockly.toolbox_.width;
  260. };
  261. /** @return {!boolean} Indicates if a block is currently being dragged. */
  262. Ardublockly.blocklyIsDragging = function () {
  263. return (Blockly.dragMode_ != 0) ? true : false;
  264. };
  265. Ardublockly.blocklyCut = function () {
  266. if (Blockly.selected) {
  267. Blockly.copy_(Blockly.selected);
  268. Blockly.selected.dispose(true, true);
  269. }
  270. };
  271. Ardublockly.blocklyCopy = function () {
  272. if (Blockly.selected) {
  273. Blockly.copy_(Blockly.selected);
  274. }
  275. };
  276. Ardublockly.blocklyPaste = function () {
  277. if (Blockly.clipboardXml_) {
  278. Blockly.hideChaff();
  279. Blockly.clipboardSource_.paste(Blockly.clipboardXml_);
  280. }
  281. };
  282. Ardublockly.blocklyDelete = function () {
  283. if (Blockly.selected && Blockly.selected.isDeletable()) {
  284. Blockly.hideChaff();
  285. Blockly.selected.dispose(true, true);
  286. }
  287. };
  288. /** @return {XMLHttpRequest} An XML HTTP Request multi-browser compatible. */
  289. Ardublockly.ajaxRequest = function () {
  290. var request;
  291. try {
  292. // Firefox, Chrome, IE7+, Opera, Safari
  293. request = new XMLHttpRequest();
  294. } catch (e) {
  295. try {
  296. // IE6 and earlier
  297. request = new ActiveXObject('Msxml2.XMLHTTP');
  298. } catch (e) {
  299. try {
  300. request = new ActiveXObject('Microsoft.XMLHTTP');
  301. } catch (e) {
  302. throw 'Your browser does not support AJAX';
  303. request = null;
  304. }
  305. }
  306. }
  307. return request;
  308. };
  309. /**
  310. * get a workspace snapshot on blocks
  311. * handle with blocks imgs with onload function twice
  312. * in order to convert img into base64 format data for download
  313. * @param {String} name name of pic to store as
  314. */
  315. Ardublockly.workspace_capture = async function (name) {
  316. let _header = window.location.protocol + "//"
  317. let _href = window.location.origin + "/";
  318. let ws = blockpy.components.editor.blockly.svgBlockCanvas_.cloneNode(true);
  319. ws.removeAttribute("width");
  320. ws.removeAttribute("height");
  321. ws.removeAttribute("transform");
  322. let styleElem = document.createElementNS("http://www.w3.org/2000/svg", "style");
  323. styleElem.textContent = Blockly.Css.CONTENT.join('');
  324. ws.insertBefore(styleElem, ws.firstChild);
  325. let bbox = blockpy.components.editor.blockly.svgBlockCanvas_.getBBox();
  326. let canvas = document.createElement("canvas");
  327. // canvas.width = Math.ceil(bbox.width + 120);
  328. // canvas.height = Math.ceil(bbox.height + 50);
  329. canvas.width = Math.ceil(Math.abs(bbox.x) + bbox.width + 120);
  330. canvas.height = Math.ceil(Math.abs(bbox.y) + bbox.height + 50);
  331. canvas.appendChild(ws);
  332. let ctx = canvas.getContext("2d");
  333. let xml = new XMLSerializer().serializeToString(ws);
  334. let test = /xlink:href="[a-zA-z]+:\/\/[^\s]*"/g;
  335. let test1 = /xlink:href="[^\s]*"/g;
  336. let arr = xml.match(test1)
  337. if (arr != null) {
  338. let loadImage = function () {
  339. return new Promise((resolve) => {
  340. let length = arr.length;
  341. let count = 0;
  342. for (let i = 0; i < length; i++) {
  343. let dataURL;
  344. let canvas_inline = document.createElement("canvas");
  345. let ctx1 = canvas_inline.getContext("2d");
  346. let image_item = new Image();
  347. image_item.setAttribute("src", _header + arr[i].replace("xlink:href=\"", _href).replace("\"", ""));
  348. image_item.onload = function () {
  349. //convert image to base64
  350. canvas_inline.width = image_item.width;
  351. canvas_inline.height = image_item.height;
  352. ctx1.drawImage(image_item, 0, 0);
  353. dataURL = canvas_inline.toDataURL("image/png", 1);
  354. xml = xml.replace(arr[i], "xlink:href=\"" + dataURL + "\"");
  355. count++;
  356. }
  357. }
  358. let timer = setInterval(() => {
  359. if (count == length) {
  360. return resolve(true);
  361. clearInterval(timer);
  362. }
  363. });
  364. });
  365. }
  366. await loadImage();
  367. }
  368. xml = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="' + canvas.width + '" height="' + canvas.height + '" viewBox="' + bbox.x + ' ' + bbox.y + ' ' + canvas.width + ' ' + canvas.height + '"><rect width="100%" height="100%" fill="white"></rect>' + xml + '</svg>';
  369. var img = new Image();
  370. img.setAttribute("src", 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(xml))));
  371. img.onload = function () {
  372. ctx.drawImage(img, 5, 5);
  373. var canvasdata = canvas.toDataURL("image/png", 1);
  374. var a = document.createElement("a");
  375. a.download = name + ".png";
  376. a.href = canvasdata;
  377. document.body.appendChild(a);
  378. a.click();
  379. }
  380. }