/** * @license * Visual Blocks Editor * * Copyright 2012 Google Inc. * https://developers.google.com/blockly/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview Loading and saving blocks with localStorage and cloud storage. * @author q.neutron@gmail.com (Quynh Neutron) */ 'use strict'; // Create a namespace. var BlocklyStorage = {}; BlocklyStorage.STORAGE_URL = 'https://glowing-fire-4998.firebaseio.com'; BlocklyStorage.isLinkUrl = function () { var op = ['openModal', 'close']; return window.location.hash.length > 1 && op.indexOf(window.location.hash.substring(1)) === -1; }; /** * Backup code blocks to localStorage. * @param {!Blockly.WorkspaceSvg} workspace Workspace. * @private */ BlocklyStorage.backupBlocks_ = function (workspace) { if ('localStorage' in window) { var xml = Blockly.Xml.workspaceToDom(workspace); // Gets the current URL, not including the hash. var url = window.location.origin; window.localStorage.setItem(url, Blockly.Xml.domToText(xml)); } }; /** * Bind the localStorage backup function to the unload event. * @param {Blockly.WorkspaceSvg} opt_workspace Workspace. */ BlocklyStorage.backupOnUnload = function (opt_workspace) { var workspace = opt_workspace || Blockly.getMainWorkspace(); window.addEventListener('unload', function () { if (!BlocklyStorage.isLinkUrl()) { BlocklyStorage.backupBlocks_(workspace); } }, false); }; /** * Restore code blocks from localStorage. * @param {Blockly.WorkspaceSvg} opt_workspace Workspace. */ BlocklyStorage.restoreBlocks = function (opt_workspace) { var url = window.location.origin; if ('localStorage' in window && window.localStorage[url]) { var workspace = opt_workspace || Blockly.getMainWorkspace(); var xml = Blockly.Xml.textToDom(window.localStorage[url]); Blockly.Xml.domToWorkspace(xml, workspace); } }; /** * Save blocks to database and return a link containing key to XML. * @param {Blockly.WorkspaceSvg} opt_workspace Workspace. */ BlocklyStorage.link = function (opt_workspace) { var workspace = opt_workspace || Blockly.getMainWorkspace(); var xml = Blockly.Xml.workspaceToDom(workspace); var data = Blockly.Xml.domToText(xml); BlocklyStorage.makeRequest_('/blockly', 'xml', data, workspace); }; /** * Retrieve XML text from database using given key. * @param {string} key Key to XML, obtained from href. * @param {Blockly.WorkspaceSvg} opt_workspace Workspace. */ BlocklyStorage.retrieveXml = function (key, opt_workspace) { var workspace = opt_workspace || Blockly.getMainWorkspace(); BlocklyStorage.makeRequest_('/blockly', 'key', key, workspace); }; /** * Global reference to current AJAX request. * @type {XMLHttpRequest} * @private */ BlocklyStorage.httpRequest_ = null; /** * Fire a new AJAX request. * @param {string} url URL to fetch. * @param {string} name Name of parameter. * @param {string} content Content of parameter. * @param {!Blockly.WorkspaceSvg} workspace Workspace. * @private */ BlocklyStorage.makeRequest_ = function (url, name, content, workspace) { if (BlocklyStorage.httpRequest_) { // AJAX call is in-flight. BlocklyStorage.httpRequest_.abort(); } BlocklyStorage.httpRequest_ = new XMLHttpRequest(); BlocklyStorage.httpRequest_.name = name; BlocklyStorage.httpRequest_.onreadystatechange = BlocklyStorage.handleRequest_; switch (name) { case 'xml': BlocklyStorage.httpRequest_.open('POST', BlocklyStorage.STORAGE_URL + url + '.json'); BlocklyStorage.httpRequest_.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); BlocklyStorage.httpRequest_.send(JSON.stringify({ xml: content })); break; case 'key': BlocklyStorage.httpRequest_.open('GET', BlocklyStorage.STORAGE_URL + url + '/' + content + '.json'); BlocklyStorage.httpRequest_.send(); break; } BlocklyStorage.httpRequest_.workspace = workspace; }; /** * Callback function for AJAX call. * @private */ BlocklyStorage.handleRequest_ = function () { if (BlocklyStorage.httpRequest_.readyState == 4) { if (BlocklyStorage.httpRequest_.status != 200) { BlocklyStorage.alert(BlocklyStorage.HTTPREQUEST_ERROR + '\n' + 'httpRequest_.status: ' + BlocklyStorage.httpRequest_.status); } else { var data = BlocklyStorage.httpRequest_.responseText.trim(); if (BlocklyStorage.httpRequest_.name == 'xml') { data = JSON.parse(data).name; window.location.hash = data; BlocklyStorage.alert(BlocklyStorage.LINK_ALERT.replace('%1', window.location.href)); } else if (BlocklyStorage.httpRequest_.name == 'key') { if (!data.length) { BlocklyStorage.alert(BlocklyStorage.HASH_ERROR.replace('%1', window.location.hash)); } else { data = JSON.parse(data).xml; BlocklyStorage.loadXml_(data, BlocklyStorage.httpRequest_.workspace); } } BlocklyStorage.monitorChanges_(BlocklyStorage.httpRequest_.workspace); } BlocklyStorage.httpRequest_ = null; } }; /** * Start monitoring the workspace. If a change is made that changes the XML, * clear the key from the URL. Stop monitoring the workspace once such a * change is detected. * @param {!Blockly.WorkspaceSvg} workspace Workspace. * @private */ BlocklyStorage.monitorChanges_ = function (workspace) { var startXmlDom = Blockly.Xml.workspaceToDom(workspace); var startXmlText = Blockly.Xml.domToText(startXmlDom); function change() { var xmlDom = Blockly.Xml.workspaceToDom(workspace); var xmlText = Blockly.Xml.domToText(xmlDom); if (startXmlText != xmlText) { history.pushState('', document.title, window.location.pathname + window.location.search); workspace.removeChangeListener(bindData); } } var bindData = workspace.addChangeListener(change); }; /** * Load blocks from XML. * @param {string} xml Text representation of XML. * @param {!Blockly.WorkspaceSvg} workspace Workspace. * @private */ BlocklyStorage.loadXml_ = function (xml, workspace) { try { xml = Blockly.Xml.textToDom(xml); } catch (e) { BlocklyStorage.alert(BlocklyStorage.XML_ERROR + '\nXML: ' + xml); return; } // Clear the workspace to avoid merge. workspace.clear(); Blockly.Xml.domToWorkspace(xml, workspace); }; /** * Present a text message to the user. * Designed to be overridden if an app has custom dialogs, or a butter bar. * @param {string} message Text to alert. */ BlocklyStorage.alert = function (message) { window.alert(message); };