<commit_before>// JavaScript Document
/*
	tool_replace_component_data
*/

// TOOL_REPLACE_COMPONENT_DATA CLASS
var tool_replace_component_data = new function() {

	// LOCAL VARS
	this.url_trigger = DEDALO_LIB_BASE_URL + '/tools/tool_replace_component_data/trigger.tool_replace_component_data.php' ;

	/**
	* REPLACE_DATA
	* Exec SSE petition to trigger for propagate curent value to all found records
	* @param dom object button_obj
	*/
	this.replace_data = function(button_obj) {

		if (!confirm(get_label.seguro)) return false;

		//var $button_obj 	= $(button_obj)

		//return 	console.log( window.top.document ); //.querySelector("#dialog_page_iframe").dialog

		var component_tipo	= button_obj.dataset.component_tipo,
			parent 			= button_obj.dataset.parent,
			section_tipo	= button_obj.dataset.section_tipo,
			paginator_div 	= window.top.document.querySelector(".css_wrap_rows_paginator"),
			response_div 	= document.querySelector("#tool_replace_component_data_response"),
			//button_cancel 	= button_obj.parentNode.querySelector(".button_cancel"),
			button_replace 	= button_obj.parentNode.querySelector(".button_replace")

		var mydata = {  'mode'			: 'replace_data',
						'component_tipo': component_tipo,
						'parent'		: parent ,
						'section_tipo'	: section_tipo,
						'top_tipo'		: page_globals.top_tipo
					}
					//return console.log(mydata);
		var json_data = JSON.stringify( mydata )

		//var wrap_div_tool = $button_obj.parents('.wrap_tool:first');
		var wrap_div_tool = button_obj.parentNode.parentNode.parentNode.querySelector(".wrap_tool");
		//html_page.loading_content( wrap_div_tool, 1 );

		// Spinner
		//response_div.html("
Please wait..
");
		response_div.innerHTML = "
Please wait..
";

		// Hide button cancel
		//button_cancel.hide()
		//button_cancel.style.display = 'none';

		// Hide button replace
		//button_replace.hide()
		button_replace.style.display = 'none';

		var source  = new EventSource(tool_replace_component_data.url_trigger+"?mode=replace_data&json_data="+json_data);
		var msg 	= ""

		// message
		source.addEventListener('message', function(e) {
					//console.log(e);

					msg = JSON.parse(e.data);
					//console.log(msg);

					// Notification msg ok
					//response_div.html(msg);
					response_div.innerHTML = msg;

					// END SCRIPT
					if(/ok/i.test(msg)) { //data_percent >= 100 ||
						// Close connection
						source.close();

						// Remove paginator div from edit window (DELETE, now the selection is sustained)
						//if (paginator_div) paginator_div.remove()

						// Remove spinner
						//html_page.loading_content( wrap_div_tool, 0 );

						// Close button
						//response_div.append( "
"+ get_label.cerrar +"
 ");
						//response_div.innerHTML += "
"+ get_label.cerrar +"
 ";
					}

				}, false);

		// error
		source.addEventListener('error', function(e) {
			//console.log(e);

			// Close connection
			source.close();

			// Remove spinner
			//html_page.loading_content( wrap_div_tool, 0 );

			//alert("EventSource failed. "+ e );
			//response_div.html("
Sorry. Error on proccess data
");
			response_div.innerHTML += "
Sorry. Error on proccess data
";

		}, false);

		//source.onerror = function(e) {
		//};

		/* AJAX MODEL
			var oReq = new XMLHttpRequest();

			oReq.addEventListener("progress", updateProgress);
			oReq.addEventListener("load", transferComplete);
			oReq.addEventListener("error", transferFailed);
			oReq.addEventListener("abort", transferCanceled);

			oReq.open();

			// ...

			// progress on transfers from the server to the client (downloads)
			function updateProgress (oEvent) {
			  	console.log(oEvent);
			}

			function transferComplete(evt) {
			  console.log("The transfer is complete.");
			}

			function transferFailed(evt) {
			  console.log("An error occurred while transferring the file.");
			}

			function transferCanceled(evt) {
			  console.log("The transfer has been canceled by the user.");
			}
			*/

	}//end replace_data

};//end tool_replace_component_data<commit_msg>remove spaces<commit_after>// JavaScript Document
/*
	tool_replace_component_data
*/

// TOOL_REPLACE_COMPONENT_DATA CLASS
var tool_replace_component_data = new function() {

	// LOCAL VARS
	this.url_trigger = DEDALO_LIB_BASE_URL + '/tools/tool_replace_component_data/trigger.tool_replace_component_data.php' ;

	/**
	* REPLACE_DATA
	* Exec SSE petition to trigger for propagate curent value to all found records
	* @param dom object button_obj
	*/
	this.replace_data = function(button_obj) {

		if (!confirm(get_label.seguro)) return false;

		//var $button_obj 	= $(button_obj)

		//return 	console.log( window.top.document ); //.querySelector("#dialog_page_iframe").dialog

		var component_tipo	= button_obj.dataset.component_tipo,
			parent 			= button_obj.dataset.parent,
			section_tipo	= button_obj.dataset.section_tipo,
			paginator_div 	= window.top.document.querySelector(".css_wrap_rows_paginator"),
			response_div 	= document.querySelector("#tool_replace_component_data_response"),
			//button_cancel 	= button_obj.parentNode.querySelector(".button_cancel"),
			button_replace 	= button_obj.parentNode.querySelector(".button_replace")

		var mydata = {  'mode'			: 'replace_data',
						'component_tipo': component_tipo,
						'parent'		: parent,
						'section_tipo'	: section_tipo,
						'top_tipo'		: page_globals.top_tipo
					}; // return console.log(mydata);

		var json_data = JSON.stringify( mydata )

		//var wrap_div_tool = $button_obj.parents('.wrap_tool:first');
		var wrap_div_tool = button_obj.parentNode.parentNode.parentNode.querySelector(".wrap_tool");
		//html_page.loading_content( wrap_div_tool, 1 );

		// Spinner
		//response_div.html("
Please wait..
");
		response_div.innerHTML = "
Please wait..
";

		// Hide button cancel
		//button_cancel.hide()
		//button_cancel.style.display = 'none';

		// Hide button replace
		//button_replace.hide()
		button_replace.style.display = 'none';

		var source  = new EventSource(tool_replace_component_data.url_trigger+"?mode=replace_data&json_data="+json_data);
		var msg 	= ""

		// message
		source.addEventListener('message', function(e) {
					//console.log(e);

					msg = JSON.parse(e.data);
					//console.log(msg);

					// Notification msg ok
					//response_div.html(msg);
					response_div.innerHTML = msg;

					// END SCRIPT
					if(/ok/i.test(msg)) { //data_percent >= 100 ||
						// Close connection
						source.close();

						// Remove paginator div from edit window (DELETE, now the selection is sustained)
						//if (paginator_div) paginator_div.remove()

						// Remove spinner
						//html_page.loading_content( wrap_div_tool, 0 );

						// Close button
						//response_div.append( "
"+ get_label.cerrar +"
 ");
						//response_div.innerHTML += "
"+ get_label.cerrar +"
 ";
					}

				}, false);

		// error
		source.addEventListener('error', function(e) {
			//console.log(e);

			// Close connection
			source.close();

			// Remove spinner
			//html_page.loading_content( wrap_div_tool, 0 );

			//alert("EventSource failed. "+ e );
			//response_div.html("
Sorry. Error on proccess data
");
			response_div.innerHTML += "
Sorry. Error on proccess data
";

		}, false);

		//source.onerror = function(e) {
		//};

		/* AJAX MODEL
			var oReq = new XMLHttpRequest();

			oReq.addEventListener("progress", updateProgress);
			oReq.addEventListener("load", transferComplete);
			oReq.addEventListener("error", transferFailed);
			oReq.addEventListener("abort", transferCanceled);

			oReq.open();

			// ...

			// progress on transfers from the server to the client (downloads)
			function updateProgress (oEvent) {
			  	console.log(oEvent);
			}

			function transferComplete(evt) {
			  console.log("The transfer is complete.");
			}

			function transferFailed(evt) {
			  console.log("An error occurred while transferring the file.");
			}

			function transferCanceled(evt) {
			  console.log("The transfer has been canceled by the user.");
			}
			*/

	}//end replace_data

};//end tool_replace_component_data<|endoftext|>


<commit_before>"use strict";

function Reuq(app) {
  var rq = this;
  this.templates = {};
  this.app = app;
  this.app.resources = app.resources || {};
  this.app.locals = app.locals || {};
  this.utils = this.getUtils();

  this._storeTemplates();
  // submit
  $('body').on('submit', '[rq-form]:not([rq-tmpl] [rq-form]):not([rq-tmpl][rq-form])', function(e) {
    e.preventDefault();
    rq.utils.submit($(this))
  });

  (function(rq) {
    var resources = rq.app.resources;
    var locals = rq.app.locals || {};

    Object.keys(locals).forEach(function(name) {
      rq.setLocal(name, rq.getLocal(name));
    });
    //load all resources set to autoload
    Object.keys(resources).forEach(function(resourceName) {
      // if autoload is not set, autoload it by default
      if (resources[resourceName]['autoload'] === undefined || resources[resourceName]['autoload']) {
        rq.getResource(resourceName, true);
        rq.render(resourceName, null);
      }
    });

    rq.addEvents();
    // should this be run 1st inetead?
    if (typeof rq.app.onInit === 'function') {
      rq.app.onInit.apply(rq);
    }
  })(this);
}

//render
Reuq.prototype.render = function(templateName, data) {
  var processedTemplate = this._processTemplate(templateName, data);
  this._render(templateName, processedTemplate);
}

Reuq.prototype._processTemplate = function(templateName, data) {
  var rq = this;

  function sanitize(value) {
    var blacklist = {
      '&': '&',
      '<': '<',
      '>': '>',
      '"': '"',
      "'": '''
    };
    return value.replace(/[&<>"']/g, function(m) {
      return blacklist[m];
    })
  }

  function compile(template, data) {
    template = template.replace(/\[\[(\w+)\]\]/g, function() {
      var expression = arguments[1];
      // parse value to string
      var value = "" + (data[expression] || rq.app.dynamicProperties[expression].apply(rq, [data]));
      return sanitize(value);
    });
    return template;
  }

  var templateObj = this.templates[templateName];
  var resourceName = templateObj.resourceName;
  var template = templateObj.html;
  var $template = $(template);

  // process rq-rsrc-loading
  if (resourceName && this.app.resources[resourceName].loading) {
    $template.not('[rq-rsrc-loading], [rq-rsrc-loading] *').remove();
  } else {
    $template.find('[rq-rsrc-loading], [rq-rsrc-loading] *').remove();
  }

  //process iteration
  $template.find('[rq-iter], [rq-iter-self]').each(function(id, el) {
    var $el = $(el);

    if ($el.is("[rq-iter]")) {
      var listKey = $el.attr('rq-iter');
      var list = data[listKey] || [];
    } else {
      // then it's rq-iter-self
      var list = data || [];
    }
    list.forEach(function(listItem) {
      var $compiled = $(compile($el.prop('outerHTML'), listItem));
      $compiled.removeAttr('rq-iter');
      $el.after($compiled);
    });
    $el.remove();
  });

  //process ifs
  $template.find('[rq-if]').each(function(id, el) {
    var $el = $(el);
    var condition = $el.attr('rq-if');
    if (!data[condition]) {
      $el.remove();
    }
  })

  //process if-nots
  $template.find('[rq-if-not]').each(function(id, el) {
    var $el = $(el);
    var condition = $el.attr('rq-if-not');
    if (data[condition]) {
      $el.remove();
    }
  });

  template = $template.prop('outerHTML');
  $template = $(compile(template, data))

  //process src
  $template.find('[rq-src]').each(function(id, el) {
    var $el = $(el);
    $el.attr('src', $el.attr('rq-src'));
    $el.removeAttr('rq-src');
  })

  return $template.prop('outerHTML');
}

Reuq.prototype._render = function(templateName, processedTemplate) {
  //remove previously rendered template
  $('[from-tmpl=' + templateName + ']').remove();

  var $template = $('[rq-tmpl][rq-tmpl=' + templateName + ']');
  var $newTemplate = $(processedTemplate).removeAttr('hidden').removeAttr('rq-tmpl');
  $newTemplate.attr('from-tmpl', templateName);
  $template.after($newTemplate);
  this.addEvents($newTemplate);
}

Reuq.prototype.cacheIsValid = function(resourceName) {
  var resource = this.app.resources[resourceName]

  if (resource.shouldReload) {
    return false;
  } else {
    var cacheTimeout = resource.cacheTimeout || 10; //minutes
    var timeoutDate = new Date(resource.updatedAt.getTime() + cacheTimeout * 60000);
    return timeoutDate > new Date();
  }
}

Reuq.prototype.invalidateResourceCache = function(resourceName) {
  this.app.resources[resourceName].shouldReload = true;
}

Reuq.prototype.getResource = function(resourceName, force, cb) {
  // coerce optional arguments to appropriate values
  if (arguments.length < 3 && typeof force === 'function') {
    cb = force;
    force = false;
  }
  var rq = this;
  var resource = this.app.resources[resourceName];
  if (resource.data && !force && this.cacheIsValid(resourceName)) {
    cb(resource.data);
  } else {
    resource.loading = true;

    var url = typeof resource.url === 'function' ? resource.url(this) : resource.url;
    $.ajax({
      url: url,
      dataType: "json",
      beforeSend: function(xhr) {
        Object.keys(resource.headers || {}).forEach(function(header) {
          xhr.setRequestHeader(header, resource.headers[header]);
        })
      },
      success: function(resp) {
        resource.loading = false;
        resource.loaded = true;

        rq.setResource(resourceName, resource.dataKey ? resp[resource.dataKey] : resp);

        if (typeof cb === 'function') {
          cb(resource.data);
        }
      }
    })
  }
}

Reuq.prototype.setResource = function(resourceName, data) {
  var rq = this;
  var resource = this.app.resources[resourceName];
  resource.data = data;
  resource.shouldReload = false;
  resource.updatedAt = new Date();
  this.runResourceSubscribers(resourceName, data);

  $('[rq-tmpl][rq-rsrc=' + resourceName + ']:not([manual-render])').each(function(id, el) {
    rq.render($(el).attr('rq-tmpl'), data);
  });
}

Reuq.prototype.runResourceSubscribers = function(resourceName, data) {
  var resource = this.app.resources[resourceName];
  data = data || resource.data;
  this.runSubscribers(resource.subscribers, data)
}

Reuq.prototype.updateResource = function(resourceName, cb) {
  var rq = this;
  this.getResource(resourceName, function(data) {
    rq.setResource(resourceName, cb(data));
  });
}

Reuq.prototype.setLocal = function(name, data) {
  var rq = this;
  // add fallback in case it's a newly dynamically set local
  var local = this.app.locals[name] = this.app.locals[name] || {};
  local.data = data;
  this.runLocalSubscribers(name, data);

  $('[rq-tmpl][rq-local=' + name + ']:not([manual-render])').each(function(id, el) {
    rq.render($(el).attr('rq-tmpl'), data);
  });
}

Reuq.prototype.getLocal = function(name) {
  return this.app.locals[name].data;
}

Reuq.prototype.updateLocal = function(name, cb) {
  var data = this.getLocal(name);
  this.setLocal(name, cb(data));
}

Reuq.prototype.runLocalSubscribers = function(name, data) {
  var local = this.app.locals[name];
  data = data || local.data;
  this.runSubscribers(local.subscribers, data);
}

Reuq.prototype.runSubscribers = function(subscribers, data) {
  var rq = this;
  if (subscribers) {
    subscribers.forEach(function(subscriber) {
      var path = subscriber.split('.');
      var fn = rq.app[path[0]][path[1]];
      fn.apply(rq, [data]);
    })
  }
}

//storeTemplates
Reuq.prototype._storeTemplates = function() {
  var rq = this;
  $('[rq-tmpl]').each(function(id, el) {
    var $el = $(el);
    rq.templates[$el.attr('rq-tmpl')] = {
      html: $el.prop('outerHTML'),
      dom: $el,
      localDataName: $el.attr('rq-local'),
      resourceName: $el.attr('rq-rsrc')
    };
  });
}

Reuq.prototype.addEvents = function($dom) {
  $dom = $dom || $('body');
  var rq = this;
  var evtSelector = '[rq-evt]:not([rq-tmpl] [rq-evt]):not([rq-tmpl][rq-evt])';
  $dom.find(evtSelector).addBack(evtSelector).each(function(id, el) {
    var $el = $(el);
    var evtConfig = $el.attr('rq-evt').split(' ')
    var evtType = evtConfig[0];
    var evtHandler = rq.app.eventHandlers[evtConfig[1]];
    $el.on(evtType, function(e) {
      var handlerArgs = [{event: e, target: $el}].concat(evtConfig.slice(2))
      evtHandler.apply(rq, handlerArgs);
    })
  });
}

Reuq.prototype.getUtils = function() {
  var rq = this;

  return {
    submit: function(form) {
      var data = form.serialize();
      var url = form.attr('action');
      var type = form.attr('method');

      var beforeSend = function(xhr) {
        var headersKey = form.attr('rq-form-headers');
        if (headersKey) {
          var headers = rq.app.fn[headersKey].apply(rq);
          Object.keys(headers || {}).forEach(function(header) {
            xhr.setRequestHeader(header, headers[header]);
          })
        }
      }

      $.ajax({ type: type, url: url, data: data, beforeSend: beforeSend })
        .done(function(data, status, jqXHR) {
          var cb = form.attr('rq-cb-done');
          if (cb) {
            rq.app.callbacks[cb].apply(rq, [data, status, form]);
          }
        })
        .fail(function(jqXHR, status, error) {
          var cb = form.attr('rq-cb-fail');
          if (cb) {
            rq.app.callbacks[cb](error, status, form, jqXHR);
          }
        });
    }
  }
}

$('head').append('<style type="text/css">[rq-tmpl] {display: none !important;}</style>');
window.Rq = window.Reuq = Reuq;
<commit_msg>refactor: moe robust `if` processing + `@` based dynamicProperties<commit_after>"use strict";

function Reuq(app) {
  var rq = this;
  this.templates = {};
  this.app = app;
  this.app.resources = app.resources || {};
  this.app.locals = app.locals || {};
  this.utils = this.getUtils();

  this._storeTemplates();
  // submit
  $('body').on('submit', '[rq-form]:not([rq-tmpl] [rq-form]):not([rq-tmpl][rq-form])', function(e) {
    e.preventDefault();
    rq.utils.submit($(this))
  });

  (function(rq) {
    var resources = rq.app.resources;
    var locals = rq.app.locals || {};

    Object.keys(locals).forEach(function(name) {
      rq.setLocal(name, rq.getLocal(name));
    });
    //load all resources set to autoload
    Object.keys(resources).forEach(function(resourceName) {
      // if autoload is not set, autoload it by default
      if (resources[resourceName]['autoload'] === undefined || resources[resourceName]['autoload']) {
        rq.getResource(resourceName, true);
        rq.render(resourceName, null);
      }
    });

    rq.addEvents();
    // should this be run 1st inetead?
    if (typeof rq.app.onInit === 'function') {
      rq.app.onInit.apply(rq);
    }
  })(this);
}

//render
Reuq.prototype.render = function(templateName, data) {
  var processedTemplate = this._processTemplate(templateName, data);
  this._render(templateName, processedTemplate);
}

Reuq.prototype._processTemplate = function(templateName, data) {
  var rq = this;

  function sanitize(value) {
    var blacklist = {
      '&': '&',
      '<': '<',
      '>': '>',
      '"': '"',
      "'": '''
    };
    return value.replace(/[&<>"']/g, function(m) {
      return blacklist[m];
    })
  }

  function isDynamicProperty(property) {
    return property[0] === "@";
  }

  function getDynamicPropertyValue(property, data) {
    // remove '@' character
    var expression = property.slice(1)
    return rq.app.dynamicProperties[expression].apply(rq, [data]);
  }

  function getPropertyValue(property, data){
    return isDynamicProperty(property) ? getDynamicPropertyValue(property, data) : data[property]
  }

  function compile(template, data) {
    var $template = $(template)

    //process ifs
    $template.find('[rq-if]').each(function(id, el) {
      var $el = $(el);
      var condition = $el.attr('rq-if');
      $el.removeAttr('rq-if');

      if (!getPropertyValue(condition, data)) {
        $el.remove();
      }
    })

    //process if-nots
    $template.find('[rq-if-not]').each(function(id, el) {
      var $el = $(el);
      var condition = $el.attr('rq-if-not');
      $el.removeAttr('rq-if-not');

      if (getPropertyValue(condition, data)) {
        $el.remove();
      }
    });

    template = $template.prop('outerHTML');

    template = template.replace(/\[\[(\w+|@\w+)\]\]/g, function() {
      var expression = arguments[1];
      // parse value to string
      var value = "" + (getPropertyValue(expression, data));
      return sanitize(value);
    });
    return template;
  }

  var templateObj = this.templates[templateName];
  var resourceName = templateObj.resourceName;
  var template = templateObj.html;
  var $template = $(template);

  // process rq-rsrc-loading
  if (resourceName && this.app.resources[resourceName].loading) {
    $template.not('[rq-rsrc-loading], [rq-rsrc-loading] *').remove();
  } else {
    $template.find('[rq-rsrc-loading], [rq-rsrc-loading] *').remove();
  }

  //process iteration
  $template.find('[rq-iter], [rq-iter-self]').each(function(id, el) {
    var $el = $(el);

    if ($el.is("[rq-iter]")) {
      var listKey = $el.attr('rq-iter');
      var list = data[listKey] || [];
    } else {
      // then it's rq-iter-self
      var list = data || [];
    }
    list.forEach(function(listItem) {
      var $compiled = $(compile($el.prop('outerHTML'), listItem));
      $compiled.removeAttr('rq-iter');
      $el.after($compiled);
    });
    $el.remove();
  });

  template = $template.prop('outerHTML');
  $template = $(compile(template, data))

  //process src
  $template.find('[rq-src]').each(function(id, el) {
    var $el = $(el);
    $el.attr('src', $el.attr('rq-src'));
    $el.removeAttr('rq-src');
  })

  return $template.prop('outerHTML');
}

Reuq.prototype._render = function(templateName, processedTemplate) {
  //remove previously rendered template
  $('[from-tmpl=' + templateName + ']').remove();

  var $template = $('[rq-tmpl][rq-tmpl=' + templateName + ']');
  var $newTemplate = $(processedTemplate).removeAttr('hidden').removeAttr('rq-tmpl');
  $newTemplate.attr('from-tmpl', templateName);
  $template.after($newTemplate);
  this.addEvents($newTemplate);
}

Reuq.prototype.cacheIsValid = function(resourceName) {
  var resource = this.app.resources[resourceName]

  if (resource.shouldReload) {
    return false;
  } else {
    var cacheTimeout = resource.cacheTimeout || 10; //minutes
    var timeoutDate = new Date(resource.updatedAt.getTime() + cacheTimeout * 60000);
    return timeoutDate > new Date();
  }
}

Reuq.prototype.invalidateResourceCache = function(resourceName) {
  this.app.resources[resourceName].shouldReload = true;
}

Reuq.prototype.getResource = function(resourceName, force, cb) {
  // coerce optional arguments to appropriate values
  if (arguments.length < 3 && typeof force === 'function') {
    cb = force;
    force = false;
  }
  var rq = this;
  var resource = this.app.resources[resourceName];
  if (resource.data && !force && this.cacheIsValid(resourceName)) {
    cb(resource.data);
  } else {
    resource.loading = true;

    var url = typeof resource.url === 'function' ? resource.url(this) : resource.url;
    $.ajax({
      url: url,
      dataType: "json",
      beforeSend: function(xhr) {
        Object.keys(resource.headers || {}).forEach(function(header) {
          xhr.setRequestHeader(header, resource.headers[header]);
        })
      },
      success: function(resp) {
        resource.loading = false;
        resource.loaded = true;

        rq.setResource(resourceName, resource.dataKey ? resp[resource.dataKey] : resp);

        if (typeof cb === 'function') {
          cb(resource.data);
        }
      }
    })
  }
}

Reuq.prototype.setResource = function(resourceName, data) {
  var rq = this;
  var resource = this.app.resources[resourceName];
  resource.data = data;
  resource.shouldReload = false;
  resource.updatedAt = new Date();
  this.runResourceSubscribers(resourceName, data);

  $('[rq-tmpl][rq-rsrc=' + resourceName + ']:not([manual-render])').each(function(id, el) {
    rq.render($(el).attr('rq-tmpl'), data);
  });
}

Reuq.prototype.runResourceSubscribers = function(resourceName, data) {
  var resource = this.app.resources[resourceName];
  data = data || resource.data;
  this.runSubscribers(resource.subscribers, data)
}

Reuq.prototype.updateResource = function(resourceName, cb) {
  var rq = this;
  this.getResource(resourceName, function(data) {
    rq.setResource(resourceName, cb(data));
  });
}

Reuq.prototype.setLocal = function(name, data) {
  var rq = this;
  // add fallback in case it's a newly dynamically set local
  var local = this.app.locals[name] = this.app.locals[name] || {};
  local.data = data;
  this.runLocalSubscribers(name, data);

  $('[rq-tmpl][rq-local=' + name + ']:not([manual-render])').each(function(id, el) {
    rq.render($(el).attr('rq-tmpl'), data);
  });
}

Reuq.prototype.getLocal = function(name) {
  return this.app.locals[name].data;
}

Reuq.prototype.updateLocal = function(name, cb) {
  var data = this.getLocal(name);
  this.setLocal(name, cb(data));
}

Reuq.prototype.runLocalSubscribers = function(name, data) {
  var local = this.app.locals[name];
  data = data || local.data;
  this.runSubscribers(local.subscribers, data);
}

Reuq.prototype.runSubscribers = function(subscribers, data) {
  var rq = this;
  if (subscribers) {
    subscribers.forEach(function(subscriber) {
      var path = subscriber.split('.');
      var fn = rq.app[path[0]][path[1]];
      fn.apply(rq, [data]);
    })
  }
}

//storeTemplates
Reuq.prototype._storeTemplates = function() {
  var rq = this;
  $('[rq-tmpl]').each(function(id, el) {
    var $el = $(el);
    rq.templates[$el.attr('rq-tmpl')] = {
      html: $el.prop('outerHTML'),
      dom: $el,
      localDataName: $el.attr('rq-local'),
      resourceName: $el.attr('rq-rsrc')
    };
  });
}

Reuq.prototype.addEvents = function($dom) {
  $dom = $dom || $('body');
  var rq = this;
  var evtSelector = '[rq-evt]:not([rq-tmpl] [rq-evt]):not([rq-tmpl][rq-evt])';
  $dom.find(evtSelector).addBack(evtSelector).each(function(id, el) {
    var $el = $(el);
    var evtConfig = $el.attr('rq-evt').split(' ')
    var evtType = evtConfig[0];
    var evtHandler = rq.app.eventHandlers[evtConfig[1]];
    $el.on(evtType, function(e) {
      var handlerArgs = [{event: e, target: $el}].concat(evtConfig.slice(2))
      evtHandler.apply(rq, handlerArgs);
    })
  });
}

Reuq.prototype.getUtils = function() {
  var rq = this;

  return {
    submit: function(form) {
      var data = form.serialize();
      var url = form.attr('action');
      var type = form.attr('method');

      var beforeSend = function(xhr) {
        var headersKey = form.attr('rq-form-headers');
        if (headersKey) {
          var headers = rq.app.fn[headersKey].apply(rq);
          Object.keys(headers || {}).forEach(function(header) {
            xhr.setRequestHeader(header, headers[header]);
          })
        }
      }

      $.ajax({ type: type, url: url, data: data, beforeSend: beforeSend })
        .done(function(data, status, jqXHR) {
          var cb = form.attr('rq-cb-done');
          if (cb) {
            rq.app.callbacks[cb].apply(rq, [data, status, form]);
          }
        })
        .fail(function(jqXHR, status, error) {
          var cb = form.attr('rq-cb-fail');
          if (cb) {
            rq.app.callbacks[cb](error, status, form, jqXHR);
          }
        });
    }
  }
}

$('head').append('<style type="text/css">[rq-tmpl] {display: none !important;}</style>');
window.Rq = window.Reuq = Reuq;
<|endoftext|>


<commit_before>const { exec } = require('child_process');
const path = require('path');
const fs = require('fs');
const jsondiffpatch = require('jsondiffpatch');
const beforePackageJSON = require('../../../package.json');
const Command = require('../Command');

class UpdateCommand extends Command {
  constructor(bot) {
    super(bot);

    this.props.help = {
      name: 'update',
      description: 'update the bot',
      usage: 'update [commit/branch]',
    };

    this.setConf({
      permLevel: 2,
    });
  }

  run(msg, args) {
    const commitOrBranch = args[0];
    let embedData = {
      title: 'Updating',
      color: '0xFB9738',
      description: '\u200B',
      fields: [],
      footer: {
        text: this.bot.user.username,
        icon_url: this.bot.user.avatarURL(),
      },
    };
    let message;

    return msg.channel.send({
      embed: embedData,
    })
    .then(m => {
      message = m;
      return this.exec('git pull');
    })
    .then(stdout => {
      if (stdout.includes('Already up-to-date')) {
        return this.addFieldToEmbed(message, embedData, {
          name: 'Git Pull',
          value: 'Already up-to-date',
        }).then(m => {
          embedData = m.embeds[0];
          return Promise.reject('No update');
        });
      }
      return this.addFieldToEmbed(message, embedData, {
        name: 'Git Pull',
        value: `\`\`\`sh\n${stdout}\n\`\`\``,
      });
    })
    .then(() => {
      if (commitOrBranch) return this.exec(`git checkout ${commitOrBranch}`);
      return;
    })
    .then(this.getDepsToInstall)
    .then(info => {
      if (!info) return Promise.resolve();
      return this.addFieldToEmbed(message, embedData, {
        name: 'Dependencies',
        value: [
          ...(info.install.length ? ['**Install:**', [...info.install].map(e => `- \`${e[0]}@${e[1]}\`\n`).join('')] : []),
          ...(info.update.length ? ['**Update:**', info.update.map(e => `- \`${e[0]}@${e[1]} -> ${e[2]}\`\n`).join('')] : []),
          ...(info.remove.length ? ['**Remove:**', [...info.remove].map(e => `- \`${e[0]}@${e[1]}\`\n`).join('')] : []),
        ].join('\n'),
      }).then(() => info);
    })
    .then(info => {
      if (!info) return Promise.resolve();
      return this.installDeps(info).then(stdouts =>
        this.addFieldToEmbed(message, embedData, {
          name: 'Yarn',
          value: stdouts.map(stdout => `\`\`\`sh\n${stdout}\n\`\`\``).join('\n'),
        })
      );
    })
    .then(() => message.channel.send({ embed: {
      color: 0x2ECC71,
      title: 'Updating',
      description: 'Restarting...',
    } }))
    .then(() => {
      Log.info('RESTARTING - Executed `update` command');
      process.exit(0);
    })
    .catch(err => {
      if (err === 'No update') return;
      return this.commandError(msg, err);
    });
  }

  getDepsToInstall() {
    return new Promise((resolve, reject) => {
      fs.readFile(path.resolve(__dirname, '../../../package.json'), (err, content) => {
        if (err) return reject(err);
        const afterPackageJSON = JSON.parse(content);
        delete afterPackageJSON.dependencies.debug;
        const diff = jsondiffpatch.diff(beforePackageJSON.dependencies, afterPackageJSON.dependencies);
        if (!diff) return resolve();
        let data = {
          install: Object.keys(diff).filter(e => diff[e].length === 1).map(e => [e, diff[e][0]]),
          update: Object.keys(diff).filter(e => diff[e].length === 2).map(e => [e, diff[e][0], diff[e][1]]),
          remove: Object.keys(diff).filter(e => diff[e].length === 3).map(e => [e, diff[e][0]]),
        };
        resolve(data);
      });
    });
  }

  async installDeps(data) {
    let stdouts = [
      data.install.length && await this.exec(`yarn add --no-progress ${data.install.map(e => `${e[0]}@${e[1]}`).join(' ')}`),
      data.update.length && await this.exec(`yarn upgrade --no-progress ${data.update.map(e => `${e[0]}@${e[1]}`).join(' ')}`),
      data.remove.length && await this.exec(`yarn remove --no-progress ${data.remove.map(e => e[0]).join(' ')}`),
    ];
    return stdouts.filter(e => !!e);
  }

  addFieldToEmbed(message, data, field) {
    data.fields.push(field);
    return message.edit({ embed: data });
  }

  exec(cmd, opts = {}) {
    return new Promise((resolve, reject) => {
      exec(cmd, opts, (err, stdout, stderr) => {
        if (err) return reject(stderr);
        resolve(stdout);
      });
    });
  }
}

module.exports = UpdateCommand;
<commit_msg>fix(discord: commands): fix color on updating embed<commit_after>const { exec } = require('child_process');
const path = require('path');
const fs = require('fs');
const jsondiffpatch = require('jsondiffpatch');
const beforePackageJSON = require('../../../package.json');
const Command = require('../Command');

class UpdateCommand extends Command {
  constructor(bot) {
    super(bot);

    this.props.help = {
      name: 'update',
      description: 'update the bot',
      usage: 'update [commit/branch]',
    };

    this.setConf({
      permLevel: 2,
    });
  }

  run(msg, args) {
    const commitOrBranch = args[0];
    let embedData = {
      title: 'Updating',
      color: 0xFB9738,
      description: '\u200B',
      fields: [],
      footer: {
        text: this.bot.user.username,
        icon_url: this.bot.user.avatarURL(),
      },
    };
    let message;

    return msg.channel.send({
      embed: embedData,
    })
    .then(m => {
      message = m;
      return this.exec('git pull');
    })
    .then(stdout => {
      if (stdout.includes('Already up-to-date')) {
        return this.addFieldToEmbed(message, embedData, {
          name: 'Git Pull',
          value: 'Already up-to-date',
        }).then(m => {
          embedData = m.embeds[0];
          return Promise.reject('No update');
        });
      }
      return this.addFieldToEmbed(message, embedData, {
        name: 'Git Pull',
        value: `\`\`\`sh\n${stdout}\n\`\`\``,
      });
    })
    .then(() => {
      if (commitOrBranch) return this.exec(`git checkout ${commitOrBranch}`);
      return;
    })
    .then(this.getDepsToInstall)
    .then(info => {
      if (!info) return Promise.resolve();
      return this.addFieldToEmbed(message, embedData, {
        name: 'Dependencies',
        value: [
          ...(info.install.length ? ['**Install:**', [...info.install].map(e => `- \`${e[0]}@${e[1]}\`\n`).join('')] : []),
          ...(info.update.length ? ['**Update:**', info.update.map(e => `- \`${e[0]}@${e[1]} -> ${e[2]}\`\n`).join('')] : []),
          ...(info.remove.length ? ['**Remove:**', [...info.remove].map(e => `- \`${e[0]}@${e[1]}\`\n`).join('')] : []),
        ].join('\n'),
      }).then(() => info);
    })
    .then(info => {
      if (!info) return Promise.resolve();
      return this.installDeps(info).then(stdouts =>
        this.addFieldToEmbed(message, embedData, {
          name: 'Yarn',
          value: stdouts.map(stdout => `\`\`\`sh\n${stdout}\n\`\`\``).join('\n'),
        })
      );
    })
    .then(() => message.channel.send({ embed: {
      color: 0x2ECC71,
      title: 'Updating',
      description: 'Restarting...',
    } }))
    .then(() => {
      Log.info('RESTARTING - Executed `update` command');
      process.exit(0);
    })
    .catch(err => {
      if (err === 'No update') return;
      return this.commandError(msg, err);
    });
  }

  getDepsToInstall() {
    return new Promise((resolve, reject) => {
      fs.readFile(path.resolve(__dirname, '../../../package.json'), (err, content) => {
        if (err) return reject(err);
        const afterPackageJSON = JSON.parse(content);
        delete afterPackageJSON.dependencies.debug;
        const diff = jsondiffpatch.diff(beforePackageJSON.dependencies, afterPackageJSON.dependencies);
        if (!diff) return resolve();
        let data = {
          install: Object.keys(diff).filter(e => diff[e].length === 1).map(e => [e, diff[e][0]]),
          update: Object.keys(diff).filter(e => diff[e].length === 2).map(e => [e, diff[e][0], diff[e][1]]),
          remove: Object.keys(diff).filter(e => diff[e].length === 3).map(e => [e, diff[e][0]]),
        };
        resolve(data);
      });
    });
  }

  async installDeps(data) {
    let stdouts = [
      data.install.length && await this.exec(`yarn add --no-progress ${data.install.map(e => `${e[0]}@${e[1]}`).join(' ')}`),
      data.update.length && await this.exec(`yarn upgrade --no-progress ${data.update.map(e => `${e[0]}@${e[1]}`).join(' ')}`),
      data.remove.length && await this.exec(`yarn remove --no-progress ${data.remove.map(e => e[0]).join(' ')}`),
    ];
    return stdouts.filter(e => !!e);
  }

  addFieldToEmbed(message, data, field) {
    data.fields.push(field);
    return message.edit({ embed: data });
  }

  exec(cmd, opts = {}) {
    return new Promise((resolve, reject) => {
      exec(cmd, opts, (err, stdout, stderr) => {
        if (err) return reject(stderr);
        resolve(stdout);
      });
    });
  }
}

module.exports = UpdateCommand;
<|endoftext|>


<commit_before>// Copyright 2017-2020, University of Colorado Boulder

/**
 * IO type for Nodes that can save their own index (if phetioState: true).  Can be used to customize z-order
 * or layout order.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import ObjectIO from '../../../tandem/js/types/ObjectIO.js';
import VoidIO from '../../../tandem/js/types/VoidIO.js';
import scenery from '../scenery.js';
import NodeIO from './NodeIO.js';

class IndexedNodeIO extends NodeIO {

  // @public
  static toStateObject( node ) {
    const stateObject = {};
    if ( node.parents[ 0 ] ) {
      stateObject.index = node.parents[ 0 ].indexOfChild( node );
    }
    return stateObject;
  }

  // @public
  static applyState( node, fromStateObject ) {
    if ( node.parents[ 0 ] ) {
      node.parents[ 0 ].moveChildToIndex( node, fromStateObject.index );
    }
  }
}

IndexedNodeIO.methods = {
  moveForward: {
    returnType: VoidIO,
    parameterTypes: [],
    implementation: function() {
      return this.phetioObject.moveForward();
    },
    documentation: 'Move this node one index forward in each of its parents.  If the node is already at the front, this is a no-op.'
  },

  moveBackward: {
    returnType: VoidIO,
    parameterTypes: [],
    implementation: function() {
      return this.phetioObject.moveBackward();
    },
    documentation: 'Move this node one index backward in each of its parents.  If the node is already at the back, this is a no-op.'
  }
};

IndexedNodeIO.validator = { valueType: scenery.Node };
IndexedNodeIO.documentation = 'Node that can be moved forward/back by index, which specifies z-order and/or layout order';
IndexedNodeIO.typeName = 'IndexedNodeIO';
ObjectIO.validateSubtype( IndexedNodeIO );

scenery.register( 'IndexedNodeIO', IndexedNodeIO );
export default IndexedNodeIO;<commit_msg>update copyright dates from daily grunt work<commit_after>// Copyright 2020, University of Colorado Boulder

/**
 * IO type for Nodes that can save their own index (if phetioState: true).  Can be used to customize z-order
 * or layout order.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import ObjectIO from '../../../tandem/js/types/ObjectIO.js';
import VoidIO from '../../../tandem/js/types/VoidIO.js';
import scenery from '../scenery.js';
import NodeIO from './NodeIO.js';

class IndexedNodeIO extends NodeIO {

  // @public
  static toStateObject( node ) {
    const stateObject = {};
    if ( node.parents[ 0 ] ) {
      stateObject.index = node.parents[ 0 ].indexOfChild( node );
    }
    return stateObject;
  }

  // @public
  static applyState( node, fromStateObject ) {
    if ( node.parents[ 0 ] ) {
      node.parents[ 0 ].moveChildToIndex( node, fromStateObject.index );
    }
  }
}

IndexedNodeIO.methods = {
  moveForward: {
    returnType: VoidIO,
    parameterTypes: [],
    implementation: function() {
      return this.phetioObject.moveForward();
    },
    documentation: 'Move this node one index forward in each of its parents.  If the node is already at the front, this is a no-op.'
  },

  moveBackward: {
    returnType: VoidIO,
    parameterTypes: [],
    implementation: function() {
      return this.phetioObject.moveBackward();
    },
    documentation: 'Move this node one index backward in each of its parents.  If the node is already at the back, this is a no-op.'
  }
};

IndexedNodeIO.validator = { valueType: scenery.Node };
IndexedNodeIO.documentation = 'Node that can be moved forward/back by index, which specifies z-order and/or layout order';
IndexedNodeIO.typeName = 'IndexedNodeIO';
ObjectIO.validateSubtype( IndexedNodeIO );

scenery.register( 'IndexedNodeIO', IndexedNodeIO );
export default IndexedNodeIO;<|endoftext|>


<commit_before>const CACHE_NAME = 'v0.0';
const URLS_TO_CACHE = ['/', '/index.html'];

self.addEventListener('fetch', function(event){
    if(is_a_ressource(event.request)){
        //console.log("> FETCH RESSOURCE", event.request.url)
        return fetchRessource(event);
    }else if(is_an_api_call(event.request)){
        //console.log("> FETCH API", event.request.url)
        return fetchApi(event);
    }else if(is_an_index(event.request)){
        //console.log("> FETCH INDEX", event.request.url)
        return fetchIndex(event);
    }else{
        //console.log("> FETCH FALLBACK", event.request.url)
        return cacheFallback(event);
    }
});

self.addEventListener('activate', function(event){
    //console.log("> ACTIVATE")
    vacuum(event)
});
self.addEventListener('install', function(event){
    //console.log("> INSTALL SERVICE WORKER", navigator)
    if (self.skipWaiting) { self.skipWaiting(); }
})

////////////////////////////////////////
// ASSETS AND RESSOURCES
////////////////////////////////////////

function is_a_ressource(request){
    return ['css', 'js', 'img', 'logo', 'manifest.json', 'favicon.ico'].indexOf(pathname(request)[0]) >= 0 ? true : false;
}

/*
 * cache agressively but refresh the cache if possible
 */
function fetchRessource(event){
    event.respondWith(
        caches.open(CACHE_NAME).then(function(cache){
            return cache.match(event.request)
                .then(function(response){
                    if(response){
                        fetchAndCache(event).catch(nil)
                        return response;
                    }else{
                        return Promise.reject("OUPS");
                    }
                })
                .catch(function(err){
                    return fetchAndCache(event);
                });
        })
    );

    function fetchAndCache(event){
        // A request is a stream and can only be consumed once. Since we are consuming this
        // once by cache and once by the browser for fetch, we need to clone the response as
        // seen on https://developers.google.com/web/fundamentals/getting-started/primers/service-workers
        const request = event.request.clone();

        return fetch(request)
            .then(function(response){
                if(!response){ return response; }

                // A response is a stream and can only because we want the browser to consume the
                // response as well as the cache consuming the response, we need to clone it
                const responseToCache = response.clone();
                caches.open(CACHE_NAME).then(function(cache){
                    cache.put(event.request, responseToCache);
                });
                return response;
            });
    }
}

////////////////////////////////////////
// API CALL
////////////////////////////////////////
function is_an_api_call(request){
    return pathname(request)[0] === 'api' ? true : false;
}

function fetchApi(event){
}

////////////////////////////////////////
// INDEX CALL
////////////////////////////////////////
function is_an_index(request){
    return ['login', 'files', 'view', 'logout'].indexOf(pathname(request)[0]) >= 0? true : false;
}
function fetchIndex(event){
    event.request.url = host(event.request);
    event.respondWith(
        caches.open(CACHE_NAME).then(function(cache){
            return cache.match('/').then(function(response){
                return response || fetch('/').then(function(response) {
                    if(response && response.status === 200){
                        cache.put('/', response.clone());
                    }
                    return response;
                })
            })
        })
    )
}

////////////////////////////////////////
// OTHER STUFF
////////////////////////////////////////
function cacheFallback(event){
    event.respondWith(
        caches.open(CACHE_NAME).then(function(cache){
            return cache.match(event.request).then(function(response){
                if(response){
                    return response;
                }else{
                    return fetch(event.request.clone())
                        .then(function(response){
                            cache.put(event.request, response.clone())
                            return response;
                        });
                }
            });
        })
    )
}

////////////////////////////////////////
// HELPERS
////////////////////////////////////////

function vacuum(event){
    return event.waitUntil(
        caches.keys().then(function(cachesName){
            return Promise.all(cachesName.map(function(cacheName){
                if(cacheName !== CACHE_NAME){
                    return caches.delete(cacheName);
                }
            }));
        })
    );
}

function host(request){
    return request.url.replace(/(http[s]?\:\/\/[^\/]*\/).*/, '$1');
}
function pathname(request){
    return request.url.replace(/^http[s]?:\/\/[^\/]*\//, '').split('/')
}
function nil(e){}
<commit_msg>cleanup (cache): setup caching policies<commit_after>const CACHE_NAME = 'v0.0';
const URLS_TO_CACHE = ['/', '/index.html'];
const DELAY_BEFORE_SENDING_CACHE = 500;

self.addEventListener('fetch', function(event){
    if(is_a_ressource(event.request)){
        return fetchRessource(event);
    }else if(is_an_api_call(event.request)){
        return fetchApi(event);
    }else if(is_an_index(event.request)){
        return fetchIndex(event);
    }else{
        return cacheFallback(event);
    }
});

self.addEventListener('activate', function(event){
    vacuum(event)
});
self.addEventListener('install', function(event){
    if (self.skipWaiting) { self.skipWaiting(); }
})

////////////////////////////////////////
// ASSETS AND RESSOURCES
////////////////////////////////////////

function is_a_ressource(request){
    return ['css', 'js', 'img', 'logo', 'manifest.json', 'favicon.ico'].indexOf(pathname(request)[0]) >= 0 ? true : false;
}

/*
 * cache agressively but refresh the cache if possible
 */
function fetchRessource(event){
    if(navigator.standalone === true || navigator.onLine === false){
        event.respondWith(cacheThenNetwork(event.request))
    }else{
        event.respondWith(networkThenCache(event.request));
    }
}

////////////////////////////////////////
// API CALL
////////////////////////////////////////
function is_an_api_call(request){
    return pathname(request)[0] === 'api' ? true : false;
}

function fetchApi(event){
}

////////////////////////////////////////
// INDEX CALL
////////////////////////////////////////
function is_an_index(request){
    return ['login', 'files', 'view', 'logout'].indexOf(pathname(request)[0]) >= 0? true : false;
}
function fetchIndex(event){
    if(navigator.standalone === true || navigator.onLine === false){
        event.respondWith(cacheThenNetwork(event.request))
    }else{
        event.respondWith(networkThenCache(event.request));
    }
}

////////////////////////////////////////
// OTHER STUFF
////////////////////////////////////////
function cacheFallback(event){
    event.respondWith(cacheThenNetwork(event.request));
}

////////////////////////////////////////
// HELPERS
////////////////////////////////////////

function vacuum(event){
    return event.waitUntil(
        caches.keys().then(function(cachesName){
            return Promise.all(cachesName.map(function(cacheName){
                if(cacheName !== CACHE_NAME){
                    return caches.delete(cacheName);
                }
            }));
        })
    );
}

function host(request){
    return request.url.replace(/(http[s]?\:\/\/[^\/]*\/).*/, '$1');
}
function pathname(request){
    return request.url.replace(/^http[s]?:\/\/[^\/]*\//, '').split('/')
}
function nil(e){}

function cacheThenNetwork(request){
    return caches.open(CACHE_NAME).then(function(cache){
        return cache.match(request)
            .then(function(response){
                if(response){
                    fetchAndCache(request).catch(nil)
                    return response;
                }else{
                    return Promise.reject("OUPS");
                }
            })
            .catch(function(err){
                return fetchAndCache(request);
            });
    });

    function fetchAndCache(_request){
        // A request is a stream and can only be consumed once. Since we are consuming this
        // once by cache and once by the browser for fetch, we need to clone the response as
        // seen on https://developers.google.com/web/fundamentals/getting-started/primers/service-workers
        return fetch(_request.clone && _request.clone() || _request)
            .then(function(response){
                if(!response){ return response; }

                // A response is a stream and can only because we want the browser to consume the
                // response as well as the cache consuming the response, we need to clone it
                const responseClone = response.clone();
                caches.open(CACHE_NAME).then(function(cache){
                    cache.put(_request, responseClone);
                });
                return response;
            });
    }
}

function networkThenCache(request){
    let timeoutId;

    return Promise.race([
        cache(request.clone && request.clone() || request),
        network(request.clone && request.clone() || request)
    ])

    function network(request){
        return fetch(request)
            .then(function(response){
                const responseClone = response.clone();
                caches.open(CACHE_NAME).then(function(cache){
                    cache.put(request, responseClone);
                });
                return Promise.resolve(response);
            })
            .catch(function(){
                return cache(request.clone && request.clone() || request)
            });
    }

    function cache(_request){
        return timeout()
            .then(function(){ return caches.open(CACHE_NAME)})
            .then(function(_cache){
                return _cache.match(_request);
            });

        function timeout(){
            return new Promise(function(resolve) {
                setTimeout(function() {
                    resolve();
                }, DELAY_BEFORE_SENDING_CACHE);
            });
        }
    }
}
<|endoftext|>


<commit_before>/** Spreadsheet **/
(function() {

function getRowLabel(value){
  if (this.options.spreadsheet.tickFormatter){
    //TODO maybe pass the xaxis formatter to the custom tick formatter as an opt-out?
    return this.options.spreadsheet.tickFormatter(value);
  }
  else {
    var t = _.find(this.axes.x.ticks, function(t){return t.v == value;});
    if (t) {
      return t.label;
    }
    return value;
  }
}

var
  D = Flotr.DOM,
  _ = Flotr._;

Flotr.addPlugin('spreadsheet', {
  options: {
    show: false,           // => show the data grid using two tabs
    tabGraphLabel: 'Graph',
    tabDataLabel: 'Data',
    toolbarDownload: 'Download CSV', // @todo: add better language support
    toolbarSelectAll: 'Select all',
    csvFileSeparator: ',',
    decimalSeparator: '.',
    tickFormatter: null
  },
  /**
   * Builds the tabs in the DOM
   */
  callbacks: {
    'flotr:afterconstruct': function(){
      // @TODO necessary?
      //this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove');

      if (!this.options.spreadsheet.show) return;

      var ss = this.spreadsheet,
        container = D.node('

'),
        graph = D.node('
'+this.options.spreadsheet.tabGraphLabel+'
'),
        data = D.node('
'+this.options.spreadsheet.tabDataLabel+'
'),
        offset;

      ss.tabsContainer = container;
      ss.tabs = { graph : graph, data : data };

      D.insert(container, graph);
      D.insert(container, data);
      D.insert(this.el, container);

      offset = D.size(data).height + 2;
      this.plotOffset.bottom += offset;

      D.setStyles(container, {top: this.canvasHeight-offset+'px'});

      this.
        _observe(graph, 'click',  function(){ss.showTab('graph');}).
        _observe(data, 'click', function(){ss.showTab('data');});

      this.spreadsheet.activeTab = 'graph';
    }
  },
  /**
   * Builds a matrix of the data to make the correspondance between the x values and the y values :
   * X value => Y values from the axes
   * @return {Array} The data grid
   */
  loadDataGrid: function(){
    if (this.seriesData) return this.seriesData;

    var s = this.series,
        dg = [];

    /* The data grid is a 2 dimensions array. There is a row for each X value.
     * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one)
    **/
    _.each(s, function(serie, i){
      _.each(serie.data, function (v) {
        var x = v[0],
            y = v[1],
            r = _.detect(dg, function(row) {return row[0] === x;});
        if (r) {
          r[i+1] = y;
        } else {
          var newRow = [];
          newRow[0] = x;
          newRow[i+1] = y;
          dg.push(newRow);
        }
      });
    });

    // The data grid is sorted by x value
    this.seriesData = _.sortBy(dg, function (v) {
      return v[0];
    });
    return this.seriesData;
  },
  /**
   * Constructs the data table for the spreadsheet
   * @todo make a spreadsheet manager (Flotr.Spreadsheet)
   * @return {Element} The resulting table element
   */
  constructDataGrid: function(){
    // If the data grid has already been built, nothing to do here
    if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid;

    var s = this.series,
        datagrid = this.spreadsheet.loadDataGrid(),
        colgroup = ['<colgroup><col />'],
        buttonDownload, buttonSelect, t;

    // First row : series' labels
    var html = ['

'];
    html.push(' 	');
    _.each(s, function(serie,i){
      html.push(''+(serie.label || String.fromCharCode(65+i))+'	');
      colgroup.push('<col />');
    });
    html.push('
');
    // Data rows
    _.each(datagrid, function(row){
      html.push('
');
      _.times(s.length+1, function(i){
        var tag = 'td',
            value = row[i],
            // TODO: do we really want to handle problems with floating point
            // precision here?
            content = (!_.isUndefined(value) ? Math.round(value*100000)/100000 : '');
        if (i === 0) {
          tag = 'th';
          var label = getRowLabel.call(this, content);
          if (label) content = label;
        }

        html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+'</'+tag+'>');
      }, this);
      html.push('
');
    }, this);
    colgroup.push('</colgroup>');
    t = D.node(html.join(''));

    /**
     * @TODO disabled this
    if (!Flotr.isIE || Flotr.isIE == 9) {
      function handleMouseout(){
        t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover');
      }
      function handleMouseover(e){
        var td = e.element(),
          siblings = td.previousSiblings();
        t.select('th[scope=col]')[siblings.length-1].addClassName('hover');
        t.select('colgroup col')[siblings.length].addClassName('hover');
      }
      _.each(t.select('td'), function(td) {
        Flotr.EventAdapter.
          observe(td, 'mouseover', handleMouseover).
          observe(td, 'mouseout', handleMouseout);
      });
    }
    */

    buttonDownload = D.node(
      '<button type="button" class="flotr-datagrid-toolbar-button">' +
      this.options.spreadsheet.toolbarDownload +
      '</button>');

    buttonSelect = D.node(
      '<button type="button" class="flotr-datagrid-toolbar-button">' +
      this.options.spreadsheet.toolbarSelectAll+
      '</button>');

    this.
      _observe(buttonDownload, 'click', _.bind(this.spreadsheet.downloadCSV, this)).
      _observe(buttonSelect, 'click', _.bind(this.spreadsheet.selectAllData, this));

    var toolbar = D.node('

');
    D.insert(toolbar, buttonDownload);
    D.insert(toolbar, buttonSelect);

    var containerHeight =this.canvasHeight - D.size(this.spreadsheet.tabsContainer).height-2,
        container = D.node('

');

    D.insert(container, toolbar);
    D.insert(container, t);
    D.insert(this.el, container);
    this.spreadsheet.datagrid = t;
    this.spreadsheet.container = container;

    return t;
  },
  /**
   * Shows the specified tab, by its name
   * @todo make a tab manager (Flotr.Tabs)
   * @param {String} tabName - The tab name
   */
  showTab: function(tabName){
    if (this.spreadsheet.activeTab === tabName){
      return;
    }
    switch(tabName) {
      case 'graph':
        D.hide(this.spreadsheet.container);
        D.removeClass(this.spreadsheet.tabs.data, 'selected');
        D.addClass(this.spreadsheet.tabs.graph, 'selected');
      break;
      case 'data':
        if (!this.spreadsheet.datagrid)
          this.spreadsheet.constructDataGrid();
        D.show(this.spreadsheet.container);
        D.addClass(this.spreadsheet.tabs.data, 'selected');
        D.removeClass(this.spreadsheet.tabs.graph, 'selected');
      break;
    }
    this.spreadsheet.activeTab = tabName;
  },
  /**
   * Selects the data table in the DOM for copy/paste
   */
  selectAllData: function(){
    if (this.spreadsheet.tabs) {
      var selection, range, doc, win, node = this.spreadsheet.constructDataGrid();

      this.spreadsheet.showTab('data');

      // deferred to be able to select the table
      setTimeout(function () {
        if ((doc = node.ownerDocument) && (win = doc.defaultView) &&
            win.getSelection && doc.createRange &&
            (selection = window.getSelection()) &&
            selection.removeAllRanges) {
            range = doc.createRange();
            range.selectNode(node);
            selection.removeAllRanges();
            selection.addRange(range);
        }
        else if (document.body && document.body.createTextRange &&
                (range = document.body.createTextRange())) {
            range.moveToElementText(node);
            range.select();
        }
      }, 0);
      return true;
    }
    else return false;
  },
  /**
   * Converts the data into CSV in order to download a file
   */
  downloadCSV: function(){
    var csv = '',
        series = this.series,
        options = this.options,
        dg = this.spreadsheet.loadDataGrid(),
        separator = encodeURIComponent(options.spreadsheet.csvFileSeparator);

    if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) {
      throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")";
    }

    // The first row
    _.each(series, function(serie, i){
      csv += separator+'"'+(serie.label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"';
    });

    csv += "%0D%0A"; // \r\n

    // For each row
    csv += _.reduce(dg, function(memo, row){
      var rowLabel = getRowLabel.call(this, row[0]) || '';
      rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"';
      var numbers = row.slice(1).join(separator);
      if (options.spreadsheet.decimalSeparator !== '.') {
        numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator);
      }
      return memo + rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n
    }, '', this);

    if (Flotr.isIE && Flotr.isIE < 9) {
      csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r');
      window.open().document.write(csv);
    }
    else window.open('data:text/csv,'+csv);
  }
});
})();
<commit_msg>allow to specify which tab is initially shown<commit_after>/** Spreadsheet **/
(function() {

function getRowLabel(value){
  if (this.options.spreadsheet.tickFormatter){
    //TODO maybe pass the xaxis formatter to the custom tick formatter as an opt-out?
    return this.options.spreadsheet.tickFormatter(value);
  }
  else {
    var t = _.find(this.axes.x.ticks, function(t){return t.v == value;});
    if (t) {
      return t.label;
    }
    return value;
  }
}

var
  D = Flotr.DOM,
  _ = Flotr._;

Flotr.addPlugin('spreadsheet', {
  options: {
    show: false,           // => show the data grid using two tabs
    tabGraphLabel: 'Graph',
    tabDataLabel: 'Data',
    toolbarDownload: 'Download CSV', // @todo: add better language support
    toolbarSelectAll: 'Select all',
    csvFileSeparator: ',',
    decimalSeparator: '.',
    tickFormatter: null,
    initialTab: 'graph'
  },
  /**
   * Builds the tabs in the DOM
   */
  callbacks: {
    'flotr:afterconstruct': function(){
      // @TODO necessary?
      //this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove');

      if (!this.options.spreadsheet.show) return;

      var ss = this.spreadsheet,
        container = D.node('

'),
        graph = D.node('
'+this.options.spreadsheet.tabGraphLabel+'
'),
        data = D.node('
'+this.options.spreadsheet.tabDataLabel+'
'),
        offset;

      ss.tabsContainer = container;
      ss.tabs = { graph : graph, data : data };

      D.insert(container, graph);
      D.insert(container, data);
      D.insert(this.el, container);

      offset = D.size(data).height + 2;
      this.plotOffset.bottom += offset;

      D.setStyles(container, {top: this.canvasHeight-offset+'px'});

      this.
        _observe(graph, 'click',  function(){ss.showTab('graph');}).
        _observe(data, 'click', function(){ss.showTab('data');});
      if (this.options.spreadsheet.initialTab !== 'graph'){
        ss.showTab(this.options.spreadsheet.initialTab);
      }
    }
  },
  /**
   * Builds a matrix of the data to make the correspondance between the x values and the y values :
   * X value => Y values from the axes
   * @return {Array} The data grid
   */
  loadDataGrid: function(){
    if (this.seriesData) return this.seriesData;

    var s = this.series,
        dg = [];

    /* The data grid is a 2 dimensions array. There is a row for each X value.
     * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one)
    **/
    _.each(s, function(serie, i){
      _.each(serie.data, function (v) {
        var x = v[0],
            y = v[1],
            r = _.detect(dg, function(row) {return row[0] === x;});
        if (r) {
          r[i+1] = y;
        } else {
          var newRow = [];
          newRow[0] = x;
          newRow[i+1] = y;
          dg.push(newRow);
        }
      });
    });

    // The data grid is sorted by x value
    this.seriesData = _.sortBy(dg, function (v) {
      return v[0];
    });
    return this.seriesData;
  },
  /**
   * Constructs the data table for the spreadsheet
   * @todo make a spreadsheet manager (Flotr.Spreadsheet)
   * @return {Element} The resulting table element
   */
  constructDataGrid: function(){
    // If the data grid has already been built, nothing to do here
    if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid;

    var s = this.series,
        datagrid = this.spreadsheet.loadDataGrid(),
        colgroup = ['<colgroup><col />'],
        buttonDownload, buttonSelect, t;

    // First row : series' labels
    var html = ['

'];
    html.push(' 	');
    _.each(s, function(serie,i){
      html.push(''+(serie.label || String.fromCharCode(65+i))+'	');
      colgroup.push('<col />');
    });
    html.push('
');
    // Data rows
    _.each(datagrid, function(row){
      html.push('
');
      _.times(s.length+1, function(i){
        var tag = 'td',
            value = row[i],
            // TODO: do we really want to handle problems with floating point
            // precision here?
            content = (!_.isUndefined(value) ? Math.round(value*100000)/100000 : '');
        if (i === 0) {
          tag = 'th';
          var label = getRowLabel.call(this, content);
          if (label) content = label;
        }

        html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+'</'+tag+'>');
      }, this);
      html.push('
');
    }, this);
    colgroup.push('</colgroup>');
    t = D.node(html.join(''));

    /**
     * @TODO disabled this
    if (!Flotr.isIE || Flotr.isIE == 9) {
      function handleMouseout(){
        t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover');
      }
      function handleMouseover(e){
        var td = e.element(),
          siblings = td.previousSiblings();
        t.select('th[scope=col]')[siblings.length-1].addClassName('hover');
        t.select('colgroup col')[siblings.length].addClassName('hover');
      }
      _.each(t.select('td'), function(td) {
        Flotr.EventAdapter.
          observe(td, 'mouseover', handleMouseover).
          observe(td, 'mouseout', handleMouseout);
      });
    }
    */

    buttonDownload = D.node(
      '<button type="button" class="flotr-datagrid-toolbar-button">' +
      this.options.spreadsheet.toolbarDownload +
      '</button>');

    buttonSelect = D.node(
      '<button type="button" class="flotr-datagrid-toolbar-button">' +
      this.options.spreadsheet.toolbarSelectAll+
      '</button>');

    this.
      _observe(buttonDownload, 'click', _.bind(this.spreadsheet.downloadCSV, this)).
      _observe(buttonSelect, 'click', _.bind(this.spreadsheet.selectAllData, this));

    var toolbar = D.node('

');
    D.insert(toolbar, buttonDownload);
    D.insert(toolbar, buttonSelect);

    var containerHeight =this.canvasHeight - D.size(this.spreadsheet.tabsContainer).height-2,
        container = D.node('

');

    D.insert(container, toolbar);
    D.insert(container, t);
    D.insert(this.el, container);
    this.spreadsheet.datagrid = t;
    this.spreadsheet.container = container;

    return t;
  },
  /**
   * Shows the specified tab, by its name
   * @todo make a tab manager (Flotr.Tabs)
   * @param {String} tabName - The tab name
   */
  showTab: function(tabName){
    if (this.spreadsheet.activeTab === tabName){
      return;
    }
    switch(tabName) {
      case 'graph':
        D.hide(this.spreadsheet.container);
        D.removeClass(this.spreadsheet.tabs.data, 'selected');
        D.addClass(this.spreadsheet.tabs.graph, 'selected');
      break;
      case 'data':
        if (!this.spreadsheet.datagrid)
          this.spreadsheet.constructDataGrid();
        D.show(this.spreadsheet.container);
        D.addClass(this.spreadsheet.tabs.data, 'selected');
        D.removeClass(this.spreadsheet.tabs.graph, 'selected');
      break;
    }
    this.spreadsheet.activeTab = tabName;
  },
  /**
   * Selects the data table in the DOM for copy/paste
   */
  selectAllData: function(){
    if (this.spreadsheet.tabs) {
      var selection, range, doc, win, node = this.spreadsheet.constructDataGrid();

      this.spreadsheet.showTab('data');

      // deferred to be able to select the table
      setTimeout(function () {
        if ((doc = node.ownerDocument) && (win = doc.defaultView) &&
            win.getSelection && doc.createRange &&
            (selection = window.getSelection()) &&
            selection.removeAllRanges) {
            range = doc.createRange();
            range.selectNode(node);
            selection.removeAllRanges();
            selection.addRange(range);
        }
        else if (document.body && document.body.createTextRange &&
                (range = document.body.createTextRange())) {
            range.moveToElementText(node);
            range.select();
        }
      }, 0);
      return true;
    }
    else return false;
  },
  /**
   * Converts the data into CSV in order to download a file
   */
  downloadCSV: function(){
    var csv = '',
        series = this.series,
        options = this.options,
        dg = this.spreadsheet.loadDataGrid(),
        separator = encodeURIComponent(options.spreadsheet.csvFileSeparator);

    if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) {
      throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")";
    }

    // The first row
    _.each(series, function(serie, i){
      csv += separator+'"'+(serie.label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"';
    });

    csv += "%0D%0A"; // \r\n

    // For each row
    csv += _.reduce(dg, function(memo, row){
      var rowLabel = getRowLabel.call(this, row[0]) || '';
      rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"';
      var numbers = row.slice(1).join(separator);
      if (options.spreadsheet.decimalSeparator !== '.') {
        numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator);
      }
      return memo + rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n
    }, '', this);

    if (Flotr.isIE && Flotr.isIE < 9) {
      csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r');
      window.open().document.write(csv);
    }
    else window.open('data:text/csv,'+csv);
  }
});
})();
<|endoftext|>


<commit_before>import * as THREE from './lib/three';

import props from './props';
import { tempQuaternion, tempVector, createInstancedMesh } from './utils/three';
import viewer from './viewer';
import settings from './settings';
import audio from './audio';
import { getCostumeColor, getRoomColor, recordCostumeColor, recordRoomColor } from './theme/colors';
import streamJSON from './lib/stream-json';

import dummyTextureUrl from './public/dummy.png';

const PROTOCOL = location.protocol;
const PERFORMANCE_ELEMENT_COUNT = 21;
const LIMB_ELEMENT_COUNT = 7;

let roomIndex = 0;
let wallMesh;
let wallMeshes;
let roomMesh;
let roomMeshes;
let headMesh;
let handMesh;

const roomOffset = new THREE.Vector3(0, settings.roomHeight * 0.5, 0);
const roomsGroup = new THREE.Group();
roomsGroup.matrixAutoUpdate = false;

const debugMesh = new THREE.Mesh(
  new THREE.BoxGeometry(0, 0, 0),
  new THREE.MeshBasicMaterial({
    map: new THREE.TextureLoader().load(dummyTextureUrl),
  })
);
debugMesh.frustumCulled = false;

const ROTATION_MATRIX = new THREE.Matrix4().makeRotationAxis(
  new THREE.Vector3(0, 1, 0).normalize(),
  Math.PI
);
const IDENTITY_MATRIX = new THREE.Matrix4();

const secondsToFrames = (seconds) => Math.floor((seconds % (audio.loopDuration * 2)) * 90);

const getPosition = (positions, arrayOffset, offset) => tempVector(
  positions[arrayOffset] * 0.0001,
  positions[arrayOffset + 1] * 0.0001,
  positions[arrayOffset + 2] * 0.0001 - settings.roomOffset
).add(offset);

const getQuaternion = (positions, arrayOffset) => tempQuaternion(
  positions[arrayOffset + 3] * 0.0001,
  positions[arrayOffset + 4] * 0.0001,
  positions[arrayOffset + 5] * 0.0001,
  positions[arrayOffset + 6] * 0.0001
);

const transformMesh = (
  instancedMesh,
  positions,
  index,
  arrayOffset,
  scale,
  color,
  offset,
) => {
  instancedMesh.setPositionAt(
    index,
    getPosition(positions, arrayOffset, offset)
  );
  instancedMesh.setQuaternionAt(
    index,
    getQuaternion(positions, arrayOffset, offset)
  );
  instancedMesh.setScaleAt(
    index,
    tempVector(scale, scale, scale)
  );
  instancedMesh.setColorAt(
    index,
    color,
  );
  instancedMesh.needsUpdate();
};

export default class Room {
  constructor({ url, recording, index }) {
    this.placementIndex = index === undefined
      ? roomIndex
      : index;
    this.index = roomIndex;
    roomIndex += 1;

    this.isRecording = !!recording;
    this.url = url;
    if (recording) {
      this.hideHead = recording.hideHead;
      this.frames = recording.frames;
      this.changeColor(recordRoomColor);
    }

    this.costumeColor = this.isRecording
      ? recordCostumeColor
      : getCostumeColor(this.index);

    this.position = new THREE.Vector3(
      0,
      0,
      settings.roomOffset + (this.placementIndex * (settings.roomDepth + 0.001)),
    );
    this.updatePosition();
    this.costumeColor = this.isRecording
      ? recordCostumeColor
      : getCostumeColor(this.index);
  }

  load(callback) {
    const frames = [];
    this.streamer = streamJSON(
      `${PROTOCOL}//d1nylz9ljdxzkb.cloudfront.net/${this.url}`,
      (error, json) => {
        if (error || !json) {
          if (callback) {
            return callback(error);
          }
        }
        if (!this.frames) {
          // First JSON is meta object:
          const meta = JSON.parse(json);
          this.frames = frames;
          this.hideHead = meta.hideHead;
          this.layerCount = meta.count;
        } else {
          frames.push(json);
        }
      }
    );
  }

  updatePosition() {
    const position = tempVector().add(this.position).add(roomOffset);
    for (const i in roomMeshes) {
      roomMeshes[i].setPositionAt(this.index, position);
      roomMeshes[i].needsUpdate('position');

      if (this.index < wallMeshes[i].numInstances) {
        wallMeshes[i].setPositionAt(this.index, position);
        wallMeshes[i].needsUpdate('position');
      }
    }
  }

  changeColor(color) {
    for (const i in roomMeshes) {
      const mesh = roomMeshes[i];
      mesh.setColorAt(this.index, color);
      mesh.needsUpdate('color');

      const wall = wallMeshes[i];
      wall.setColorAt(this.index, color);
      wall.needsUpdate('color');
    }
  }

  getHeadPosition(index, seconds) {
    return getPosition(
      this.frames[secondsToFrames(seconds)],
      index * PERFORMANCE_ELEMENT_COUNT,
      this.position
    ).applyMatrix4(roomsGroup.matrix);
  }

  gotoTime(seconds, maxLayers) {
    const { frames } = this;
    if (!frames) return;

    const frameNumber = secondsToFrames(seconds);
    if (frames.length <= frameNumber) return;
    let positions = frames[frameNumber];
    if (!positions) return;

    // TODO: figure out why we can't use just 'positions.length / PERFORMANCE_ELEMENT_COUNT' here:
    let count = this.layerCount || (positions.length / PERFORMANCE_ELEMENT_COUNT);
    if (maxLayers !== undefined) {
      count = Math.min(maxLayers, count);
    }

    // In orthographic mode, scale up the meshes:
    const scale = roomMesh === roomMeshes.orthographic ? 1.3 : 1;

    // Check if data is still a string:
    if (positions[0] === '[') {
      positions = frames[frameNumber] = JSON.parse(positions);
    }

    for (let i = 0; i < count; i++) {
      if (!this.hideHead) {
        transformMesh(
          headMesh,
          positions,
          headMesh.geometry.maxInstancedCount++,
          i * PERFORMANCE_ELEMENT_COUNT,
          scale,
          this.costumeColor,
          this.position
        );
      }
      transformMesh(
        handMesh,
        positions,
        handMesh.geometry.maxInstancedCount++,
        i * PERFORMANCE_ELEMENT_COUNT + LIMB_ELEMENT_COUNT,
        scale,
        this.costumeColor,
        this.position
      );
      transformMesh(
        handMesh,
        positions,
        handMesh.geometry.maxInstancedCount++,
        i * PERFORMANCE_ELEMENT_COUNT + LIMB_ELEMENT_COUNT * 2,
        scale,
        this.costumeColor,
        this.position
      );
    }
  }

  destroy() {
    roomsGroup.remove(this.headMesh);
    roomsGroup.remove(this.handMesh);
    if (this.streamer) this.streamer.cancel();
  }
}

Room.clear = () => {
  handMesh.geometry.maxInstancedCount = headMesh.geometry.maxInstancedCount = 0;
};

Room.switchModel = (model) => {
  roomsGroup.remove(wallMesh);
  roomsGroup.remove(roomMesh);
  wallMesh = wallMeshes[model];
  roomMesh = roomMeshes[model];
  roomsGroup.add(wallMesh);
  roomsGroup.add(roomMesh);
};

Room.reset = ({ showAllWalls } = {}) => {
  roomsGroup.matrix.copy(IDENTITY_MATRIX);
  if (roomMesh) roomsGroup.remove(roomMesh);
  if (wallMesh) roomsGroup.remove(wallMesh);
  roomIndex = 0;
  wallMeshes = {
    default: createInstancedMesh({
      count: showAllWalls ? settings.loopCount : 1,
      geometry: props.wall.geometry,
      color: getRoomColor,
      material: props.wall.material,
    }),
    orthographic: createInstancedMesh({
      count: showAllWalls ? settings.loopCount : 1,
      geometry: props.orthographicWall.geometry,
      color: getRoomColor,
      material: props.orthographicWall.material,
    }),
  };
  roomMeshes = {
    default: createInstancedMesh({
      count: settings.loopCount,
      geometry: props.room.geometry,
      color: getRoomColor,
      material: props.room.material,
    }),
    orthographic: createInstancedMesh({
      count: settings.loopCount,
      geometry: props.orthographicRoom.geometry,
      color: getRoomColor,
      material: props.orthographicRoom.material,
    }),
  };
  roomMeshes.default.receiveShadow = true;
  roomMeshes.orthographic.receiveShadow = true;

  wallMesh = wallMeshes.default;
  roomMesh = roomMeshes.default;

  headMesh = createInstancedMesh({
    count: settings.loopCount * 10,
    geometry: props.head.geometry,
  });
  headMesh.geometry.maxInstancedCount = 0;

  handMesh = createInstancedMesh({
    count: settings.loopCount * 10 * 2,
    geometry: props.hand.geometry,
  });
  handMesh.geometry.maxInstancedCount = 0;

  roomsGroup.add(headMesh);
  roomsGroup.add(handMesh);
  roomsGroup.add(wallMesh);
  roomsGroup.add(roomMesh);
  viewer.scene.add(roomsGroup);

  // Move an extra invisible object3d with a texture to the end of scene's children
  // array in order to solve a texture glitch as described in:
  // https://github.com/puckey/you-move-me/issues/129
  viewer.scene.add(debugMesh);
};

Room.rotate180 = () => {
  roomsGroup.matrix.copy(ROTATION_MATRIX);
};
<commit_msg>remove old head and hand mesh on reset<commit_after>import * as THREE from './lib/three';

import props from './props';
import { tempQuaternion, tempVector, createInstancedMesh } from './utils/three';
import viewer from './viewer';
import settings from './settings';
import audio from './audio';
import { getCostumeColor, getRoomColor, recordCostumeColor, recordRoomColor } from './theme/colors';
import streamJSON from './lib/stream-json';

import dummyTextureUrl from './public/dummy.png';

const PROTOCOL = location.protocol;
const PERFORMANCE_ELEMENT_COUNT = 21;
const LIMB_ELEMENT_COUNT = 7;

let roomIndex = 0;
let wallMesh;
let wallMeshes;
let roomMesh;
let roomMeshes;
let headMesh;
let handMesh;

const roomOffset = new THREE.Vector3(0, settings.roomHeight * 0.5, 0);
const roomsGroup = new THREE.Group();
roomsGroup.matrixAutoUpdate = false;

const debugMesh = new THREE.Mesh(
  new THREE.BoxGeometry(0, 0, 0),
  new THREE.MeshBasicMaterial({
    map: new THREE.TextureLoader().load(dummyTextureUrl),
  })
);
debugMesh.frustumCulled = false;

const ROTATION_MATRIX = new THREE.Matrix4().makeRotationAxis(
  new THREE.Vector3(0, 1, 0).normalize(),
  Math.PI
);
const IDENTITY_MATRIX = new THREE.Matrix4();

const secondsToFrames = (seconds) => Math.floor((seconds % (audio.loopDuration * 2)) * 90);

const getPosition = (positions, arrayOffset, offset) => tempVector(
  positions[arrayOffset] * 0.0001,
  positions[arrayOffset + 1] * 0.0001,
  positions[arrayOffset + 2] * 0.0001 - settings.roomOffset
).add(offset);

const getQuaternion = (positions, arrayOffset) => tempQuaternion(
  positions[arrayOffset + 3] * 0.0001,
  positions[arrayOffset + 4] * 0.0001,
  positions[arrayOffset + 5] * 0.0001,
  positions[arrayOffset + 6] * 0.0001
);

const transformMesh = (
  instancedMesh,
  positions,
  index,
  arrayOffset,
  scale,
  color,
  offset,
) => {
  instancedMesh.setPositionAt(
    index,
    getPosition(positions, arrayOffset, offset)
  );
  instancedMesh.setQuaternionAt(
    index,
    getQuaternion(positions, arrayOffset, offset)
  );
  instancedMesh.setScaleAt(
    index,
    tempVector(scale, scale, scale)
  );
  instancedMesh.setColorAt(
    index,
    color,
  );
  instancedMesh.needsUpdate();
};

export default class Room {
  constructor({ url, recording, index }) {
    this.placementIndex = index === undefined
      ? roomIndex
      : index;
    this.index = roomIndex;
    roomIndex += 1;

    this.isRecording = !!recording;
    this.url = url;
    if (recording) {
      this.hideHead = recording.hideHead;
      this.frames = recording.frames;
      this.changeColor(recordRoomColor);
    }

    this.costumeColor = this.isRecording
      ? recordCostumeColor
      : getCostumeColor(this.index);

    this.position = new THREE.Vector3(
      0,
      0,
      settings.roomOffset + (this.placementIndex * (settings.roomDepth + 0.001)),
    );
    this.updatePosition();
    this.costumeColor = this.isRecording
      ? recordCostumeColor
      : getCostumeColor(this.index);
  }

  load(callback) {
    const frames = [];
    this.streamer = streamJSON(
      `${PROTOCOL}//d1nylz9ljdxzkb.cloudfront.net/${this.url}`,
      (error, json) => {
        if (error || !json) {
          if (callback) {
            return callback(error);
          }
        }
        if (!this.frames) {
          // First JSON is meta object:
          const meta = JSON.parse(json);
          this.frames = frames;
          this.hideHead = meta.hideHead;
          this.layerCount = meta.count;
        } else {
          frames.push(json);
        }
      }
    );
  }

  updatePosition() {
    const position = tempVector().add(this.position).add(roomOffset);
    for (const i in roomMeshes) {
      roomMeshes[i].setPositionAt(this.index, position);
      roomMeshes[i].needsUpdate('position');

      if (this.index < wallMeshes[i].numInstances) {
        wallMeshes[i].setPositionAt(this.index, position);
        wallMeshes[i].needsUpdate('position');
      }
    }
  }

  changeColor(color) {
    for (const i in roomMeshes) {
      const mesh = roomMeshes[i];
      mesh.setColorAt(this.index, color);
      mesh.needsUpdate('color');

      const wall = wallMeshes[i];
      wall.setColorAt(this.index, color);
      wall.needsUpdate('color');
    }
  }

  getHeadPosition(index, seconds) {
    return getPosition(
      this.frames[secondsToFrames(seconds)],
      index * PERFORMANCE_ELEMENT_COUNT,
      this.position
    ).applyMatrix4(roomsGroup.matrix);
  }

  gotoTime(seconds, maxLayers) {
    const { frames } = this;
    if (!frames) return;

    const frameNumber = secondsToFrames(seconds);
    if (frames.length <= frameNumber) return;
    let positions = frames[frameNumber];
    if (!positions) return;

    // TODO: figure out why we can't use just 'positions.length / PERFORMANCE_ELEMENT_COUNT' here:
    let count = this.layerCount || (positions.length / PERFORMANCE_ELEMENT_COUNT);
    if (maxLayers !== undefined) {
      count = Math.min(maxLayers, count);
    }

    // In orthographic mode, scale up the meshes:
    const scale = roomMesh === roomMeshes.orthographic ? 1.3 : 1;

    // Check if data is still a string:
    if (positions[0] === '[') {
      positions = frames[frameNumber] = JSON.parse(positions);
    }

    for (let i = 0; i < count; i++) {
      if (!this.hideHead) {
        transformMesh(
          headMesh,
          positions,
          headMesh.geometry.maxInstancedCount++,
          i * PERFORMANCE_ELEMENT_COUNT,
          scale,
          this.costumeColor,
          this.position
        );
      }
      transformMesh(
        handMesh,
        positions,
        handMesh.geometry.maxInstancedCount++,
        i * PERFORMANCE_ELEMENT_COUNT + LIMB_ELEMENT_COUNT,
        scale,
        this.costumeColor,
        this.position
      );
      transformMesh(
        handMesh,
        positions,
        handMesh.geometry.maxInstancedCount++,
        i * PERFORMANCE_ELEMENT_COUNT + LIMB_ELEMENT_COUNT * 2,
        scale,
        this.costumeColor,
        this.position
      );
    }
  }

  destroy() {
    roomsGroup.remove(this.headMesh);
    roomsGroup.remove(this.handMesh);
    if (this.streamer) this.streamer.cancel();
  }
}

Room.clear = () => {
  handMesh.geometry.maxInstancedCount = headMesh.geometry.maxInstancedCount = 0;
};

Room.switchModel = (model) => {
  roomsGroup.remove(wallMesh);
  roomsGroup.remove(roomMesh);
  wallMesh = wallMeshes[model];
  roomMesh = roomMeshes[model];
  roomsGroup.add(wallMesh);
  roomsGroup.add(roomMesh);
};

Room.reset = ({ showAllWalls } = {}) => {
  roomsGroup.matrix.copy(IDENTITY_MATRIX);
  if (roomMesh) roomsGroup.remove(roomMesh);
  if (wallMesh) roomsGroup.remove(wallMesh);
  if (headMesh) roomsGroup.remove(headMesh);
  if (handMesh) roomsGroup.remove(handMesh);

  roomIndex = 0;
  wallMeshes = {
    default: createInstancedMesh({
      count: showAllWalls ? settings.loopCount : 1,
      geometry: props.wall.geometry,
      color: getRoomColor,
      material: props.wall.material,
    }),
    orthographic: createInstancedMesh({
      count: showAllWalls ? settings.loopCount : 1,
      geometry: props.orthographicWall.geometry,
      color: getRoomColor,
      material: props.orthographicWall.material,
    }),
  };
  roomMeshes = {
    default: createInstancedMesh({
      count: settings.loopCount,
      geometry: props.room.geometry,
      color: getRoomColor,
      material: props.room.material,
    }),
    orthographic: createInstancedMesh({
      count: settings.loopCount,
      geometry: props.orthographicRoom.geometry,
      color: getRoomColor,
      material: props.orthographicRoom.material,
    }),
  };
  roomMeshes.default.receiveShadow = true;
  roomMeshes.orthographic.receiveShadow = true;

  wallMesh = wallMeshes.default;
  roomMesh = roomMeshes.default;

  headMesh = createInstancedMesh({
    count: settings.loopCount * 10,
    geometry: props.head.geometry,
  });
  headMesh.geometry.maxInstancedCount = 0;

  handMesh = createInstancedMesh({
    count: settings.loopCount * 10 * 2,
    geometry: props.hand.geometry,
  });
  handMesh.geometry.maxInstancedCount = 0;

  roomsGroup.add(headMesh);
  roomsGroup.add(handMesh);
  roomsGroup.add(wallMesh);
  roomsGroup.add(roomMesh);
  viewer.scene.add(roomsGroup);

  // Move an extra invisible object3d with a texture to the end of scene's children
  // array in order to solve a texture glitch as described in:
  // https://github.com/puckey/you-move-me/issues/129
  viewer.scene.add(debugMesh);
};

Room.rotate180 = () => {
  roomsGroup.matrix.copy(ROTATION_MATRIX);
};
<|endoftext|>


<commit_before>/* @flow */

import React, { PropTypes } from 'react'
import { ScrollView } from 'react-native'
import KeyboardAwareMixin from './KeyboardAwareMixin'

const KeyboardAwareScrollView = React.createClass({
  propTypes: {
    ...ScrollView.propTypes,
    viewIsInsideTabBar: PropTypes.bool,
    resetScrollToCoords: PropTypes.shape({
      x: PropTypes.number,
      y: PropTypes.number,
    }),
  },
  mixins: [KeyboardAwareMixin],

  componentWillMount: function () {
    this.setViewIsInsideTabBar(this.props.viewIsInsideTabBar)
    this.setResetScrollToCoords(this.props.resetScrollToCoords)
  },

  render: function () {
    return (
      <ScrollView
        ref='_rnkasv_keyboardView'
        keyboardDismissMode='interactive'
        contentInset={{bottom: this.state.keyboardSpace}}
        showsVerticalScrollIndicator={true}
        scrollEventThrottle={0}
        {...this.props}
        onScroll={e => {
          this.handleOnScroll(e)
          this.props.onScroll && this.props.onScroll(e)
        }}
        >
        {this.props.children}
      </ScrollView>
    )
  },
})

export default KeyboardAwareScrollView
<commit_msg>Update KeyboardAwareScrollView.js<commit_after>/* @flow */

import React from 'react'
import { ScrollView } from 'react-native'
import PropTypes from 'prop-types'
import KeyboardAwareMixin from './KeyboardAwareMixin'

const KeyboardAwareScrollView = React.createClass({
  propTypes: {
    ...ScrollView.propTypes,
    viewIsInsideTabBar: PropTypes.bool,
    resetScrollToCoords: PropTypes.shape({
      x: PropTypes.number,
      y: PropTypes.number,
    }),
  },
  mixins: [KeyboardAwareMixin],

  componentWillMount: function () {
    this.setViewIsInsideTabBar(this.props.viewIsInsideTabBar)
    this.setResetScrollToCoords(this.props.resetScrollToCoords)
  },

  render: function () {
    return (
      <ScrollView
        ref='_rnkasv_keyboardView'
        keyboardDismissMode='interactive'
        contentInset={{bottom: this.state.keyboardSpace}}
        showsVerticalScrollIndicator={true}
        scrollEventThrottle={0}
        {...this.props}
        onScroll={e => {
          this.handleOnScroll(e)
          this.props.onScroll && this.props.onScroll(e)
        }}
        >
        {this.props.children}
      </ScrollView>
    )
  },
})

export default KeyboardAwareScrollView
<|endoftext|>


<commit_before>/**
 * @author: Wentao Shang
 * Ported to node.js
 * See COPYING for copyright and distribution information.
 */

var KeyManager = function KeyManager() {

this.certificate =

this.privateKey =
};

var globalKeyManager = new KeyManager();

<commit_msg>A little clean up<commit_after><|endoftext|>


<commit_before>import React from 'react-native'
import { Provider } from 'react-redux/native'
import store from './redux/store'
import { loadInitialData } from './redux/store'
import App from './containers/app'
import {Actions} from '../node_modules/react-native-router-flux'
import _ from 'lodash' // TOFIX: hrmmmm... not sure about having this one in...
import * as serverActions from './redux/actions/serverActions'
import * as shareActions from './redux/actions/shareActions'
import ActivityAndroid from '../node_modules/react-native-activity-android'

const {
  DeviceEventEmitter,
  Platform,
  AppStateIOS,
  Alert,
} = React

// This is necessary because React Components will not have mounted (and thus Router) when we receive the
// event from the Android Native Module. So, I set some state on the redux store and when the components
// are ready they can use that to route to the appropriate component (ShareContainer). TOFIX

DeviceEventEmitter.addListener(
  'IntentReceived',
  (data) => {
    store.dispatch(shareActions.androidIntentReceieved(data))
  }
)

class Root extends React.Component {

  constructor(props) {
    super(props)

    this.lastIntentUrlReceived = store.getState().share.lastIntentUrlReceived
    this.lastIntentTitleReceived = store.getState().share.lastIntentTitleReceived

    if (this.lastIntentUrlReceived) {
      setTimeout(() => this.loadInitialDataFromReduxStorage(), 1000) // TOFIX: hacky hack hack...FIXME!
    } else {
      this.loadInitialDataFromReduxStorage()
    }
  }

  componentDidMount() {
    if (!__DEV__ && Platform.OS === 'ios') { //eslint-disable-line no-undef
      const CodePush = require('../node_modules/react-native-code-push')
      CodePush.sync({ updateDialog: true, installMode: CodePush.InstallMode.IMMEDIATE })
    }
    this.subscribeToAppLifecycleEvents()
  }

  componentWillUnmount() {
    this.removeAppLifecycleEventListeners()
  }

  // TOFIX: do this on LOAD action of redux-store, this is logic that should be in the actions not component
  loadInitialDataFromReduxStorage() {
    loadInitialData((newState) => {
      if (_.get(newState, 'entities.currentUser')) {
        if (_.get(newState, 'entities.friends')) {
          store.dispatch(shareActions.resetSelectedFriends(newState.entities.friends))
          store.dispatch(serverActions.resetRequestCount()) //TOFIX: dirty hack; requestCount gets persisted on the entities key
        }
        if (!this.lastIntentUrlReceived) {
          Actions.MainContainer()
          this.refreshData()
        }
      } else {
        // TOFIX: Actions may not have all of the routes by this point (race condition)
        Actions.SignUp()
      }
    })
  }

  subscribeToAppLifecycleEvents() {
    if (Platform.OS === 'ios') {
      AppStateIOS.addEventListener('change', (appState) => {
        if (appState === 'active') {
          this.refreshData()
        }
      })
    } else {
      ActivityAndroid.addEventListener('activityResume', () => {
        this.refreshData()
      })
    }
  }

  removeAppLifecycleEventListeners() {
    if (Platform.OS === 'ios') {
      AppStateIOS.removeEventListener('change', this._handleAppStateChange)
    } else {
      // TOFIX: don't cleanup for android right now; will be removing react-native-activity-android soon once I upgrade RN
    }
  }

  refreshData() {
    // We need our friends list before links received or we'll have "unknown sender" in the inbox cells
    store.dispatch(serverActions.fetchFriends())
    .then(() => {
      store.dispatch(serverActions.fetchLinksReceived())
      store.dispatch(serverActions.fetchLinksSent())
    })
  }

  render () {
    return (
      <Provider store={store}>
        {() => <App lastIntentUrlReceived={this.lastIntentUrlReceived} lastIntentTitleReceived={this.lastIntentTitleReceived} />}
      </Provider>
    )
  }
}

export default Root
<commit_msg>check codepush on app foreground<commit_after>import React from 'react-native'
import { Provider } from 'react-redux/native'
import store from './redux/store'
import { loadInitialData } from './redux/store'
import App from './containers/app'
import {Actions} from '../node_modules/react-native-router-flux'
import _ from 'lodash' // TOFIX: hrmmmm... not sure about having this one in...
import * as serverActions from './redux/actions/serverActions'
import * as shareActions from './redux/actions/shareActions'
import ActivityAndroid from '../node_modules/react-native-activity-android'

const {
  DeviceEventEmitter,
  Platform,
  AppStateIOS,
  Alert,
} = React

// This is necessary because React Components will not have mounted (and thus Router) when we receive the
// event from the Android Native Module. So, I set some state on the redux store and when the components
// are ready they can use that to route to the appropriate component (ShareContainer). TOFIX

DeviceEventEmitter.addListener(
  'IntentReceived',
  (data) => {
    store.dispatch(shareActions.androidIntentReceieved(data))
  }
)

let CodePush
if (!__DEV__ && Platform.OS === 'ios') { //eslint-disable-line no-undef
  CodePush = require('../node_modules/react-native-code-push')
}

class Root extends React.Component {

  constructor(props) {
    super(props)

    this.lastIntentUrlReceived = store.getState().share.lastIntentUrlReceived
    this.lastIntentTitleReceived = store.getState().share.lastIntentTitleReceived

    if (this.lastIntentUrlReceived) {
      setTimeout(() => this.loadInitialDataFromReduxStorage(), 1000) // TOFIX: hacky hack hack...FIXME!
    } else {
      this.loadInitialDataFromReduxStorage()
    }
  }

  componentDidMount() {
    if (!__DEV__ && Platform.OS === 'ios') { //eslint-disable-line no-undef
      CodePush.sync({ updateDialog: true, installMode: CodePush.InstallMode.IMMEDIATE })
    }
    this.subscribeToAppLifecycleEvents()
  }

  componentWillUnmount() {
    this.removeAppLifecycleEventListeners()
  }

  // TOFIX: do this on LOAD action of redux-store, this is logic that should be in the actions not component
  loadInitialDataFromReduxStorage() {
    loadInitialData((newState) => {
      if (_.get(newState, 'entities.currentUser')) {
        if (_.get(newState, 'entities.friends')) {
          store.dispatch(shareActions.resetSelectedFriends(newState.entities.friends))
          store.dispatch(serverActions.resetRequestCount()) //TOFIX: dirty hack; requestCount gets persisted on the entities key
        }
        if (!this.lastIntentUrlReceived) {
          Actions.MainContainer()
          this.refreshData()
        }
      } else {
        // TOFIX: Actions may not have all of the routes by this point (race condition)
        Actions.SignUp()
      }
    })
  }

  subscribeToAppLifecycleEvents() {
    if (Platform.OS === 'ios') {
      AppStateIOS.addEventListener('change', (appState) => {
        if (appState === 'active') {
          this.refreshData()
          if (!__DEV__) { //eslint-disable-line no-undef
            CodePush.sync({ updateDialog: true, installMode: CodePush.InstallMode.IMMEDIATE })
          }
        }
      })
    } else {
      ActivityAndroid.addEventListener('activityResume', () => {
        this.refreshData()
      })
    }
  }

  removeAppLifecycleEventListeners() {
    if (Platform.OS === 'ios') {
      AppStateIOS.removeEventListener('change', this._handleAppStateChange)
    } else {
      // TOFIX: don't cleanup for android right now; will be removing react-native-activity-android soon once I upgrade RN
    }
  }

  refreshData() {
    // We need our friends list before links received or we'll have "unknown sender" in the inbox cells
    store.dispatch(serverActions.fetchFriends())
    .then(() => {
      store.dispatch(serverActions.fetchLinksReceived())
      store.dispatch(serverActions.fetchLinksSent())
    })
  }

  render () {
    return (
      <Provider store={store}>
        {() => <App lastIntentUrlReceived={this.lastIntentUrlReceived} lastIntentTitleReceived={this.lastIntentTitleReceived} />}
      </Provider>
    )
  }
}

export default Root
<|endoftext|>


<commit_before>// Service for exporting as JSON
panelApp.factory('filesystem', function(chromeExtension) {

  // taken from:
  // http://html5-demos.appspot.com/static/html5storage/index.html#slide59

  // TODO: error handlers?

  return {
    exportJSON: function (name, data) {
      //TODO: file size/limits? 1024*1024
       window.webkitRequestFileSystem(window.TEMPORARY, 1024*1024, function (fs) {
        fs.root.getFile(name + '.json', {create: true}, function (fileEntry) {
          fileEntry.createWriter(function(fileWriter) {

            var builder = new WebKitBlobBuilder();
            builder.append(JSON.stringify(data));
            var blob = builder.getBlob('text/plain');

            fileWriter.onwriteend = function () {
              // navigate to file, will download
              //location.href = fileEntry.toURL();
              window.open(fileEntry.toURL());
            };

            fileWriter.write(blob);
          }, function() {});
        }, function() {});
      }, function() {});
    }
  };
});
<commit_msg>fixed filesystem service to use Blob instead of BlobBuilder<commit_after>// Service for exporting as JSON
panelApp.factory('filesystem', function(chromeExtension) {

  // taken from:
  // http://html5-demos.appspot.com/static/html5storage/index.html#slide59

  // TODO: error handlers?

  return {
    exportJSON: function (name, data) {
      //TODO: file size/limits? 1024*1024
       window.webkitRequestFileSystem(window.TEMPORARY, 1024*1024, function (fs) {
        fs.root.getFile(name + '.json', {create: true}, function (fileEntry) {
          fileEntry.createWriter(function(fileWriter) {

            var blob = new Blob([ JSON.stringify(data) ], { type: 'text/plain' });

            fileWriter.onwriteend = function () {
              // navigate to file, will download
              //location.href = fileEntry.toURL();
              window.open(fileEntry.toURL());
            };

            fileWriter.write(blob);
          }, function() {});
        }, function() {});
      }, function() {});
    }
  };
});
<|endoftext|>