<commit_before>/*
 * Quote-Context JS Library
 * https://github.com/neotext/neotext-quote-context/
 *
 * Copyright 2016, Tim Langeman
 * http://www.openpolitics.com/tim
 *
 * Licensed under the MIT license:
 * http://www.opensource.org/licenses/MIT
 *
 * This is a jQuery function that locates all "blockquote" and "q" tags
 * within an html document and calls the neotext.net web service to
 * locate contextual info about the requested quote.
 *
 * The neotext web service returns a json dictionary and this script
 * injects the returned contextual data into hidden html elements to be
 * displayed when the user hovers over or clicks on the cited quote.
 *
 * Dependencies:
 *  - jQuery: https://jquery.com/
 *  - Sha1: https://github.com/chrisveness/crypto/blob/master/sha1.js
*/

popup_library = "jQuery";
hidden_container = "neotext_container";	// div in footer than holds injected json data, requires css class to hide
jQuery.curCSS = 'jQuery.css';
version_num = "0.02";
current_page_url = window.location.href;

jQuery.fn.quoteContext = function() {
	// Add "before" and "after" sections to quote excerpts
	// Designed to work for "blockquote" and "q" tags

	//Setup hidden div to store all quote metadata

	jQuery(this).each(function(){
		// Loop through all the submitted tags (blockquote or q tags) and see if any have a cite attribute
		if( jQuery(this).attr("cite") ){
			var blockcite = jQuery(this);
			var cited_url = blockcite.attr("cite");
			var citing_url = current_page_url;  //'http://localhost/thesis.html';
			var citing_quote = blockcite.text();

			// If they have a cite tag, check to see if its hash is already saved
			if (cited_url.length > 3){
				var tag_type = jQuery(this)[0].tagName.toLowerCase();
				var url_quote_text = trim_encode(citing_quote) + '|' + trim_encode(citing_url) + '|' + trim_encode(cited_url);
				var quote_hash = Sha1.hash(url_quote_text);
				var shard = quote_hash.substring(0,2);
				var read_base = 'http://read.neotext.net/quote/';
				var read_url = read_base.concat("sha1/", version_num, "/", shard, "/", quote_hash, ".json");
				var json = null;

				//See if a json summary of this quote was already created and uploaded to the content delivery network
			    jQuery.ajax({
			        type: "GET",
					url: read_url,
			        dataType: "json",
			        success: function(json) {
			            add_quote_to_dom(tag_type, json );
						console.log("Neotext Found: " + read_url);
						console.log("       Quote: " + citing_quote);
			        },
			        error: function() {
						submit_quote_to_neotext(citing_quote, citing_url, cited_url, quote_hash)
						console.log("Neotext Missed: " + read_url);
						console.log("       Quote: " + citing_quote);
			        }
			    });

				function add_quote_to_dom(tag_type, json ) {
					if ( tag_type == "q"){
						var q_id = "hidden_" + json['sha1'];

						//Add content to a hidden div, so that the popup can later grab it
						jQuery("#" + hidden_container).append(
							"
.. " +
								json['cited_context_before'] + " " + " " + json['citing_quote'] + " " +
								json['cited_context_after'] + "..
" +
								"
Read more | " +
								"Close

");

						//Style quote as a link that calls the popup expander:  return this.onclick()
						blockcite.wrapInner("");
					}
					else if ( tag_type == "blockquote"){
						//Fill 'before' and 'after' divs and then quickly hide them
						blockcite.before("
 \

.. " + json["cited_context_before"] + "

");

						blockcite.after("
 \

" + json["cited_context_after"] + " ..

");

						var context_before = jQuery("#quote_before_" + json['sha1']);
						var context_after = jQuery("#quote_after_" + json['sha1']);

						context_before.hide();
						context_after.hide();

						//Display arrows if content is found
						if( json['cited_context_before'].length > 0){
						context_before.before("
 \
							▲
");
						}
						if( json['cited_context_after'].length > 0){
						context_after.after("
 \
							▼
");
						}
					}
				}

			/* Call Neotext.net webservice to retreive quote from origin
			*  Calculate before and after offsets
			*  Save results to database and output json */
			function submit_quote_to_neotext(citing_quote, citing_url, cited_url, quote_hash) {
			    jQuery.ajax({
			        type: "POST",
			        url: 'http://write.neotext.webfactional.com/quote/',
					data: {'citing_quote': citing_quote, 'citing_url': citing_url, 'cited_url': cited_url},
			        dataType: "json",
			        success: function(json) {
			            add_quote_to_dom( json );
						return json;
			        },
			        error: function() {
						console.log("Neotext: unable to find quote on original site: \n'" + citing_quote + "'\n" + cited_url + "\n" + trim_regex(quote_hash));
			        }
			    });
			  }
			} // if url.length is not blank
		}	// if "this" has a "cite" attribute
	});	   //   jQuery(this).each(function() { : blockquote, or q tag

};

function toggle_quote(section, id){
	jQuery("#" + id).fadeToggle();
}

function expand_popup(tag, hidden_popup_id){
  if (popup_library == "highslide"){
	return hs.htmlExpand(tag, {maincontentId: hidden_popup_id });
  }
  else {
	jQuery.curCSS = jQuery.css;

	// Setup Initial Dialog box
	jQuery("#" + hidden_popup_id).dialog({
		autoOpen: false,
		closeOnEscape: true,
		closeText: "hide",
		draggableType: true,
		resizable: true,
		width: 400,
		modal: false,
		title: 'powered by neotext.net',
		hide: { effect: "size", duration: 400 },
		show: { effect: "scale", duration: 400 },
	  });

	// Add centering and other settings
	jQuery("#" + hidden_popup_id).dialog("option", "position", { at: "center center", of: tag}
		).dialog("option", "hide", { effect: "size", duration: 400 }
		).dialog("option", "show", { effect: "scale", duration: 400 }
		).dialog( {"title" : "powered by neotext.net"}
		).dialog("open"
		).blur(
		//console.log("logging Neotext dialog blur ..")
	);

	// Close popup when you click outside of it
	jQuery(document).mouseup(function(e) {
	  var popupbox = jQuery(".ui-widget-overlay");
	  if (popupbox.has(e.target).length === 0){
	  	 //$("#" + hidden_popup_id).dialog("close");
	  }
	});

	return false; // Don't follow link

  }
}

function close_popup(hidden_popup_id){
	// assumes jQuery library
	jQuery(hidden_popup_id).dialog("close");
}

//Source: http://stackoverflow.com/questions/10032024/how-to-remove-leading-and-trailing-white-spaces-from-a-given-html-string
// Credit:  KhanSharp:  (used for backward compatibility.
//          trim() introduced in javascript 1.8.1)
function trim_regex(str){
  return str.replace(/^[ ]+|[ ]+$/g,'')
}

function trim_encode(str){
	 //trimmed_str = trim_regex(str);
     return str.trim();
}
<commit_msg>Remove anchor from URL<commit_after>/*
 * Quote-Context JS Library
 * https://github.com/neotext/neotext-quote-context/
 *
 * Copyright 2016, Tim Langeman
 * http://www.openpolitics.com/tim
 *
 * Licensed under the MIT license:
 * http://www.opensource.org/licenses/MIT
 *
 * This is a jQuery function that locates all "blockquote" and "q" tags
 * within an html document and calls the neotext.net web service to
 * locate contextual info about the requested quote.
 *
 * The neotext web service returns a json dictionary and this script
 * injects the returned contextual data into hidden html elements to be
 * displayed when the user hovers over or clicks on the cited quote.
 *
 * Dependencies:
 *  - jQuery: https://jquery.com/
 *  - Sha1: https://github.com/chrisveness/crypto/blob/master/sha1.js
*/

popup_library = "jQuery";
hidden_container = "neotext_container";	// div in footer than holds injected json data, requires css class to hide
jQuery.curCSS = 'jQuery.css';
version_num = "0.02";

// Remove anchor from URL
current_page_url = window.location.href.split('#')[0]
//current_page_url = window.location.href;

jQuery.fn.quoteContext = function() {
	// Add "before" and "after" sections to quote excerpts
	// Designed to work for "blockquote" and "q" tags

	//Setup hidden div to store all quote metadata

	jQuery(this).each(function(){
		// Loop through all the submitted tags (blockquote or q tags) and see if any have a cite attribute
		if( jQuery(this).attr("cite") ){
			var blockcite = jQuery(this);
			var cited_url = blockcite.attr("cite");
			var citing_url = current_page_url;  //'http://localhost/thesis.html';
			var citing_quote = blockcite.text();

			// If they have a cite tag, check to see if its hash is already saved
			if (cited_url.length > 3){
				var tag_type = jQuery(this)[0].tagName.toLowerCase();
				var url_quote_text = trim_encode(citing_quote) + '|' + trim_encode(citing_url) + '|' + trim_encode(cited_url);
				var quote_hash = Sha1.hash(url_quote_text);
				var shard = quote_hash.substring(0,2);
				var read_base = 'http://read.neotext.net/quote/';
				var read_url = read_base.concat("sha1/", version_num, "/", shard, "/", quote_hash, ".json");
				var json = null;

				//See if a json summary of this quote was already created and uploaded to the content delivery network
			    jQuery.ajax({
			        type: "GET",
					url: read_url,
			        dataType: "json",
			        success: function(json) {
			            add_quote_to_dom(tag_type, json );
						console.log("Neotext Found: " + read_url);
						console.log("       Quote: " + citing_quote);
			        },
			        error: function() {
						submit_quote_to_neotext(citing_quote, citing_url, cited_url, quote_hash)
						console.log("Neotext Missed: " + read_url);
						console.log("       Quote: " + citing_quote);
			        }
			    });

				function add_quote_to_dom(tag_type, json ) {
					if ( tag_type == "q"){
						var q_id = "hidden_" + json['sha1'];

						//Add content to a hidden div, so that the popup can later grab it
						jQuery("#" + hidden_container).append(
							"
.. " +
								json['cited_context_before'] + " " + " " + json['citing_quote'] + " " +
								json['cited_context_after'] + "..
" +
								"
Read more | " +
								"Close

");

						//Style quote as a link that calls the popup expander:  return this.onclick()
						blockcite.wrapInner("");
					}
					else if ( tag_type == "blockquote"){
						//Fill 'before' and 'after' divs and then quickly hide them
						blockcite.before("
 \

.. " + json["cited_context_before"] + "

");

						blockcite.after("
 \

" + json["cited_context_after"] + " ..

");

						var context_before = jQuery("#quote_before_" + json['sha1']);
						var context_after = jQuery("#quote_after_" + json['sha1']);

						context_before.hide();
						context_after.hide();

						//Display arrows if content is found
						if( json['cited_context_before'].length > 0){
						context_before.before("
 \
							▲
");
						}
						if( json['cited_context_after'].length > 0){
						context_after.after("
 \
							▼
");
						}
					}
				}

			/* Call Neotext.net webservice to retreive quote from origin
			*  Calculate before and after offsets
			*  Save results to database and output json */
			function submit_quote_to_neotext(citing_quote, citing_url, cited_url, quote_hash) {
			    jQuery.ajax({
			        type: "POST",
			        url: 'http://write.neotext.webfactional.com/quote/',
					data: {'citing_quote': citing_quote, 'citing_url': citing_url, 'cited_url': cited_url},
			        dataType: "json",
			        success: function(json) {
			            add_quote_to_dom( json );
						return json;
			        },
			        error: function() {
						console.log("Neotext: unable to find quote on original site: \n'" + citing_quote + "'\n" + cited_url + "\n" + trim_regex(quote_hash));
			        }
			    });
			  }
			} // if url.length is not blank
		}	// if "this" has a "cite" attribute
	});	   //   jQuery(this).each(function() { : blockquote, or q tag

};

function toggle_quote(section, id){
	jQuery("#" + id).fadeToggle();
}

function expand_popup(tag, hidden_popup_id){
  if (popup_library == "highslide"){
	return hs.htmlExpand(tag, {maincontentId: hidden_popup_id });
  }
  else {
	jQuery.curCSS = jQuery.css;

	// Setup Initial Dialog box
	jQuery("#" + hidden_popup_id).dialog({
		autoOpen: false,
		closeOnEscape: true,
		closeText: "hide",
		draggableType: true,
		resizable: true,
		width: 400,
		modal: false,
		title: 'powered by neotext.net',
		hide: { effect: "size", duration: 400 },
		show: { effect: "scale", duration: 400 },
	  });

	// Add centering and other settings
	jQuery("#" + hidden_popup_id).dialog("option", "position", { at: "center center", of: tag}
		).dialog("option", "hide", { effect: "size", duration: 400 }
		).dialog("option", "show", { effect: "scale", duration: 400 }
		).dialog( {"title" : "powered by neotext.net"}
		).dialog("open"
		).blur(
		//console.log("logging Neotext dialog blur ..")
	);

	// Close popup when you click outside of it
	jQuery(document).mouseup(function(e) {
	  var popupbox = jQuery(".ui-widget-overlay");
	  if (popupbox.has(e.target).length === 0){
	  	 //$("#" + hidden_popup_id).dialog("close");
	  }
	});

	return false; // Don't follow link

  }
}

function close_popup(hidden_popup_id){
	// assumes jQuery library
	jQuery(hidden_popup_id).dialog("close");
}

//Source: http://stackoverflow.com/questions/10032024/how-to-remove-leading-and-trailing-white-spaces-from-a-given-html-string
// Credit:  KhanSharp:  (used for backward compatibility.
//          trim() introduced in javascript 1.8.1)
function trim_regex(str){
  return str.replace(/^[ ]+|[ ]+$/g,'')
}

function trim_encode(str){
	 //trimmed_str = trim_regex(str);
     return str.trim();
}
<|endoftext|>


<commit_before>// Copyright 2002-2012, University of Colorado

/**
 * Cubic Bezier segment.
 *
 * See http://www.cis.usouthal.edu/~hain/general/Publications/Bezier/BezierFlattening.pdf for info
 *
 * @author Jonathan Olson <olsonsjc@gmail.com>
 */

define( function( require ) {
  "use strict";

  var assert = require( 'ASSERT/assert' )( 'scenery' );

  var scenery = require( 'SCENERY/scenery' );

  var Bounds2 = require( 'DOT/Bounds2' );
  var Vector2 = require( 'DOT/Vector2' );

  var Segment = require( 'SCENERY/shapes/segments/Segment' );
  var Piece = require( 'SCENERY/shapes/pieces/Piece' );
  require( 'SCENERY/shapes/segments/Quadratic' );

  Segment.Cubic = function( start, control1, control2, end, skipComputations ) {
    this.start = start;
    this.control1 = control1;
    this.control2 = control2;
    this.end = end;

    // allows us to skip unnecessary computation in the subdivision steps
    if ( skipComputations ) {
      return;
    }

    this.startTangent = this.tangentAt( 0 ).normalized();
    this.endTangent = this.tangentAt( 1 ).normalized();

    if ( start.equals( end, 0 ) && start.equals( control1, 0 ) && start.equals( control2, 0 ) ) {
      this.invalid = true;
      return;
    }

    // from http://www.cis.usouthal.edu/~hain/general/Publications/Bezier/BezierFlattening.pdf
    this.r = control1.minus( start ).normalized();
    this.s = this.r.perpendicular();

    var a = start.times( -1 ).plus( control1.times( 3 ) ).plus( control2.times( -3 ) ).plus( end );
    var b = start.times( 3 ).plus( control1.times( -6 ) ).plus( control2.times( 3 ) );
    var c = start.times( -3 ).plus( control1.times( 3 ) );
    var d = start;

    var aPerp = a.perpendicular();
    var bPerp = b.perpendicular();
    var aPerpDotB = aPerp.dot( b );

    this.tCusp = -0.5 * ( aPerp.dot( c ) / aPerpDotB );
    this.tDeterminant = this.tCusp * this.tCusp - ( 1 / 3 ) * ( bPerp.dot( c ) / aPerpDotB );
    if ( this.tDeterminant >= 0 ) {
      var sqrtDet = Math.sqrt( this.tDeterminant );
      this.tInflection1 = this.tCusp - sqrtDet;
      this.tInflection2 = this.tCusp + sqrtDet;
    }

    if ( this.hasCusp() ) {
      // if there is a cusp, we'll split at the cusp into two quadratic bezier curves.
      // see http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.94.8088&rep=rep1&type=pdf (Singularities of rational Bezier curves - J Monterde, 2001)
      var subdividedAtCusp = this.subdivided( this.tCusp, true );
      this.startQuadratic = new Segment.Quadratic( subdividedAtCusp[0].start, subdividedAtCusp[0].control1, subdividedAtCusp[0].end, false );
      this.endQuadratic = new Segment.Quadratic( subdividedAtCusp[1].start, subdividedAtCusp[1].control2, subdividedAtCusp[1].end, false );
    }

    this.bounds = Bounds2.NOTHING;
    this.bounds = this.bounds.withPoint( this.start );
    this.bounds = this.bounds.withPoint( this.end );

    /*---------------------------------------------------------------------------*
    * Bounds
    *----------------------------------------------------------------------------*/

    // finds what t values the cubic extrema are at (if any).
    function extremaT( v0, v1, v2, v3 ) {
      var det = v1 * v1 - v1 * v2 + v2 * v2 - v1 * v3 - v2 * v0 + v3 * v0;
      if ( det < 0 ) {
        return [];
      }
      var sqrt = Math.sqrt( det );
      var a = 2 * v1 * v1 - v2 - v0;
      var b = 3 * v1 - 3 * v2 + v3 - v0;
      return [
        ( a - sqrt ) / b,
        ( a + sqrt ) / b
      ];
    }

    var cubic = this;
    _.each( extremaT( this.start.x, this.control1.x, this.control2.x, this.end.x ), function( t ) {
      cubic.bounds = cubic.bounds.withPoint( cubic.positionAt( t ) );
    } );
    _.each( extremaT( this.start.y, this.control1.y, this.control2.y, this.end.y ), function( t ) {
      cubic.bounds = cubic.bounds.withPoint( cubic.positionAt( t ) );
    } );

    if ( this.hasCusp() ) {
      this.bounds = this.bounds.withPoint( this.positionAt( this.tCusp ) );
    }
  };
  Segment.Cubic.prototype = {
    hasCusp: function() {
      var epsilon = 0.000001; // TODO: make this available to change?
      return this.tangentAt( this.tCusp ).magnitude() < epsilon && this.tCusp >= 0 && this.tCusp <= 1;
    },

    // position: (1 - t)^3*start + 3*(1 - t)^2*t*control1 + 3*(1 - t) t^2*control2 + t^3*end
    positionAt: function( t ) {
      var mt = 1 - t;
      return this.start.times( mt * mt * mt ).plus( this.control1.times( 3 * mt * mt * t ) ).plus( this.control2.times( 3 * mt * t * t ) ).plus( this.end.times( t * t * t ) );
    },

    // derivative: -3 p0 (1 - t)^2 + 3 p1 (1 - t)^2 - 6 p1 (1 - t) t + 6 p2 (1 - t) t - 3 p2 t^2 + 3 p3 t^2
    tangentAt: function( t ) {
      var mt = 1 - t;
      return this.start.times( -3 * mt * mt ).plus( this.control1.times( 3 * mt * mt - 6 * mt * t ) ).plus( this.control2.times( 6 * mt * t - 3 * t * t ) ).plus( this.end.times( 3 * t * t ) );
    },

    toRS: function( point ) {
      var firstVector = point.minus( this.start );
      return new Vector2( firstVector.dot( this.r ), firstVector.dot( this.s ) );
    },

    subdivided: function( t, skipComputations ) {
      // de Casteljau method
      // TODO: add a 'bisect' or 'between' method for vectors?
      var left = this.start.blend( this.control1, t );
      var right = this.control2.blend( this.end, t );
      var middle = this.control1.blend( this.control2, t );
      var leftMid = left.blend( middle, t );
      var rightMid = middle.blend( right, t );
      var mid = leftMid.blend( rightMid, t );
      return [
        new Segment.Cubic( this.start, left, leftMid, mid, skipComputations ),
        new Segment.Cubic( mid, rightMid, right, this.end, skipComputations )
      ];
    },

    toPieces: function() {
      return [ new Piece.CubicCurveTo( this.control1, this.control2, this.end ) ];
    },

    getSVGPathFragment: function() {
      return 'C ' + this.control1.x + ' ' + this.control1.y + ' ' + this.control2.x + ' ' + this.control2.y + ' ' + this.end.x + ' ' + this.end.y;
    },

    strokeLeft: function( lineWidth ) {
      throw new Error( 'Segment.Cubic.strokeLeft unimplemented' ); // TODO: implement
      //return this.offsetTo( -lineWidth / 2, false );
    },

    strokeRight: function( lineWidth ) {
      throw new Error( 'Segment.Cubic.strokeRight unimplemented' ); // TODO: implement
      //return this.offsetTo( lineWidth / 2, true );
    },

    intersectsBounds: function( bounds ) {
      throw new Error( 'Segment.Cubic.intersectsBounds unimplemented' ); // TODO: implement
    },

    // returns the resultant winding number of this ray intersecting this segment.
    windingIntersection: function( ray ) {
      throw new Error( 'Segment.Cubic.windingIntersection unimplemented' ); // TODO: implement
    }
  };

  return Segment.Cubic;
} );
<commit_msg>Adding offset curves for Cubic<commit_after>// Copyright 2002-2012, University of Colorado

/**
 * Cubic Bezier segment.
 *
 * See http://www.cis.usouthal.edu/~hain/general/Publications/Bezier/BezierFlattening.pdf for info
 *
 * @author Jonathan Olson <olsonsjc@gmail.com>
 */

define( function( require ) {
  "use strict";

  var assert = require( 'ASSERT/assert' )( 'scenery' );

  var scenery = require( 'SCENERY/scenery' );

  var Bounds2 = require( 'DOT/Bounds2' );
  var Vector2 = require( 'DOT/Vector2' );

  var Segment = require( 'SCENERY/shapes/segments/Segment' );
  var Piece = require( 'SCENERY/shapes/pieces/Piece' );
  require( 'SCENERY/shapes/segments/Quadratic' );

  Segment.Cubic = function( start, control1, control2, end, skipComputations ) {
    this.start = start;
    this.control1 = control1;
    this.control2 = control2;
    this.end = end;

    // allows us to skip unnecessary computation in the subdivision steps
    if ( skipComputations ) {
      return;
    }

    this.startTangent = this.tangentAt( 0 ).normalized();
    this.endTangent = this.tangentAt( 1 ).normalized();

    if ( start.equals( end, 0 ) && start.equals( control1, 0 ) && start.equals( control2, 0 ) ) {
      this.invalid = true;
      return;
    }

    // from http://www.cis.usouthal.edu/~hain/general/Publications/Bezier/BezierFlattening.pdf
    this.r = control1.minus( start ).normalized();
    this.s = this.r.perpendicular();

    var a = start.times( -1 ).plus( control1.times( 3 ) ).plus( control2.times( -3 ) ).plus( end );
    var b = start.times( 3 ).plus( control1.times( -6 ) ).plus( control2.times( 3 ) );
    var c = start.times( -3 ).plus( control1.times( 3 ) );
    var d = start;

    var aPerp = a.perpendicular();
    var bPerp = b.perpendicular();
    var aPerpDotB = aPerp.dot( b );

    this.tCusp = -0.5 * ( aPerp.dot( c ) / aPerpDotB );
    this.tDeterminant = this.tCusp * this.tCusp - ( 1 / 3 ) * ( bPerp.dot( c ) / aPerpDotB );
    if ( this.tDeterminant >= 0 ) {
      var sqrtDet = Math.sqrt( this.tDeterminant );
      this.tInflection1 = this.tCusp - sqrtDet;
      this.tInflection2 = this.tCusp + sqrtDet;
    }

    if ( this.hasCusp() ) {
      // if there is a cusp, we'll split at the cusp into two quadratic bezier curves.
      // see http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.94.8088&rep=rep1&type=pdf (Singularities of rational Bezier curves - J Monterde, 2001)
      var subdividedAtCusp = this.subdivided( this.tCusp, true );
      this.startQuadratic = new Segment.Quadratic( subdividedAtCusp[0].start, subdividedAtCusp[0].control1, subdividedAtCusp[0].end, false );
      this.endQuadratic = new Segment.Quadratic( subdividedAtCusp[1].start, subdividedAtCusp[1].control2, subdividedAtCusp[1].end, false );
    }

    this.bounds = Bounds2.NOTHING;
    this.bounds = this.bounds.withPoint( this.start );
    this.bounds = this.bounds.withPoint( this.end );

    /*---------------------------------------------------------------------------*
    * Bounds
    *----------------------------------------------------------------------------*/

    // finds what t values the cubic extrema are at (if any).
    function extremaT( v0, v1, v2, v3 ) {
      var det = v1 * v1 - v1 * v2 + v2 * v2 - v1 * v3 - v2 * v0 + v3 * v0;
      if ( det < 0 ) {
        return [];
      }
      var sqrt = Math.sqrt( det );
      var a = 2 * v1 * v1 - v2 - v0;
      var b = 3 * v1 - 3 * v2 + v3 - v0;
      return [
        ( a - sqrt ) / b,
        ( a + sqrt ) / b
      ];
    }

    var cubic = this;
    _.each( extremaT( this.start.x, this.control1.x, this.control2.x, this.end.x ), function( t ) {
      cubic.bounds = cubic.bounds.withPoint( cubic.positionAt( t ) );
    } );
    _.each( extremaT( this.start.y, this.control1.y, this.control2.y, this.end.y ), function( t ) {
      cubic.bounds = cubic.bounds.withPoint( cubic.positionAt( t ) );
    } );

    if ( this.hasCusp() ) {
      this.bounds = this.bounds.withPoint( this.positionAt( this.tCusp ) );
    }
  };
  Segment.Cubic.prototype = {
    hasCusp: function() {
      var epsilon = 0.000001; // TODO: make this available to change?
      return this.tangentAt( this.tCusp ).magnitude() < epsilon && this.tCusp >= 0 && this.tCusp <= 1;
    },

    // position: (1 - t)^3*start + 3*(1 - t)^2*t*control1 + 3*(1 - t) t^2*control2 + t^3*end
    positionAt: function( t ) {
      var mt = 1 - t;
      return this.start.times( mt * mt * mt ).plus( this.control1.times( 3 * mt * mt * t ) ).plus( this.control2.times( 3 * mt * t * t ) ).plus( this.end.times( t * t * t ) );
    },

    // derivative: -3 p0 (1 - t)^2 + 3 p1 (1 - t)^2 - 6 p1 (1 - t) t + 6 p2 (1 - t) t - 3 p2 t^2 + 3 p3 t^2
    tangentAt: function( t ) {
      var mt = 1 - t;
      return this.start.times( -3 * mt * mt ).plus( this.control1.times( 3 * mt * mt - 6 * mt * t ) ).plus( this.control2.times( 6 * mt * t - 3 * t * t ) ).plus( this.end.times( 3 * t * t ) );
    },

    toRS: function( point ) {
      var firstVector = point.minus( this.start );
      return new Vector2( firstVector.dot( this.r ), firstVector.dot( this.s ) );
    },

    subdivided: function( t, skipComputations ) {
      // de Casteljau method
      // TODO: add a 'bisect' or 'between' method for vectors?
      var left = this.start.blend( this.control1, t );
      var right = this.control2.blend( this.end, t );
      var middle = this.control1.blend( this.control2, t );
      var leftMid = left.blend( middle, t );
      var rightMid = middle.blend( right, t );
      var mid = leftMid.blend( rightMid, t );
      return [
        new Segment.Cubic( this.start, left, leftMid, mid, skipComputations ),
        new Segment.Cubic( mid, rightMid, right, this.end, skipComputations )
      ];
    },

    offsetTo: function( r, reverse ) {
      // TODO: implement more accurate method at http://www.antigrain.com/research/adaptive_bezier/index.html
      // TODO: or more recently (and relevantly): http://www.cis.usouthal.edu/~hain/general/Publications/Bezier/BezierFlattening.pdf

      // how many segments to create (possibly make this more adaptive?)
      var quantity = 32;

      var result = [];
      for ( var i = 1; i < quantity; i++ ) {
        var t = i - ( quantity - 1 );
        if ( reverse ) {
          t = 1 - t;
        }

        var point = this.positionAt( t ).plus( this.tangentAt( t ).perpendicular().normalized().times( r ) );
        result.push( new Piece.LineTo( point ) );
      }

      return result;
    },

    toPieces: function() {
      return [ new Piece.CubicCurveTo( this.control1, this.control2, this.end ) ];
    },

    getSVGPathFragment: function() {
      return 'C ' + this.control1.x + ' ' + this.control1.y + ' ' + this.control2.x + ' ' + this.control2.y + ' ' + this.end.x + ' ' + this.end.y;
    },

    strokeLeft: function( lineWidth ) {
      return this.offsetTo( -lineWidth / 2, false );
    },

    strokeRight: function( lineWidth ) {
      return this.offsetTo( lineWidth / 2, true );
    },

    intersectsBounds: function( bounds ) {
      throw new Error( 'Segment.Cubic.intersectsBounds unimplemented' ); // TODO: implement
    },

    // returns the resultant winding number of this ray intersecting this segment.
    windingIntersection: function( ray ) {
      throw new Error( 'Segment.Cubic.windingIntersection unimplemented' ); // TODO: implement
    }
  };

  return Segment.Cubic;
} );
<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * An asynchronous (async.queue) queue with a concurrency of one (meaning that only one task is executed at a time)
 * that is used to serialize submissions of strings for a translation.
 *
 * @author Aaron Davis
 * @author John Blanco
 */
/* eslint-env node */
'use strict';

// modules
const async = require( 'async' );
const fs = require( 'fs' );
const LongTermStringStorage = require( './LongTermStringStorage' );
const nodeFetch = require( 'node-fetch' ); // eslint-disable-line
const query = require( 'pg-query' ); // eslint-disable-line
const winston = require( 'winston' );
const RosettaConstants = require( './RosettaConstants' );
const _ = require( 'underscore' ); // eslint-disable-line

// constants
const HTML_SIMS_DIRECTORY = global.preferences.htmlSimsDirectory;
const PRODUCTION_SERVER_URL = RosettaConstants.PRODUCTION_SERVER_URL;
const SKIP_BUILD_REQUEST = typeof global.preferences.debugRosettaSkipBuildRequest !== 'undefined' &&
                           global.preferences.debugRosettaSkipBuildRequest === 'true';

// TODO: Remove this comment (it's a change that was committed to the new chipper-2.0 branch to test it out).

/**
 * task queue into which translation request are pushed
 * @public
 */
module.exports.stringSubmissionQueue = async.queue( function( task, taskCallback ) {

  const req = task.req;
  const res = task.res;
  const targetLocale = req.params.targetLocale;
  const simName = req.params.simName;
  const userId = ( req.session.userId ) ? req.session.userId : 0;

  // Define an object that will contain the sets of strings that need to be stored.  The keys for this object will be
  // the sim or library name, aka the 'repo', and the strings will be in the form needed for long term string storage.
  // Multiple sims and/or libraries may need to be committed as part of a single translation due to shared code.
  const stringSets = {};

  // flags for checking if strings have changed for a given sim or lib
  const stringsChangedFlags = {};

  // Extract the string sets from the submitted translation.  The body of the request (req.body) contains an object with
  // all of the strings submitted through the POST request from the translation utility. The keys are in the form
  // "[repository] [key]" and the values are the strings, for example: "chains chains.title": "Chains".
  _.keys( req.body ).forEach( function( submittedStringKey ) {

    // data submitted is in the form "[repository] [key]", for example "area-builder area-builder.title"
    let repoAndStringKey = submittedStringKey.split( ' ' );
    let repo = repoAndStringKey[ 0 ];
    let stringKey = repoAndStringKey[ 1 ];

    // if this sim or lib isn't represent yet, add it
    if ( !stringSets[ repo ] ) {
      stringSets[ repo ] = {};
    }

    // get the value of the string
    let stringValue = req.body[ submittedStringKey ];

    // check if the string is already in translatedStrings to get the history if it exists
    let translatedString = req.session.translatedStrings[ targetLocale ] &&
                           req.session.translatedStrings[ targetLocale ][ repo ] &&
                           req.session.translatedStrings[ targetLocale ][ repo ][ stringKey ];
    let history = translatedString && translatedString.history;
    let oldValue = ( history && history.length ) ? history[ history.length - 1 ].newValue : '';

    // handle special case of multi-line string
    if ( oldValue.indexOf( '\n' ) > -1 ) {
      // TODO: temp debug code for issue #144, remove once issue is resolved.
      winston.log( 'info', 'detected multi-line string' );
      winston.log( 'info', 'oldValue = ' + oldValue );
      winston.log( 'info', 'prior to replace operation, oldValue === stringValue = ' + ( oldValue === stringValue ) );
      // TODO: end of first debug code section
      oldValue = oldValue.replace( /\n/g, '\\n' );
      // TODO: temp debug code for issue #144, remove once issue is resolved.
      winston.log( 'info', 'oldValue (after replace operation) = ' + oldValue );
      winston.log( 'info', 'after the replace operation, oldValue === stringValue = ' + ( oldValue === stringValue ) );
      // TODO: end of second debug code section
    }

    // only add the update if the value has changed
    if ( oldValue !== stringValue ) {
      let newHistoryEntry = {
        userId: ( req.session.userId ) ? req.session.userId : 'phet-test',
        timestamp: Date.now(),
        oldValue: oldValue,
        newValue: stringValue,
        explanation: null // TODO
      };

      if ( history ) {
        history.push( newHistoryEntry );
      }
      else {
        history = [ newHistoryEntry ];
      }
      stringSets[ repo ][ stringKey ] = {
        value: stringValue,
        history: history
      };

      // set flag indicating that a string has changed
      if ( !stringsChangedFlags[ repo ] ) {
        stringsChangedFlags[ repo ] = true;
      }
    }
    else if ( translatedString ) {
      stringSets[ repo ][ stringKey ] = translatedString;
    }
  } );

  // make a list of only the string sets that have changed
  const changedStringSets = _.pick( stringSets, _.keys( stringsChangedFlags ) );

  // save the modified strings to long-term storage
  let stringSavePromises = [];
  _.keys( changedStringSets ).forEach( function( simOrLibName ) {
    stringSavePromises.push(
      LongTermStringStorage.saveStrings( simOrLibName, targetLocale, changedStringSets[ simOrLibName ] )
    );
  } );

  // when all the save operations are complete, build the new or updated translation
  Promise.all( stringSavePromises )
    .then( results => {

      // request the build from the build server
      requestBuild( simName, userId, targetLocale )
        .then( () => {

          winston.log( 'info', 'build request successfully submitted to build server' );

          // render the page that indicates that the translation was successfully submitted
          res.render( 'translation-submit.html', {
            title: 'Translation submitted',
            simName: simName,
            targetLocale: targetLocale
          } );

          taskCallback();
        } )
        .catch( ( err ) => {

          winston.log( 'error', 'problem building translation, err = ' + err );

          // render an error page
          res.render( 'error.html', {
            title: 'Translation Utility Error',
            message: 'An error occurred when trying to build and deploy the translation.',
            errorDetails: err,
            timestamp: new Date().getTime()
          } );

          taskCallback();
        } );

      // clear out short-term storage, since these strings were successfully stored in the long-term area
      deleteStringsFromDB( userId, targetLocale, _.keys( stringSets ) );
    } )
    .catch( err => {

      // render an error page
      res.render( 'error.html', {
        title: 'Translation Utility Error',
        message: 'An error occurred when trying to save the translated strings.',
        errorDetails: err,
        timestamp: new Date().getTime()
      } );
    } );
}, 1 );

/**
 * delete the strings that are stored in short-term storage
 * @private
 */
async function deleteStringsFromDB( userID, locale, simOrLibNames ) {

  winston.log(
    'info',
    'removing strings from short term storage for userID = ' + userID + ', sim/libs = ' + simOrLibNames + ', locale = ' + locale
  );

  const simOrLibNamesString = simOrLibNames.join( ' OR ' );

  const deleteQuery = 'DELETE FROM saved_translations WHERE user_id = $1 AND locale = $2 AND (' + simOrLibNamesString + ')';
  winston.log( 'info', 'running SQL command: ' + deleteQuery );
  query( deleteQuery, [ userID, locale ], function( err ) {
    if ( !err ) {
      winston.log( 'info', 'successfully deleted saved strings' );
      return true;
    }
    else {
      winston.log( 'warning', 'problem while trying to remove strings from short term storage, err = ' + err );
      return false;
    }
  } );
}

/**
 * get the latest version of the specified simulation by getting metadata from server
 * @param simName
 * @return {string}
 * @private
 */
async function getLatestSimVersion( simName ) {
  const URL = PRODUCTION_SERVER_URL +
              '/services/metadata/1.2/simulations?format=json&type=html&locale=en&simulation=' +
              simName +
              '&summary';
  const response = await nodeFetch( URL );
  const responseJSON = await response.json();
  if ( !responseJSON.projects ) {
    winston.log( 'warn', 'metadata not found for sim ' + simName + ', attempting to get version info from deployment dirs' );
    const version = getLatestSimVersionFromFS( simName );
    if ( version ){
      return version;
    }
    else{
      throw new Error( 'unable to get version info from file system' );
    }
  }
  else {
    return responseJSON.projects[ 0 ].version.string;
  }
}

/**
 * Get the latest sim version by looking directly at the directories where the sims are kept, i.e. not through an HTTP
 * request.  This exists primarily to support testing, since there are some sims that are used for translation testing
 * (primarily the 'chains' sim at the time of this writing) whose version information is not available through PhET's
 * metadata API.  For this to work when testing on localhost, a fake directory will need to gbe set up on the local
 * machine, and the file path to said directory will need to be set up in the build-local.json file.  The version info
 * for the sims will need to be manually maintained.
 * @param simName
 * @return {Promise.<string>}
 */
function getLatestSimVersionFromFS( simName ) {

  // get the directory names in this sim's directory, should mostly be version numbers
  let dirNames = fs.readdirSync( HTML_SIMS_DIRECTORY + '/' + simName );

  // filter out anything that doesn't look like a version identifier (example of valid version ID is 1.2.3)
  dirNames = dirNames.filter( function( dirName ) {
    const dirNameTokenized = dirName.split( '.' );
    if ( dirNameTokenized.length !== 3 ) {
      return false;
    }
    for ( let i = 0; i < 3; i++ ) {
      if ( isNaN( dirNameTokenized[ i ] ) ) {
        return false;
      }
    }
    return true;
  } );

  // sort the list of version identifiers from oldest to newest, necessary because default order is lexicographic
  dirNames.sort( function( a, b ) {
    const aTokenized = a.split( '.' );
    const bTokenized = b.split( '.' );
    let result = 0;
    for ( let i = 0; i < aTokenized.length; i++ ) {
      if ( parseInt( aTokenized[ i ], 10 ) < parseInt( bTokenized[ i ], 10 ) ) {
        result = -1;
        break;
      }
      else if ( parseInt( aTokenized[ i ], 10 ) > parseInt( bTokenized[ i ], 10 ) ) {
        result = 1;
        break;
      }
    }
    return result;
  } );

  // get the latest version identifier
  return dirNames[ dirNames.length - 1 ];
}

/**
 * @param {string} simName
 * @param {string} version
 * @return {Promise.<string>} - JSON data with dependencies
 */
async function getDependencies( simName, version ) {
  const URL = PRODUCTION_SERVER_URL +
              '/sims/html/' +
              simName +
              '/' +
              version +
              '/dependencies.json';
  const response = await nodeFetch( URL );
  if ( response.status === 200 ){
    return await response.text();
  }
  else{
    throw new Error( 'unable to get dependencies for sim ' + simName + ', version ' + version + '; response.status = ' + response.status );
  }
}

/**
 * request a build from the build server
 * @return Promise
 * @private
 */
async function requestBuild( simName, userID, locale ) {

  winston.log( 'info', 'build requested for sim = ' + simName + ', locale = ' + locale );
  const latestVersionOfSim = await getLatestSimVersion( simName );
  winston.log( 'info', 'latest sim version = ' + latestVersionOfSim );
  const dependencies = await getDependencies( simName, latestVersionOfSim );
  const requestObject = {
    api: '2.0',
    dependencies: dependencies,
    simName: simName,
    version: latestVersionOfSim,
    locales: [ locale ],
    servers: [ 'production' ],
    brands: [ 'phet' ],
    translatorId: userID,
    authorizationCode: global.preferences.buildServerAuthorizationCode
  };

  const url = PRODUCTION_SERVER_URL + '/deploy-html-simulation';

  if ( !SKIP_BUILD_REQUEST ){

    winston.log( 'info', 'sending build request to server, URL = ' + url );

    // send off the request, and return the resulting promise
    const buildRequestResponse = await nodeFetch( url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify( requestObject )
    } );
    if ( buildRequestResponse.status === 200 ){
      winston.log( 'info', 'build request returned status ' + buildRequestResponse.status );
      return true;
    }
    else{
      throw new Error( 'build request unsuccessful, status = ' + buildRequestResponse.status );
    }
  }
  else{

    // The build request is being skipped due to the setting of a debug flag.  This capability was added to allow the
    // build request to be debugged without sending a bunch of bogus requests to the build server.
    winston.log( 'warn', 'build request skipped due to setting of debug flag' );
  }
}
<commit_msg>added 202 as a good response code from the build server<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * An asynchronous (async.queue) queue with a concurrency of one (meaning that only one task is executed at a time)
 * that is used to serialize submissions of strings for a translation.
 *
 * @author Aaron Davis
 * @author John Blanco
 */
/* eslint-env node */
'use strict';

// modules
const async = require( 'async' );
const fs = require( 'fs' );
const LongTermStringStorage = require( './LongTermStringStorage' );
const nodeFetch = require( 'node-fetch' ); // eslint-disable-line
const query = require( 'pg-query' ); // eslint-disable-line
const winston = require( 'winston' );
const RosettaConstants = require( './RosettaConstants' );
const _ = require( 'underscore' ); // eslint-disable-line

// constants
const HTML_SIMS_DIRECTORY = global.preferences.htmlSimsDirectory;
const PRODUCTION_SERVER_URL = RosettaConstants.PRODUCTION_SERVER_URL;
const SKIP_BUILD_REQUEST = typeof global.preferences.debugRosettaSkipBuildRequest !== 'undefined' &&
                           global.preferences.debugRosettaSkipBuildRequest === 'true';

// TODO: Remove this comment (it's a change that was committed to the new chipper-2.0 branch to test it out).

/**
 * task queue into which translation request are pushed
 * @public
 */
module.exports.stringSubmissionQueue = async.queue( function( task, taskCallback ) {

  const req = task.req;
  const res = task.res;
  const targetLocale = req.params.targetLocale;
  const simName = req.params.simName;
  const userId = ( req.session.userId ) ? req.session.userId : 0;

  // Define an object that will contain the sets of strings that need to be stored.  The keys for this object will be
  // the sim or library name, aka the 'repo', and the strings will be in the form needed for long term string storage.
  // Multiple sims and/or libraries may need to be committed as part of a single translation due to shared code.
  const stringSets = {};

  // flags for checking if strings have changed for a given sim or lib
  const stringsChangedFlags = {};

  // Extract the string sets from the submitted translation.  The body of the request (req.body) contains an object with
  // all of the strings submitted through the POST request from the translation utility. The keys are in the form
  // "[repository] [key]" and the values are the strings, for example: "chains chains.title": "Chains".
  _.keys( req.body ).forEach( function( submittedStringKey ) {

    // data submitted is in the form "[repository] [key]", for example "area-builder area-builder.title"
    let repoAndStringKey = submittedStringKey.split( ' ' );
    let repo = repoAndStringKey[ 0 ];
    let stringKey = repoAndStringKey[ 1 ];

    // if this sim or lib isn't represent yet, add it
    if ( !stringSets[ repo ] ) {
      stringSets[ repo ] = {};
    }

    // get the value of the string
    let stringValue = req.body[ submittedStringKey ];

    // check if the string is already in translatedStrings to get the history if it exists
    let translatedString = req.session.translatedStrings[ targetLocale ] &&
                           req.session.translatedStrings[ targetLocale ][ repo ] &&
                           req.session.translatedStrings[ targetLocale ][ repo ][ stringKey ];
    let history = translatedString && translatedString.history;
    let oldValue = ( history && history.length ) ? history[ history.length - 1 ].newValue : '';

    // handle special case of multi-line string
    if ( oldValue.indexOf( '\n' ) > -1 ) {
      // TODO: temp debug code for issue #144, remove once issue is resolved.
      winston.log( 'info', 'detected multi-line string' );
      winston.log( 'info', 'oldValue = ' + oldValue );
      winston.log( 'info', 'prior to replace operation, oldValue === stringValue = ' + ( oldValue === stringValue ) );
      // TODO: end of first debug code section
      oldValue = oldValue.replace( /\n/g, '\\n' );
      // TODO: temp debug code for issue #144, remove once issue is resolved.
      winston.log( 'info', 'oldValue (after replace operation) = ' + oldValue );
      winston.log( 'info', 'after the replace operation, oldValue === stringValue = ' + ( oldValue === stringValue ) );
      // TODO: end of second debug code section
    }

    // only add the update if the value has changed
    if ( oldValue !== stringValue ) {
      let newHistoryEntry = {
        userId: ( req.session.userId ) ? req.session.userId : 'phet-test',
        timestamp: Date.now(),
        oldValue: oldValue,
        newValue: stringValue,
        explanation: null // TODO
      };

      if ( history ) {
        history.push( newHistoryEntry );
      }
      else {
        history = [ newHistoryEntry ];
      }
      stringSets[ repo ][ stringKey ] = {
        value: stringValue,
        history: history
      };

      // set flag indicating that a string has changed
      if ( !stringsChangedFlags[ repo ] ) {
        stringsChangedFlags[ repo ] = true;
      }
    }
    else if ( translatedString ) {
      stringSets[ repo ][ stringKey ] = translatedString;
    }
  } );

  // make a list of only the string sets that have changed
  const changedStringSets = _.pick( stringSets, _.keys( stringsChangedFlags ) );

  // save the modified strings to long-term storage
  let stringSavePromises = [];
  _.keys( changedStringSets ).forEach( function( simOrLibName ) {
    stringSavePromises.push(
      LongTermStringStorage.saveStrings( simOrLibName, targetLocale, changedStringSets[ simOrLibName ] )
    );
  } );

  // when all the save operations are complete, build the new or updated translation
  Promise.all( stringSavePromises )
    .then( results => {

      // request the build from the build server
      requestBuild( simName, userId, targetLocale )
        .then( () => {

          winston.log( 'info', 'build request successfully submitted to build server' );

          // render the page that indicates that the translation was successfully submitted
          res.render( 'translation-submit.html', {
            title: 'Translation submitted',
            simName: simName,
            targetLocale: targetLocale
          } );

          taskCallback();
        } )
        .catch( ( err ) => {

          winston.log( 'error', 'problem building translation, err = ' + err );

          // render an error page
          res.render( 'error.html', {
            title: 'Translation Utility Error',
            message: 'An error occurred when trying to build and deploy the translation.',
            errorDetails: err,
            timestamp: new Date().getTime()
          } );

          taskCallback();
        } );

      // clear out short-term storage, since these strings were successfully stored in the long-term area
      deleteStringsFromDB( userId, targetLocale, _.keys( stringSets ) );
    } )
    .catch( err => {

      // render an error page
      res.render( 'error.html', {
        title: 'Translation Utility Error',
        message: 'An error occurred when trying to save the translated strings.',
        errorDetails: err,
        timestamp: new Date().getTime()
      } );
    } );
}, 1 );

/**
 * delete the strings that are stored in short-term storage
 * @private
 */
async function deleteStringsFromDB( userID, locale, simOrLibNames ) {

  winston.log(
    'info',
    'removing strings from short term storage for userID = ' + userID + ', sim/libs = ' + simOrLibNames + ', locale = ' + locale
  );

  const simOrLibNamesString = simOrLibNames.join( ' OR ' );

  const deleteQuery = 'DELETE FROM saved_translations WHERE user_id = $1 AND locale = $2 AND (' + simOrLibNamesString + ')';
  winston.log( 'info', 'running SQL command: ' + deleteQuery );
  query( deleteQuery, [ userID, locale ], function( err ) {
    if ( !err ) {
      winston.log( 'info', 'successfully deleted saved strings' );
      return true;
    }
    else {
      winston.log( 'warning', 'problem while trying to remove strings from short term storage, err = ' + err );
      return false;
    }
  } );
}

/**
 * get the latest version of the specified simulation by getting metadata from server
 * @param simName
 * @return {string}
 * @private
 */
async function getLatestSimVersion( simName ) {
  const URL = PRODUCTION_SERVER_URL +
              '/services/metadata/1.2/simulations?format=json&type=html&locale=en&simulation=' +
              simName +
              '&summary';
  const response = await nodeFetch( URL );
  const responseJSON = await response.json();
  if ( !responseJSON.projects ) {
    winston.log( 'warn', 'metadata not found for sim ' + simName + ', attempting to get version info from deployment dirs' );
    const version = getLatestSimVersionFromFS( simName );
    if ( version ){
      return version;
    }
    else{
      throw new Error( 'unable to get version info from file system' );
    }
  }
  else {
    return responseJSON.projects[ 0 ].version.string;
  }
}

/**
 * Get the latest sim version by looking directly at the directories where the sims are kept, i.e. not through an HTTP
 * request.  This exists primarily to support testing, since there are some sims that are used for translation testing
 * (primarily the 'chains' sim at the time of this writing) whose version information is not available through PhET's
 * metadata API.  For this to work when testing on localhost, a fake directory will need to gbe set up on the local
 * machine, and the file path to said directory will need to be set up in the build-local.json file.  The version info
 * for the sims will need to be manually maintained.
 * @param simName
 * @return {Promise.<string>}
 */
function getLatestSimVersionFromFS( simName ) {

  // get the directory names in this sim's directory, should mostly be version numbers
  let dirNames = fs.readdirSync( HTML_SIMS_DIRECTORY + '/' + simName );

  // filter out anything that doesn't look like a version identifier (example of valid version ID is 1.2.3)
  dirNames = dirNames.filter( function( dirName ) {
    const dirNameTokenized = dirName.split( '.' );
    if ( dirNameTokenized.length !== 3 ) {
      return false;
    }
    for ( let i = 0; i < 3; i++ ) {
      if ( isNaN( dirNameTokenized[ i ] ) ) {
        return false;
      }
    }
    return true;
  } );

  // sort the list of version identifiers from oldest to newest, necessary because default order is lexicographic
  dirNames.sort( function( a, b ) {
    const aTokenized = a.split( '.' );
    const bTokenized = b.split( '.' );
    let result = 0;
    for ( let i = 0; i < aTokenized.length; i++ ) {
      if ( parseInt( aTokenized[ i ], 10 ) < parseInt( bTokenized[ i ], 10 ) ) {
        result = -1;
        break;
      }
      else if ( parseInt( aTokenized[ i ], 10 ) > parseInt( bTokenized[ i ], 10 ) ) {
        result = 1;
        break;
      }
    }
    return result;
  } );

  // get the latest version identifier
  return dirNames[ dirNames.length - 1 ];
}

/**
 * @param {string} simName
 * @param {string} version
 * @return {Promise.<string>} - JSON data with dependencies
 */
async function getDependencies( simName, version ) {
  const URL = PRODUCTION_SERVER_URL +
              '/sims/html/' +
              simName +
              '/' +
              version +
              '/dependencies.json';
  const response = await nodeFetch( URL );
  if ( response.status === 200 ){
    return await response.text();
  }
  else{
    throw new Error( 'unable to get dependencies for sim ' + simName + ', version ' + version + '; response.status = ' + response.status );
  }
}

/**
 * request a build from the build server
 * @return Promise
 * @private
 */
async function requestBuild( simName, userID, locale ) {

  winston.log( 'info', 'build requested for sim = ' + simName + ', locale = ' + locale );
  const latestVersionOfSim = await getLatestSimVersion( simName );
  winston.log( 'info', 'latest sim version = ' + latestVersionOfSim );
  const dependencies = await getDependencies( simName, latestVersionOfSim );
  const requestObject = {
    api: '2.0',
    dependencies: dependencies,
    simName: simName,
    version: latestVersionOfSim,
    locales: [ locale ],
    servers: [ 'production' ],
    brands: [ 'phet' ],
    translatorId: userID,
    authorizationCode: global.preferences.buildServerAuthorizationCode
  };

  const url = PRODUCTION_SERVER_URL + '/deploy-html-simulation';

  if ( !SKIP_BUILD_REQUEST ){

    winston.log( 'info', 'sending build request to server, URL = ' + url );

    // send off the request, and return the resulting promise
    const buildRequestResponse = await nodeFetch( url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify( requestObject )
    } );
    if ( buildRequestResponse.status === 200 || buildRequestResponse.status === 202 ){
      winston.log( 'info', 'build request returned status ' + buildRequestResponse.status );
      return true;
    }
    else{
      throw new Error( 'build request unsuccessful, status = ' + buildRequestResponse.status );
    }
  }
  else{

    // The build request is being skipped due to the setting of a debug flag.  This capability was added to allow the
    // build request to be debugged without sending a bunch of bogus requests to the build server.
    winston.log( 'warn', 'build request skipped due to setting of debug flag' );
  }
}
<|endoftext|>


<commit_before>(function ($) {
	$.fn.tigronDatetimepicker = function (options) {
		if (typeof options.postFormat === 'undefined') {
			options.postFormat = options.format;
		}
		var getInput = function(element) {
			if ($(element).is('input')) {
				input = $(element);
			} else {
				input = $(element).find($(element).data('DateTimePicker').datepickerInput());
				if (input.length === 0) {
					input = $(element).find('input[type=text]');
				} else if (!input.is('input')) {
					throw new Error('CSS class "' + $(element).data('DateTimePicker').datepickerInput() + '" cannot be applied to non input element');
				}
			}
			return input;
		}

		cleaned_options = jQuery.extend(true, {}, options);
		delete cleaned_options.postFormat;

		$(this).datetimepicker(cleaned_options).on('dp.change', function(e) {
			input = getInput( $(this) );
			classname = $(input).attr('data-hidden-class');
			if (e.date === false) {
				value = '';
			} else {
				value = e.date.format(options.postFormat);
			}
			$('input.' + classname).val(value);
		});

		var count = $('input[class^="datetimepicker_"]').length + 1;

		$.each($(this), function(element, value) {
			input = getInput($(this));

			if ($(this).data('DateTimePicker').date() === null) {
				value = '';
			} else {
				value = $(this).data('DateTimePicker').date().format(options.postFormat);
				$(this).data('DateTimePicker').viewDate($(this).data('DateTimePicker').date());
			}

			if ($(input).next().length > 0 && $(input).next().is('[class*="datetimepicker_"]')) {
				return true;
			}

			$(input).after( $('<input>').attr('type', 'hidden').attr('name', $(input).attr('name')).val( value ).addClass('datetimepicker_' + count) );
			$(input).attr('data-hidden-class', 'datetimepicker_' + count);
			$(input).removeAttr('name');
			count++;
		})
	};
})(jQuery);
<commit_msg>prevent autocomplete on hidden datefield<commit_after>(function ($) {
	$.fn.tigronDatetimepicker = function (options) {
		if (typeof options.postFormat === 'undefined') {
			options.postFormat = options.format;
		}
		var getInput = function(element) {
			if ($(element).is('input')) {
				input = $(element);
			} else {
				input = $(element).find($(element).data('DateTimePicker').datepickerInput());
				if (input.length === 0) {
					input = $(element).find('input[type=text]');
				} else if (!input.is('input')) {
					throw new Error('CSS class "' + $(element).data('DateTimePicker').datepickerInput() + '" cannot be applied to non input element');
				}
			}
			return input;
		}

		cleaned_options = jQuery.extend(true, {}, options);
		delete cleaned_options.postFormat;

		$.each($(this), function(element, value) {

			$(this).addClass('datetimepicker-input').attr('data-toggle', 'datetimepicker')
			value = $(this).val();
			if (typeof value !== 'undefined') {
				$(this).attr('data-value', value);
				$(this).removeAttr('value');
				cleaned_options['defaultDate'] = value;
			}
			$(this).datetimepicker(cleaned_options)
		});

		$(this).on('change.datetimepicker', function(e) {
			input = getInput( $(this) );
			classname = $(input).attr('data-hidden-class');

			if (e.date === false || e.date == undefined) {
				value = '';
			} else {
				value = e.date.format(options.postFormat);
			}
			$('input.' + classname).val(value);
		});

		var count = $('input[class^="datetimepicker_"]').length + 1;

		$.each($(this), function(element, value) {
			input = getInput($(this));

			if ($(this).data('datetimepicker').date() === null) {
				value = '';
			} else {
				value = $(this).data('datetimepicker').date().format(options.postFormat);
				$(this).data('datetimepicker').viewDate($(this).data('datetimepicker').date());
			}

			if ($(input).next().length > 0 && $(input).next().is('[class*="datetimepicker_"]')) {
				return true;
			}

			$(input).after(
				$('<input>')
				   .attr('type', 'hidden')
				   .attr('autocomplete', 'off')
				   .attr('name', $(input).attr('name')).val( value )
				   .addClass('datetimepicker_' + count)
			);
			$(input).attr('data-hidden-class', 'datetimepicker_' + count);
			$(input).removeAttr('name');
			count++;
		})
	};
})(jQuery);
<|endoftext|>


<commit_before>/**
* Generated by PluginGenerator from webgme on Mon Apr 28 2014 13:58:17 GMT-0500 (Central Daylight Time).
*/

define(['plugin/PluginConfig',
    'plugin/PluginBase',
    'plugin/FmuImporter/FmuImporter/FMU',
    'jszip',
    'xmljsonconverter'], function (PluginConfig, PluginBase, FmuMetaTypes, JSZip, Converter) {
    'use strict';

    /**
    * Initializes a new instance of FmuImporter.
    * @class
    * @augments {PluginBase}
    * @classdesc This class represents the plugin FmuImporter.
    * @constructor
    */
    var FmuImporter = function () {
        // Call base class' constructor.
        PluginBase.call(this);
    };

    // Prototypal inheritance from PluginBase.
    FmuImporter.prototype = Object.create(PluginBase.prototype);
    FmuImporter.prototype.constructor = FmuImporter;

    /**
    * Gets the name of the FmuImporter.
    * @returns {string} The name of the plugin.
    * @public
    */
    FmuImporter.prototype.getName = function () {
        return "FMU Model Importer";
    };

    /**
    * Gets the semantic version (semver.org) of the FmuImporter.
    * @returns {string} The version of the plugin.
    * @public
    */
    FmuImporter.prototype.getVersion = function () {
        return "0.1.0";
    };

    /**
    * Gets the description of the FmuImporter.
    * @returns {string} The description of the plugin.
    * @public
    */
    FmuImporter.prototype.getDescription = function () {
        return "Creates WebGME models for one or more uploaded FMUs";
    };

    /**
    * Gets the configuration structure for the FmuImporter.
    * The ConfigurationStructure defines the configuration for the plugin
    * and will be used to populate the GUI when invoking the plugin from webGME.
    * @returns {object} The version of the plugin.
    * @public
    */
    FmuImporter.prototype.getConfigStructure = function () {
        return [
            {
                "name": "UploadedArtifact",
                "displayName": "FMUs",
                "description": "Click and drag existing compiled FMU(s)",
                //"value": "46f9efe35185b3f19cfeeefbf98d22107bbd1b8f", // this is the 'default config'
                "value": "0101da04257bf60436b20beb44433b6a45b84e77", // this is the 'default config'
                "valueType": "asset",
                "readOnly": false
            }
        ];
    };

    /**
    * Main function for the plugin to execute. This will perform the execution.
    * Notes:
    * - Always log with the provided logger.[error,warning,info,debug].
    * - Do NOT put any user interaction logic UI, etc. inside this method.
    * - callback always has to be called even if error happened.
    *
    * @param {function(string, plugin.PluginResult)} mainCallback - the result callback
    */
    FmuImporter.prototype.main = function (mainCallback) {
        // Use self to access core, project, result, logger etc from PluginBase.
        // These are all instantiated at this point.
        var self = this,
            selectedNode = self.activeNode,
            selectedNodeMetaType = self.getMetaType(selectedNode),
            currentConfig = self.getCurrentConfig(),
            currentConfigString = JSON.stringify(currentConfig, null, 4),
            artifactHash = currentConfig.UploadedArtifact,
            fmuHash,
            i;

        self.logger.debug('Entering FmuImporter main');

        // get all 'possible' object types from the MetaModel
        this.updateMETA(FmuMetaTypes);

        // TODO: Z/P please look at my context checking...
        if (!self.isMetaTypeOf(selectedNode, FmuMetaTypes.FMU_Library)) {
            var msg = "FmuImporter must be called from an FMU_Library!";
            self.logger.error(msg);
            self.result.setSuccess(false);
            mainCallback(msg, self.result);
            return;
        }

        self.logger.debug('CurrentConfig:');
        self.logger.debug(currentConfigString);

        var getFmuModelDescriptionsCallback = function (err, hashFmuDescriptionMap) {
            if (err) {
                self.logger.error(err);
                return;
            }

            for (fmuHash in hashFmuDescriptionMap) {
                self.createNewFmu(selectedNode, hashFmuDescriptionMap[fmuHash]);
            }

            // This will save the changes. If you don't want to save;
            // exclude self.save and call callback directly from this scope.
            self.result.setSuccess(true);
            self.save('Saving FmuImporter results to database...', function (err) {
                mainCallback(null, self.result);
            });
        };

        self.getFmuModelDescriptions(artifactHash, getFmuModelDescriptionsCallback);
    };

    FmuImporter.prototype.createNewFmu = function (parentNode, fmuModelDescription) {
        var self = this,
            newFmuNode,
            newFmuChildNode,
            fmuInfo = fmuModelDescription["fmiModelDescription"],
            modelicaName = fmuInfo["_modelName"],
            fmuName = fmuInfo["_modelIdentifier"],
            splitNames = modelicaName.split('.'),
            modelVariables = fmuInfo["ModelVariables"],
            scalarVariables = modelVariables["ScalarVariable"],
            numVariables = scalarVariables.length,
            variable,
            varName,
            variability,
            causality,
            valueRef,
            description,
            varTypeInfo,
            value,
            i,
            paramX = 400,
            paramY = 100,
            inputX = 100,
            inputY = 100,
            outputX = 700,
            outputY = 100,
            offsetY = 60;

        self.logger.info("Creating " + fmuName);
        // Create the new FMU in current context
        newFmuNode = self.core.createNode({parent: parentNode, base: FmuMetaTypes.FMU});
        self.core.setAttribute(newFmuNode, 'name', fmuName);

        // Create the Inputs, Outputs, Parameters
        for (i = 0; i < numVariables; i += 1) {
            variable = scalarVariables[i];
            varName = variable["_name"];
            variability = variable["_variability"];
            causality = variable["_causality"];
            description = "";
            value = "";
            valueRef = variable["_valueReference"];
            varTypeInfo = self.getVariableTypeInfo(variable);

            if (varName.split('')[0] === '_') {
                continue;  // TODO: revisit this; we might need to make these as properties
            }

            if (variable.hasOwnProperty("_description")) {
                description = variable["_description"];
            }

            if (causality === "input") {

                // Create Input
                newFmuChildNode = self.core.createNode({parent: newFmuNode, base: FmuMetaTypes.Input});
                self.core.setAttribute(newFmuChildNode, 'name', varName);
                self.core.setAttribute(newFmuChildNode, 'description', description);
                self.core.setAttribute(newFmuChildNode, 'fmiValueRef', valueRef);
                self.core.setRegistry(newFmuChildNode, 'position', {x: inputX, y: inputY});
                inputY += offsetY;

            } else if (causality === "output") {

                // Create Output
                newFmuChildNode = self.core.createNode({parent: newFmuNode, base: FmuMetaTypes.Output});
                self.core.setAttribute(newFmuChildNode, 'name', varName);
                self.core.setAttribute(newFmuChildNode, 'description', description);
                self.core.setAttribute(newFmuChildNode, 'fmiValueRef', valueRef);
                self.core.setRegistry(newFmuChildNode, 'position', {x: outputX, y: outputY});
                outputY += offsetY;

            } else if (causality === "internal") {
                if (variability === "parameter") {
                    if (varTypeInfo.hasOwnProperty("_start")) {
                        value = varTypeInfo["_start"]
                    }

                    newFmuChildNode = self.core.createNode({parent: newFmuNode, base: FmuMetaTypes.Parameter});
                    self.core.setAttribute(newFmuChildNode, 'name', varName);
                    self.core.setAttribute(newFmuChildNode, 'value', value);
                    self.core.setAttribute(newFmuChildNode, 'defaultValue', value);
                    self.core.setAttribute(newFmuChildNode, 'description', description);
                    self.core.setAttribute(newFmuChildNode, 'fmiValueRef', valueRef);
                    self.core.setRegistry(newFmuChildNode, 'position', {x: paramX, y: paramY});
                    paramY += offsetY;
                }
            }
        }
    };

    FmuImporter.prototype.getVariableTypeInfo = function (variableDict) {
        var typeInfo = {};

        if (variableDict.hasOwnProperty("Real")) {
            typeInfo = variableDict["Real"];
            typeInfo["_type"] = "Real";
        } else if (variableDict.hasOwnProperty("Boolean")) {
            typeInfo = variableDict["Boolean"];
            typeInfo["_type"] = "Boolean";
        } else if (variableDict.hasOwnProperty("Integer")) {
            typeInfo = variableDict["Integer"];
            typeInfo["_type"] = "Integer";
        } else if (variableDict.hasOwnProperty("Enumeration")) {
            typeInfo = variableDict["Enumeration"];
            typeInfo["_type"] = "Enumeration";
        }

        return typeInfo;
    };

    FmuImporter.prototype.getFmuModelDescriptions = function (uploadedFileHash, callback) {
        var self = this;

        var blobGetObjectCallback = function (err, content) {
            if (err) {
                callback(err);
                return;
            }

            var zip,
                fmuZip,
                fmuFileHash = 32,
                modelDescriptionXml,
                modelDescriptionJson,
                modelDescriptionMap = {},
                fmusWithinZip,
                numFmus,
                i;

            // TODO: what if the content is not a ZIP? TODO: check metadata
            zip = new JSZip(content);

            modelDescriptionXml = zip.file("modelDescription.xml");

            if (modelDescriptionXml === null) {
                // we might have a zip with multiple fmus within
                fmusWithinZip = zip.file(/\.fmu/);
                numFmus = fmusWithinZip.length;

                for (i = 0; i < numFmus; i += 1) {
                    fmuZip = new JSZip(fmusWithinZip[i].asArrayBuffer());
                    fmuFileHash += 1;
                    modelDescriptionXml = fmuZip.file("modelDescription.xml");
                    if (modelDescriptionXml != null) {
                        modelDescriptionJson = self.convertXml2Json(modelDescriptionXml.asText());
                        modelDescriptionMap[fmuFileHash] = modelDescriptionJson;
                    } else {
                        self.logger.error('Could not extract fmu modelDescription');
                        continue;
                    }
                }
            } else {
                modelDescriptionJson = self.convertXml2Json(modelDescriptionXml.asText());
                modelDescriptionMap[uploadedFileHash] = modelDescriptionJson;
            }

            // return .\modelDescription.xml
            callback(null, modelDescriptionMap);
        };

        self.blobClient.getObject(uploadedFileHash, blobGetObjectCallback);
    };

    FmuImporter.prototype.convertXml2Json = function (modelDescriptionXml) {
        // TODO: what if modelDescriptionXml is NOT an xml?

        var self = this,
            converter = new Converter.Xml2json(null, {skipWSText: true}),
            obj = converter.xmlStr2json(modelDescriptionXml);

        return obj;
    };

    /**
     * Finds and returns the node object defining the meta type for the given node.
     * @param node - Node to be check for type.
     * @returns {Object} - Node object defining the meta type of node.
     */
    FmuImporter.prototype.getMetaType = function (node) {
        var self = this,
            name;
        while (node) {
            name = self.core.getAttribute(node, 'name');
            if (self.META.hasOwnProperty(name) && self.core.getPath(self.META[name]) === self.core.getPath(node)) {
                break;
            }
            node = self.core.getBase(node);
        }
        return node;
    };

    /**
     * Checks if the given node is of the given meta-type.
     * Usage: <tt>self.isMetaTypeOf(aNode, self.META['FCO']);</tt>
     * @param node - Node to be check for type.
     * @param metaTypeObj - Node object defining the meta type.
     * @returns {boolean} - True if the given object was of the META type.
     */
    FmuImporter.prototype.isMetaTypeOf = function (node, metaTypeObj) {
        var self = this;
        while (node) {
            if (self.core.getGuid(node) === self.core.getGuid(metaTypeObj)) {
                return true;
            }
            node = self.core.getBase(node);
        }
        return false;
    };

    return FmuImporter;
});<commit_msg>handle multiple FMUs, give some feedback.<commit_after>/**
* Generated by PluginGenerator from webgme on Mon Apr 28 2014 13:58:17 GMT-0500 (Central Daylight Time).
*/

define(['plugin/PluginConfig',
    'plugin/PluginBase',
    'plugin/FmuImporter/FmuImporter/FMU',
    'jszip',
    'xmljsonconverter'], function (PluginConfig, PluginBase, FmuMetaTypes, JSZip, Converter) {
    'use strict';

    /**
    * Initializes a new instance of FmuImporter.
    * @class
    * @augments {PluginBase}
    * @classdesc This class represents the plugin FmuImporter.
    * @constructor
    */
    var FmuImporter = function () {
        // Call base class' constructor.
        PluginBase.call(this);
    };

    // Prototypal inheritance from PluginBase.
    FmuImporter.prototype = Object.create(PluginBase.prototype);
    FmuImporter.prototype.constructor = FmuImporter;

    /**
    * Gets the name of the FmuImporter.
    * @returns {string} The name of the plugin.
    * @public
    */
    FmuImporter.prototype.getName = function () {
        return "FMU Model Importer";
    };

    /**
    * Gets the semantic version (semver.org) of the FmuImporter.
    * @returns {string} The version of the plugin.
    * @public
    */
    FmuImporter.prototype.getVersion = function () {
        return "0.1.0";
    };

    /**
    * Gets the description of the FmuImporter.
    * @returns {string} The description of the plugin.
    * @public
    */
    FmuImporter.prototype.getDescription = function () {
        return "Creates WebGME models for one or more uploaded FMUs";
    };

    /**
    * Gets the configuration structure for the FmuImporter.
    * The ConfigurationStructure defines the configuration for the plugin
    * and will be used to populate the GUI when invoking the plugin from webGME.
    * @returns {object} The version of the plugin.
    * @public
    */
    FmuImporter.prototype.getConfigStructure = function () {
        return [
            {
                "name": "UploadedArtifact",
                "displayName": "FMUs",
                "description": "Click and drag existing compiled FMU(s)",
                //"value": "167d532ae62ec4ce73f085fffba2091ac29f487b", // multiple txt.zip in zip
                "value": "46f9efe35185b3f19cfeeefbf98d22107bbd1b8f", // multiple fmus in zip
                //"value": "0101da04257bf60436b20beb44433b6a45b84e77", // single fmu
                "valueType": "asset",
                "readOnly": false
            }
        ];
    };

    /**
    * Main function for the plugin to execute. This will perform the execution.
    * Notes:
    * - Always log with the provided logger.[error,warning,info,debug].
    * - Do NOT put any user interaction logic UI, etc. inside this method.
    * - callback always has to be called even if error happened.
    *
    * @param {function(string, plugin.PluginResult)} mainCallback - the result callback
    */
    FmuImporter.prototype.main = function (mainCallback) {
        // Use self to access core, project, result, logger etc from PluginBase.
        // These are all instantiated at this point.
        var self = this,
            selectedNode = self.activeNode,
            fmuLibraryNode,
            currentConfig = self.getCurrentConfig(),
            currentConfigString = JSON.stringify(currentConfig, null, 4),
            artifactHash = currentConfig.UploadedArtifact,
            fmuHash,
            numUploaded,
            numCreated = 0;

        self.logger.debug('Entering FmuImporter main');

        // get all 'possible' object types from the MetaModel
        this.updateMETA(FmuMetaTypes);

        // TODO: Z/P please look at my context checking...
        if (!self.isMetaTypeOf(selectedNode, FmuMetaTypes.FMU_Library)) {
            var msg = "FmuImporter must be called from an FMU_Library!";
            self.logger.error(msg);
            self.createMessage(selectedNode, msg);
            self.result.setSuccess(false);
            mainCallback(msg, self.result);
            return;
        } else {
            fmuLibraryNode = selectedNode;
        }

        self.logger.debug('CurrentConfig:');
        self.logger.debug(currentConfigString);

        var getFmuModelDescriptionsCallback = function (err, hashFmuDescriptionMap) {
            if (err) {
                self.logger.error(err);
                return;
            }

            numUploaded = Object.keys(hashFmuDescriptionMap).length;

            for (fmuHash in hashFmuDescriptionMap) {
                if (self.createNewFmu(fmuLibraryNode, fmuHash, hashFmuDescriptionMap[fmuHash])) {
                    numCreated += 1;
                }
            }

            // This will save the changes. If you don't want to save;
            // exclude self.save and call callback directly from this scope.
            self.result.setSuccess(true);
            self.createMessage(selectedNode, numCreated + " FMUs created out of " + numUploaded + " uploaded.");
            self.save('Saving FmuImporter results to database...', function (err) {
                mainCallback(null, self.result);
            });
        };

        self.getFmuModelDescriptions(artifactHash, getFmuModelDescriptionsCallback);
    };

    FmuImporter.prototype.createNewFmu = function (parentNode, fmuHash, fmuModelDescription) {
        var self = this,
            newFmuNode,
            newFmuChildNode,
            fmuInfo = fmuModelDescription["fmiModelDescription"],
            modelicaName = fmuInfo["_modelName"],
            fmuName = fmuInfo["_modelIdentifier"],
            splitNames = modelicaName.split('.'),
            modelVariables = fmuInfo["ModelVariables"],
            scalarVariables = modelVariables["ScalarVariable"],
            numVariables = scalarVariables.length,
            variable,
            varName,
            variability,
            causality,
            valueRef,
            description,
            varTypeInfo,
            value,
            i,
            paramX = 400,
            paramY = 100,
            inputX = 100,
            inputY = 100,
            outputX = 700,
            outputY = 100,
            offsetY = 60;

        self.logger.info("Creating " + fmuName);
        // Create the new FMU in current context
        newFmuNode = self.core.createNode({parent: parentNode, base: FmuMetaTypes.FMU});
        self.core.setAttribute(newFmuNode, 'name', fmuName);
        self.core.setAttribute(newFmuNode, 'resource', fmuHash);

        // Create the Inputs, Outputs, Parameters
        for (i = 0; i < numVariables; i += 1) {
            variable = scalarVariables[i];
            varName = variable["_name"];
            variability = variable["_variability"];
            causality = variable["_causality"];
            description = "";
            value = "";
            valueRef = variable["_valueReference"];
            varTypeInfo = self.getVariableTypeInfo(variable);

            if (varName.split('')[0] === '_') {
                continue;  // TODO: revisit this; we might need to make these as properties
            }

            if (variable.hasOwnProperty("_description")) {
                description = variable["_description"];
            }

            if (causality === "input") {

                // Create Input
                newFmuChildNode = self.core.createNode({parent: newFmuNode, base: FmuMetaTypes.Input});
                self.core.setAttribute(newFmuChildNode, 'name', varName);
                self.core.setAttribute(newFmuChildNode, 'description', description);
                self.core.setAttribute(newFmuChildNode, 'fmiValueRef', valueRef);
                self.core.setRegistry(newFmuChildNode, 'position', {x: inputX, y: inputY});
                inputY += offsetY;

            } else if (causality === "output") {

                // Create Output
                newFmuChildNode = self.core.createNode({parent: newFmuNode, base: FmuMetaTypes.Output});
                self.core.setAttribute(newFmuChildNode, 'name', varName);
                self.core.setAttribute(newFmuChildNode, 'description', description);
                self.core.setAttribute(newFmuChildNode, 'fmiValueRef', valueRef);
                self.core.setRegistry(newFmuChildNode, 'position', {x: outputX, y: outputY});
                outputY += offsetY;

            } else if (causality === "internal") {
                if (variability === "parameter") {
                    if (varTypeInfo.hasOwnProperty("_start")) {
                        value = varTypeInfo["_start"]
                    }

                    newFmuChildNode = self.core.createNode({parent: newFmuNode, base: FmuMetaTypes.Parameter});
                    self.core.setAttribute(newFmuChildNode, 'name', varName);
                    self.core.setAttribute(newFmuChildNode, 'value', value);
                    self.core.setAttribute(newFmuChildNode, 'defaultValue', value);
                    self.core.setAttribute(newFmuChildNode, 'description', description);
                    self.core.setAttribute(newFmuChildNode, 'fmiValueRef', valueRef);
                    self.core.setRegistry(newFmuChildNode, 'position', {x: paramX, y: paramY});
                    paramY += offsetY;
                }
            }
        }

        if (newFmuNode) {
            return true;
        } else {
            return false;
        }
    };

    FmuImporter.prototype.getVariableTypeInfo = function (variableDict) {
        var typeInfo = {};

        if (variableDict.hasOwnProperty("Real")) {
            typeInfo = variableDict["Real"];
            typeInfo["_type"] = "Real";
        } else if (variableDict.hasOwnProperty("Boolean")) {
            typeInfo = variableDict["Boolean"];
            typeInfo["_type"] = "Boolean";
        } else if (variableDict.hasOwnProperty("Integer")) {
            typeInfo = variableDict["Integer"];
            typeInfo["_type"] = "Integer";
        } else if (variableDict.hasOwnProperty("Enumeration")) {
            typeInfo = variableDict["Enumeration"];
            typeInfo["_type"] = "Enumeration";
        }

        return typeInfo;
    };

    FmuImporter.prototype.getFmuModelDescriptions = function (uploadedFileHash, callback) {
        var self = this;

        var blobGetObjectCallback = function (err, objectContent) {
            if (err) {
                callback(err);
                return;
            }

            var zip,
                fmuContent,
                fmuZip,
                fmuFileHash = 32,
                modelDescriptionXml,
                modelDescriptionJson,
                modelDescriptionMap = {},
                fmusWithinZip,
                numFmus,
                i;

            // TODO: what if the content is not a ZIP? TODO: check metadata
            zip = new JSZip(objectContent);

            modelDescriptionXml = zip.file("modelDescription.xml");

            if (modelDescriptionXml === null) {
                // we might have a zip with multiple fmus within
                fmusWithinZip = zip.file(/\.fmu/);
                numFmus = fmusWithinZip.length;

                for (i = 0; i < numFmus; i += 1) {
                    fmuContent = fmusWithinZip[i].asArrayBuffer();
                    fmuZip = new JSZip(fmuContent);
                    //fmuFileHash = self.blobClient.getHash(fmuContent);
                    fmuFileHash += 1;  // need to get the actual download (soft-link) hash for this content
                    modelDescriptionXml = fmuZip.file("modelDescription.xml");
                    if (modelDescriptionXml != null) {
                        modelDescriptionJson = self.convertXml2Json(modelDescriptionXml.asText());
                        modelDescriptionMap[fmuFileHash] = modelDescriptionJson;
                    } else {
                        self.logger.error('Could not extract fmu modelDescription');
                        continue;
                    }
                }
            } else {
                modelDescriptionJson = self.convertXml2Json(modelDescriptionXml.asText());
                modelDescriptionMap[uploadedFileHash] = modelDescriptionJson;
            }

            // return .\modelDescription.xml
            callback(null, modelDescriptionMap);
        };

        self.blobClient.getObject(uploadedFileHash, blobGetObjectCallback);
    };

    FmuImporter.prototype.convertXml2Json = function (modelDescriptionXml) {
        // TODO: what if modelDescriptionXml is NOT an xml?

        var self = this,
            converter = new Converter.Xml2json(null, {skipWSText: true}),
            obj = converter.xmlStr2json(modelDescriptionXml);

        return obj;
    };

    /**
     * Finds and returns the node object defining the meta type for the given node.
     * @param node - Node to be check for type.
     * @returns {Object} - Node object defining the meta type of node.
     */
    FmuImporter.prototype.getMetaType = function (node) {
        var self = this,
            name;
        while (node) {
            name = self.core.getAttribute(node, 'name');
            if (self.META.hasOwnProperty(name) && self.core.getPath(self.META[name]) === self.core.getPath(node)) {
                break;
            }
            node = self.core.getBase(node);
        }
        return node;
    };

    /**
     * Checks if the given node is of the given meta-type.
     * Usage: <tt>self.isMetaTypeOf(aNode, self.META['FCO']);</tt>
     * @param node - Node to be check for type.
     * @param metaTypeObj - Node object defining the meta type.
     * @returns {boolean} - True if the given object was of the META type.
     */
    FmuImporter.prototype.isMetaTypeOf = function (node, metaTypeObj) {
        var self = this;
        while (node) {
            if (self.core.getGuid(node) === self.core.getGuid(metaTypeObj)) {
                return true;
            }
            node = self.core.getBase(node);
        }
        return false;
    };

    return FmuImporter;
});<|endoftext|>