|
|
- // Copyright (c) 2014,2015 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
-
- function format(str, data) {
- str = str.replace(/{([a-zA-Z0-9.]*)}/g,
- function (match, name) {
- x = data;
- name.split('.').forEach(function (v) {
- if (x === undefined) {
- console.log('Undefined value in template string', str, name, x, v);
- }
- x = x[v];
- });
- return x;
- });
- return str.replace(/{_([a-zA-Z0-9]+)}/g,
- function (match, item) {
- return _(item);
- });
- };
-
-
- function canvasPixelRatio() {
- var devicePixelRatio = window.devicePixelRatio || 1;
- var context = document.querySelector('#myCanvas').getContext('2d');
- var backingStoreRatio = context.webkitBackingStorePixelRatio ||
- context.mozBackingStorePixelRatio ||
- context.msBackingStorePixelRatio ||
- context.oBackingStorePixelRatio ||
- context.backingStorePixelRatio || 1;
- return devicePixelRatio / backingStoreRatio;
- };
-
-
- function windowHeight() {
- var onAndroid = /Android/i.test(navigator.userAgent);
- if (onAndroid) {
- return window.outerHeight;
- } else {
- return window.innerHeight;
- }
- };
-
-
- function windowWidth() {
- var onAndroid = /Android/i.test(navigator.userAgent);
- if (onAndroid) {
- return window.outerWidth;
- } else {
- return window.innerWidth;
- }
- };
-
-
- function httpGet(projectName) {
- var xmlHttp = null;
-
- xmlHttp = new XMLHttpRequest();
-
- if (projectName === null) {
- xmlHttp.open("GET", window.server, false);
- xmlHttp.setRequestHeader('x-api-key', '3tgTzMXbbw6xEKX7');
- } else {
- xmlHttp.open("GET", window.server + projectName, false);
- xmlHttp.setRequestHeader('x-api-key', '3tgTzMXbbw6xEKX7');
- }
- xmlHttp.send();
- if (xmlHttp.status > 299) {
- throw 'Error from server';
- }
- return xmlHttp.responseText;
- };
-
-
- function httpPost(projectName, data) {
- var xmlHttp = null;
- xmlHttp = new XMLHttpRequest();
- xmlHttp.open("POST", window.server + projectName, false);
- xmlHttp.setRequestHeader('x-api-key', '3tgTzMXbbw6xEKX7');
- xmlHttp.send(data);
- // return xmlHttp.responseText;
- return 'https://apps.facebook.com/turtleblocks/?file=' + projectName;
- };
-
-
- function HttpRequest(url, loadCallback, userCallback) {
- // userCallback is an optional callback-handler.
- var req = this.request = new XMLHttpRequest();
- this.handler = loadCallback;
- this.url = url;
- this.localmode = Boolean(self.location.href.search(/^file:/i) === 0);
- this.userCallback = userCallback;
-
- var objref = this;
- try {
- req.open('GET', url);
- req.onreadystatechange = function() { objref.handler(); };
- req.send('');
- }
- catch(e) {
- if (self.console) console.log('Failed to load resource from ' + url + ': Network error.');
- if (typeof userCallback === 'function') userCallback(false, 'network error');
- this.request = this.handler = this.userCallback = null;
- }
- };
-
-
- function docByTagName(tag) {
- document.getElementsByTagName(tag);
- };
-
-
- function docById(id) {
- return document.getElementById(id);
- };
-
-
- function last(myList) {
- var i = myList.length;
- if (i === 0) {
- return null;
- } else {
- return myList[i - 1];
- }
- };
-
-
- function doSVG(canvas, logo, turtles, width, height, scale) {
- // Aggregate SVG output from each turtle. If there is none, use
- // the MUSICICON.
- var MUSICICON = '<g transform="matrix(20,0,0,20,-2500,-200)"> <g style="font-size:20px;font-family:Sans;text-anchor:end;fill:#000000" transform="translate(0.32906,-0.2)"> <path d="m 138.47094,26.82 q 0,-1.16 1.24,-2.02 0.96,-0.64 1.94,-0.64 0.68,0.02 1.18,0.34 l 0,-11.84 0.44,0 0,12.94 q 0,1.32 -1.34,2.1 -0.86,0.5 -1.8,0.5 -0.98,0 -1.44,-0.7 -0.22,-0.32 -0.22,-0.68 z" /> </g> <g transform="translate(-12.52094,4.8)" style="font-size:20px;font-family:Sans;text-anchor:end;fill:#000000"> <path d="m 138.47094,26.82 q 0,-1.16 1.24,-2.02 0.96,-0.64 1.94,-0.64 0.68,0.02 1.18,0.34 l 0,-11.84 0.44,0 0,12.94 q 0,1.32 -1.34,2.1 -0.86,0.5 -1.8,0.5 -0.98,0 -1.44,-0.7 -0.22,-0.32 -0.22,-0.68 z" /> </g> <path style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 130.81346,17 12.29007,-5 0,2 -12.29007,5 z" /> </g>';
-
- var turtleSVG = '';
- for (var turtle in turtles.turtleList) {
- turtles.turtleList[turtle].closeSVG();
- turtleSVG += turtles.turtleList[turtle].svgOutput;
- }
-
- var svg = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '">\n';
- svg += '<g transform="scale(' + scale + ',' + scale + ')">\n';
- svg += logo.svgOutput;
- svg += '</g>';
- if (turtleSVG === '') {
- svg += MUSICICON;
- } else {
- svg += turtleSVG;
- }
- svg += '</svg>';
- return svg;
- };
-
-
- function isSVGEmpty(turtles) {
- for (var turtle in turtles.turtleList) {
- turtles.turtleList[turtle].closeSVG();
- if (turtles.turtleList[turtle].svgOutput !== '') {
- return false;
- }
- }
- return true;
- };
-
-
- function fileExt(file) {
- var parts = file.split('.');
- if (parts.length === 1 || (parts[0] === '' && parts.length === 2)) {
- return '';
- }
- return parts.pop();
- };
-
-
- function fileBasename(file) {
- var parts = file.split('.');
- if (parts.length === 1) {
- return parts[0];
- } else if (parts[0] === '' && parts.length === 2) {
- return file;
- } else {
- parts.pop(); // throw away suffix
- return parts.join('.');
- }
- };
-
-
- // Needed to generate new data for localization.ini
- // var translated = "";
- function _(text) {
- replaced = text;
- replace = [",", "(", ")", "?", "¿", "<", ">", ".", '"\n', '"', ":", "%s", "%d", "/", "'", ";", "×", "!", "¡"];
- for (var p = 0; p < replace.length; p++) {
- replaced = replaced.replace(replace[p], "");
- }
- replaced = replaced.replace(/ /g, '-');
- // Needed to generate new data for localization.ini
- // txt = "\n" + replaced + " = " + text;
- // if (translated.lastIndexOf(txt) === -1) {
- // translated = translated + txt;
- // }
- // You can log translated in console.log(translated)
- try {
- translation = document.webL10n.get(replaced);
- if (translation === '') {
- translation = text;
- };
- return translation;
- } catch (e) {
- console.log('i18n error: ' + text);
- return text;
- }
- };
-
- function toTitleCase(str) {
- if (typeof str !== 'string')
- return;
- var tempStr = '';
- if (str.length > 1)
- tempStr = str.substring(1);
- return str.toUpperCase()[0] + tempStr;
- }
-
- function processRawPluginData(rawData, palettes, blocks, errorMsg, evalFlowDict, evalArgDict, evalParameterDict, evalSetterDict, evalOnStartList, evalOnStopList) {
- // console.log(rawData);
- var lineData = rawData.split('\n');
- var cleanData = '';
-
- // We need to remove blank lines and comments and then
- // join the data back together for processing as JSON.
- for (var i = 0; i < lineData.length; i++) {
- if (lineData[i].length === 0) {
- continue;
- }
- if (lineData[i][0] === '/') {
- continue;
- }
- cleanData += lineData[i];
- }
-
- // Note to plugin developers: You may want to comment out this
- // try/catch while debugging your plugin.
- try {
- var obj = processPluginData(cleanData.replace(/\n/g,''), palettes, blocks, evalFlowDict, evalArgDict, evalParameterDict, evalSetterDict, evalOnStartList, evalOnStopList);
- } catch (e) {
- var obj = null;
- errorMsg('Error loading plugin: ' + e);
- }
- return obj;
- };
-
-
- function processPluginData(pluginData, palettes, blocks, evalFlowDict, evalArgDict, evalParameterDict, evalSetterDict, evalOnStartList, evalOnStopList) {
- // Plugins are JSON-encoded dictionaries.
- // console.log(pluginData);
- var obj = JSON.parse(pluginData);
-
- // Create a palette entry.
- var newPalette = false;
- if ('PALETTEPLUGINS' in obj) {
- for (var name in obj['PALETTEPLUGINS']) {
- PALETTEICONS[name] = obj['PALETTEPLUGINS'][name];
- var fillColor = '#ff0066';
- if ('PALETTEFILLCOLORS' in obj) {
- if (name in obj['PALETTEFILLCOLORS']) {
- var fillColor = obj['PALETTEFILLCOLORS'][name];
- // console.log(fillColor);
- }
- }
- PALETTEFILLCOLORS[name] = fillColor;
-
- var strokeColor = '#ef003e';
- if ('PALETTESTROKECOLORS' in obj) {
- if (name in obj['PALETTESTROKECOLORS']) {
- var strokeColor = obj['PALETTESTROKECOLORS'][name];
- // console.log(strokeColor);
- }
- }
- PALETTESTROKECOLORS[name] = strokeColor;
-
- var highlightColor = '#ffb1b3';
- if ('PALETTEHIGHLIGHTCOLORS' in obj) {
- if (name in obj['PALETTEHIGHLIGHTCOLORS']) {
- var highlightColor = obj['PALETTEHIGHLIGHTCOLORS'][name];
- // console.log(highlightColor);
- }
- }
- PALETTEHIGHLIGHTCOLORS[name] = highlightColor;
-
- var strokeHighlightColor = '#404040';
- if ('HIGHLIGHTSTROKECOLORS' in obj) {
- if (name in obj['HIGHLIGHTSTROKECOLORS']) {
- var strokeHighlightColor = obj['HIGHLIGHTSTROKECOLORS'][name];
- // console.log(highlightColor);
- }
- }
- HIGHLIGHTSTROKECOLORS[name] = strokeHighlightColor;
-
- if (name in palettes.buttons) {
- console.log('palette ' + name + ' already exists');
- } else {
- console.log('adding palette ' + name);
- palettes.add(name);
- newPalette = true;
- }
- }
- }
-
- if (newPalette) {
- try {
- palettes.makePalettes();
- } catch (e) {
- console.log('makePalettes: ' + e);
- }
- }
-
- // Define the image blocks
- if ('IMAGES' in obj) {
- for (var blkName in obj['IMAGES']) {
- pluginsImages[blkName] = obj['IMAGES'][blkName];
- }
- }
-
- // Populate the flow-block dictionary, i.e., the code that is
- // eval'd by this block.
- if ('FLOWPLUGINS' in obj) {
- for (var flow in obj['FLOWPLUGINS']) {
- evalFlowDict[flow] = obj['FLOWPLUGINS'][flow];
- }
- }
-
- // Populate the arg-block dictionary, i.e., the code that is
- // eval'd by this block.
- if ('ARGPLUGINS' in obj) {
- for (var arg in obj['ARGPLUGINS']) {
- evalArgDict[arg] = obj['ARGPLUGINS'][arg];
- }
- }
-
- // Populate the setter dictionary, i.e., the code that is
- // used to set a value block.
- if ('SETTERPLUGINS' in obj) {
- for (var setter in obj['SETTERPLUGINS']) {
- evalSetterDict[setter] = obj['SETTERPLUGINS'][setter];
- }
- }
-
- // Create the plugin protoblocks.
- if ('BLOCKPLUGINS' in obj) {
- for (var block in obj['BLOCKPLUGINS']) {
- console.log('adding plugin block ' + block);
- try {
- eval(obj['BLOCKPLUGINS'][block]);
- } catch (e) {
- console.log('Failed to load plugin for ' + block + ': ' + e);
- }
- }
- }
-
- // Create the globals.
- if ('GLOBALS' in obj) {
- eval(obj['GLOBALS']);
- }
-
- if ('PARAMETERPLUGINS' in obj) {
- for (var parameter in obj['PARAMETERPLUGINS']) {
- evalParameterDict[parameter] = obj['PARAMETERPLUGINS'][parameter];
- }
- }
-
- // Code to execute when plugin is loaded
- if ('ONLOAD' in obj) {
- for (var arg in obj['ONLOAD']) {
- eval(obj['ONLOAD'][arg]);
- }
- }
-
- // Code to execute when turtle code is started
- if ('ONSTART' in obj) {
- for (var arg in obj['ONSTART']) {
- evalOnStartList[arg] = obj['ONSTART'][arg];
- }
- }
-
- // Code to execute when turtle code is stopped
- if ('ONSTOP' in obj) {
- for (var arg in obj['ONSTOP']) {
- evalOnStopList[arg] = obj['ONSTOP'][arg];
- }
- }
-
- // Push the protoblocks onto their palettes.
- for (var protoblock in blocks.protoBlockDict) {
- if (blocks.protoBlockDict[protoblock].palette === undefined) {
- console.log('Cannot find palette for protoblock ' + protoblock);
- } else {
- blocks.protoBlockDict[protoblock].palette.add(blocks.protoBlockDict[protoblock]);
- }
- }
-
- palettes.updatePalettes();
-
- // Populate the lists of block types.
- blocks.findBlockTypes();
-
- // Return the object in case we need to save it to local storage.
- return obj;
- };
-
-
- function updatePluginObj(obj) {
- for (var name in obj['PALETTEPLUGINS']) {
- pluginObjs['PALETTEPLUGINS'][name] = obj['PALETTEPLUGINS'][name];
- }
-
- for (var name in obj['PALETTEFILLCOLORS']) {
- pluginObjs['PALETTEFILLCOLORS'][name] = obj['PALETTEFILLCOLORS'][name];
- }
-
- for (var name in obj['PALETTESTROKECOLORS']) {
- pluginObjs['PALETTESTROKECOLORS'][name] = obj['PALETTESTROKECOLORS'][name];
- }
-
- for (var name in obj['PALETTEHIGHLIGHTCOLORS']) {
- pluginObjs['PALETTEHIGHLIGHTCOLORS'][name] = obj['PALETTEHIGHLIGHTCOLORS'][name];
- }
-
- for (var flow in obj['FLOWPLUGINS']) {
- pluginObjs['FLOWPLUGINS'][flow] = obj['FLOWPLUGINS'][flow];
- }
-
- for (var arg in obj['ARGPLUGINS']) {
- pluginObjs['ARGPLUGINS'][arg] = obj['ARGPLUGINS'][arg];
- }
-
- for (var block in obj['BLOCKPLUGINS']) {
- pluginObjs['BLOCKPLUGINS'][block] = obj['BLOCKPLUGINS'][block];
- }
-
- if ('GLOBALS' in obj) {
- if (!('GLOBALS' in pluginObjs)) {
- pluginObjs['GLOBALS'] = '';
- }
- pluginObjs['GLOBALS'] += obj['GLOBALS'];
- }
-
- if ('IMAGES' in obj) {
- pluginObjs['IMAGES'] = obj['IMAGES'];
- }
-
- for (var name in obj['ONLOAD']) {
- pluginObjs['ONLOAD'][name] = obj['ONLOAD'][name];
- }
-
- for (var name in obj['ONSTART']) {
- pluginObjs['ONSTART'][name] = obj['ONSTART'][name];
- }
-
- for (var name in obj['ONSTOP']) {
- pluginObjs['ONSTOP'][name] = obj['ONSTOP'][name];
- }
- };
-
-
- function preparePluginExports(obj) {
- // add obj to plugin dictionary and return as JSON encoded text
- updatePluginObj(obj);
-
- return JSON.stringify(pluginObjs);
- };
-
-
- function processMacroData(macroData, palettes, blocks, macroDict) {
- // Macros are stored in a JSON-encoded dictionary.
- if (macroData !== '{}') {
- var obj = JSON.parse(macroData);
- palettes.add('myblocks', 'black', '#a0a0a0');
-
- for (var name in obj) {
- console.log('adding ' + name + ' to macroDict');
- macroDict[name] = obj[name];
- blocks.addToMyPalette(name, macroDict[name]);
- }
-
- palettes.makePalettes();
- }
- };
-
-
- function prepareMacroExports(name, stack, macroDict) {
- if (name !== null) {
- macroDict[name] = stack;
- }
-
- return JSON.stringify(macroDict);
- };
-
-
- function doSaveSVG(logo, desc) {
- var svg = doSVG(logo.canvas, logo, logo.turtles, logo.canvas.width, logo.canvas.height, 1.0);
- download(desc, 'data:image/svg+xml;utf8,' + svg, desc, '"width=' + logo.canvas.width + ', height=' + logo.canvas.height + '"');
- };
-
-
- function doSaveLilypond(logo, desc) {
- download(desc, 'data:text;utf8,' + encodeURIComponent(logo.lilypondOutput), desc, '"width=' + logo.canvas.width + ', height=' + logo.canvas.height + '"');
- };
-
-
- function download(filename, data) {
- var a = document.createElement('a');
- a.setAttribute('href', data);
- a.setAttribute('download', filename);
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- };
-
- // Some block-specific code
-
- // Publish to FB
- function doPublish(desc) {
- var url = doSave();
- console.log('push ' + url + ' to FB');
- var descElem = docById("description");
- var msg = desc + ' ' + descElem.value + ' ' + url;
- console.log('comment: ' + msg);
- var post_cb = function() {
- FB.api('/me/feed', 'post', {
- message: msg
- });
- };
-
- FB.login(post_cb, {
- scope: 'publish_actions'
- });
- };
-
-
- // TODO: Move to camera plugin
- var hasSetupCamera = false;
- function doUseCamera(args, turtles, turtle, isVideo, cameraID, setCameraID, errorMsg) {
- var w = 320;
- var h = 240;
-
- var streaming = false;
- var video = document.querySelector('#camVideo');
- var canvas = document.querySelector('#camCanvas');
- navigator.getMedia = (navigator.getUserMedia ||
- navigator.mozGetUserMedia ||
- navigator.webkitGetUserMedia ||
- navigator.msGetUserMedia);
- if (navigator.getMedia === undefined) {
- errorMsg('Your browser does not support the webcam');
- }
-
- if (!hasSetupCamera) {
- navigator.getMedia(
- {video: true, audio: false},
- function (stream) {
- if (navigator.mozGetUserMedia) {
- video.mozSrcObject = stream;
- } else {
- var vendorURL = window.URL || window.webkitURL;
- video.src = vendorURL.createObjectURL(stream);
- }
- video.play();
- hasSetupCamera = true;
- }, function (error) {
- errorMsg('Could not connect to camera');
- console.log('Could not connect to camera', error);
- });
- } else {
- streaming = true;
- video.play();
- if (isVideo) {
- cameraID = window.setInterval(draw, 100);
- setCameraID(cameraID);
- } else {
- draw();
- }
- }
-
- video.addEventListener('canplay', function (event) {
- console.log('canplay', streaming, hasSetupCamera);
- if (!streaming) {
- video.setAttribute('width', w);
- video.setAttribute('height', h);
- canvas.setAttribute('width', w);
- canvas.setAttribute('height', h);
- streaming = true;
-
- if (isVideo) {
- cameraID = window.setInterval(draw, 100);
- setCameraID(cameraID);
- } else {
- draw();
- }
- }
- }, false);
-
- function draw() {
- canvas.width = w;
- canvas.height = h;
- canvas.getContext('2d').drawImage(video, 0, 0, w, h);
- var data = canvas.toDataURL('image/png');
- turtles.turtleList[turtle].doShowImage(args[0], data);
- };
- };
-
-
- function doStopVideoCam(cameraID, setCameraID) {
- if (cameraID !== null) {
- window.clearInterval(cameraID);
- }
-
- setCameraID(null);
- document.querySelector('#camVideo').pause();
- };
-
-
- function hideDOMLabel() {
- var textLabel = docById('textLabel');
- if (textLabel !== null) {
- textLabel.style.display = 'none';
- }
-
- var numberLabel = docById('numberLabel');
- if (numberLabel !== null) {
- numberLabel.style.display = 'none';
- }
-
- var solfegeLabel = docById('solfegeLabel');
- if (solfegeLabel !== null) {
- solfegeLabel.style.display = 'none';
- }
-
- var notenameLabel = docById('notenameLabel');
- if (notenameLabel !== null) {
- notenameLabel.style.display = 'none';
- }
-
- var noteattrLabel = docById('noteattrLabel');
- if (noteattrLabel !== null) {
- noteattrLabel.style.display = 'none';
- }
-
- var drumnameLabel = docById('drumnameLabel');
- if (drumnameLabel !== null) {
- drumnameLabel.style.display = 'none';
- }
-
- var voicenameLabel = docById('voicenameLabel');
- if (voicenameLabel !== null) {
- voicenameLabel.style.display = 'none';
- }
-
- var modenameLabel = docById('modenameLabel');
- if (modenameLabel !== null) {
- modenameLabel.style.display = 'none';
- }
- };
-
-
- function displayMsg(blocks, text) {
- /*
- var msgContainer = blocks.msgText.parent;
- msgContainer.visible = true;
- blocks.msgText.text = text;
- msgContainer.updateCache();
- blocks.stage.setChildIndex(msgContainer, blocks.stage.getNumChildren() - 1);
- */
- return;
- };
-
-
- function toFixed2 (d) {
- // Return number as fixed 2 precision
- var floor = Math.floor(d);
- if (d !== floor) {
- return d.toFixed(2).toString();
- } else {
- return d.toString();
- }
- };
-
-
- function mixedNumber (d) {
- // Return number as a mixed fraction string, e.g., "2 1/4"
- var floor = Math.floor(d);
- if (d > floor) {
- var obj = rationalToFraction(d - floor);
- if (floor === 0) {
- return obj[0] + '/' + obj[1];
- } else {
- if (obj[0] === 1 && obj[1] === 1) {
- return floor + 1;
- } else {
- if (obj[1] > 99) {
- return d.toFixed(2);
- } else {
- return floor + ' ' + obj[0] + '/' + obj[1];
- }
- }
- }
- } else {
- return d.toString();
- }
- };
-
-
- function LCD(a, b) {
- return Math.abs((a * b) / GCD(a, b));
- };
-
-
- function GCD(a, b) {
- a = Math.abs(a);
- b = Math.abs(b);
-
- while(b) {
- var n = b;
- b = a % b;
- a = n;
- }
-
- return a;
- };
-
-
- function rationalToFraction (d) {
- /*
- Convert float to its approximate fractional representation. '''
-
- This code was translated to JavaScript from the answers at
- http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-\
- readable-fractions/681534#681534
-
- For example:
- >>> 3./5
- 0.59999999999999998
-
- >>> rationalToFraction(3./5)
- "3/5"
-
- */
-
- if (d > 1) {
- var invert = true;
- d = 1 / d;
- } else {
- var invert = false;
- }
-
- var df = 1.0;
- var top = 1;
- var bot = 1;
-
- while (Math.abs(df - d) > 0.00000001) {
- if (df < d) {
- top += 1;
- } else {
- bot += 1;
- top = Math.floor(d * bot);
- }
-
- df = top / bot;
- }
-
- if (bot === 0 || top === 0) {
- return [0, 1];
- }
-
- if (invert) {
- return [bot, top];
- } else {
- return [top, bot];
- }
- };
|