/** * @license Licensed under the Apache License, Version 2.0 (the "License"): * http://www.apache.org/licenses/LICENSE-2.0 * * @fileoverview General javaScript for Arduino app with material design. */ 'use strict'; /** Create a namespace for the application. */ var Ardublockly = Ardublockly || {}; /** Initialize function for Ardublockly, to be called on page load. */ Ardublockly.init = function () { // Lang init must run first for the rest of the page to pick the right msgs Ardublockly.initLanguage(); // Inject Blockly into content_blocks and fetch additional blocks Ardublockly.injectBlockly(document.getElementsByClassName('blockpy-editor')[0], Ardublockly.TOOLBOX_XML, 'blockly/'); // // /* Inject wifi Blockly into content_blocks and fetch additional blocks // // * set it invisiable // // * visiable only when switch // // */ Ardublockly.injectBlockly(document.getElementsByClassName('blockpy-editor')[0], Ardublockly.WIFITOOLBOX_XML, 'blockly/'); // Ardublockly.importExtraBlocks(); Ardublockly.designJsInit(); // Ardublockly.renderContent(); // Ardublockly.bindDesignEventListeners(); Ardublockly.bindActionFunctions(); // Ardublockly.bindBlocklyEventListeners(); // blockpy.components.editor.blockly.toolbox_.HtmlDiv = $('.blocklyToolboxDiv')[0]; // $('.blocklyToolboxDiv')[1].style.display = 'none'; // Check if not running locally // if (document.location.hostname != 'localhost') { // Ardublockly.openNotConnectedModal(); // } }; /** * Binds functions to each of the buttons, nav links, and related. * or binds functions to dom for drag files from local folder */ Ardublockly.bindActionFunctions = function () { // Navigation buttons // Ardublockly.bindClick_('modal_import_btn', () => { // Ardublockly.loadUserXmlFile(); // $("#Storage_import_modal").modal("close"); // }); $('#modal_import_btn').click(e => { $('#xmlFileImport > input').click(); $("#Storage_import_modal").modal("close"); }); // Ardublockly.bindClick_('modal_exportFileBtn', () => { // Ardublockly.saveXmlFile(); // $("#Storage_export_modal").modal("close"); // }); $('#modal_exportFileBtn').click(() => { downloadXml(); $("#Storage_export_modal").modal("close"); }); Ardublockly.bindClick_('button_delete', () => { Ardublockly.discardAllBlocks(); // blockpy.components.editor.clearBlocksFromXml(); }); Ardublockly.bindClick_('modal_exportSnapBtn', () => { let name = $("#sketch_name").val(); Ardublockly.workspace_capture(name); $("#Storage_export_modal").modal("close"); }); Ardublockly.bindClick_('workspace_screenshot', () => { let name = $("#sketch_name").val(); Ardublockly.workspace_capture(name); }); // Side menu buttons, they also close the side menu // Ardublockly.bindClick_('button_toggle_toolbox', Ardublockly.toogleToolbox); Ardublockly.binddrag_('main_content', (e) => { if (e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types[0] == "Files") { Ardublockly.handleDragEnter(e); $("#main_shadow").css("display", "block"); } }); // Ardublockly.binddrag_('main_shadow', (e) => { // Ardublockly.handleDragEnter(e); // $("#main_shadow").css("display", "block"); // }, (e) => { // Ardublockly.handleDragLeave(e); // $("#main_shadow").css("display", "none"); // }); // Ardublockly.binddrop_('main_shadow', (e) => { // Ardublockly.handleFileSelect(e); // $("#main_shadow").css("display", "none"); // }); Ardublockly.binddrag_('shadow_content', (e) => { Ardublockly.handleDragEnter(e); $("#main_shadow").css("display", "block"); }, (e) => { Ardublockly.handleDragLeave(e); $("#main_shadow").css("display", "none"); }); Ardublockly.binddrop_('shadow_content', (e) => { Ardublockly.handleFileSelect(e); $("#main_shadow").css("display", "none"); }); }; /** * Loads an XML file from the server and replaces the current blocks into the * Blockly workspace. * @param {!string} xmlFile Server location of the XML file to load. */ Ardublockly.loadServerXmlFile = function (xmlFile) { var loadXmlfileAccepted = function () { // loadXmlBlockFile loads the file asynchronously and needs a callback var loadXmlCb = function (sucess) { if (sucess) { Ardublockly.renderContent(); } else { setTimeout(() => { Ardublockly.alertMessage( Ardublockly.getLocalStr('ErrorBlockTitle'), Ardublockly.getLocalStr('ErrorBlockBody'), false); }, 500); } }; var connectionErrorCb = function () { // Ardublockly.openNotConnectedModal(); }; Ardublockly.loadXmlBlockFile(xmlFile, loadXmlCb, connectionErrorCb); }; if (Ardublockly.isWorkspaceEmpty()) { loadXmlfileAccepted(); } else { Ardublockly.alertMessage( Ardublockly.getLocalStr('loadNewBlocksTitle'), Ardublockly.getLocalStr('loadNewBlocksBody'), true, loadXmlfileAccepted); } }; /** * Loads an XML file from the users file system and adds the blocks into the * Blockly workspace. */ Ardublockly.loadUserXmlFile = function () { // Create File Reader event listener function var parseInputXMLfile = function (e) { Ardublockly.FileReaderLocal(e.target.files[0]); }; // Create once invisible browse button with event listener, and click it var selectFile = document.getElementById('select_file'); if (selectFile === null) { var selectFileDom = document.createElement('INPUT'); selectFileDom.type = 'file'; selectFileDom.id = 'select_file'; var selectFileWrapperDom = document.createElement('DIV'); selectFileWrapperDom.id = 'select_file_wrapper'; selectFileWrapperDom.style.display = 'none'; selectFileWrapperDom.appendChild(selectFileDom); document.body.appendChild(selectFileWrapperDom); selectFile = document.getElementById('select_file'); selectFile.addEventListener('change', parseInputXMLfile, false); } selectFile.click(); }; /** * Creates an XML file containing the blocks from the Blockly workspace and * prompts the users to save it into their local file system. */ Ardublockly.saveXmlFile = function () { Ardublockly.saveTextFileAs( document.getElementById('sketch_name').value + '.xml', Ardublockly.generateXml()); }; /** * Creates an Arduino Sketch file containing the Arduino code generated from * the Blockly workspace and prompts the users to save it into their local file * system. */ Ardublockly.saveSketchFile = function () { Ardublockly.saveTextFileAs( document.getElementById('sketch_name').value + '.ino', Ardublockly.generateArduino()); }; /*debug mode on 2018-06-13 */ /** * Creates an Arduino Sketch file containing the Arduino code generated from * the Blockly workspace and prompts the users to save it into cloud file * system. */ Ardublockly.saveSketchFileCloud = function () { Ardublockly.saveTextFileCloudAs( document.getElementById('sketch_name').value + '.ino', Ardublockly.generateArduino()); }; /** * Creates an text file with the input content and files name, and prompts the * users to save it into their local file system. * @param {!string} fileName Name for the file to be saved. * @param {!string} content Text datd to be saved in to the file. */ Ardublockly.saveTextFileAs = function (fileName, content) { var blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); saveAs(blob, fileName); }; /*debug mode on 2018-06-13 */ /** * Creates an text file with the input content and files name, and prompts the * users to save it into cloud file system. * @param {!string} fileName Name for the file to be saved. * @param {!string} content Text datd to be saved in to the file. */ Ardublockly.saveTextFileAs = function (fileName, content) { var blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); saveAs(blob, fileName); }; /** Populate the workspace blocks with the XML written in the XML text area. */ Ardublockly.XmlTextareaToBlocks = function () { var success = Ardublockly.replaceBlocksfromXml( document.getElementById('content_xml').value); if (success) { Ardublockly.renderContent(); } else { Ardublockly.alertMessage( Ardublockly.getLocalStr('invalidXmlTitle'), Ardublockly.getLocalStr('invalidXmlBody'), false); } }; /** * Private variable to save the previous version of the Arduino Code. * @type {!String} * @private */ Ardublockly.PREV_ARDUINO_CODE_ = 'void setup() {\n\n}\n\n\nvoid loop() {\n\n}'; /** * Populate the Arduino Code and Blocks XML panels with content generated from * the blocks. */ Ardublockly.renderContent = function () { // Only regenerate the code if a block is not being dragged if (Ardublockly.blocklyIsDragging()) return; // Render Arduino Code with latest change highlight and syntax highlighting var arduinoCode = Ardublockly.generateArduino(); if (arduinoCode !== Ardublockly.PREV_ARDUINO_CODE_) { var diff = JsDiff.diffWords(Ardublockly.PREV_ARDUINO_CODE_, arduinoCode); var resultStringArray = []; for (var i = 0; i < diff.length; i++) { if (!diff[i].removed) { var escapedCode = diff[i].value.replace(//g, '>'); if (diff[i].added) { resultStringArray.push( '' + escapedCode + ''); } else { resultStringArray.push(escapedCode); } } } // document.getElementById('content_arduino').innerHTML = // prettyPrintOne(resultStringArray.join(''), 'cpp', false); Ardublockly.PREV_ARDUINO_CODE_ = arduinoCode; } // Generate plain XML into element // document.getElementById('content_xml').value = Ardublockly.generateXml(); }; /** * Private variable to indicate if the toolbox is meant to be shown. * @type {!boolean} * @private */ Ardublockly.TOOLBAR_SHOWING_ = true; /** * Toggles the blockly toolbox and the Ardublockly toolbox button On and Off. * Uses namespace member variable TOOLBAR_SHOWING_ to toggle state. */ Ardublockly.toogleToolbox = function () { if (Ardublockly.TOOLBAR_SHOWING_) { Ardublockly.blocklyCloseToolbox(); Ardublockly.displayToolbox(false); } else { Ardublockly.displayToolbox(true); } Ardublockly.TOOLBAR_SHOWING_ = !Ardublockly.TOOLBAR_SHOWING_; }; /** @return {boolean} Indicates if the toolbox is currently visible. */ Ardublockly.isToolboxVisible = function () { return Ardublockly.TOOLBAR_SHOWING_; }; /** * Lazy loads the additional block JS files from the ./block directory. * Initialises any additional Ardublockly extensions. * TODO: Loads the examples into the examples modal */ Ardublockly.importExtraBlocks = function () { /** * Parses the JSON data to find the block and languages js files. * @param {jsonDataObj} jsonDataObj JSON in JavaScript object format, null * indicates an error occurred. * @return {undefined} Might exit early if response is null. */ var jsonDataCb = function (jsonDataObj) { if (jsonDataObj === null) return Ardublockly.openNotConnectedModal(); if (jsonDataObj.categories !== undefined) { var head = document.getElementsByTagName('head')[0]; for (var catDir in jsonDataObj.categories) { var blocksJsLoad = document.createElement('script'); blocksJsLoad.src = '../blocks/' + catDir + '/blocks.js'; head.appendChild(blocksJsLoad); var blocksLangJsLoad = document.createElement('script'); blocksLangJsLoad.src = '../blocks/' + catDir + '/msg/' + 'messages.js'; //'lang/' + Ardublockly.LANG + '.js'; head.appendChild(blocksLangJsLoad); var blocksGeneratorJsLoad = document.createElement('script'); blocksGeneratorJsLoad.src = '../blocks/' + catDir + '/generator_arduino.js'; head.appendChild(blocksGeneratorJsLoad); // Check if the blocks add additional Ardublockly functionality var extensions = jsonDataObj.categories[catDir].extensions; if (extensions) { for (var i = 0; i < extensions.length; i++) { var blockExtensionJsLoad = document.createElement('script'); blockExtensionJsLoad.src = '../blocks/' + catDir + '/extensions.js'; head.appendChild(blockExtensionJsLoad); // Add function to scheduler as lazy loading has to complete first setTimeout(function (category, extension) { var extensionNamespaces = extension.split('.'); var extensionCall = window; var invalidFunc = false; for (var j = 0; j < extensionNamespaces.length; j++) { extensionCall = extensionCall[extensionNamespaces[j]]; if (extensionCall === undefined) { invalidFunc = true; break; } } if (typeof extensionCall != 'function') { invalidFunc = true; } if (invalidFunc) { throw 'Blocks ' + category.categoryName + ' extension "' + extension + '" is not a valid function.'; } else { extensionCall(); } }, 800, jsonDataObj.categories[catDir], extensions[i]); } } } } }; // Reads the JSON data containing all block categories from ./blocks directory // TODO: Now reading a local file, to be replaced by server generated JSON Ardublockly.getJsonData('../blocks/blocks_data.json', jsonDataCb); }; /** Opens a modal with a list of categories to add or remove to the toolbox */ Ardublockly.openExtraCategoriesSelect = function () { /** * Parses the JSON data from the server into a list of additional categories. * @param {jsonDataObj} jsonDataObj JSON in JavaScript object format, null * indicates an error occurred. * @return {undefined} Might exit early if response is null. */ var jsonDataCb = function (jsonDataObj) { if (jsonDataObj === null) return Ardublockly.openNotConnectedModal(); var htmlContent = document.createElement('div'); if (jsonDataObj.categories !== undefined) { for (var catDir in jsonDataObj.categories) { // Function required to maintain each loop variable scope separated (function (cat) { var clickBind = function (tickValue) { if (tickValue) { var catDom = (new DOMParser()).parseFromString( cat.toolbox.join(''), 'text/xml').firstChild; Ardublockly.addToolboxCategory(cat.toolboxName, catDom); } else { Ardublockly.removeToolboxCategory(cat.toolboxName); } }; htmlContent.appendChild(Ardublockly.createExtraBlocksCatHtml( cat.categoryName, cat.description, clickBind)); })(jsonDataObj.categories[catDir]); } } Ardublockly.openAdditionalBlocksModal(htmlContent); }; // Reads the JSON data containing all block categories from ./blocks directory // TODO: Now reading a local file, to be replaced by server generated JSON Ardublockly.getJsonData('../blocks/blocks_data.json', jsonDataCb); }; /** Informs the user that the selected function is not yet implemented. */ Ardublockly.functionNotImplemented = function () { Ardublockly.shortMessage('Function not yet implemented'); }; /** * Interface to display messages with a possible action. * @param {!string} title HTML to include in title. * @param {!element} body HTML to include in body. * @param {boolean=} confirm Indicates if the user is shown a single option (ok) * or an option to cancel, with an action applied to the "ok". * @param {string=|function=} callback If confirm option is selected this would * be the function called when clicked 'OK'. */ Ardublockly.alertMessage = function (title, body, confirm, callback) { Ardublockly.materialAlert(title, body, confirm, function () { callback(); $("#gen_alert_ok_link").unbind("click"); }); }; Ardublockly.alertExampleMessage = function (title, body, confirm, callback) { Ardublockly.exampleAlert(title, body, confirm, function () { }); }; Ardublockly.customAlertMessage = function (title, body, customText, callback) { Ardublockly.customAlert(title, body, customText, function () { callback(); $("#cus_alert_button").unbind("click"); }); }; /** * Interface to displays a short message, which disappears after a time out. * @param {!string} message Text to be temporarily displayed. */ Ardublockly.shortMessage = function (message) { Ardublockly.MaterialToast(message); }; /** * Bind a function to a button's click event. * On touch enabled browsers, ontouchend is treated as equivalent to onclick. * @param {!Element|string} el Button element or ID thereof. * @param {!function} func Event handler to bind. */ Ardublockly.bindClick_ = function (el, func) { if (typeof el == 'string') { el = document.getElementById(el); } // Need to ensure both, touch and click, events don't fire for the same thing var propagateOnce = function (e) { e.stopPropagation(); e.preventDefault(); func(); }; el.addEventListener('ontouchend', propagateOnce); el.addEventListener('click', propagateOnce); }; /** * Bind a function to enable Toolbox scroll by touch on mobile media device. * @param {!Element|string} e element of touch node. */ Ardublockly.bindTouchMove = function (e) { let startY = e.originalEvent.targetTouches[0].pageY; $(e.currentTarget).on('touchmove', moveCallback); $(e.currentTarget).on('touchend', touchEndCallback); function moveCallback(e) { let endY = e.originalEvent.targetTouches[0].pageY; let val = $(e.currentTarget).scrollTop() + (startY - endY) / 2; val = val < 0 ? 0 : val; $(e.currentTarget).scrollTop(val); startY = endY; } function touchEndCallback() { $(e.currentTarget).off('touchmove', moveCallback); $(e.currentTarget).off('touchend', touchEndCallback); } }; /** * Bind a function to a drag event. * drag a file into the webpage * @param {!Element|string} el drag element or ID thereof. * @param {!function} func1 Drag over handler to bind. */ Ardublockly.binddrag_ = function (el, func1, func2) { if (typeof el == 'string') { el = document.getElementById(el); } el.addEventListener('dragenter', func1); el.addEventListener('dragover', (e) => { e.stopPropagation(); e.preventDefault(); }); el.addEventListener('dragleave', func2); } /** * Bind a function to a drop event. * drop a file into the webpage * @param {!Element|string} el drag element or ID thereof. * @param {!function} func Event handler to bind. */ Ardublockly.binddrop_ = function (el, func) { if (typeof el == 'string') { el = document.getElementById(el); } el.addEventListener('drop', func); } /** * handle File Select by drag from local folder */ Ardublockly.handleFileSelect = function (e, files) { e.stopPropagation(); e.preventDefault(); var files = files ? files : e.dataTransfer.files; //检测是否是拖拽文件到页面的操作 if (files.length == 0) { return false; } //检测文件是不是xml if (files[0].type.indexOf('text/xml') === -1) { Ardublockly.alertMessage( Ardublockly.getLocalStr('ErrorFileDragTitle'), Ardublockly.getLocalStr('ErrorFileDragBody'), false); return false; } Ardublockly.FileReaderLocal(files[0]); $('#sketch_name').val(files[0].name.match(/(.+).xml$/)[1]); } /** * handle drag over function of local file */ Ardublockly.handleDragEnter = function (e) { e.stopPropagation(); e.preventDefault(); e.dataTransfer.dropEffect = "copy"; } /** * handle drag file leave the designated area */ Ardublockly.handleDragLeave = function (e) { e.stopPropagation(); e.preventDefault(); } /** * FileReader : read file and render it into workspace * @param {Element} xmlFile an object abtain filename and xml. */ Ardublockly.FileReaderLocal = function (xmlFile) { var filename = xmlFile.name; var extensionPosition = filename.lastIndexOf('.'); if (extensionPosition !== -1) { filename = filename.substr(0, extensionPosition); } var reader = new FileReader(); reader.onload = function () { if (Ardublockly.isWorkspaceEmpty()) { Ardublockly.alertExampleMessage( '', Ardublockly.getLocalStr('loadBlockBody'), true, {}); } else { Ardublockly.alertExampleMessage( '', Ardublockly.getLocalStr('loadBlockBody'), true, {}); } var result_xml = reader.result; try { $("#mode")[0].selectedIndex = 0; $("#mode")[0].onchange(); $('.selectMode_input')[0].value = Ardublockly.LOCALISED_TEXT.ai_module; // document.getElementById("list").getElementsByTagName("li")[0].onclick(); } catch (e) { return false; } var success = Ardublockly.replaceBlocksfromXml(result_xml); if (!success) { Ardublockly.alertMessage(Ardublockly.getLocalStr('ErrorBlockTitle'), Ardublockly.getLocalStr('ErrorBlockBody'), false); } }; reader.readAsText(xmlFile); setTimeout(() => { $('#loading').css({ 'display': 'none' }); $('#example_alert').modal('close'); }, 500) } /** Resizes the container for the Blockly workspace. */ Ardublockly.resizeBlocklyWorkspace = function () { var contentBlocks = document.getElementsByClassName('blockpy-editor')[0]; var wrapperPanelSize = Ardublockly.getBBox_(document.getElementById('blockpy-content')); contentBlocks.style.top = wrapperPanelSize.y + 'px'; contentBlocks.style.left = wrapperPanelSize.x + 'px'; // Height and width need to be set, read back, then set again to // compensate for scrollbars. contentBlocks.style.height = wrapperPanelSize.height + 'px'; contentBlocks.style.height = (2 * wrapperPanelSize.height - contentBlocks.offsetHeight) + 'px'; contentBlocks.style.width = wrapperPanelSize.width + 'px'; contentBlocks.style.width = (2 * wrapperPanelSize.width - contentBlocks.offsetWidth) + 'px'; }; /** * Compute the absolute coordinates and dimensions of an HTML element. * @param {!Element} element Element to match. * @return {!Object} Contains height, width, x, and y properties. * @private */ Ardublockly.getBBox_ = function (element) { var height = element.offsetHeight; var width = element.offsetWidth; var x = 0; var y = 0; do { x += element.offsetLeft; y += element.offsetTop; element = element.offsetParent; } while (element); return { height: height, width: width, x: x, y: y }; }; Blockly.asyncSvgResize = function (a) { Blockly.svgResizePending_ || (a || (a = Blockly.getMainWorkspace()), Blockly.svgResizePending_ = !0, setTimeout(function () { Blockly.svgResize(a) }, 0)) }; /** * Save blocks as XML file. Note that MSIE 11 does not support * @param {String} filename */ function downloadXml() { var _xml = blockpy.components.editor.getBlocksFromXml(); _xml.setAttribute("type", $("#mode")[0].selectedIndex == 1 ? "AI" : "IoT") var data = Blockly.Xml.domToPrettyText(_xml); let filename = $('#sketch_name').val(); let reg = new RegExp(/[^a-z0-9]/) // Make safe filename = reg.test(filename) ? filename : filename.replace(/[^a-z0-9]/gi, '_').toLowerCase(); filename = filename + '.xml' var blob = new Blob([data], { type: 'text/plain;charset=utf-8' }); // if (window.navigator.msSaveOrOpenBlob) { // window.navigator.msSaveBlob(blob, filename); // } else { var elem = window.document.createElement('a'); elem.href = window.URL.createObjectURL(blob); elem.download = filename; document.body.appendChild(elem); elem.click(); document.body.removeChild(elem); // } }