// Copyright (C) 2015 Sam Parkinson // Copyright (C) 2016-17 Walter Bender // This program is free software; you can redistribute it and/or // modify it under the terms of the The GNU Affero General Public // License as published by the Free Software Foundation; either // version 3 of the License, or (at your option) any later version. // // You should have received a copy of the GNU Affero General Public // License along with this library; if not, write to the Free Software // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA const MUSICBLOCKSPREFIX = 'MusicBlocks_'; const APIKEY = '3tgTzMXbbw6xEKX7'; const EMPTYIMAGE = 'data:image/svg+xml;base64,' + btoa('') const SERVER = 'https://turtle.sugarlabs.org/server/'; window.server = SERVER; 'https://turtle.sugarlabs.org/server/'; // '/server/'; //{NAME} will be replaced with project name if (_THIS_IS_MUSIC_BLOCKS_) { var SHAREURL = 'https://walterbender.github.io/musicblocks/index.html?file={name}&run=True'; } else { var SHAREURL = 'https://walterbender.github.io/turtleblocksjs/index.html?file={name}&run=True'; } const NAMESUBTEXT = '{name}'; const LOCAL_PROJECT_STYLE ='\ '; //style block is for the tooltip. _NUM_ will be replaced with a unique number const LOCAL_PROJECT_TEMPLATE ='\
  • \ \
    \
    \ ' + _('Open') + ' \ ' + _('Delete') + ' \ ' + _('Publish') + ' \ \ \
    \
    \ Copy the link to share your project:\ \
    \
    \ ' + _('Download') + ' \
    \
  • ' const GLOBAL_PROJECT_TEMPLATE = '\ \
    \ {title}
    \ \ \
    \
    \ Copy the link to share your project:\ \
    \
    \ ' + _('Download') + ' \
    '; function PlanetModel(controller) { this.controller = controller; this.localProjects = []; this.globalProjects = []; this.localChanged = false; this.globalImagesCache = {}; this.updated = function () {}; this.addGlobalElement = function () {}; this.stop = false; this.count = 0; var model = this; if (sugarizerCompatibility.isInsideSugarizer()) { storage = sugarizerCompatibility.data; } else { storage = localStorage; } this.start = function (cb,glo) { model.updated = cb; model.addGlobalElement = glo; model.stop = false; var myNode = document.querySelector('.planet .content.w'); while (myNode.firstChild) { myNode.removeChild(myNode.firstChild); } this.redoLocalStorageData(); model.updated(); this.downloadWorldWideProjects(); }; this.downloadWorldWideProjects = function () { jQuery.ajax({ url: SERVER, headers: { 'x-api-key': APIKEY } }).done(function (l) { model.globalProjects = []; model.stop = false; var todo = []; l.forEach(function (name, i) { if (name.indexOf('.b64') !== -1) { if (_THIS_IS_MUSIC_BLOCKS_) { todo.push(name); } else if (!(name.slice(0, MUSICBLOCKSPREFIX.length) == MUSICBLOCKSPREFIX)) { todo.push(name); } } }); model.count = 0; model.getImages(todo); }); }; this.getImages = function (todo) { if (model.stop === true) { return; } var image = todo.pop(); if (image === undefined) { return; } var name = image.replace('.b64', ''); var mbcheck = false; if (_THIS_IS_MUSIC_BLOCKS_) { if (name.slice(0, MUSICBLOCKSPREFIX.length) === MUSICBLOCKSPREFIX){ name = name.substring(MUSICBLOCKSPREFIX.length); mbcheck = true; } } if (model.globalImagesCache[image] !== undefined) { model.globalProjects.push({title: name, img: model.globalImagesCache[image]}); model.addGlobalElement(model.globalProjects[model.globalProjects.length-1], model.count); model.count++; model.getImages(todo); } else { jQuery.ajax({ url: SERVER + image, headers: { 'x-api-key' : '3tgTzMXbbw6xEKX7' }, dataType: 'text' }).done(function (d) { if (!validateImageData(d)) { d = 'images/planetgraphic.png'; // EMPTYIMAGE; } if (mbcheck) { d = 'images/planetgraphic.png'; } model.globalImagesCache[image] = d; model.globalProjects.push({title: name, img: d, url: image}); model.addGlobalElement(model.globalProjects[model.globalProjects.length-1], model.count); model.count++; model.getImages(todo); }); } }; this.redoLocalStorageData = function () { this.localProjects = []; s = JSON.stringify(localStorage); s = s.replace(/\\n/g, "\\n") .replace(/\\'/g, "\\'") .replace(/\\"/g, '\\"') .replace(/\\&/g, "\\&") .replace(/\\r/g, "\\r") .replace(/\\t/g, "\\t") .replace(/\\b/g, "\\b") .replace(/\\f/g, "\\f"); s = s.replace(/[\u0000-\u0019]+/g,""); var l = JSON.parse(s); Object.keys(l).forEach(function (p, i) { var img = localStorage['SESSIONIMAGE' + p]; if (img === 'undefined') { img = 'images/planetgraphic.png'; // EMPTYIMAGE; } var e = { title: p, img: img, data: localStorage['SESSION' + p], current: p === localStorage.currentProject } if (e.current) { model.localProjects.unshift(e); } else { model.localProjects.push(e); } }); this.localChanged = true; }; this.uniqueName = function (base) { var l = JSON.parse(localStorage.allProjects); if (l.indexOf(base) === -1) { return base; } var i = 1; while (true) { var name = base + ' ' + i; if (l.indexOf(name) === -1) { return name; } i++; } }; this.newProject = function () { var name = this.uniqueName('My Project'); model.prepLoadingProject(name); this.controller.sendAllToTrash(true, true); model.stop = true; }; this.renameProject = function (oldName, newName, current) { if (current) { localStorage.currentProject = newName; } var l = JSON.parse(localStorage.allProjects); l[l.indexOf(oldName)] = newName; localStorage.allProjects = JSON.stringify(l); localStorage['SESSIONIMAGE' + newName] = localStorage['SESSIONIMAGE' + oldName]; localStorage['SESSION' + newName] = localStorage['SESSION' + oldName]; localStorage['SESSIONIMAGE' + oldName] = undefined; localStorage['SESSION' + oldName] = undefined; model.redoLocalStorageData(); }; this.delete = function (name) { var l = JSON.parse(localStorage.allProjects); l.splice(l.indexOf(name), 1); localStorage.allProjects = JSON.stringify(l); localStorage['SESSIONIMAGE' + name] = undefined; localStorage['SESSION' + name] = undefined; model.redoLocalStorageData(); model.updated(); }; //Opens up projects in the "On my device" section this.open = function (name, data) { localStorage.currentProject = name; model.controller.sendAllToTrash(false, true); model.controller.loadRawProject(data); model.stop = true; }; //Adds the project from "Worldwide" to the "On my deivce" //section when download button is clicked this.prepLoadingProject = function (name) { localStorage.currentProject = name; var l = JSON.parse(localStorage.allProjects); l.push(name); localStorage.allProjects = JSON.stringify(l); }; this.load = function (name) { model.prepLoadingProject(name); model.controller.sendAllToTrash(false, false); jQuery.ajax({ url: SERVER + name + '.tb', headers: { 'x-api-key' : '3tgTzMXbbw6xEKX7' }, dataType: 'text', error: function (XMLHttpRequest, textStatus, errorThrown) { jQuery.ajax({ url: SERVER + MUSICBLOCKSPREFIX + name + '.tb', headers: { 'x-api-key' : '3tgTzMXbbw6xEKX7' }, dataType: 'text', }).done(function (d) { model.controller.loadRawProject(d); model.stop = true; }); } }).done(function (d) { model.controller.loadRawProject(d); model.stop = true; }); }; this.getPublishableName = function (name) { return name.replace(/['!"#$%&\\'()\*+,\-\.\/:;<=>?@\[\\\]\^`{|}~']/g, '').replace(/ /g, '_'); }; this.publish = function (name, data, image) { // Show busy cursor. document.body.style.cursor = 'wait'; setTimeout(function () { name = model.getPublishableName(name); if (_THIS_IS_MUSIC_BLOCKS_) { name = MUSICBLOCKSPREFIX + name; } httpPost(name + '.tb', data); httpPost(name + '.b64', image); //TODO: append project at beginning //model.downloadWorldWideProjects(); // Restore default cursor. document.body.style.cursor = 'default'; }, 250); }; }; function PlanetView(model, controller) { this.model = model; this.controller = controller; var planet = this; // for future reference document.querySelector('.planet .new') .addEventListener('click', function () { planet.model.newProject(); planet.controller.hide(); }); document.querySelector('#myOpenFile') .addEventListener('change', function(event) { planet.controller.hide(); }); document.querySelector('.planet .open') .addEventListener('click', function () { document.querySelector('#myOpenFile').focus(); document.querySelector('#myOpenFile').click(); window.scroll(0, 0); }); document.querySelector('.planet .back') .addEventListener('click', function () { planet.controller.hide(); }); this.update = function () { // This is werid var model = this; // console.log('update'); if (model.localChanged) { html = ''; html = html + LOCAL_PROJECT_STYLE; model.localProjects.forEach(function (project, i) { html = html + format(LOCAL_PROJECT_TEMPLATE, project).replace(new RegExp('_NUM_', 'g'), i.toString()); // console.log(i); // console.log(project); }); document.querySelector('.planet .content.l').innerHTML = html; var eles = document.querySelectorAll('.planet .content.l li'); Array.prototype.forEach.call(eles, function (ele, i) { // console.log(i); // console.log(ele); ele.querySelector('.open') .addEventListener('click', planet.open(ele)); ele.querySelector('.publish') .addEventListener('click', planet.publish(ele)); ele.querySelector('.share') .addEventListener('click', planet.share(ele,i)); ele.querySelector('.download') .addEventListener('click', planet.download(ele)); ele.querySelector('.delete') .addEventListener('click', planet.delete(ele)); ele.querySelector('input') .addEventListener('change', planet.input(ele)); ele.querySelector('.thumbnail') .addEventListener('click', planet.open(ele)); }); model.localChanged = false; } }; this.addGlobalElement = function (glob, i){ var d = document.createElement('li'); d.setAttribute('url', glob.url); d.setAttribute('title', glob.title); html = ''; html += format(GLOBAL_PROJECT_TEMPLATE, glob).replace(new RegExp('_NUM_', 'g'), i.toString()); d.innerHTML = html; var htmldata = d; // console.log(htmldata); htmldata.querySelector('.thumbnail') .addEventListener('click', planet.load(htmldata)); htmldata.querySelector('.download') .addEventListener('click', planet.load(htmldata)); htmldata.querySelector('.share') .addEventListener('click', planet.planetshare(htmldata,i)); document.querySelector('.planet .content.w').appendChild(htmldata); } this.load = function (ele) { return function () { planet.model.load(ele.attributes.title.value); planet.controller.hide(); } }; this.publish = function (ele) { return function () { planet.model.publish(ele.attributes.title.value, ele.attributes.data.value, ele.querySelector('img').src); } }; this.share = function (ele, i) { return function () { planet.model.publish(ele.attributes.title.value, ele.attributes.data.value, ele.querySelector('img').src); if (_THIS_IS_MUSIC_BLOCKS_) { var url = SHAREURL.replace(NAMESUBTEXT, MUSICBLOCKSPREFIX + planet.model.getPublishableName(ele.attributes.title.value) + '.tb'); } else { var url = SHAREURL.replace(NAMESUBTEXT, planet.model.getPublishableName(ele.attributes.title.value) + '.tb'); } var n = i.toString(); docById('shareurldiv'+n).style.visibility = 'visible'; docById('shareurlbox'+n).style.visibility = 'visible'; docById('shareurltri'+n).style.visibility = 'visible'; docById('shareurlbox'+n).value = url; docById('shareurlbox'+n).focus(); docById('shareurlbox'+n).select(); }; }; this.planetshare = function (ele, i) { return function () { if (_THIS_IS_MUSIC_BLOCKS_) { var url = SHAREURL.replace(NAMESUBTEXT, MUSICBLOCKSPREFIX + planet.model.getPublishableName(ele.attributes.title.value) + '.tb'); } else { var url = SHAREURL.replace(NAMESUBTEXT, planet.model.getPublishableName(ele.attributes.title.value) + '.tb'); } var n = i.toString(); docById('plshareurldiv'+n).style.visibility = 'visible'; docById('plshareurlbox'+n).style.visibility = 'visible'; docById('plshareurltri'+n).style.visibility = 'visible'; docById('plshareurlbox'+n).value = url; docById('plshareurlbox'+n).focus(); docById('plshareurlbox'+n).select(); }; }; this.download = function (ele) { return function () { download(ele.attributes.title.value + '.tb', 'data:text/plain;charset=utf-8,' + ele.attributes.data.value); } }; this.open = function (ele) { return function () { docById('statusDiv').style.visibility = localStorage.getItem('isStatusHidden'); docById('statusButtonsDiv').style.visibility = localStorage.getItem('isStatusHidden'); docById('statusTableDiv').style.visibility = localStorage.getItem('isStatusHidden'); if (_THIS_IS_MUSIC_BLOCKS_) { docById('ptmDiv').style.visibility = localStorage.getItem('isMatrixHidden'); docById('ptmButtonsDiv').style.visibility = localStorage.getItem('isMatrixHidden'); docById('ptmTableDiv').style.visibility = localStorage.getItem('isMatrixHidden'); docById('pscDiv').style.visibility = localStorage.getItem('isStaircaseHidden'); docById('pscButtonsDiv').style.visibility = localStorage.getItem('isStaircaseHidden'); docById('pscTableDiv').style.visibility = localStorage.getItem('isStaircaseHidden'); docById('sliderDiv').style.visibility = localStorage.getItem('isSliderHidden'); docById('sliderButtonsDiv').style.visibility = localStorage.getItem('isSliderHidden'); docById('sliderTableDiv').style.visibility = localStorage.getItem('isSliderHidden'); docById('pdmDiv').style.visibility = localStorage.getItem('isPitchDrumMatrixHidden'); docById('pdmButtonsDiv').style.visibility = localStorage.getItem('isPitchDrumMatrixHidden'); docById('pdmTableDiv').style.visibility = localStorage.getItem('isPitchDrumMatrixHidden'); docById('rulerDiv').style.visibility = localStorage.getItem('isRhythmRulerHidden'); docById('rulerButtonsDiv').style.visibility = localStorage.getItem('isRhythmRulerHidden'); docById('rulerTableDiv').style.visibility = localStorage.getItem('isRhythmRulerHidden'); docById('modeDiv').style.visibility = localStorage.getItem('isModeWidgetHidden'); docById('modeButtonsDiv').style.visibility = localStorage.getItem('isModeWidgetHidden'); docById('modeTableDiv').style.visibility = localStorage.getItem('isModeWidgetHidden'); // Don't reopen the tempo widget since we didn't just hide it, but also closed it. // docById('tempoDiv').style.visibility = localStorage.getItem('isTempoHidden'); // docById('tempoButtonsDiv').style.visibility = localStorage.getItem('isTempoHidden'); } if (ele.attributes.current.value === 'true') { planet.controller.hide(); return; } planet.model.open(ele.attributes.title.value, ele.attributes.data.value); planet.controller.hide(); } }; this.delete = function (ele) { return function () { var title = ele.attributes.title.value; planet.model.delete(title); } }; this.input = function (ele) { return function () { var newName = ele.querySelector('input').value; var oldName = ele.attributes.title.value; var current = ele.attributes.current.value === 'true'; planet.model.renameProject(oldName, newName, current); ele.attributes.title.value = newName; } }; }; // A viewer for sample projects function SamplesViewer () { this.stage = null; this.sendAllToTrash = null; this.loadProject = null; this.loadRawProject = null; this.init = function () { this.samples = this; // for future reference // i18n for section titles document.querySelector('#planetTitle').innerHTML = _('Planet'); document.querySelector('#planetMyDevice').innerHTML = _('On my device'); document.querySelector('#planetWorldwide').innerHTML = _('Worldwide'); this.model = new PlanetModel(this); this.view = new PlanetView(this.model, this); } this.setClear = function (trash) { this.sendAllToTrash = trash; return this; } this.setLoad = function (load) { this.loadProject = load; return this; }; this.setStage = function (stage) { this._stage = stage; return this; }; this.setLoadRaw = function (loadRawProject) { this.loadRawProject = loadRawProject; return this; }; this.setRefreshCanvas = function (refreshCanvas) { this._refreshCanvas = refreshCanvas; return this; }; this.setServer = function (server) { this.server = server; }; this.hide = function () { document.querySelector('.planet').style.display = 'none'; document.querySelector('body').classList.remove('samples-shown'); document.querySelector('.canvasHolder').classList.remove('hide'); document.querySelector('#theme-color').content = platformColor.header; this.samples._stage.enableDOMEvents(true); window.scroll(0, 0); }; this.show = function () { document.querySelector('.planet').style.display = ''; document.querySelector('body').classList.add('samples-shown'); document.querySelector('.canvasHolder').classList.add('hide'); document.querySelector('#theme-color').content = '#8bc34a'; var that = this; setTimeout(function () { // Time to release the mouse that.samples._stage.enableDOMEvents(false); }, 250); window.scroll(0, 0); this.model.start(this.view.update,this.view.addGlobalElement); return true; }; }; function validateImageData(d) { if(d === undefined) { return false; } if(d.indexOf('data:image') !== 0){ return false; } else { var data = d.split(','); if(data[1].length == 0){ return false; } } return true; };