<commit_before>var fs = require('fs');
var isects = require('2d-polygon-self-intersections');
/* 	from https://www.npmjs.com/package/2d-polygon-self-intersections
	for n segments and k intersections,
	this is O(n^2)
	It can be optimised, but can be optimised to O((n + k) log n) through Bentley–Ottmann algorithm
	Which is an improvement for small k (k < o(n2 / log n))
  See possibly https://github.com/e-cloud/sweepline
	Future work this algorithm to allow interior LinearRing's and/or multipolygon input, since main.js should be enabled to handle multipolygon input.
*/

// constructors // TODO: check default index.

var Vertex = function (coord) {
  this.coord = coord; // [x,y]
};

var Polygon = function (vtxList) {
  this.vtxList = vtxList || []; // Array of Vertex
};

var LineSegment = function (start, end) {
  this.start = start; // Vertex
  this.end = end; // Vertex
};

var MultiPolygon = function (polyList) {
  this.polyList = polyList || []; // Array of Polygon
};

var nVertex = function (v, param, index, l) {
  this.v = v; // Vertex
  this.param = param;
  this.index = index;
  this.l = l; // LineSegment
};

var Intersection = function (v, origin1, origin2, index1, index2, winding) {
  this.v = v; // Vertex
  this.origin1 = origin1; // Vertex
  this.origin2 = origin2; // Vertex
  this.index1 = index1;
  this.index2 = index2;
  this.winding = winding;
};

module.exports = function(feature) {

  // process input
  var geom = feature.geometry;
  if (geom.coordinates.length>1) throw new Error("A Polygon input without interior LinearRing's is required");
  var coord = geom.coordinates[0]; // From here on work with exterior LinearRing

  // compute intersection points
  var isectsData = [];
  var isectsPoints = isects(coord, function filterFn(r, o, s0, e0, p, s1, e1, unique){
    if (unique) { // Note: this may cause problems when there are two self-intersections at the same point
          // compute parameters:
          var ot = (r[0]-s0[0])/(e0[0]-s0[0]); // or equally: (r[1]-s0[1])/(e0[1]-s0[1])
          var pt = (r[0]-s1[0])/(e1[0]-s1[0]); // or equally: (r[1]-s1[1])/(e1[1]-s1[1]))
          //var pt = ;
        isectsData.push([r, o, ot, s0, e0, p, pt, s1, e1]);
    }
  });
  console.log(JSON.stringify(isectsData));

  // If no intersections are found, we can stop here
  if (isectsPoints.length == 0) return feature;
  else {

    // Build intersection master list and polygon edge array
    var polygonEdgeArray = []; // List of nVertex's, each linking to a next nVertex
    var intersectionList = []; // List of Intersection's
    var lineSegmentList = []; // List of LineSegment's

    intersectionCounter = 0;
    for (var i = 0; i < coord.length-1; i++) {
  //    console.log(0,i);
  //  	console.log(JSON.stringify(coord[i]));
      var currentVertex = new Vertex(coord[i]);
      var currentIntersection = new Intersection(currentVertex, currentVertex); // rest is undefined at this point
      var currentLineSegment = new LineSegment(new Vertex(coord[i]), new Vertex(coord[i+1]));
      polygonEdgeArray.push(currentVertex, 0, -1, currentLineSegment); // TODO: may LineSegment just be the index?
      intersectionList.push(currentIntersection);
      lineSegmentList.push(currentLineSegment);
      intersectionCounter++;
    }

  //console.log(JSON.stringify(lineSegmentList));
  //console.log(JSON.stringify(polygonEdgeArray));

    return {
      type: 'Feature',
      geometry: {
        type: "MultiPolygon",
        coordinates: [[[[0,0],[1,0],[1,1],[0,0]]]]
        },
      properties: {
        winding: [1]
        }
    };
  }
};
<commit_msg>polygonEdgeArray and instersectionList made<commit_after>var fs = require('fs');
var isects = require('2d-polygon-self-intersections');
/* 	from https://www.npmjs.com/package/2d-polygon-self-intersections
	for n segments and k intersections,
	this is O(n^2)
	It can be optimised, but can be optimised to O((n + k) log n) through Bentley–Ottmann algorithm
	Which is an improvement for small k (k < o(n2 / log n))
  See possibly https://github.com/e-cloud/sweepline
	Future work this algorithm to allow interior LinearRing's and/or multipolygon input, since main.js should be enabled to handle multipolygon input.
*/

// TODO: check default index.
// TODO: can we work with indeces for line segments?

// constructors
var Vertex = function (coord) {
  this.coord = coord; // [x,y]
};

var Polygon = function (vtxList) {
  this.vtxList = vtxList || []; // array of Vertex
};

/* // Not used since we simply use an index for the polygon edges, which is equal to origin/origin1
var LineSegment = function (start, end) { // polygon edge
  this.start = start; // start vertex - Vertex
  this.end = end; // end vertex - Vertex
};
*/

var MultiPolygon = function (polyList) {
  this.polyList = polyList || []; // Array of Polygon
};

var nVertex = function (v, param, index, l, incomming) { // (polygon- or intersection-) pseudo-vertex. There are two per self-intersection.
  this.v = v; // the vetrex of this pseudo-vertex - Vertex
  this.param = param; // fractional distance on its origin polygon edge
  this.index = index; // the index in the intersection list of the next pseudo-vertex when following its origin polygon edge
  this.l = l; // index of the the crossing polygon edge that created this pseudo-vertex
  this.incomming = incomming; // index of incomming polygon edge
};
// NOTE: added incomming edge

var Intersection = function (v, origin1, origin2, index1, index2, winding) { // pair of pseudo-vertices. There is one per self-intersection. The order of 1 and 2 is not important but should be invariant.
  this.v = v; // the vetrex of this pair of pseudo-vertex - Vertex
  this.origin1 = origin1; // index of origin polygon vertex of the first polygon edge creating this intersection
  this.origin2 = origin2; // index of origin polygon vertex of the second polygon edge creating this intersection
  this.index1 = index1; // index in the intersection list of the next intersection when comming from origin1 and following that polygon edge
  this.index2 = index2; // index in the intersection list of the next intersection when comming from origin2 and following that polygon edge
  this.winding = winding;
};
// NOTE: we use an index for origins

module.exports = function(feature) {

  // process input
  var geom = feature.geometry;
  if (geom.coordinates.length>1) throw new Error("A Polygon input without interior LinearRing's is required");
  var coord = geom.coordinates[0]; // From here on work with exterior LinearRing

  // compute self-intersection points
  var isectsData = [];
  var isectsPoints = isects(coord, function filterFn(r, o, s0, e0, p, s1, e1, unique){
    // compute parameters t: how far are the self-intersections on both polygon edges o and p: ot and pt in [0,1]
    var ot = (r[0]-s0[0])/(e0[0]-s0[0]); // or equally: (r[1]-s0[1])/(e0[1]-s0[1])
    var pt = (r[0]-s1[0])/(e1[0]-s1[0]); // or equally: (r[1]-s1[1])/(e1[1]-s1[1]))
    isectsData.push([r, o, s0, e0, p, s1, e1, unique, ot, pt]);
  });
//console.log(JSON.stringify(isectsData.length));

  // If no intersections are found, we can stop here
  if (isectsData.length == 0) return feature;
  else {

    // Build intersection master list and polygon edge array
    var polygonEdgeArray = []; // List of List of nVertex's encounted when following the polygon edges as they are given in input.
    // NOTE: we did not fill in polygon vertices
    var intersectionList = []; // List of Intersection's. Order does not matter, but should be invariant.

    // Build polygonEdgeArray and intersectionList, without indices
    // 1) Add polygon vertices to polygonEdgeArray (as nVertex) and to intersectionList (as Intersection)
    for (var i = 0; i < coord.length-1; i++) {
    	var currentVertex = new Vertex(coord[i]);
      /*
      var currentNVertex = new nVertex(currentVertex, 0, undefined, undefined, i); // index is undefined yet
      // Put currentNVertex as next in polygonEdgeArray
      polygonEdgeArray.push(currentNVertex);
      */
      polygonEdgeArray.push([]);
      var currentIntersection = new Intersection(currentVertex, i, undefined, undefined, -1, undefined); // index1 and winding are undefined yet, and origin2 is -1 for polygon-pseudo-vertex
      // Put currentIntersection as next in intersectionList
      intersectionList.push(currentIntersection);
    };
    // 2) Add self-intersection vertices to polygonEdgeArray (as nVertex) and (if unique) to intersectionList (as Intersection)
    for (var i = 0; i < isectsData.length; i++) {
    	var currentIsect = isectsData[i];
      var currentVertex = new Vertex(currentIsect[0]);
      var currentNVertex = new nVertex(currentVertex, currentIsect[8], undefined, currentIsect[4], currentIsect[1]);
      // Put currentNVertex in polygonEdgeArray in the list of the after nVertex with same l and closest, smaller t
      polygonEdgeArray[currentIsect[1]].push(currentNVertex);
      // Put currentIntersection as next in intersectionList
      if (currentIsect[7]){
        var currentIntersection = new Intersection(currentVertex, currentIsect[1], currentIsect[4], undefined, undefined, undefined); // index1 and winding are undefined yet, and origin2 is -1 for polygon-pseudo-vertex
        intersectionList.push(currentIntersection);
      };
    };
    for (var i = 0; i < coord.length-1; i++) {
      polygonEdgeArray[i].sort(function(a, b){
      var keyA = a.param,
          keyB = b.param;
      // Compare the 2 dates
      if(keyA < keyB) return -1;
      if(keyA > keyB) return 1;
      return 0;
      });
    };
    //console.log(JSON.stringify(polygonEdgeArray));
    //console.log(JSON.stringify(intersectionList));

    var intersectionQueue = []; // List of nVertex

    // Fill indices
    // Start at outer (convex) point and jump though intersectionList and make polygon by storing vertices.
    // When arriving at a pseudo-vertex that is not a polygon-pseudo-vertex, store it.
    // When done, take next nVertex from queue (check if ...)
    // It's 'index' will tell you where to jump next in the intersectionList if you want to make a polygon on the other side

  //console.log(JSON.stringify(polygonEdgeArray));
  //console.log(JSON.stringify(intersectionList));

    return {
      type: 'Feature',
      geometry: {
        type: "MultiPolygon",
        coordinates: [[[[0,0],[1,0],[1,1],[0,0]]]]
        },
      properties: {
        winding: [1]
        }
    };
  }
};
<|endoftext|>


<commit_before>(function($){

    function fixFormsAction() {
        $('form[data-action-fix!="false"][data-action-fix!="no"]').each(function(idx, form){
            $(form).attr('method', 'GET');
        });
    }

    $(document).ready(function(){
        // if forms action needs to be fixed for WP7 or BB6 platforms then..
        if ("true" == $('body').attr('data-do-fix-forms')) {
            // do fix actions for the forms already loaded
            fixFormsAction();
            // and for any future forms loaded with AJAX
            $('body').ajaxSuccess(function(){
                fixFormsAction();
            });
        }
    });

    // Rho: Android 2.2 doesn't define WebKitTransitionEvent, though animations
    // still work
    if (navigator.userAgent.indexOf("Android 2") != -1) {
        //$.support.WebKitAnimationEvent = true;
        $.support.cssTransitions = true;
    }

    // Set to 30 seconds; Ajax request doesn't come back if >= 60
    $.ajaxSetup({
        timeout: 30000,
        headers: {
            'Transition-Enabled': true
        }
    });

    $.ajaxPrefilter(function(options, originalOptions, jqXHR){
        var origSuccess = options.success;

        options.success = function(html, textStatus, jqXHR) {
            if (jqXHR.getResponseHeader('Wait-Page')) {
                // We cannot just do nothing on wait-page being received, because
                // at this moment jQM already have isPageTransitioning lock is set.
                // Due to this lock is private part of jQM we have no control on it.
                // So we are going to tag Wait-Page HTML content by some HTML attribute
                // to detect it in "pagebeforechange" event handler and then perform
                // preventDefault() to let jQM to release isPageTransitioning lock.
                origSuccess('

');
            } else {
                origSuccess(html);
            }
        }

    });

    $(document).bind( "pagebeforechange", function(e, data) {
        // We only want to handle changePage() calls where the caller is
        // providing us already loaded page.
        if ( !(typeof data.toPage === "string") ) {
            var pageDiv = data.toPage[0];
            if ("true" === pageDiv.getAttribute("data-rho-wait-page")) {
                //Make sure to tell changePage() we've handled this call so it doesn't
                //have to do anything. So jQM can release isPageTransitioning lock
                e.preventDefault();
            }
        }
    });

    //shared page enhancements
	function enhancePage( $page, role ) {
		// If a role was specified, make sure the data-role attribute
		// on the page element is in sync.
		if( role ) {
			$page.attr( "data-" + $.mobile.ns + "role", role );
		}

		//run page plugin
		$page.page();
	}

    // hijack $.mobile.loadPage function
    var path = $.mobile.path;
    var original_loadPage = $.mobile.loadPage;
    // introduce custom initialization parameter support
    original_loadPage.defaults.loadMsgDelay = $.mobile.loadingMessageDelay || original_loadPage.defaults.loadMsgDelay;

    $.mobile.loadPage = function( url, options ) {

        // if no page HTML code provided, then call original function
        if(!options["html"]) {
            return original_loadPage(url, options);
        }

		// This function uses deferred notifications to let callers
		// know when the page is done loading, or if an error has occurred.
		var deferred = $.Deferred(),

			// The default loadPage options with overrides specified by
			// the caller.
			settings = $.extend( {}, $.mobile.loadPage.defaults, options ),

			// The DOM element for the page after it has been loaded.
			page = null,

			// If the reloadPage option is true, and the page is already
			// in the DOM, dupCachedPage will be set to the page element
			// so that it can be removed after the new version of the
			// page is loaded off the network.
			dupCachedPage = null,

			// determine the current base url
			findBaseWithDefault = function(){
				var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) );
				return closestBase || documentBase.hrefNoHash;
			},

            /*
			// The absolute version of the URL passed into the function. This
			// version of the URL may contain dialog/subpage params in it.
			absUrl = path.makeUrlAbsolute( url, findBaseWithDefault() );
            */
            absUrl = "";

		// If the caller provided data, and we're using "get" request,
		// append the data to the URL.
		if ( settings.data && settings.type === "get" ) {
			absUrl = path.addSearchParams( absUrl, settings.data );
			settings.data = undefined;
		}

		// If the caller is using a "post" request, reloadPage must be true
		if(  settings.data && settings.type === "post" ){
			settings.reloadPage = true;
		}

			// The absolute version of the URL minus any dialog/subpage params.
			// In otherwords the real URL of the page to be loaded.
		var fileUrl = path.getFilePath( absUrl ),

			// The version of the Url actually stored in the data-url attribute of
			// the page. For embedded pages, it is just the id of the page. For pages
			// within the same domain as the document base, it is the site relative
			// path. For cross-domain pages (Phone Gap only) the entire absolute Url
			// used to load the page.
			dataUrl = path.convertUrlToDataUrl( absUrl );

		// Make sure we have a pageContainer to work with.
		settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;

		// Check to see if the page already exists in the DOM.
		page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" );

		// If we failed to find the page, check to see if the url is a
		// reference to an embedded page. If so, it may have been dynamically
		// injected by a developer, in which case it would be lacking a data-url
		// attribute and in need of enhancement.
		if ( page.length === 0 && dataUrl && !path.isPath( dataUrl ) ) {
			page = settings.pageContainer.children( "#" + dataUrl )
				.attr( "data-" + $.mobile.ns + "url", dataUrl )
		}

		// If we failed to find a page in the DOM, check the URL to see if it
		// refers to the first page in the application.
		if ( page.length === 0 && $.mobile.firstPage && absUrl && path.isFirstPageUrl( absUrl ) ) {
			page = $( $.mobile.firstPage );
		}

        /*
		// Reset base to the default document base.
		if ( base ) {
			base.reset();
		}
        */

        /*
		// If the page we are interested in is already in the DOM,
		// and the caller did not indicate that we should force a
		// reload of the file, we are done. Otherwise, track the
		// existing page as a duplicated.
		if ( page.length ) {
			if ( !settings.reloadPage ) {
				enhancePage( page, settings.role );
				deferred.resolve( absUrl, options, page );
				return deferred.promise();
			}
			dupCachedPage = page;
		}
        */

        var mpc = settings.pageContainer,
            pblEvent = new $.Event( "pagebeforeload" ),
            triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings };

        // Let listeners know we're about to load a page.
        mpc.trigger( pblEvent, triggerData );

        // If the default behavior is prevented, stop here!
        if( pblEvent.isDefaultPrevented() ){
            return deferred.promise();
        }

        if ( settings.showLoadMsg ) {

            // This configurable timeout allows cached pages a brief delay to load without showing a message
            var loadMsgDelay = setTimeout(function(){
                    $.mobile.showPageLoadingMsg();
                }, settings.loadMsgDelay ),

                // Shared logic for clearing timeout and removing message.
                hideMsg = function(){

                    // Stop message show timer
                    clearTimeout( loadMsgDelay );

                    // Hide loading message
                    $.mobile.hidePageLoadingMsg();
                };
        }

        setHtml(options.html);

        function setHtml( html ) {
            //pre-parse html to check for a data-url,
            //use it as the new fileUrl, base path, etc
            var all = $( "

" ),

                    //page title regexp
                    newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1,

                    // TODO handle dialogs again
                    pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ),
                    dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" );

            // data-url must be provided for the base tag so resource requests can be directed to the
            // correct url. loading into a temprorary element makes these requests immediately
            if( pageElemRegex.test( html )
                    && RegExp.$1
                    && dataUrlRegex.test( RegExp.$1 )
                    && RegExp.$1 ) {
                url = fileUrl = path.getFilePath( RegExp.$1 );
            }
            /*
            else{

            }
            */

            /*
            if ( base ) {
                base.set( fileUrl );
            }
            */

            //workaround to allow scripts to execute when included in page divs
            all.get( 0 ).innerHTML = html;
            page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first();

            //if page elem couldn't be found, create one and insert the body element's contents
            if( !page.length ){
                page = $( "
" + html.split( /<\/?body[^>]*>/gmi )[1] + "
" );
            }

            if ( newPageTitle && !page.jqmData( "title" ) ) {
                page.jqmData( "title", newPageTitle );
            }

            //rewrite src and href attrs to use a base url
            if( !$.support.dynamicBaseTag ) {
                var newPath = path.get( fileUrl );
                page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() {
                    var thisAttr = $( this ).is( '[href]' ) ? 'href' :
                            $(this).is('[src]') ? 'src' : 'action',
                        thisUrl = $( this ).attr( thisAttr );

                    // XXX_jblas: We need to fix this so that it removes the document
                    //            base URL, and then prepends with the new page URL.
                    //if full path exists and is same, chop it - helps IE out
                    thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' );

                    if( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
                        $( this ).attr( thisAttr, newPath + thisUrl );
                    }
                });
            }

            //append to page and enhance
            // TODO taging a page with external to make sure that embedded pages aren't removed
            //      by the various page handling code is bad. Having page handling code in many
            //      places is bad. Solutions post 1.0
            page
                .attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) )
                .attr( "data-" + $.mobile.ns + "external-page", true )
                .appendTo( settings.pageContainer );

            // wait for page creation to leverage options defined on widget
            page.one( 'pagecreate', $.mobile._bindPageRemove );

            enhancePage( page, settings.role );

            // Enhancing the page may result in new dialogs/sub pages being inserted
            // into the DOM. If the original absUrl refers to a sub-page, that is the
            // real page we are interested in.
            if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) {
                page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" );
            }

            //bind pageHide to removePage after it's hidden, if the page options specify to do so

            // Remove loading message.
            if ( settings.showLoadMsg ) {
                hideMsg();
            }

            // Add the page reference to our triggerData.
            triggerData.page = page;

            // Let listeners know the page loaded successfully.
            settings.pageContainer.trigger( "pageload", triggerData );

            deferred.resolve( absUrl, options, page, dupCachedPage );
        }

		return deferred.promise();
	};
    // copy original defaults
    $.mobile.loadPage.defaults = original_loadPage.defaults;

    function insertAsyncPage(data) {
        $.mobile.loadPage("inline://", {html: data})
            .done(function( url, options, newPage, dupCachedPage ) {
                options.duplicateCachedPage = dupCachedPage;
                $.mobile.changePage( newPage, options );
            });
    }

    window.Rho = window.Rho || {};
    window.Rho.jqm = $.mobile;
    // Rho: add insertAsyncPage callback for transitions
    window.Rho.insertAsyncPage = insertAsyncPage;

})(jQuery);
<commit_msg>app template: minor javascript comment fix<commit_after>(function($){

    function fixFormsAction() {
        $('form[data-action-fix!="false"][data-action-fix!="no"]').each(function(idx, form){
            $(form).attr('method', 'GET');
        });
    }

    $(document).ready(function(){
        // if forms action needs to be fixed for WP7 or BB6 platforms then..
        if ("true" == $('body').attr('data-do-fix-forms')) {
            // do fix actions for the forms already loaded
            fixFormsAction();
            // and for any future forms loaded with AJAX
            $('body').ajaxSuccess(function(){
                fixFormsAction();
            });
        }
    });

    // Rho: Android 2.2 doesn't define WebKitTransitionEvent, though animations
    // still work
    if (navigator.userAgent.indexOf("Android 2") != -1) {
        //$.support.WebKitAnimationEvent = true;
        $.support.cssTransitions = true;
    }

    // Set to 30 seconds; Ajax request doesn't come back if >= 60
    $.ajaxSetup({
        timeout: 30000,
        headers: {
            'Transition-Enabled': true
        }
    });

    $.ajaxPrefilter(function(options, originalOptions, jqXHR){
        var origSuccess = options.success;

        options.success = function(html, textStatus, jqXHR) {
            if (jqXHR.getResponseHeader('Wait-Page')) {
                // We cannot just do nothing on wait-page being received, because
                // at this moment jQM already have isPageTransitioning lock is set.
                // Due to this lock is private part of jQM we have no control on it.
                // So we are going to tag Wait-Page HTML content by some HTML attribute
                // to detect it in "pagebeforechange" event handler and then perform
                // preventDefault() to let jQM to release isPageTransitioning lock.
                origSuccess('

');
            } else {
                origSuccess(html);
            }
        }

    });

    $(document).bind( "pagebeforechange", function(e, data) {
        // We only want to handle changePage() calls where the caller is
        // providing us an already loaded page.
        if ( !(typeof data.toPage === "string") ) {
            var pageDiv = data.toPage[0];
            if ("true" === pageDiv.getAttribute("data-rho-wait-page")) {
                //Make sure to tell changePage() we've handled this call so it doesn't
                //have to do anything. So jQM can release isPageTransitioning lock
                e.preventDefault();
            }
        }
    });

    //shared page enhancements
	function enhancePage( $page, role ) {
		// If a role was specified, make sure the data-role attribute
		// on the page element is in sync.
		if( role ) {
			$page.attr( "data-" + $.mobile.ns + "role", role );
		}

		//run page plugin
		$page.page();
	}

    // hijack $.mobile.loadPage function
    var path = $.mobile.path;
    var original_loadPage = $.mobile.loadPage;
    // introduce custom initialization parameter support
    original_loadPage.defaults.loadMsgDelay = $.mobile.loadingMessageDelay || original_loadPage.defaults.loadMsgDelay;

    $.mobile.loadPage = function( url, options ) {

        // if no page HTML code provided, then call original function
        if(!options["html"]) {
            return original_loadPage(url, options);
        }

		// This function uses deferred notifications to let callers
		// know when the page is done loading, or if an error has occurred.
		var deferred = $.Deferred(),

			// The default loadPage options with overrides specified by
			// the caller.
			settings = $.extend( {}, $.mobile.loadPage.defaults, options ),

			// The DOM element for the page after it has been loaded.
			page = null,

			// If the reloadPage option is true, and the page is already
			// in the DOM, dupCachedPage will be set to the page element
			// so that it can be removed after the new version of the
			// page is loaded off the network.
			dupCachedPage = null,

			// determine the current base url
			findBaseWithDefault = function(){
				var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) );
				return closestBase || documentBase.hrefNoHash;
			},

            /*
			// The absolute version of the URL passed into the function. This
			// version of the URL may contain dialog/subpage params in it.
			absUrl = path.makeUrlAbsolute( url, findBaseWithDefault() );
            */
            absUrl = "";

		// If the caller provided data, and we're using "get" request,
		// append the data to the URL.
		if ( settings.data && settings.type === "get" ) {
			absUrl = path.addSearchParams( absUrl, settings.data );
			settings.data = undefined;
		}

		// If the caller is using a "post" request, reloadPage must be true
		if(  settings.data && settings.type === "post" ){
			settings.reloadPage = true;
		}

			// The absolute version of the URL minus any dialog/subpage params.
			// In otherwords the real URL of the page to be loaded.
		var fileUrl = path.getFilePath( absUrl ),

			// The version of the Url actually stored in the data-url attribute of
			// the page. For embedded pages, it is just the id of the page. For pages
			// within the same domain as the document base, it is the site relative
			// path. For cross-domain pages (Phone Gap only) the entire absolute Url
			// used to load the page.
			dataUrl = path.convertUrlToDataUrl( absUrl );

		// Make sure we have a pageContainer to work with.
		settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;

		// Check to see if the page already exists in the DOM.
		page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" );

		// If we failed to find the page, check to see if the url is a
		// reference to an embedded page. If so, it may have been dynamically
		// injected by a developer, in which case it would be lacking a data-url
		// attribute and in need of enhancement.
		if ( page.length === 0 && dataUrl && !path.isPath( dataUrl ) ) {
			page = settings.pageContainer.children( "#" + dataUrl )
				.attr( "data-" + $.mobile.ns + "url", dataUrl )
		}

		// If we failed to find a page in the DOM, check the URL to see if it
		// refers to the first page in the application.
		if ( page.length === 0 && $.mobile.firstPage && absUrl && path.isFirstPageUrl( absUrl ) ) {
			page = $( $.mobile.firstPage );
		}

        /*
		// Reset base to the default document base.
		if ( base ) {
			base.reset();
		}
        */

        /*
		// If the page we are interested in is already in the DOM,
		// and the caller did not indicate that we should force a
		// reload of the file, we are done. Otherwise, track the
		// existing page as a duplicated.
		if ( page.length ) {
			if ( !settings.reloadPage ) {
				enhancePage( page, settings.role );
				deferred.resolve( absUrl, options, page );
				return deferred.promise();
			}
			dupCachedPage = page;
		}
        */

        var mpc = settings.pageContainer,
            pblEvent = new $.Event( "pagebeforeload" ),
            triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings };

        // Let listeners know we're about to load a page.
        mpc.trigger( pblEvent, triggerData );

        // If the default behavior is prevented, stop here!
        if( pblEvent.isDefaultPrevented() ){
            return deferred.promise();
        }

        if ( settings.showLoadMsg ) {

            // This configurable timeout allows cached pages a brief delay to load without showing a message
            var loadMsgDelay = setTimeout(function(){
                    $.mobile.showPageLoadingMsg();
                }, settings.loadMsgDelay ),

                // Shared logic for clearing timeout and removing message.
                hideMsg = function(){

                    // Stop message show timer
                    clearTimeout( loadMsgDelay );

                    // Hide loading message
                    $.mobile.hidePageLoadingMsg();
                };
        }

        setHtml(options.html);

        function setHtml( html ) {
            //pre-parse html to check for a data-url,
            //use it as the new fileUrl, base path, etc
            var all = $( "

" ),

                    //page title regexp
                    newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1,

                    // TODO handle dialogs again
                    pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ),
                    dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" );

            // data-url must be provided for the base tag so resource requests can be directed to the
            // correct url. loading into a temprorary element makes these requests immediately
            if( pageElemRegex.test( html )
                    && RegExp.$1
                    && dataUrlRegex.test( RegExp.$1 )
                    && RegExp.$1 ) {
                url = fileUrl = path.getFilePath( RegExp.$1 );
            }
            /*
            else{

            }
            */

            /*
            if ( base ) {
                base.set( fileUrl );
            }
            */

            //workaround to allow scripts to execute when included in page divs
            all.get( 0 ).innerHTML = html;
            page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first();

            //if page elem couldn't be found, create one and insert the body element's contents
            if( !page.length ){
                page = $( "
" + html.split( /<\/?body[^>]*>/gmi )[1] + "
" );
            }

            if ( newPageTitle && !page.jqmData( "title" ) ) {
                page.jqmData( "title", newPageTitle );
            }

            //rewrite src and href attrs to use a base url
            if( !$.support.dynamicBaseTag ) {
                var newPath = path.get( fileUrl );
                page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() {
                    var thisAttr = $( this ).is( '[href]' ) ? 'href' :
                            $(this).is('[src]') ? 'src' : 'action',
                        thisUrl = $( this ).attr( thisAttr );

                    // XXX_jblas: We need to fix this so that it removes the document
                    //            base URL, and then prepends with the new page URL.
                    //if full path exists and is same, chop it - helps IE out
                    thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' );

                    if( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
                        $( this ).attr( thisAttr, newPath + thisUrl );
                    }
                });
            }

            //append to page and enhance
            // TODO taging a page with external to make sure that embedded pages aren't removed
            //      by the various page handling code is bad. Having page handling code in many
            //      places is bad. Solutions post 1.0
            page
                .attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) )
                .attr( "data-" + $.mobile.ns + "external-page", true )
                .appendTo( settings.pageContainer );

            // wait for page creation to leverage options defined on widget
            page.one( 'pagecreate', $.mobile._bindPageRemove );

            enhancePage( page, settings.role );

            // Enhancing the page may result in new dialogs/sub pages being inserted
            // into the DOM. If the original absUrl refers to a sub-page, that is the
            // real page we are interested in.
            if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) {
                page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" );
            }

            //bind pageHide to removePage after it's hidden, if the page options specify to do so

            // Remove loading message.
            if ( settings.showLoadMsg ) {
                hideMsg();
            }

            // Add the page reference to our triggerData.
            triggerData.page = page;

            // Let listeners know the page loaded successfully.
            settings.pageContainer.trigger( "pageload", triggerData );

            deferred.resolve( absUrl, options, page, dupCachedPage );
        }

		return deferred.promise();
	};
    // copy original defaults
    $.mobile.loadPage.defaults = original_loadPage.defaults;

    function insertAsyncPage(data) {
        $.mobile.loadPage("inline://", {html: data})
            .done(function( url, options, newPage, dupCachedPage ) {
                options.duplicateCachedPage = dupCachedPage;
                $.mobile.changePage( newPage, options );
            });
    }

    window.Rho = window.Rho || {};
    window.Rho.jqm = $.mobile;
    // Rho: add insertAsyncPage callback for transitions
    window.Rho.insertAsyncPage = insertAsyncPage;

})(jQuery);
<|endoftext|>


<commit_before>/* ---------------------------------------------------- +/

## Location ##

Code related to the location template

/+ ---------------------------------------------------- */

//Template.location.created = function () {
//};

//Template.location.helpers({
    //myHelper: function () {
    //}
//});

Template.location.rendered = function () {
    // Fade in the main location wrapper
    $('.location').addClass('animated fadeIn92');

    /**
     * Setup image area
     */
    var width = 1800,
        height = 1080 - 100,
        svg = d3.select(".images").append("svg")
            .attr("class", 'svg-canvas')
            .attr("width", width)
            .attr("height", height);

    /**
     * Setup timeline area
     */
    var time = d3.select(".time").append("svg")
        .attr("class", 'time-canvas')
        .attr("width", width - 30)
        .attr("height", 300);

    /**
     * Setup big picture area
     */

    //bigPicSVG = d3.select(".big-picture").append("svg")
        //.attr("class", 'svg-big-picture')
        //.attr("width", 800)
        //.attr("height", 600);

    /**
     * We have to do our D3 stuff in here because of Meteor's client server
     * stuff. I don't exactly understand...
     *
     * TODO: Research
     */
    Deps.autorun(function () {
        /**
         * Wait for a half-second to let the collections load.
         *
         * TODO: figure out why we need to do this. Before I was doing this,
         * the image counts would get set three times with successively
         * increasing numbers.
         */
        window.setTimeout(function() {
            renderLocation();
        }, 500);

        function renderLocation() {
            /**
             * Render each image
             */
            var imagesCursor = Images.find();
            var imagesCount = imagesCursor.count();
            var images = imagesCursor.fetch();
            images = _.sortBy(images, function(image) {
                return image.date;
            });
            firstYear = _.first(images).date.substring(0,4);
            lastYear = _.last(images).date.substring(0,4);
            var timelineMargin = 50;
            widthInterval = parseInt((width - timelineMargin) / imagesCount);
            var firstImageWidth = images[0].thumbWidth;
            var lastImageWidth = images[parseInt(imagesCount - 1)].thumbWidth;
            _.each(images, function(image, i) {
                drawImage(svg, image, i, imagesCount, firstImageWidth, lastImageWidth, timelineMargin, widthInterval);
            });

            /**
             * Print the start and end years of the timeline
             */
            // Start
            time.append('svg:text')
                .attr("x", 10)
                .attr("y", 35)
                .attr("class", 'time-label-start')
                .text(firstYear);

            // End
            time.append('svg:text')
                .attr("x", 1675)
                .attr("y", 35)
                .attr("class", 'time-label-end')
                .text(lastYear);

            /**
             * Render the timeline handle
             */
            time.append('rect')
                .attr("width", "50")
                .attr("height", "50")
                .attr('class', 'time-handle-rect')
                .attr("x", 0)
                .attr("y", 0);

        }
    });

    /**
     * Render each image
     */
    function drawImage(svg, image, i, imagesCount, firstImageWidth, lastImageWidth, timelineMargin, widthInterval) {
        /**
         * Image properties
         */
        var imageBorder = 5;
        var delay = 2; // Milliseconds to delay the animation per image
        var dur = 500; // Milliseconds for the image animation

        /**
         * Image positioning
         */
        // The x calue for the first image at the left edge of the timeline
        var leftEdge = (timelineMargin / 2) - imageBorder;

        // Centers for the first and last images
        var firstCenterX = leftEdge + ( ( firstImageWidth + 15 ) / 2 );
        var lastCenterX = 1760 - ( ( lastImageWidth + 15 ) / 2 );

        var centerX;
        var translateX;
        if (i === 0) {
            centerX = firstCenterX;
            leftX = leftEdge;
            console.log('First leftX - ', leftX);
            console.log('First centerX - ', centerX);
            console.log('First image.thumbWidth - ', image.thumbWidth);
            translateX = leftX;
        }

        // The x value for the last image at the right edge of the timeline
        //
        // The last values account for the shadow offset in the group
        var rightEdge = (timelineMargin / 2) + 1760 - lastImageWidth - (imageBorder * 2) - 5;
        if (i == (imagesCount - 1)){
            leftX = rightEdge;
            centerX = lastCenterX;
            translateX = leftX;
            console.log('Last leftX - ', leftX);
            console.log('Last centerX - ', centerX);
            console.log('Last image.thumbWidth - ', image.thumbWidth);
        }
        //console.log('re', rightEdge);

        var centerGaps = (lastCenterX - firstCenterX) / ( imagesCount - 2 );

        // Find the proper interval between images, for the rest of the images
        var rightEdgeCenter = rightEdge + (firstImageWidth / 2);
        var centerInterval = (rightEdgeCenter - leftEdge) / (imagesCount - 2);

        if ((i !== 0) && (i != (imagesCount - 1))) {
            centerX = firstCenterX + (centerGaps * i);
            // 15 for white border and shadow
            leftX = centerX - ( ( parseInt( image.thumbWidth ) + 15 ) / 2);
            translateX = leftX;

            // Old way
            //leftX = leftEdge + (centerInterval * i) - ((image.thumbWidth + 15));
            //centerX = leftEdge + (centerInterval * i) - ((image.thumbWidth + 15) / 2);

        }

        // Put the images at the top of the timeline
        var bottomY = (835 - image.thumbHeight);
        // Adding dev height for order testing
        //var bottomY = (835 - image.thumbHeight - ( i * 5 ) - 60 );
        var translate = 'translate(' + translateX + ',' + bottomY + ')';

        /**
         * Picture group parent
         */
        var pictureGroup = svg.append("g")
            .attr('class', 'picture-group ' + 'picture-' + i)
            .attr('data-index', i)
            .attr('data-id', image._id)
            .attr('data-date', image.date)
            .attr('data-location', image.creationPlace)
            .attr('data-photographer', image.photographer)
            .attr('data-title', image.title)
            .attr('data-xw', image.expandedWidth)
            .attr('data-xh', image.expandedHeight)
            .attr('data-description', image.description)
            .attr("transform", function (){
                return translate;
            });

            /**
            * Picture drop shadow
            */
            var filter = pictureGroup.append("defs")
                .append("filter")
                .attr("id", "blur")
                .append("feGaussianBlur")
                .attr("stdDeviation", 5);
            pictureGroup.append('rect')
                .attr("x", 0)
                .attr("y", 0)
                .style('fill', '#000')
                .attr("width", image.thumbWidth + (imageBorder * 2))
                .attr("height", image.thumbHeight + (imageBorder * 2))
                .attr('class', 'child')
                .attr("filter", "url(#blur)");

            /**
            * Picture white border
            */
            pictureGroup.append('rect')
                // Positions are relative to the group
                .attr("x", (0 - imageBorder))
                .attr("y", (0 - imageBorder))
                .attr("width", image.thumbWidth + (imageBorder * 2))
                .attr("height", image.thumbHeight + (imageBorder * 2))
                .attr('class', 'child location-matte');

            // Image
            pictureGroup.append('image')
                .attr("xlink:href", "/images/thumbnails/" + image._id + ".jpg")
                .attr("data-id", image._id)
                .attr("data-location", image.generalLocationDs)
                //// Simulate scaling form the center of the image
                .attr("width", image.thumbWidth)
                .attr("height", image.thumbHeight)
                .attr('location', image.generalLocationDs)
                .attr('class', 'child');

        /**
         * Picture starting state
         *
         * Scale at 0
         * All child elements with an opacity of 0
         */
        pictureGroup
            .attr("transform", function (){
                transform = translate + ',scale(0)';
                return transform;
            });
        pictureGroup.selectAll('.child')
            .attr("opacity", "0");

        /**
         * Picture - Animate in.
         *
         * Scale and opacity at 1
         */
        pictureGroup
            .transition()
            .delay(i * delay) // Stagger the markers animating in
            .attr("transform", function (){
                transform = translate + ',scale(1)';
                return transform;
            })
            .duration(dur);
        pictureGroup.selectAll('.child')
            .transition()
            .delay(i * delay) // Stagger the markers animating in
            .attr("opacity", "1")
            .duration(dur);

        // Dev position helper
        var devGroup = svg.append("g");
        //var devRectLeft = devGroup.append('rect')
            //.attr("x", (leftX))
            //.attr("y", (835))
            //.attr("width", 5)
            //.attr("height", 40)
            //.attr('class', 'child dev-dot-left');

        // Left - white top
        var devRectLeft = devGroup.append('rect')
            .attr("x", (leftX))
            .attr("y", (780))
            .attr("width", 5)
            .attr("height", 20)
            .attr('class', 'child dev-dot-left');

        // Center - yellow bottom
        var devRectCenter = devGroup.append('rect')
            .attr("x", (centerX))
            .attr("y", (835))
            .attr("width", 5)
            .attr("height", 20)
            .attr('class', 'child dev-dot-center');

        // First - red
        if (i === 0) {
            devRectLeft
                .attr('class', 'child dev-dot-left dev-dot-left-one');
            devRectCenter
                .attr('class', 'child dev-dot-center dev-dot-center-one');
        }

        // 2nd - pink
        if (i === 1) {
            devRectLeft
                .attr('class', 'child dev-dot-left dev-dot-left-two');
            devRectCenter
                .attr('class', 'child dev-dot-center dev-dot-center-two');
        }

        // Last - green
        if (i == (imagesCount - 1)){
            devRectLeft
                .attr('class', 'child dev-dot-left dev-dot-left-last');
            devRectCenter
                .attr('class', 'child dev-dot-center dev-dot-center-last');
        }

    }
};

Template.location.events({
    // Desired functionality, but disabled for testing
    //'mousemove .container': function (e) {
    'click .container': function (e) {

        /**
         * Setup basic objects and widths
         */
        var timeline = $('.time-canvas');

        // Count elements in our SVG element to get number of images
        var imagesCount = d3.selectAll('.picture-group')[0].length;

        // Pixel interval between images
        var intervalWidth = (timeline.width() / imagesCount);

        // Mouse position relative to the timeline
        var posX = e.pageX - timeline.parent().offset().left;
        var posInterval = Math.floor(posX / intervalWidth);

        /**
         * Position the handle relative to our pointer event
         */
        var handle = d3.select('.time-handle-rect');
        var handleWidth = handle.attr('width');
        var handleWidthHalf = (handleWidth / 2);

        // Prevent the handle from going off the edge of the timeline
        if (posX < handleWidthHalf) {
            posX = (handleWidthHalf);
            posInterval = 1;
        }
        if (posX > 1760 - handleWidthHalf) {
            posX = (1760 - handleWidthHalf);
            posInterval = imagesCount;
        }

        // Set the middle of the handle to the mouse position
        handle.attr('x', (posX - handleWidthHalf));

        /**
         * Scale the images based on mouse position
         *
         * Make the images nearest the cursor the biggest
         */
        d3.selectAll('.picture-group').each( function(d, i){

            /**
             * Determine a scale value based on mouse position
             */
            i = Number(d3.select(this).attr("data-index"));
            var distance = posInterval - i;
            //var distanceScale;
            if (distance === 0) {
                distanceScale = 1;
            }
            else {
                var minVal = 0.3;
                var maxVal = 0.7;
                distanceScale = ( minVal + (maxVal - minVal) * (1 / (Math.abs(posInterval - i))));
            }

            /**
             * Transform the picture group
             */
            var pictureGroup = d3.select(this);
            var imageInGroup = pictureGroup.select('image');
            var imageHeight = imageInGroup
                .attr('height');
            var imageWidth = imageInGroup
                .attr('width');
            // Get the current transform object
            var t = d3.transform(pictureGroup.attr('transform'));
            // Set the scale value, without changing other attributes
            // This allows the image to stay at its current X,Y position
            // while scaling.
            t.scale = [distanceScale, distanceScale];
            var translateY;
            if (posInterval == i) {
                translateY = (835 - (imageHeight * distanceScale)) - 50;
            }
            else {
                translateY = (835 - (imageHeight * distanceScale));
            }
            // TODO: Figure out how to get the X values to scale with the scale() value
            //
            //var translateX = t.translate[0] + ((imageWidth / 2) * distanceScale);
            //var translateX = t.translate[0] + ((imageWidth / 2) * distanceScale);
            var translateX = t.translate[0];
            t.translate = [
                translateX, // Keep the X axis in placename
                translateY
            ];
            // Turn the transform back into a string for SVG
            transformString = t.toString();
            pictureGroup
                .transition()
                .duration(100)
                .ease('circle-out')
                .attr("transform", transformString);
        });

        /**
         * Display detail information about the photograph
         */
        // Get the image data from the thumbnail data objects
        hlImg = $('g[data-index=' + posInterval + ']');

        // Set the title
        hlImgTitle = hlImg.data('title');
        $('.image-detail h4').text(hlImgTitle);

        hlImgDescription = hlImg.data('description');
        $('.image-detail span.image-description').text(hlImgDescription);
        $('.image-detail span.image-location').text(hlImg.data('location'));
        $('.image-detail span.image-date').text(hlImg.data('date'));
        $('.image-detail span.image-photographer').text(hlImg.data('photographer'));

        hlImgId = hlImg.data('id');
        hlImgExWidth = hlImg.data('xw');
        hlImgExHeight = hlImg.data('xh');
        //bigPic = d3.select("g.big-picture");

        // Only change the image when we need to
        var imagePath = '/images/expanded/' + hlImgId + '.jpg';
        if ($('.big-picture-image').attr('src') != imagePath) {
            $('.big-picture-image').attr('src', imagePath).stop(true, true).hide().fadeIn(400);
            $('.big-picture-image').attr('height', (parseInt(hlImgExHeight) / 2));
        }

        /**
         * Dev mouse position data
         */
        $('.dev-mouse').html(
            'Rel pointer X = ' + posX + '
' +
            'Interval width = ' + intervalWidth + '
' +
            'Pos interval = ' + posInterval + '
'
        );

    },

    'click .back': function(e, instance){
        console.clear();
    },

    'click .delete': function(e, instance){
        var location = this;
        e.preventDefault();
        Meteor.call('removeLocation', location, function(error, result){
            alert('Location deleted.');
            Router.go('/');
        });
    }

});
<commit_msg>Cleaning up variables and comments<commit_after>/* ---------------------------------------------------- +/

## Location ##

Code related to the location template

/+ ---------------------------------------------------- */

//Template.location.created = function () {
//};

//Template.location.helpers({
    //myHelper: function () {
    //}
//});

Template.location.rendered = function () {
    // Fade in the main location wrapper
    $('.location').addClass('animated fadeIn92');

    /**
     * Setup image area
     */
    var width = 1800,
        height = 1080 - 100,
        svg = d3.select(".images").append("svg")
            .attr("class", 'svg-canvas')
            .attr("width", width)
            .attr("height", height);

    /**
     * Setup timeline area
     */
    var time = d3.select(".time").append("svg")
        .attr("class", 'time-canvas')
        .attr("width", width - 30)
        .attr("height", 300);

    /**
     * Setup big picture area
     */

    //bigPicSVG = d3.select(".big-picture").append("svg")
        //.attr("class", 'svg-big-picture')
        //.attr("width", 800)
        //.attr("height", 600);

    /**
     * We have to do our D3 stuff in here because of Meteor's client server
     * stuff. I don't exactly understand...
     *
     * TODO: Research
     */
    Deps.autorun(function () {
        /**
         * Wait for a half-second to let the collections load.
         *
         * TODO: figure out why we need to do this. Before I was doing this,
         * the image counts would get set three times with successively
         * increasing numbers.
         */
        window.setTimeout(function() {
            renderLocation();
        }, 500);

        function renderLocation() {
            /**
             * Render each image
             */
            var imagesCursor = Images.find();
            var imagesCount = imagesCursor.count();
            var images = imagesCursor.fetch();
            images = _.sortBy(images, function(image) {
                return image.date;
            });
            firstYear = _.first(images).date.substring(0,4);
            lastYear = _.last(images).date.substring(0,4);
            var timelineMargin = 50;
            widthInterval = parseInt((width - timelineMargin) / imagesCount);
            var firstImageWidth = images[0].thumbWidth;
            var lastImageWidth = images[parseInt(imagesCount - 1)].thumbWidth;
            _.each(images, function(image, i) {
                drawImage(svg, image, i, imagesCount, firstImageWidth, lastImageWidth, timelineMargin, widthInterval);
            });

            /**
             * Print the start and end years of the timeline
             */
            // Start
            time.append('svg:text')
                .attr("x", 10)
                .attr("y", 35)
                .attr("class", 'time-label-start')
                .text(firstYear);

            // End
            time.append('svg:text')
                .attr("x", 1675)
                .attr("y", 35)
                .attr("class", 'time-label-end')
                .text(lastYear);

            /**
             * Render the timeline handle
             */
            time.append('rect')
                .attr("width", "50")
                .attr("height", "50")
                .attr('class', 'time-handle-rect')
                .attr("x", 0)
                .attr("y", 0);

        }
    });

    /**
     * Render each image
     */
    function drawImage(svg, image, i, imagesCount, firstImageWidth, lastImageWidth, timelineMargin, widthInterval) {
        /**
         * Image properties
         */
        var imageBorder = 5;
        var delay = 2; // Milliseconds to delay the animation per image
        var dur = 500; // Milliseconds for the image animation
        var centerX;
        var translateX;

        /**
         * Image positioning
         */
        // This will be the X value for the first image at the left edge of the timeline.
        var timelineLeftEdge = (timelineMargin / 2);

        // The last values account for the shadow offset in the group
        // TODO - make this less of a hack
        var rightEdge = timelineLeftEdge + 1760 - lastImageWidth - ( timelineMargin / 2 ) + (imageBorder) + 10;

        // Centers for the first and last images
        // TODO - make 1760 a variable
        var firstCenterX = timelineLeftEdge + ( ( firstImageWidth + 15 ) / 2 );
        var lastCenterX = 1760 - ( ( lastImageWidth + 15 ) / 2 );

        // Values for the first image
        if (i === 0) {
            centerX = firstCenterX;
            leftX = timelineLeftEdge;
            translateX = leftX;
        }

        // Values for the last image
        if (i == (imagesCount - 1)){
            centerX = lastCenterX;
            leftX = rightEdge;
            translateX = leftX;
        }

        // Values for the rest of the image
        //
        // First find the proper interval between images, and then set the
        // position based on the i value
        //
        // 15 for white border and shadow
        // TODO make this gap a variable
        var centerInterval = (lastCenterX - firstCenterX) / ( imagesCount - 2 );
        if ((i !== 0) && (i != (imagesCount - 1))) {
            centerX = firstCenterX + (centerInterval * i);
            leftX = centerX - ( ( parseInt( image.thumbWidth ) + 15 ) / 2);
            translateX = leftX;
        }

        // Y Value for all images is the same
        //
        // This bottom aligns the images to the top of the timeline
        var bottomY = (835 - image.thumbHeight);

        // Start building the SVG translate command
        var translate = 'translate(' + translateX + ',' + bottomY + ')';

        /**
         * Picture group parent
         */
        var pictureGroup = svg.append("g")
            .attr('class', 'picture-group ' + 'picture-' + i)
            .attr('data-index', i)
            .attr('data-id', image._id)
            .attr('data-date', image.date)
            .attr('data-location', image.creationPlace)
            .attr('data-photographer', image.photographer)
            .attr('data-title', image.title)
            .attr('data-xw', image.expandedWidth)
            .attr('data-xh', image.expandedHeight)
            .attr('data-description', image.description)
            .attr("transform", function (){
                return translate;
            });

            /**
            * Picture drop shadow
            */
            var filter = pictureGroup.append("defs")
                .append("filter")
                .attr("id", "blur")
                .append("feGaussianBlur")
                .attr("stdDeviation", 5);
            pictureGroup.append('rect')
                .attr("x", 0)
                .attr("y", 0)
                .style('fill', '#000')
                .attr("width", image.thumbWidth + (imageBorder * 2))
                .attr("height", image.thumbHeight + (imageBorder * 2))
                .attr('class', 'child')
                .attr("filter", "url(#blur)");

            /**
            * Picture white border
            */
            pictureGroup.append('rect')
                // Positions are relative to the group
                .attr("x", (0 - imageBorder))
                .attr("y", (0 - imageBorder))
                .attr("width", image.thumbWidth + (imageBorder * 2))
                .attr("height", image.thumbHeight + (imageBorder * 2))
                .attr('class', 'child location-matte');

            // Image
            pictureGroup.append('image')
                .attr("xlink:href", "/images/thumbnails/" + image._id + ".jpg")
                .attr("data-id", image._id)
                .attr("data-location", image.generalLocationDs)
                //// Simulate scaling form the center of the image
                .attr("width", image.thumbWidth)
                .attr("height", image.thumbHeight)
                .attr('location', image.generalLocationDs)
                .attr('class', 'child');

        /**
         * Picture starting state
         *
         * Scale at 0
         * All child elements with an opacity of 0
         */
        pictureGroup
            .attr("transform", function (){
                transform = translate + ',scale(0)';
                return transform;
            });
        pictureGroup.selectAll('.child')
            .attr("opacity", "0");

        /**
         * Picture - Animate in.
         *
         * Scale and opacity at 1
         */
        pictureGroup
            .transition()
            .delay(i * delay) // Stagger the markers animating in
            .attr("transform", function (){
                transform = translate + ',scale(1)';
                return transform;
            })
            .duration(dur);
        pictureGroup.selectAll('.child')
            .transition()
            .delay(i * delay) // Stagger the markers animating in
            .attr("opacity", "1")
            .duration(dur);

        // Dev position helper
        var devGroup = svg.append("g");
        //var devRectLeft = devGroup.append('rect')
            //.attr("x", (leftX))
            //.attr("y", (835))
            //.attr("width", 5)
            //.attr("height", 40)
            //.attr('class', 'child dev-dot-left');

        // Left - white top
        var devRectLeft = devGroup.append('rect')
            .attr("x", (leftX))
            .attr("y", (780))
            .attr("width", 5)
            .attr("height", 20)
            .attr('class', 'child dev-dot-left');

        // Center - yellow bottom
        var devRectCenter = devGroup.append('rect')
            .attr("x", (centerX))
            .attr("y", (835))
            .attr("width", 5)
            .attr("height", 20)
            .attr('class', 'child dev-dot-center');

        // First - red
        if (i === 0) {
            devRectLeft
                .attr('class', 'child dev-dot-left dev-dot-left-one');
            devRectCenter
                .attr('class', 'child dev-dot-center dev-dot-center-one');
        }

        // 2nd - pink
        if (i === 1) {
            devRectLeft
                .attr('class', 'child dev-dot-left dev-dot-left-two');
            devRectCenter
                .attr('class', 'child dev-dot-center dev-dot-center-two');
        }

        // Last - green
        if (i == (imagesCount - 1)){
            devRectLeft
                .attr('class', 'child dev-dot-left dev-dot-left-last');
            devRectCenter
                .attr('class', 'child dev-dot-center dev-dot-center-last');
        }

    }
};

Template.location.events({
    // Desired functionality, but disabled for testing
    //'mousemove .container': function (e) {
    'click .container': function (e) {

        /**
         * Setup basic objects and widths
         */
        var timeline = $('.time-canvas');

        // Count elements in our SVG element to get number of images
        var imagesCount = d3.selectAll('.picture-group')[0].length;

        // Pixel interval between images
        var intervalWidth = (timeline.width() / imagesCount);

        // Mouse position relative to the timeline
        var posX = e.pageX - timeline.parent().offset().left;
        var posInterval = Math.floor(posX / intervalWidth);

        /**
         * Position the handle relative to our pointer event
         */
        var handle = d3.select('.time-handle-rect');
        var handleWidth = handle.attr('width');
        var handleWidthHalf = (handleWidth / 2);

        // Prevent the handle from going off the edge of the timeline
        if (posX < handleWidthHalf) {
            posX = (handleWidthHalf);
            posInterval = 1;
        }
        if (posX > 1760 - handleWidthHalf) {
            posX = (1760 - handleWidthHalf);
            posInterval = imagesCount;
        }

        // Set the middle of the handle to the mouse position
        handle.attr('x', (posX - handleWidthHalf));

        /**
         * Scale the images based on mouse position
         *
         * Make the images nearest the cursor the biggest
         */
        d3.selectAll('.picture-group').each( function(d, i){

            /**
             * Determine a scale value based on mouse position
             */
            i = Number(d3.select(this).attr("data-index"));
            var distance = posInterval - i;
            //var distanceScale;
            if (distance === 0) {
                distanceScale = 1;
            }
            else {
                var minVal = 0.3;
                var maxVal = 0.7;
                distanceScale = ( minVal + (maxVal - minVal) * (1 / (Math.abs(posInterval - i))));
            }

            /**
             * Transform the picture group
             */
            var pictureGroup = d3.select(this);
            var imageInGroup = pictureGroup.select('image');
            var imageHeight = imageInGroup
                .attr('height');
            var imageWidth = imageInGroup
                .attr('width');
            // Get the current transform object
            var t = d3.transform(pictureGroup.attr('transform'));
            // Set the scale value, without changing other attributes
            // This allows the image to stay at its current X,Y position
            // while scaling.
            t.scale = [distanceScale, distanceScale];
            var translateY;
            if (posInterval == i) {
                translateY = (835 - (imageHeight * distanceScale)) - 50;
            }
            else {
                translateY = (835 - (imageHeight * distanceScale));
            }
            // TODO: Figure out how to get the X values to scale with the scale() value
            //
            //var translateX = t.translate[0] + ((imageWidth / 2) * distanceScale);
            //var translateX = t.translate[0] + ((imageWidth / 2) * distanceScale);
            var translateX = t.translate[0];
            t.translate = [
                translateX, // Keep the X axis in placename
                translateY
            ];
            // Turn the transform back into a string for SVG
            transformString = t.toString();
            pictureGroup
                .transition()
                .duration(100)
                .ease('circle-out')
                .attr("transform", transformString);
        });

        /**
         * Display detail information about the photograph
         */
        // Get the image data from the thumbnail data objects
        hlImg = $('g[data-index=' + posInterval + ']');

        // Set the title
        hlImgTitle = hlImg.data('title');
        $('.image-detail h4').text(hlImgTitle);

        hlImgDescription = hlImg.data('description');
        $('.image-detail span.image-description').text(hlImgDescription);
        $('.image-detail span.image-location').text(hlImg.data('location'));
        $('.image-detail span.image-date').text(hlImg.data('date'));
        $('.image-detail span.image-photographer').text(hlImg.data('photographer'));

        hlImgId = hlImg.data('id');
        hlImgExWidth = hlImg.data('xw');
        hlImgExHeight = hlImg.data('xh');
        //bigPic = d3.select("g.big-picture");

        // Only change the image when we need to
        var imagePath = '/images/expanded/' + hlImgId + '.jpg';
        if ($('.big-picture-image').attr('src') != imagePath) {
            $('.big-picture-image').attr('src', imagePath).stop(true, true).hide().fadeIn(400);
            $('.big-picture-image').attr('height', (parseInt(hlImgExHeight) / 2));
        }

        /**
         * Dev mouse position data
         */
        $('.dev-mouse').html(
            'Rel pointer X = ' + posX + '
' +
            'Interval width = ' + intervalWidth + '
' +
            'Pos interval = ' + posInterval + '
'
        );

    },

    'click .back': function(e, instance){
        console.clear();
    },

    'click .delete': function(e, instance){
        var location = this;
        e.preventDefault();
        Meteor.call('removeLocation', location, function(error, result){
            alert('Location deleted.');
            Router.go('/');
        });
    }

});
<|endoftext|>


<commit_before>module.exports = createTorrent

module.exports.announceList = [
  [ 'udp://tracker.publicbt.com:80' ],
  [ 'udp://tracker.openbittorrent.com:80' ],
  [ 'udp://tracker.webtorrent.io:80' ],
  [ 'wss://tracker.webtorrent.io' ] // For WebRTC peers (see: WebTorrent.io)
]

module.exports.parseInput = parseInput

var bencode = require('bencode')
var BlockStream = require('block-stream')
var calcPieceLength = require('piece-length')
var corePath = require('path')
var FileReadStream = require('filestream/read')
var flatten = require('flatten')
var fs = require('fs')
var MultiStream = require('multistream')
var once = require('once')
var parallel = require('run-parallel')
var sha1 = require('git-sha1')
var stream = require('stream')
var Transform = stream.Transform

/**
 * Create a torrent.
 * @param  {string|File|FileList|Buffer|Stream|Array.<File|Buffer|Stream>} input
 * @param  {Object} opts
 * @param  {string=} opts.name
 * @param  {Date=} opts.creationDate
 * @param  {string=} opts.comment
 * @param  {string=} opts.createdBy
 * @param  {boolean|number=} opts.private
 * @param  {number=} opts.pieceLength
 * @param  {Array.<Array.<string>>=} opts.announceList
 * @param  {Array.<string>=} opts.urlList
 * @param  {function} cb
 * @return {Buffer} buffer of .torrent file data
 */
function createTorrent (input, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }
  if (!opts) opts = {}
  parseInput(input, opts, function (err, files) {
    if (err) return cb(err)
    onFiles(files, opts, cb)
  })
}

// TODO: support an array of paths
function parseInput (input, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }
  if (!opts) opts = {}

  if (isFileList(input))
    input = Array.prototype.slice.call(input)

  if (isBlob(input) || Buffer.isBuffer(input) || isReadable(input))
    input = [ input ]

  var files
  if (Array.isArray(input) && input.length > 0) {
    opts.name = opts.name || input[0].name
    if (opts.name === undefined)
      throw new Error('missing option \'name\' and unable to infer it from input[0].name')

    // If there's just one file, allow the name to be set by `opts.name`
    if (input.length === 1 && !input[0].name) input[0].name = opts.name

    files = input.map(function (item) {
      var file = {}

      if (isBlob(item)) {
        file.getStream = getBlobStream(item)
        file.length = item.size
      } else if (Buffer.isBuffer(item)) {
        file.getStream = getBufferStream(item)
        file.length = item.length
      } else if (isReadable(item)) {
        if (!opts.pieceLength)
          throw new Error('must specify `pieceLength` option if input is Stream')
        file.getStream = getStreamStream(item, file)
        file.length = 0
      } else {
        throw new Error('input must contain only File|Blob|Buffer|Stream objects')
      }

      if (!item.name) throw new Error('missing requied `name` property on input')
      file.path = [ item.name ]
      return file
    })
    process.nextTick(function () {
      cb(null, files)
    })
  } else if (typeof input === 'string') {
    opts.name = opts.name || corePath.basename(input)

    traversePath(getFileInfo, input, function (err, files) {
      if (err) return cb(err)

      if (Array.isArray(files)) files = flatten(files)
      else files = [ files ]

      var dirName = corePath.normalize(input) + corePath.sep
      files.forEach(function (file) {
        file.getStream = getFilePathStream(file.path)
        file.path = file.path.replace(dirName, '').split(corePath.sep)
      })

      cb(null, files)
    })
  } else {
    throw new Error('invalid input type')
  }
}

function getFileInfo (path, cb) {
  cb = once(cb)
  fs.stat(path, function (err, stat) {
    if (err) return cb(err)
    var info = {
      length: stat.size,
      path: path
    }
    cb(null, info)
  })
}

function traversePath (fn, path, cb) {
  fs.readdir(path, function (err, entries) {
    if (err && err.code === 'ENOTDIR') {
      // this is a file
      fn(path, cb)
    } else if (err) {
      // real error
      cb(err)
    } else {
      // this is a folder
      parallel(entries.map(function (entry) {
        return function (cb) {
          traversePath(fn, corePath.join(path, entry), cb)
        }
      }), cb)
    }
  })
}

function getPieceList (files, pieceLength, cb) {
  cb = once(cb)
  var pieces = '' // hex string
  var length = 0

  var streams = files.map(function (file) {
    return file.getStream
  })

  new MultiStream(streams)
    .pipe(new BlockStream(pieceLength, { nopad: true }))
    .on('data', function (chunk) {
      length += chunk.length
      pieces += sha1(chunk)
    })
    .on('end', function () {
      cb(null, new Buffer(pieces, 'hex'), length)
    })
    .on('error', cb)
}

function onFiles (files, opts, cb) {
  var announceList = opts.announceList !== undefined
    ? opts.announceList
    : module.exports.announceList // default

  var torrent = {
    info: {
      name: opts.name
    },
    announce: announceList[0][0],
    'announce-list': announceList,
    'creation date': Number(opts.creationDate) || Date.now(),
    encoding: 'UTF-8'
  }

  if (opts.comment !== undefined)
    torrent.info.comment = opts.comment

  if (opts.createdBy !== undefined)
    torrent.info['created by'] = opts.createdBy

  if (opts.private !== undefined)
    torrent.info.private = Number(opts.private)

  if (opts.urlList !== undefined)
    torrent['url-list'] = opts.urlList

  var singleFile = files.length === 1

  var pieceLength = opts.pieceLength || calcPieceLength(files.reduce(sumLength, 0))
  torrent.info['piece length'] = pieceLength

  getPieceList(files, pieceLength, function (err, pieces, torrentLength) {
    if (err) return cb(err)
    torrent.info.pieces = pieces

    files.forEach(function (file) {
      delete file.getStream
    })

    if (!singleFile) {
      torrent.info.files = files
    } else {
      torrent.info.length = torrentLength
    }

    cb(null, bencode.encode(torrent))
  })
}

/**
 * Accumulator to sum file lengths
 * @param  {number} sum
 * @param  {Object} file
 * @return {number}
 */
function sumLength (sum, file) {
  return sum + file.length
}

/**
 * Check if `obj` is a W3C `Blob` object (which `File` inherits from)
 * @param  {*} obj
 * @return {boolean}
 */
function isBlob (obj) {
  return typeof Blob !== 'undefined' && obj instanceof Blob
}

/**
 * Check if `obj` is a W3C `FileList` object
 * @param  {*} obj
 * @return {boolean}
 */
function isFileList (obj) {
  return typeof FileList === 'function' && obj instanceof FileList
}

/**
 * Check if `obj` is a node Readable stream
 * @param  {*} obj
 * @return {boolean}
 */
function isReadable (obj) {
  return typeof obj === 'object' && typeof obj.pipe === 'function'
}

/**
 * Convert a `File` to a lazy readable stream.
 * @param  {File|Blob} file
 * @return {function}
 */
function getBlobStream (file) {
  return function () {
    return new FileReadStream(file)
  }
}

/**
 * Convert a `Buffer` to a lazy readable stream.
 * @param  {Buffer} buffer
 * @return {function}
 */
function getBufferStream (buffer) {
  return function () {
    var s = new stream.PassThrough()
    s.end(buffer)
    return s
  }
}

/**
 * Convert a file path to a lazy readable stream.
 * @param  {string} path
 * @return {function}
 */
function getFilePathStream (path) {
  return function () {
    return fs.createReadStream(path)
  }
}

/**
 * Convert a readable stream to a lazy readable stream. Adds instrumentation to track
 * the number of bytes in the stream and set `file.length`.
 *
 * @param  {Stream} stream
 * @param  {Object} file
 * @return {function}
 */
function getStreamStream (stream, file) {
  return function () {
    var counter = new Transform()
    counter._transform = function (buf, enc, done) {
      file.length += buf.length
      this.push(buf)
      done()
    }
    stream.pipe(counter)
    return counter
  }
}
<commit_msg>make extra trailing slash in path irrelevant<commit_after>module.exports = createTorrent

module.exports.announceList = [
  [ 'udp://tracker.publicbt.com:80' ],
  [ 'udp://tracker.openbittorrent.com:80' ],
  [ 'udp://tracker.webtorrent.io:80' ],
  [ 'wss://tracker.webtorrent.io' ] // For WebRTC peers (see: WebTorrent.io)
]

module.exports.parseInput = parseInput

var bencode = require('bencode')
var BlockStream = require('block-stream')
var calcPieceLength = require('piece-length')
var corePath = require('path')
var FileReadStream = require('filestream/read')
var flatten = require('flatten')
var fs = require('fs')
var MultiStream = require('multistream')
var once = require('once')
var parallel = require('run-parallel')
var sha1 = require('git-sha1')
var stream = require('stream')
var Transform = stream.Transform

/**
 * Create a torrent.
 * @param  {string|File|FileList|Buffer|Stream|Array.<File|Buffer|Stream>} input
 * @param  {Object} opts
 * @param  {string=} opts.name
 * @param  {Date=} opts.creationDate
 * @param  {string=} opts.comment
 * @param  {string=} opts.createdBy
 * @param  {boolean|number=} opts.private
 * @param  {number=} opts.pieceLength
 * @param  {Array.<Array.<string>>=} opts.announceList
 * @param  {Array.<string>=} opts.urlList
 * @param  {function} cb
 * @return {Buffer} buffer of .torrent file data
 */
function createTorrent (input, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }
  if (!opts) opts = {}
  parseInput(input, opts, function (err, files) {
    if (err) return cb(err)
    onFiles(files, opts, cb)
  })
}

// TODO: support an array of paths
function parseInput (input, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }
  if (!opts) opts = {}

  if (isFileList(input))
    input = Array.prototype.slice.call(input)

  if (isBlob(input) || Buffer.isBuffer(input) || isReadable(input))
    input = [ input ]

  var files
  if (Array.isArray(input) && input.length > 0) {
    opts.name = opts.name || input[0].name
    if (opts.name === undefined)
      throw new Error('missing option \'name\' and unable to infer it from input[0].name')

    // If there's just one file, allow the name to be set by `opts.name`
    if (input.length === 1 && !input[0].name) input[0].name = opts.name

    files = input.map(function (item) {
      var file = {}

      if (isBlob(item)) {
        file.getStream = getBlobStream(item)
        file.length = item.size
      } else if (Buffer.isBuffer(item)) {
        file.getStream = getBufferStream(item)
        file.length = item.length
      } else if (isReadable(item)) {
        if (!opts.pieceLength)
          throw new Error('must specify `pieceLength` option if input is Stream')
        file.getStream = getStreamStream(item, file)
        file.length = 0
      } else {
        throw new Error('input must contain only File|Blob|Buffer|Stream objects')
      }

      if (!item.name) throw new Error('missing requied `name` property on input')
      file.path = [ item.name ]
      return file
    })
    process.nextTick(function () {
      cb(null, files)
    })
  } else if (typeof input === 'string') {
    opts.name = opts.name || corePath.basename(input)

    traversePath(getFileInfo, input, function (err, files) {
      if (err) return cb(err)

      if (Array.isArray(files)) files = flatten(files)
      else files = [ files ]

      var dirName = corePath.normalize(input)
      if (dirName[dirName.length - 1] !== corePath.sep) dirName += corePath.sep

      files.forEach(function (file) {
        file.getStream = getFilePathStream(file.path)
        file.path = file.path.replace(dirName, '').split(corePath.sep)
      })

      cb(null, files)
    })
  } else {
    throw new Error('invalid input type')
  }
}

function getFileInfo (path, cb) {
  cb = once(cb)
  fs.stat(path, function (err, stat) {
    if (err) return cb(err)
    var info = {
      length: stat.size,
      path: path
    }
    cb(null, info)
  })
}

function traversePath (fn, path, cb) {
  fs.readdir(path, function (err, entries) {
    if (err && err.code === 'ENOTDIR') {
      // this is a file
      fn(path, cb)
    } else if (err) {
      // real error
      cb(err)
    } else {
      // this is a folder
      parallel(entries.map(function (entry) {
        return function (cb) {
          traversePath(fn, corePath.join(path, entry), cb)
        }
      }), cb)
    }
  })
}

function getPieceList (files, pieceLength, cb) {
  cb = once(cb)
  var pieces = '' // hex string
  var length = 0

  var streams = files.map(function (file) {
    return file.getStream
  })

  new MultiStream(streams)
    .pipe(new BlockStream(pieceLength, { nopad: true }))
    .on('data', function (chunk) {
      length += chunk.length
      pieces += sha1(chunk)
    })
    .on('end', function () {
      cb(null, new Buffer(pieces, 'hex'), length)
    })
    .on('error', cb)
}

function onFiles (files, opts, cb) {
  var announceList = opts.announceList !== undefined
    ? opts.announceList
    : module.exports.announceList // default

  var torrent = {
    info: {
      name: opts.name
    },
    announce: announceList[0][0],
    'announce-list': announceList,
    'creation date': Number(opts.creationDate) || Date.now(),
    encoding: 'UTF-8'
  }

  if (opts.comment !== undefined)
    torrent.info.comment = opts.comment

  if (opts.createdBy !== undefined)
    torrent.info['created by'] = opts.createdBy

  if (opts.private !== undefined)
    torrent.info.private = Number(opts.private)

  if (opts.urlList !== undefined)
    torrent['url-list'] = opts.urlList

  var singleFile = files.length === 1

  var pieceLength = opts.pieceLength || calcPieceLength(files.reduce(sumLength, 0))
  torrent.info['piece length'] = pieceLength

  getPieceList(files, pieceLength, function (err, pieces, torrentLength) {
    if (err) return cb(err)
    torrent.info.pieces = pieces

    files.forEach(function (file) {
      delete file.getStream
    })

    if (!singleFile) {
      torrent.info.files = files
    } else {
      torrent.info.length = torrentLength
    }

    cb(null, bencode.encode(torrent))
  })
}

/**
 * Accumulator to sum file lengths
 * @param  {number} sum
 * @param  {Object} file
 * @return {number}
 */
function sumLength (sum, file) {
  return sum + file.length
}

/**
 * Check if `obj` is a W3C `Blob` object (which `File` inherits from)
 * @param  {*} obj
 * @return {boolean}
 */
function isBlob (obj) {
  return typeof Blob !== 'undefined' && obj instanceof Blob
}

/**
 * Check if `obj` is a W3C `FileList` object
 * @param  {*} obj
 * @return {boolean}
 */
function isFileList (obj) {
  return typeof FileList === 'function' && obj instanceof FileList
}

/**
 * Check if `obj` is a node Readable stream
 * @param  {*} obj
 * @return {boolean}
 */
function isReadable (obj) {
  return typeof obj === 'object' && typeof obj.pipe === 'function'
}

/**
 * Convert a `File` to a lazy readable stream.
 * @param  {File|Blob} file
 * @return {function}
 */
function getBlobStream (file) {
  return function () {
    return new FileReadStream(file)
  }
}

/**
 * Convert a `Buffer` to a lazy readable stream.
 * @param  {Buffer} buffer
 * @return {function}
 */
function getBufferStream (buffer) {
  return function () {
    var s = new stream.PassThrough()
    s.end(buffer)
    return s
  }
}

/**
 * Convert a file path to a lazy readable stream.
 * @param  {string} path
 * @return {function}
 */
function getFilePathStream (path) {
  return function () {
    return fs.createReadStream(path)
  }
}

/**
 * Convert a readable stream to a lazy readable stream. Adds instrumentation to track
 * the number of bytes in the stream and set `file.length`.
 *
 * @param  {Stream} stream
 * @param  {Object} file
 * @return {function}
 */
function getStreamStream (stream, file) {
  return function () {
    var counter = new Transform()
    counter._transform = function (buf, enc, done) {
      file.length += buf.length
      this.push(buf)
      done()
    }
    stream.pipe(counter)
    return counter
  }
}
<|endoftext|>


<commit_before>// http://eslint.org/docs/user-guide/configuring

module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    sourceType: 'module'
  },
  env: {
    browser: true,
  },
  extends: 'airbnb-base',
  // required to lint *.vue files
  plugins: [
    'html'
  ],
  // check if imports actually resolve
  'settings': {
    'import/resolver': {
      'webpack': {
        'config': 'build/webpack.base.conf.js'
      }
    }
  },
  // add your custom rules here
  'rules': {
    // don't require .vue extension when importing
    'import/extensions': ['error', 'always', {
      'js': 'never',
      'vue': 'never'
    }],
    // allow optionalDependencies
    'import/no-extraneous-dependencies': ['error', {
      'optionalDependencies': ['test/unit/index.js']
    }],
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
  }
}
<commit_msg>修改eslint<commit_after>// http://eslint.org/docs/user-guide/configuring

module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    sourceType: 'module'
  },
  env: {
    browser: true,
  },
  extends: 'airbnb-base',
  // required to lint *.vue files
  plugins: [
    'html'
  ],
  // check if imports actually resolve
  'settings': {
    'import/resolver': {
      'webpack': {
        'config': 'build/webpack.base.conf.js'
      }
    }
  },
  // add your custom rules here
  'rules': {
    // don't require .vue extension when importing
    'import/extensions': ['error', 'always', {
      'js': 'never',
      'vue': 'never'
    }],
    // allow optionalDependencies
    'import/no-extraneous-dependencies': ['error', {
      'optionalDependencies': ['test/unit/index.js']
    }],
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,

    // allow use console
    'no-console': 0,

    // use 4 spaces indent
    'indent': ["error", 4]
  }
}
<|endoftext|>


<commit_before>'use strict';

require('colors');
var program = require('commander');

module.exports = function (argv) {

  require('./lib/notifier.js')('v11', function() {
    program
      .version(require('./package.json').version);

    program
      .command('create <siteName>')
      .description('Create a new webhook site')
      .action(function (siteName) {
        require('./lib/create.js')({
          siteName: siteName.toLowerCase()
        });
      });

    program
      .command('init <siteName>')
      .description('Initializes a site with configuration files')
      .action(function (siteName) {
        require('./lib/init.js')({
          siteName: siteName.toLowerCase()
        });
      });

    program
      .command('preset-build')
      .description('Generates a preset-data.json file from a webhook directory')
      .action(function () {
        require('./lib/preset-build.js')();
      });

    program
      .command('update')
      .description('Updates a webhook site with the latest generate code')
      .action(function () {
        require('./lib/update.js')();
      });

    program
      .command('push')
      .description('Push webhook directory to server')
      .action(function () {
        require('./lib/push.js')();
      });

    program
      .command('deploy')
      .description('Push webhook directory to server')
      .action(function () {
        require('./lib/push.js')();
      });

    program
      .command('serve')
      .description('Serves a webhook site locally')
      .action(function () {
        require('./lib/serve.js')();
      });

    program
      .parse(argv);

    if (!program.args.length) program.help();
  });
};
<commit_msg>list all sites owner/user on<commit_after>'use strict';

require('colors');
var program = require('commander');

module.exports = function (argv) {

  require('./lib/notifier.js')('v11', function() {
    program
      .version(require('./package.json').version);

    program
      .command('create <siteName>')
      .description('Create a new webhook site')
      .action(function (siteName) {
        require('./lib/create.js')({
          siteName: siteName.toLowerCase()
        });
      });

    program
      .command('init <siteName>')
      .description('Initializes a site with configuration files')
      .action(function (siteName) {
        require('./lib/init.js')({
          siteName: siteName.toLowerCase()
        });
      });

    program
      .command('list-sites')
      .description('Lists all the sites that the user is an owner/user on')
      .action(function () {
        require('./lib/list-sites.js')();
      });

    program
      .command('preset-build')
      .description('Generates a preset-data.json file from a webhook directory')
      .action(function () {
        require('./lib/preset-build.js')();
      });

    program
      .command('update')
      .description('Updates a webhook site with the latest generate code')
      .action(function () {
        require('./lib/update.js')();
      });

    program
      .command('push')
      .description('Push webhook directory to server')
      .action(function () {
        require('./lib/push.js')();
      });

    program
      .command('deploy')
      .description('Push webhook directory to server')
      .action(function () {
        require('./lib/push.js')();
      });

    program
      .command('serve')
      .description('Serves a webhook site locally')
      .action(function () {
        require('./lib/serve.js')();
      });

    program
      .parse(argv);

    if (!program.args.length) program.help();
  });
};
<|endoftext|>


<commit_before>var babelify = require('babelify');
var brfs = require('brfs');
var browserify = require('browserify');
var chalk = require('chalk');
var connect = require('gulp-connect');
var del = require('del');
var gutil = require('gulp-util');
var less = require('gulp-less');
var merge = require('merge-stream');
var plumber = require('gulp-plumber');
var shell = require('gulp-shell');
var source = require('vinyl-source-stream');
var watchify = require('watchify');
var xtend = require('xtend');

module.exports = function (gulp) {
	function doBundle (target, name, dest) {
		return target.bundle()
			.on('error', gutil.log.bind(gutil, 'Browserify Error'))
			.pipe(source(name))
			.pipe(gulp.dest(dest))
			.pipe(connect.reload());
	}

	function watchBundle (bundle, name, dest) {
		return watchify(bundle)
			.on('log', function (message) { gutil.log(chalk.grey(message)); })
			.on('time', function (time) {
				gutil.log(chalk.green('Application built in ' + (Math.round(time / 10) / 100) + 's'));
			})
			.on('update', function (ids) {
				var changed = ids.map(function (x) {
					return chalk.blue(x.replace(__dirname, ''));
				});

				if (changed.length > 1) {
					gutil.log(changed.length + ' scripts updated:\n* ' + changed.join('\n* ') + '\nrebuilding...');
				} else {
					gutil.log(changed[0] + ' updated, rebuilding...');
				}

				doBundle(bundle, name, dest);
			});
	}

	function buildApp (entries, transforms, dest, watch) {
		var opts = xtend(watch && watchify.args, {
			entries: entries,
			debug: process.env.NODE_ENV !== 'production'
		});

		var app = browserify(opts);
		var react = browserify();

		transforms.forEach(function (target) {
			app.transform(target);
		});

		['react', 'react/addons'].forEach(function (pkg) {
			app.exclude(pkg);
			react.require(pkg);
		});

		if (watch) {
			watchBundle(app, 'app.js', dest);
		}

		return merge(doBundle(react, 'react.js', dest), doBundle(app, 'app.js', dest));
	}

	function plumb (src, transforms, dest) {
		var stream = gulp.src(src);

		transforms.forEach(function (transform) {
			stream = stream.pipe(transform());
		});

		return stream.pipe(gulp.dest(dest)).pipe(connect.reload());
	}

	var babelifyTransform = babelify.configure({
		plugins: [require('babel-plugin-object-assign')]
	});

	// Build
	gulp.task('fonts', plumb.bind(null, 'src/fonts/**', [], 'www/fonts'));
	gulp.task('html', plumb.bind(null, 'src/index.html', [], 'www'));
	gulp.task('images', plumb.bind(null, 'src/img/**', [], 'www/img'));
	gulp.task('less', plumb.bind(null, 'src/css/app.less', [less], 'www/css'));
	gulp.task('scripts', buildApp.bind(null, ['./src/js/app.js'], [babelifyTransform, brfs], './www/js'));
	gulp.task('scripts-watch', buildApp.bind(null, ['./src/js/app.js'], [babelifyTransform, brfs], './www/js', true));

	gulp.task('clean', function () { return del(['./www/*']); });
	gulp.task('build-assets', ['html', 'images', 'fonts', 'less']);
	gulp.task('build', ['build-assets', 'scripts']);
	gulp.task('watch', ['build-assets', 'scripts-watch'], function () {
		gulp.watch(['src/index.html'], ['html']);
		gulp.watch(['src/css/**/*.less'], ['less']);
		gulp.watch(['src/img/**/*.*'], ['images']);
		gulp.watch(['src/fonts/**/*.*'], ['fonts']);
	});

	// Development
	gulp.task('serve', function () {
		connect.server({
			root: 'www',
			port: 8000,
			livereload: true
		});
	});

	gulp.task('dev', ['serve', 'watch']);

	// Cordova
	gulp.task('prepare', ['build'], function () {
		return gulp.src('').pipe(plumber()).pipe(shell(['cordova prepare'], { cwd: __dirname }));
	});
};
<commit_msg>Simplifying and clarifying Browserify compiler errors<commit_after>var babelify = require('babelify');
var brfs = require('brfs');
var browserify = require('browserify');
var chalk = require('chalk');
var connect = require('gulp-connect');
var del = require('del');
var gutil = require('gulp-util');
var less = require('gulp-less');
var merge = require('merge-stream');
var plumber = require('gulp-plumber');
var shell = require('gulp-shell');
var source = require('vinyl-source-stream');
var watchify = require('watchify');
var xtend = require('xtend');

module.exports = function (gulp) {
	function doBundle (target, name, dest) {
		return target.bundle()
			.on('error', function(err) {
				var parts = err.message.split('.js: ');
				var br = '\n           ';
				var msg = parts.length === 2 ? chalk.red('Browserify Error in ') + chalk.red.underline(parts[0] + '.js') + br + parts[1] : chalk.red('Browserify Error:') + br + err.message;
				gutil.log(msg);
			})
			.pipe(source(name))
			.pipe(gulp.dest(dest))
			.pipe(connect.reload());
	}

	function watchBundle (bundle, name, dest) {
		return watchify(bundle)
			.on('log', function (message) { gutil.log(chalk.grey(message)); })
			.on('time', function (time) {
				gutil.log(chalk.green('Application built in ' + (Math.round(time / 10) / 100) + 's'));
			})
			.on('update', function (ids) {
				var changed = ids.map(function (x) {
					return chalk.blue(x.replace(__dirname, ''));
				});

				if (changed.length > 1) {
					gutil.log(changed.length + ' scripts updated:\n* ' + changed.join('\n* ') + '\nrebuilding...');
				} else {
					gutil.log(changed[0] + ' updated, rebuilding...');
				}

				doBundle(bundle, name, dest);
			});
	}

	function buildApp (entries, transforms, dest, watch) {
		var opts = xtend(watch && watchify.args, {
			entries: entries,
			debug: process.env.NODE_ENV !== 'production'
		});

		var app = browserify(opts);
		var react = browserify();

		transforms.forEach(function (target) {
			app.transform(target);
		});

		['react', 'react/addons'].forEach(function (pkg) {
			app.exclude(pkg);
			react.require(pkg);
		});

		if (watch) {
			watchBundle(app, 'app.js', dest);
		}

		return merge(doBundle(react, 'react.js', dest), doBundle(app, 'app.js', dest));
	}

	function plumb (src, transforms, dest) {
		var stream = gulp.src(src);

		transforms.forEach(function (transform) {
			stream = stream.pipe(transform());
		});

		return stream.pipe(gulp.dest(dest)).pipe(connect.reload());
	}

	var babelifyTransform = babelify.configure({
		plugins: [require('babel-plugin-object-assign')]
	});

	// Build
	gulp.task('fonts', plumb.bind(null, 'src/fonts/**', [], 'www/fonts'));
	gulp.task('html', plumb.bind(null, 'src/index.html', [], 'www'));
	gulp.task('images', plumb.bind(null, 'src/img/**', [], 'www/img'));
	gulp.task('less', plumb.bind(null, 'src/css/app.less', [less], 'www/css'));
	gulp.task('scripts', buildApp.bind(null, ['./src/js/app.js'], [babelifyTransform, brfs], './www/js'));
	gulp.task('scripts-watch', buildApp.bind(null, ['./src/js/app.js'], [babelifyTransform, brfs], './www/js', true));

	gulp.task('clean', function () { return del(['./www/*']); });
	gulp.task('build-assets', ['html', 'images', 'fonts', 'less']);
	gulp.task('build', ['build-assets', 'scripts']);
	gulp.task('watch', ['build-assets', 'scripts-watch'], function () {
		gulp.watch(['src/index.html'], ['html']);
		gulp.watch(['src/css/**/*.less'], ['less']);
		gulp.watch(['src/img/**/*.*'], ['images']);
		gulp.watch(['src/fonts/**/*.*'], ['fonts']);
	});

	// Development
	gulp.task('serve', function () {
		connect.server({
			root: 'www',
			port: 8000,
			livereload: true
		});
	});

	gulp.task('dev', ['serve', 'watch']);

	// Cordova
	gulp.task('prepare', ['build'], function () {
		return gulp.src('').pipe(plumber()).pipe(shell(['cordova prepare'], { cwd: __dirname }));
	});
};
<|endoftext|>