/** * @license Licensed under the Apache License, Version 2.0 (the "License"): * http://www.apache.org/licenses/LICENSE-2.0 * * @fileoverview Ardublockly JavaScript for the Blockly resources and bindings. */ 'use strict'; /** Create a namespace for the application. */ var Ardublockly = Ardublockly || {}; /** * Blockly main workspace. * @type Blockly.WorkspaceSvg */ Ardublockly.workspace = null; /** * Blockly workspace toolbox XML. * @type Element */ Ardublockly.xmlTree = null; /** * Injects Blockly into a given HTML element. Toolbox XMl has to be a string. * @param {!Element} blocklyEl Element to inject Blockly into. * @param {!string} toolboxXml String containing the toolbox XML content. * @param {!string} blocklyPath String containing the Blockly directory path. */ Ardublockly.injectBlockly = function(blocklyEl, toolboxXml, blocklyPath) { // Remove any trailing slashes in the blockly path if (blocklyPath.substr(-1) === '/') { blocklyPath = blocklyPath.slice(0, -1); } Ardublockly.xmlTree = Blockly.Xml.textToDom(toolboxXml); // The Toolbox menu language is edited directly from the XML nodes. Ardublockly.updateToolboxLanguage(); Ardublockly.workspace = Blockly.inject(blocklyEl, { collapse: true, comments: true, css: true, disable: true, grid: false, maxBlocks: Infinity, media: blocklyPath + '/media/', rtl: false, scrollbars: true, sounds: true, toolbox: Ardublockly.xmlTree, trashcan: true, zoom: { controls: true, wheel: false, startScale: 1.0, maxScale: 2, minScale: 0.2, scaleSpeed: 1.2 } }); // On language change the blocks have been stored in session storage Ardublockly.loadSessionStorageBlocks(); }; /** Binds the event listeners relevant to Blockly. */ Ardublockly.bindBlocklyEventListeners = function() { Ardublockly.workspace.addChangeListener(function(event) { if (event.type != Blockly.Events.UI) { Ardublockly.renderContent(); } }); // Ensure the Blockly workspace resizes accordingly window.addEventListener('resize', function() { Blockly.asyncSvgResize(Ardublockly.workspace); }, false); }; /** @return {!string} Generated Arduino code from the Blockly workspace. */ Ardublockly.generateArduino = function() { return Blockly.Arduino.workspaceToCode(Ardublockly.workspace); }; /** @return {!string} Generated XML code from the Blockly workspace. */ Ardublockly.generateXml = function() { var xmlDom = Blockly.Xml.workspaceToDom(Ardublockly.workspace); return Blockly.Xml.domToPrettyText(xmlDom); }; /** * Loads an XML file from the server and replaces the current blocks into the * Blockly workspace. * @param {!string} xmlFile XML file path in a reachable server (no local path). * @param {!function} cbSuccess Function to be called once the file is loaded. * @param {!function} cbError Function to be called if there is a connection * error to the XML server. */ Ardublockly.loadXmlBlockFile = function(xmlFile, cbSuccess, cbError) { var request = Ardublockly.ajaxRequest(); var requestCb = function() { if (request.readyState == 4) { if (request.status == 200) { var success = Ardublockly.replaceBlocksfromXml(request.responseText); cbSuccess(success); } else { cbError(); } } }; try { request.open('GET', xmlFile, true); request.onreadystatechange = requestCb; request.send(null); } catch (e) { cbError(); } }; /** * Parses the XML from its argument input to generate and replace the blocks * in the Blockly workspace. * @param {!string} blocksXml String of XML code for the blocks. * @return {!boolean} Indicates if the XML into blocks parse was successful. */ Ardublockly.replaceBlocksfromXml = function(blocksXml) { var xmlDom = null; try { xmlDom = Blockly.Xml.textToDom(blocksXml); } catch (e) { return false; } Ardublockly.workspace.clear(); var sucess = false; if (xmlDom) { sucess = Ardublockly.loadBlocksfromXmlDom(xmlDom); } return sucess; }; /** * Parses the XML from its argument to add the blocks to the workspace. * @param {!string} blocksXmlDom String of XML DOM code for the blocks. * @return {!boolean} Indicates if the XML into blocks parse was successful. */ Ardublockly.loadBlocksfromXmlDom = function(blocksXmlDom) { try { Blockly.Xml.domToWorkspace(blocksXmlDom, Ardublockly.workspace); } catch (e) { return false; } return true; }; /** * Save blocks into session storage. Note that MSIE 11 does not support * sessionStorage on file:// URLs. */ Ardublockly.saveSessionStorageBlocks = function() { if (window.sessionStorage) { var xml = Blockly.Xml.workspaceToDom(Ardublockly.workspace); var text = Blockly.Xml.domToText(xml); window.sessionStorage.loadOnceBlocks = text; } }; /** Load blocks saved on session storage and deletes them from storage. */ Ardublockly.loadSessionStorageBlocks = function() { try { var loadOnce = window.sessionStorage.loadOnceBlocks; } catch (e) { // Firefox sometimes throws a SecurityError when accessing sessionStorage. // Restarting Firefox fixes this, so it looks like a bug. var loadOnce = null; } if (loadOnce) { delete window.sessionStorage.loadOnceBlocks; var xml = Blockly.Xml.textToDom(loadOnce); Blockly.Xml.domToWorkspace(xml, Ardublockly.workspace); } }; /** Discard all blocks from the workspace. */ Ardublockly.discardAllBlocks = function() { var blockCount = Ardublockly.workspace.getAllBlocks().length; if (blockCount == 1) { Ardublockly.workspace.clear(); Ardublockly.renderContent(); } else if (blockCount > 1) { Ardublockly.alertMessage( Ardublockly.getLocalStr('discardBlocksTitle'), Ardublockly.getLocalStr('discardBlocksBody') .replace('%1', blockCount), true, function() { Ardublockly.workspace.clear(); Ardublockly.renderContent(); }); } }; /** @return {!boolean} Indicates if the Blockly workspace has blocks. */ Ardublockly.isWorkspaceEmpty = function() { return Ardublockly.workspace.getAllBlocks().length ? false : true; }; /** * Changes the Arduino board profile if different from the currently set one. * @param {string} newBoard Name of the new profile to set. */ Ardublockly.changeBlocklyArduinoBoard = function(newBoard) { if (Blockly.Arduino.Boards.selected !== Blockly.Arduino.Boards[newBoard]) { Blockly.Arduino.Boards.changeBoard(Ardublockly.workspace, newBoard); } }; /** Update the toolbox categories language. */ Ardublockly.updateToolboxLanguage = function() { var categories = ['catLogic', 'catLoops', 'catMath', 'catText', 'catVariables', 'catFunctions', 'catInputOutput', 'catTime', 'catAudio', 'catMotors', 'catComms']; var categoryNodes = Ardublockly.xmlTree.getElementsByTagName('category'); for (var i = 0, cat; cat = categoryNodes[i]; i++) { var catId = cat.getAttribute('id'); var catText = Ardublockly.getLocalStr(catId); if (catText) { cat.setAttribute('name', catText); } } }; /** * Adds a category to the current toolbox. * @param {!string} categoryTitle Toolbox category title. * @param {!Element} categoryDom Toolbox category to add add the end of tree. */ Ardublockly.addToolboxCategory = function(categoryTitle, categoryDom) { categoryDom.id = 'cat' + categoryTitle.replace(/\s+/g, ''); categoryDom.setAttribute('name', categoryTitle); Ardublockly.xmlTree.appendChild(document.createElement('sep')); Ardublockly.xmlTree.appendChild(categoryDom); Ardublockly.workspace.updateToolbox(Ardublockly.xmlTree); }; /** * Removes a category to the current toolbox. * @param {!String} categoryTitle Toolbox category name to remove from tree. */ Ardublockly.removeToolboxCategory = function(categoryTitle) { var categoryId = 'cat' + categoryTitle.replace(/\s+/g, ''); var categoryNodes = Ardublockly.xmlTree.getElementsByTagName('category'); for (var i = 0; i < categoryNodes.length; i++) { if (categoryNodes[i].getAttribute('id') === categoryId) { var previousNode = categoryNodes[i].previousElementSibling; Ardublockly.xmlTree.removeChild(categoryNodes[i]); if (previousNode && previousNode.nodeName == 'sep') { Ardublockly.xmlTree.removeChild(previousNode); } } } Ardublockly.workspace.updateToolbox(Ardublockly.xmlTree); }; /** Closes the toolbox block container sub-menu. */ Ardublockly.blocklyCloseToolbox = function() { Ardublockly.workspace.toolbox_.flyout_.hide(); }; /** @return {!integer} The width of the blockly workspace toolbox. */ Ardublockly.blocklyToolboxWidth = function() { return Ardublockly.workspace.toolbox_.width; }; /** @return {!boolean} Indicates if a block is currently being dragged. */ Ardublockly.blocklyIsDragging = function() { return (Blockly.dragMode_ != 0) ? true : false; }; /** Wraps the blockly 'cut' functionality. */ Ardublockly.blocklyCut = function() { if (Blockly.selected) { Blockly.copy_(Blockly.selected); Blockly.selected.dispose(true, true); } }; /** Wraps the blockly 'copy' functionality. */ Ardublockly.blocklyCopy = function() { if (Blockly.selected) { Blockly.copy_(Blockly.selected); } }; /** Wraps the blockly 'paste' functionality. */ Ardublockly.blocklyPaste = function() { if (Blockly.clipboardXml_) { Blockly.hideChaff(); Blockly.clipboardSource_.paste(Blockly.clipboardXml_); } }; /** Wraps the blockly 'delete' functionality. */ Ardublockly.blocklyDelete = function() { if (Blockly.selected && Blockly.selected.isDeletable()) { Blockly.hideChaff(); Blockly.selected.dispose(true, true); } }; /** @return {XMLHttpRequest} An XML HTTP Request multi-browser compatible. */ Ardublockly.ajaxRequest = function() { var request; try { // Firefox, Chrome, IE7+, Opera, Safari request = new XMLHttpRequest(); } catch (e) { try { // IE6 and earlier request = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { request = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { throw 'Your browser does not support AJAX'; request = null; } } } return request; };