<commit_before>(function(){

// taken from http://stackoverflow.com/questions/2090551/parse-query-string-in-javascript
function getQueryVariable(variable) {
  var query = window.location.search.substring(1);
  var vars = query.split("&");
  for (var i = 0; i < vars.length; i++) {
    var pair = vars[i].split("=");
    if (pair[0] == variable) {
      return unescape(pair[1]);
    }
  }
}

function switchSidebar(sidebar) {
  var sidebars = [".live-data", "#domain-infos", "#filters", "#credits"];
  for (var i in sidebars) {
    if (sidebar == sidebars[i]) {
      $(sidebars[i]).slideDown();
    } else {
      $(sidebars[i]).slideUp();
    }
  }
}

function updateGraphViaPostMessage(graph, target) {
  target.postMessage("ready", "*");
  $(window).one("message", function(event) {
    graph.update(JSON.parse(event.originalEvent.data));
  });
}

function getJsonNoMatterWhat(url, callback) {
  /* jQuery.getJSON fails silently when trying to get a local json file. jQuery.ajax
   * gets the data but then throws a mysterious error. However, the data is there and
   * perfectly parseable. Not sure why jQuery behaves this way but here's a workaround. */
  jQuery.ajax(
    {url: url,
    dataType: "json",
    error: function(xhr, errText, err) {
      var trackers = JSON.parse(xhr.responseText);
      callback(trackers);
    },
    success: function(text) {
      callback(trackers);
    }});
}

$(window).ready(function() {
	console.log('ready, Captain')
  var addon = CollusionAddon;
  var graphUrl = getQueryVariable("graph_url");
  console.log('addon: %o, graphUrl: %s', addon, graphUrl);

  $("#domain-infos").hide();

  // get list of known trackers from trackers.json file hosted on website:
  getJsonNoMatterWhat("trackers.json", function(trackers) {
    var runner = GraphRunner.Runner({
      width: addon.isInstalled() || graphUrl ? $(window).width() : 640,
      height: addon.isInstalled() || graphUrl ? $(window).height() : 480,
      trackers: trackers,
      hideFavicons: false
    });
    var graph = runner.graph;

    // Enable drag-and-drop to import collusion graph data from a json file:
    $(document.body).bind("dragover", function(event) {
      event.preventDefault();
    }).bind("drop", function(event) {
      event.preventDefault();
      var files = event.originalEvent.dataTransfer.files;
      if (files.length == 1) {
        var reader = new FileReader();
        reader.onload = function() {
          if (addon.importGraph) {
            addon.importGraph(reader.result);
            window.location.reload();
          } else
            graph.update(JSON.parse(reader.result));
        };
        reader.readAsText(files[0], "UTF-8");
      }
    });

    $("#page").width(runner.width);

    if (graphUrl) {
      if (graphUrl == "opener") {
        updateGraphViaPostMessage(graph, window.opener);
      } else if (graphUrl == "parent") {

        updateGraphViaPostMessage(graph, window.parent);
      } else
        jQuery.getJSON(graphUrl, function(data) {
          graph.update(data);
        });
      return;
    }

    if (addon.isInstalled()) {
      // You should only ever see this page if the addon is installed, anyway
      $(".live-data").fadeIn();
      addon.onGraph(runner.updateGraph);
      $("#reset-graph").click(function() {
        if (addon.resetGraph) {
          addon.resetGraph();
          window.location.reload();
        } else
          alert("You need to update your add-on to use this feature.");
      });
      $("#export-graph").click(function() {
        var data = JSON.stringify(graph.data);
        window.open("data:application/json," + data);
      });
      $("#share-graph").click(function() {
        addon.shareGraph();
        // Provide acknowledgement message, ignore further clicks:
        $("#share-graph").html("Thank you! Your graph is being uploaded. (It's OK to close this page.)");
        $("#share-graph").off("click");
      });
      $("#hide-ui").click(function() {
        $("#sidebar").slideUp();
        $("#domain-infos").slideUp();
      });
      $("#save-graph").click(function() {
        var data = JSON.stringify(graph.data);
        addon.saveGraph(data);
        alert("Graph Saved!");
      });
      $("#load-graph").click(function() {
        addon.getSavedGraph();
      });
      $("#clear-graph-history").click(function() {
        var to_save = "{}"
        addon.saveGraph(to_save);
        addon.resetGraph();
        window.location.reload();
      });
      $("#zoom-in-link").click(function() {
        runner.zoomIn();
      });
      $("#zoom-out-link").click(function() {
        runner.zoomOut();
      });
      $("#about-tab-link").click(function() {
        switchSidebar(".live-data");
      });
      $("#site-tab-link").click(function() {
        switchSidebar("#domain-infos");
      });
      $("#credits-tab-link").click(function() {
        switchSidebar("#credits");
      });
      $("#filters-link").click(function() {
        switchSidebar("#filters");
      });
	  $(window).resize(function(){
		  runner.width = $(window).width();
		  runner.height = $(window).height();
		  $('svg').width($(window).width());
		  $('svg').width($(window).width());
	  });

	  function keys(obj){
	  	  var k = [];
		  for (var key in obj){
		      k.push(key);
		  }
		  return k;
	  }

      var setFilters = function() {
        var showCookie = $("#filter-cookie").prop("checked");
        var showNonCookie = $("#filter-noncookie").prop("checked");
        console.log("Show cookie? " + showCookie + " show noncookie? " + showNonCookie);

        $("g.node").hide();
        $("line").hide();
        if ($("#filter-cookie").prop("checked")) {
          $("g.cookie").show();
          $("line.cookie").show();
        }
        if ($("#filter-noncookie").prop("checked")) {
          $("g.noncookie").show();
          $("line.noncookie").show();
        }
      };
      /* Attach onchange listener to #filter-cookie and #filter-noncookie, select all lines and nodes with
       * appropriate classes, show or hide them.*/
      $("#filter-cookie").change(setFilters);
      $("#filter-noncookie").change(setFilters);

	  function filterRecent(time){
		  // Time is one of the strings: 'launch', 'week', 'day', 'hour';
	  }

	  var setRecentFilter = function(evt){
		  console.log('show recent filter %s', keys(evt));
		  console.log('selected: ', $(evt.target).parent().text());
		  var time = $(evt.target).parent().text().split(/\s+/).pop(); // "launch|week|day|hour"
		  filterRecent(time);
	  }
	  $('#filters input[name=show_recent]').change(setRecentFilter);
    }
  });
});

})();<commit_msg>Issue #59 height wasn't adapting<commit_after>(function(){

// taken from http://stackoverflow.com/questions/2090551/parse-query-string-in-javascript
function getQueryVariable(variable) {
  var query = window.location.search.substring(1);
  var vars = query.split("&");
  for (var i = 0; i < vars.length; i++) {
    var pair = vars[i].split("=");
    if (pair[0] == variable) {
      return unescape(pair[1]);
    }
  }
}

function switchSidebar(sidebar) {
  var sidebars = [".live-data", "#domain-infos", "#filters", "#credits"];
  for (var i in sidebars) {
    if (sidebar == sidebars[i]) {
      $(sidebars[i]).slideDown();
    } else {
      $(sidebars[i]).slideUp();
    }
  }
}

function updateGraphViaPostMessage(graph, target) {
  target.postMessage("ready", "*");
  $(window).one("message", function(event) {
    graph.update(JSON.parse(event.originalEvent.data));
  });
}

function getJsonNoMatterWhat(url, callback) {
  /* jQuery.getJSON fails silently when trying to get a local json file. jQuery.ajax
   * gets the data but then throws a mysterious error. However, the data is there and
   * perfectly parseable. Not sure why jQuery behaves this way but here's a workaround. */
  jQuery.ajax(
    {url: url,
    dataType: "json",
    error: function(xhr, errText, err) {
      var trackers = JSON.parse(xhr.responseText);
      callback(trackers);
    },
    success: function(text) {
      callback(trackers);
    }});
}

$(window).ready(function() {
	console.log('ready, Captain')
  var addon = CollusionAddon;
  var graphUrl = getQueryVariable("graph_url");
  console.log('addon: %o, graphUrl: %s', addon, graphUrl);

  $("#domain-infos").hide();

  // get list of known trackers from trackers.json file hosted on website:
  getJsonNoMatterWhat("trackers.json", function(trackers) {
    var runner = GraphRunner.Runner({
      width: addon.isInstalled() || graphUrl ? $(window).width() : 640,
      height: addon.isInstalled() || graphUrl ? $(window).height() : 480,
      trackers: trackers,
      hideFavicons: false
    });
    var graph = runner.graph;

    // Enable drag-and-drop to import collusion graph data from a json file:
    $(document.body).bind("dragover", function(event) {
      event.preventDefault();
    }).bind("drop", function(event) {
      event.preventDefault();
      var files = event.originalEvent.dataTransfer.files;
      if (files.length == 1) {
        var reader = new FileReader();
        reader.onload = function() {
          if (addon.importGraph) {
            addon.importGraph(reader.result);
            window.location.reload();
          } else
            graph.update(JSON.parse(reader.result));
        };
        reader.readAsText(files[0], "UTF-8");
      }
    });

    $("#page").width(runner.width);

    if (graphUrl) {
      if (graphUrl == "opener") {
        updateGraphViaPostMessage(graph, window.opener);
      } else if (graphUrl == "parent") {

        updateGraphViaPostMessage(graph, window.parent);
      } else
        jQuery.getJSON(graphUrl, function(data) {
          graph.update(data);
        });
      return;
    }

    if (addon.isInstalled()) {
      // You should only ever see this page if the addon is installed, anyway
      $(".live-data").fadeIn();
      addon.onGraph(runner.updateGraph);
      $("#reset-graph").click(function() {
        if (addon.resetGraph) {
          addon.resetGraph();
          window.location.reload();
        } else
          alert("You need to update your add-on to use this feature.");
      });
      $("#export-graph").click(function() {
        var data = JSON.stringify(graph.data);
        window.open("data:application/json," + data);
      });
      $("#share-graph").click(function() {
        addon.shareGraph();
        // Provide acknowledgement message, ignore further clicks:
        $("#share-graph").html("Thank you! Your graph is being uploaded. (It's OK to close this page.)");
        $("#share-graph").off("click");
      });
      $("#hide-ui").click(function() {
        $("#sidebar").slideUp();
        $("#domain-infos").slideUp();
      });
      $("#save-graph").click(function() {
        var data = JSON.stringify(graph.data);
        addon.saveGraph(data);
        alert("Graph Saved!");
      });
      $("#load-graph").click(function() {
        addon.getSavedGraph();
      });
      $("#clear-graph-history").click(function() {
        var to_save = "{}"
        addon.saveGraph(to_save);
        addon.resetGraph();
        window.location.reload();
      });
      $("#zoom-in-link").click(function() {
        runner.zoomIn();
      });
      $("#zoom-out-link").click(function() {
        runner.zoomOut();
      });
      $("#about-tab-link").click(function() {
        switchSidebar(".live-data");
      });
      $("#site-tab-link").click(function() {
        switchSidebar("#domain-infos");
      });
      $("#credits-tab-link").click(function() {
        switchSidebar("#credits");
      });
      $("#filters-link").click(function() {
        switchSidebar("#filters");
      });
	  $(window).resize(function(){
		  runner.width = $(document).width();
		  runner.height = $(document).height();
		  $('svg').width($(document).width());
		  $('svg').height($(document).height());
	  });

	  function keys(obj){
	  	  var k = [];
		  for (var key in obj){
		      k.push(key);
		  }
		  return k;
	  }

      var setFilters = function() {
        var showCookie = $("#filter-cookie").prop("checked");
        var showNonCookie = $("#filter-noncookie").prop("checked");
        console.log("Show cookie? " + showCookie + " show noncookie? " + showNonCookie);

        $("g.node").hide();
        $("line").hide();
        if ($("#filter-cookie").prop("checked")) {
          $("g.cookie").show();
          $("line.cookie").show();
        }
        if ($("#filter-noncookie").prop("checked")) {
          $("g.noncookie").show();
          $("line.noncookie").show();
        }
      };
      /* Attach onchange listener to #filter-cookie and #filter-noncookie, select all lines and nodes with
       * appropriate classes, show or hide them.*/
      $("#filter-cookie").change(setFilters);
      $("#filter-noncookie").change(setFilters);

	  function filterRecent(time){
		  // Time is one of the strings: 'launch', 'week', 'day', 'hour';
	  }

	  var setRecentFilter = function(evt){
		  console.log('show recent filter %s', keys(evt));
		  console.log('selected: ', $(evt.target).parent().text());
		  var time = $(evt.target).parent().text().split(/\s+/).pop(); // "launch|week|day|hour"
		  filterRecent(time);
	  }
	  $('#filters input[name=show_recent]').change(setRecentFilter);
    }
  });
});

})();<|endoftext|>


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

/**
 * Shows an elapsed time--can be displayed in a TimerNode.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const Node = require( 'SCENERY/nodes/Node' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  const Text = require( 'SCENERY/nodes/Text' );
  const Util = require( 'DOT/Util' );

  // constants
  const DEFAULT_LARGE_FONT = new PhetFont( 20 );
  const DEFAULT_SMALL_FONT = new PhetFont( 15 );

  class TimerReadoutNode extends Rectangle {

    /**
     * @param {Property.<Number>} timeProperty
     * @param {Object} options
     */
    constructor( timeProperty, options ) {

      options = _.extend( {

        // The maximum value, in seconds, that can be shown by the TimerNode, so it can set up the size to accommodate
        // the largest string. Default value is set to 99 minutes.
        maxValue: 5940,

        // {null|Node} - optional node to show for the units, most likely to be a Text or RichText.  Note that showing
        // units changes the mode from mm:ss.mm to ss.mm units and changes from center aligned to right aligned.
        // Initialize the TimerNode with the largest possible unitsNode to make sure the text panel is large enough.
        // When the unitsNode bounds change, the layout will update.
        unitsNode: null,

        // {Font} - shown for the numbers before the decimal
        largeFont: DEFAULT_LARGE_FONT,

        // {Font} - shown for the numbers after the decimal
        smallFont: DEFAULT_SMALL_FONT
      }, options );

      const unitsNode = options.unitsNode;

      assert && assert( options.maxValue >= 0, 'TimerReadoutNode.maxValue should be non-negative' );
      assert && assert( options.maxValue < 1E21, 'TimerReadoutNode.maxValue should be less than 1E21' ); // see https://github.com/phetsims/capacitor-lab-basics/issues/244#issuecomment-433940629

      /*---------------------------------------------------------------------------*
       * Readout text
       *----------------------------------------------------------------------------*/
      const bigReadoutText = new Text( timeToBigString( 0, !!unitsNode ), { font: options.largeFont } );
      const smallReadoutText = new Text( timeToSmallString( 0 ), { font: options.smallFont } );

      // aligns the baselines of the big and small text
      smallReadoutText.bottom = smallReadoutText.bounds.maxY - bigReadoutText.bounds.minY;
      bigReadoutText.top = 0;
      const children = [ bigReadoutText, smallReadoutText ];

      if ( unitsNode ) {
        unitsNode.bottom = smallReadoutText.bottom - 1;
        children.push( unitsNode );
      }

      const readoutLayer = new Node( {
        children: children,
        pickable: false
      } );

      /*---------------------------------------------------------------------------*
       * Control logic and initial layout
       *----------------------------------------------------------------------------*/
      const updateText = value => {

        // When the timer reaches the max value, it should get "stuck" at that value, like a real stopwatch,
        // see https://github.com/phetsims/wave-interference/issues/94
        value = Math.min( value, options.maxValue );

        // Update readouts
        bigReadoutText.text = timeToBigString( value, !!unitsNode );
        smallReadoutText.text = timeToSmallString( value );

        // Update layout - when unitsNode is shown, the text is right aligned.  Otherwise it is centered.
        if ( unitsNode ) {
          smallReadoutText.right = unitsNode.left - 3;
          bigReadoutText.right = smallReadoutText.left;
        }
        else {
          smallReadoutText.left = bigReadoutText.right;
        }
      };

      // Initialize with max value so the text panel will have the max needed size.
      updateText( options.maxValue );

      /*---------------------------------------------------------------------------*
       * Readout background
       *----------------------------------------------------------------------------*/
      const parentBounds = readoutLayer.bounds.dilatedXY( 5, 2 );
      super( parentBounds, 5, 5, {
        children: [ readoutLayer ],
        fill: '#fff',
        stroke: 'rgba(0,0,0,0.5)',
        pickable: false,
        centerX: 0
      } );

      const updateTextLocation = () => {
        if ( unitsNode ) {
          const RIGHT_MARGIN = 4;
          readoutLayer.right = parentBounds.right - RIGHT_MARGIN;
        }
        else {
          readoutLayer.center = parentBounds.center;
        }
      };

      // Set initial values and layout
      timeProperty.link( updateText );
      timeProperty.link( updateTextLocation );

      let unitsNodeBoundsListener = null;

      // If the unitsNode changes size, update the layout to accommodate the new size
      if ( unitsNode ) {
        unitsNodeBoundsListener = function() {
          updateText( timeProperty.value );
          updateTextLocation();
        };
        unitsNode.on( 'bounds', unitsNodeBoundsListener );
      }

      this.disposeTimerReadoutNode = () => {
        timeProperty.unlink( updateText );
        timeProperty.unlink( updateTextLocation );
        unitsNode && unitsNode.off( unitsNodeBoundsListener );
      };
    }

    dispose() {
      this.disposeTimerReadoutNode();
    }
  }

  // the full-sized minutes and seconds string
  const timeToBigString = ( time, showUnits ) => {

    // Round to the nearest centi-part (if time is in seconds, this would be centiseconds), (compatible with timeToSmallString).
    // see https://github.com/phetsims/masses-and-springs/issues/156
    time = Util.roundSymmetric( time * 100 ) / 100;

    // When showing units, don't show the "00:" prefix, see https://github.com/phetsims/scenery-phet/issues/378
    if ( showUnits ) {
      return Math.floor( time ) + '';
    }
    else {
      const timeInSeconds = time;

      // If no units are provided, then we assume the time is in seconds, and should be shown in mm:ss.cs
      let minutes = Math.floor( timeInSeconds / 60 ) % 60;
      let seconds = Math.floor( timeInSeconds ) % 60;

      if ( seconds < 10 ) {
        seconds = '0' + seconds;
      }
      if ( minutes < 10 ) {
        minutes = '0' + minutes;
      }
      return minutes + ':' + seconds;
    }
  };

  // the smaller hundredths-of-a-second string
  const timeToSmallString = time => {

    // Round to the nearest centisecond (compatible with timeToSmallString).
    // see https://github.com/phetsims/masses-and-springs/issues/156
    time = Util.roundSymmetric( time * 100 ) / 100;

    // Rounding after mod, in case there is floating-point error
    let centitime = Util.roundSymmetric( time % 1 * 100 );
    if ( centitime < 10 ) {
      centitime = '0' + centitime;
    }
    return '.' + centitime;
  };

  // Make the default fonts public, to inform creation of optional unitsNode
  TimerReadoutNode.DEFAULT_LARGE_FONT = DEFAULT_LARGE_FONT;
  TimerReadoutNode.DEFAULT_SMALL_FONT = DEFAULT_SMALL_FONT;

  return sceneryPhet.register( 'TimerReadoutNode', TimerReadoutNode );
} );<commit_msg>Align the baseline of the text, see https://github.com/phetsims/scenery-phet/issues/425<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * Shows an elapsed time--can be displayed in a TimerNode.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const Node = require( 'SCENERY/nodes/Node' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  const Text = require( 'SCENERY/nodes/Text' );
  const Util = require( 'DOT/Util' );

  // constants
  const DEFAULT_LARGE_FONT = new PhetFont( 20 );
  const DEFAULT_SMALL_FONT = new PhetFont( 15 );

  class TimerReadoutNode extends Rectangle {

    /**
     * @param {Property.<Number>} timeProperty
     * @param {Object} options
     */
    constructor( timeProperty, options ) {

      options = _.extend( {

        // The maximum value, in seconds, that can be shown by the TimerNode, so it can set up the size to accommodate
        // the largest string. Default value is set to 99 minutes.
        maxValue: 5940,

        // {null|Node} - optional node to show for the units, most likely to be a Text or RichText.  Note that showing
        // units changes the mode from mm:ss.mm to ss.mm units and changes from center aligned to right aligned.
        // Initialize the TimerNode with the largest possible unitsNode to make sure the text panel is large enough.
        // When the unitsNode bounds change, the layout will update.
        unitsNode: null,

        // {Font} - shown for the numbers before the decimal
        largeFont: DEFAULT_LARGE_FONT,

        // {Font} - shown for the numbers after the decimal
        smallFont: DEFAULT_SMALL_FONT
      }, options );

      const unitsNode = options.unitsNode;

      assert && assert( options.maxValue >= 0, 'TimerReadoutNode.maxValue should be non-negative' );
      assert && assert( options.maxValue < 1E21, 'TimerReadoutNode.maxValue should be less than 1E21' ); // see https://github.com/phetsims/capacitor-lab-basics/issues/244#issuecomment-433940629

      /*---------------------------------------------------------------------------*
       * Readout text
       *----------------------------------------------------------------------------*/
      const bigReadoutText = new Text( timeToBigString( 0, !!unitsNode ), { font: options.largeFont } );
      const smallReadoutText = new Text( timeToSmallString( 0 ), { font: options.smallFont } );

      // aligns the baselines of the big and small text
      smallReadoutText.bottom = smallReadoutText.bounds.maxY - bigReadoutText.bounds.minY;
      bigReadoutText.top = 0;
      const children = [ bigReadoutText, smallReadoutText ];

      if ( unitsNode ) {

        // Align the baseline of the text, see https://github.com/phetsims/scenery-phet/issues/425
        unitsNode.y = smallReadoutText.y;
        children.push( unitsNode );
      }

      const readoutLayer = new Node( {
        children: children,
        pickable: false
      } );

      /*---------------------------------------------------------------------------*
       * Control logic and initial layout
       *----------------------------------------------------------------------------*/
      const updateText = value => {

        // When the timer reaches the max value, it should get "stuck" at that value, like a real stopwatch,
        // see https://github.com/phetsims/wave-interference/issues/94
        value = Math.min( value, options.maxValue );

        // Update readouts
        bigReadoutText.text = timeToBigString( value, !!unitsNode );
        smallReadoutText.text = timeToSmallString( value );

        // Update layout - when unitsNode is shown, the text is right aligned.  Otherwise it is centered.
        if ( unitsNode ) {
          smallReadoutText.right = unitsNode.left - 3;
          bigReadoutText.right = smallReadoutText.left;
        }
        else {
          smallReadoutText.left = bigReadoutText.right;
        }
      };

      // Initialize with max value so the text panel will have the max needed size.
      updateText( options.maxValue );

      /*---------------------------------------------------------------------------*
       * Readout background
       *----------------------------------------------------------------------------*/
      const parentBounds = readoutLayer.bounds.dilatedXY( 5, 2 );
      super( parentBounds, 5, 5, {
        children: [ readoutLayer ],
        fill: '#fff',
        stroke: 'rgba(0,0,0,0.5)',
        pickable: false,
        centerX: 0
      } );

      const updateTextLocation = () => {
        if ( unitsNode ) {
          const RIGHT_MARGIN = 4;
          readoutLayer.right = parentBounds.right - RIGHT_MARGIN;
        }
        else {
          readoutLayer.center = parentBounds.center;
        }
      };

      // Set initial values and layout
      timeProperty.link( updateText );
      timeProperty.link( updateTextLocation );

      let unitsNodeBoundsListener = null;

      // If the unitsNode changes size, update the layout to accommodate the new size
      if ( unitsNode ) {
        unitsNodeBoundsListener = function() {
          updateText( timeProperty.value );
          updateTextLocation();
        };
        unitsNode.on( 'bounds', unitsNodeBoundsListener );
      }

      this.disposeTimerReadoutNode = () => {
        timeProperty.unlink( updateText );
        timeProperty.unlink( updateTextLocation );
        unitsNode && unitsNode.off( unitsNodeBoundsListener );
      };
    }

    dispose() {
      this.disposeTimerReadoutNode();
    }
  }

  // the full-sized minutes and seconds string
  const timeToBigString = ( time, showUnits ) => {

    // Round to the nearest centi-part (if time is in seconds, this would be centiseconds), (compatible with timeToSmallString).
    // see https://github.com/phetsims/masses-and-springs/issues/156
    time = Util.roundSymmetric( time * 100 ) / 100;

    // When showing units, don't show the "00:" prefix, see https://github.com/phetsims/scenery-phet/issues/378
    if ( showUnits ) {
      return Math.floor( time ) + '';
    }
    else {
      const timeInSeconds = time;

      // If no units are provided, then we assume the time is in seconds, and should be shown in mm:ss.cs
      let minutes = Math.floor( timeInSeconds / 60 ) % 60;
      let seconds = Math.floor( timeInSeconds ) % 60;

      if ( seconds < 10 ) {
        seconds = '0' + seconds;
      }
      if ( minutes < 10 ) {
        minutes = '0' + minutes;
      }
      return minutes + ':' + seconds;
    }
  };

  // the smaller hundredths-of-a-second string
  const timeToSmallString = time => {

    // Round to the nearest centisecond (compatible with timeToSmallString).
    // see https://github.com/phetsims/masses-and-springs/issues/156
    time = Util.roundSymmetric( time * 100 ) / 100;

    // Rounding after mod, in case there is floating-point error
    let centitime = Util.roundSymmetric( time % 1 * 100 );
    if ( centitime < 10 ) {
      centitime = '0' + centitime;
    }
    return '.' + centitime;
  };

  // Make the default fonts public, to inform creation of optional unitsNode
  TimerReadoutNode.DEFAULT_LARGE_FONT = DEFAULT_LARGE_FONT;
  TimerReadoutNode.DEFAULT_SMALL_FONT = DEFAULT_SMALL_FONT;

  return sceneryPhet.register( 'TimerReadoutNode', TimerReadoutNode );
} );<|endoftext|>


<commit_before>/**
 * MathMLController
 *
 * @description :: Server-side logic for managing MathML
 * @help        :: See http://links.sailsjs.org/docs/controllers
 */

var MathmlController = {

	mathjaxNode: require("../../node_modules/MathJax-node/lib/mj-single.js"),
	mathjaxNodePage: require("../../node_modules/MathJax-node/lib/mj-page.js"),
	mathJaxNodeOptions: {img:false, mml:true, timeout: 10 * 1000},

	/**
	* Convert to svg and get text description.
	* @param req expected param: mathml/tex
	* @param res returns json
	*/
	convert: function(req, res) {
		var options = MathmlController.mathJaxNodeOptions;
		options.math = req.param('math');
		options.format = req.param('mathType');
		options.svg = req.param('svg');
		options.png = req.param('png');
		options.speakText = req.param('description');

		MathmlController.mathjaxNode.typeset(options, function (data) {
			if (data.errors !== "undefined") {
				//Create record for callback.
				Mathml.create({
				  //altText: "Placeholder until we get chromevox work",
				  asciiMath: req.param("mathType") == "AsciiMath" ? req.param("math") : null,
				  tex: req.param("mathType") === "inline-TeX" ? req.param("math") : null,
				  mathML: data.mml
				}).exec(function(err, mathML) {
				  // Error handling
				  if (err) {
				  	console.log(err);
				  	res.send({errors: err});
				  } else {
					data.cloudUrl = "http://" + req.headers.host + "/mathml/" + mathML.id;
					res.send(data);
				  }
				});
			} else {
				console.log(data.errors);
				res.send(data.errors);
			}

		});
	},

	svg: function(req, res) {
		var options = {};
		options.math = req.param('math');
		options.format = req.param('mathType');
		options.svg = true;
		options.speakText = true;
		MathmlController.mathjaxNode.typeset(options, function (data) {
			if (data.errors !== "undefined") {
				//Create record for callback.
				Mathml.create({
				  //altText: "Placeholder until we get chromevox work",
				  asciiMath: req.param("mathType") == "AsciiMath" ? req.param("math") : null,
				  tex: req.param("mathType") === "inline-TeX" ? req.param("math") : null,
				  mathML: data.mml
				}).exec(function(err, mathML) {
				  // Error handling
				  if (err) {
				  	console.log(err);
				  	res.send({errors: err});
				  } else {
					res.attachment("math.svg");
              		res.end(data.svg, 'UTF-8');
				  }
				});
			} else {
				console.log(data.errors);
				res.send(data.errors);
			}

		});
	},

	/**
	* Upload HTML5 and convert all equations to mathml.
	*/
	upload: function  (req, res) {
		var options = MathmlController.mathJaxNodeOptions;
		req.file('html5').upload(function (err, files) {
	    	if (err)
	        	return res.serverError(err);
	        var html5 = files[0];

	        var fs = require("fs");
	        options.html = fs.readFileSync(html5.fd);
	        options.speakText = true;
	        MathmlController.mathjaxNodePage.typeset(options, function (data) {
        	  res.attachment(html5.filename);
              res.end(data.html, 'UTF-8');
	        });
	    });
  	},

	find: function(req, res) {
		var mathMLId = req.param('id');
		var wantsjson = req.param('json');
		Mathml.find({ _id: mathMLId }).exec(function (err, mathML) {
			// XXX Error handling
			if (err) {
				return console.log(err);
			} else {
				var dbMathML = mathML[0];
				var options = MathmlController.mathJaxNodeOptions;
				options.math = dbMathML.mathML;
				options.format = "MathML";
				MathmlController.mathjaxNode.typeset(options, function (data) {
					data.mathML = dbMathML.mathML;
					data.asciiMath = dbMathML.asciiMath;
					data.cloudUrl = "http://" + req.headers.host + "/mathml/" + dbMathML.id;
					if (wantsjson !== undefined)
						return res.send(data)
					else
						return res.view({jsonurl: data.cloudUrl + '?json', mathml: dbMathML.mathML, alttext: dbMathML.altText});
				});
			}
		});
	}
}

module.exports =  MathmlController;
<commit_msg>MMLC-69 increase timeout for html5<commit_after>/**
 * MathMLController
 *
 * @description :: Server-side logic for managing MathML
 * @help        :: See http://links.sailsjs.org/docs/controllers
 */

var MathmlController = {

	mathjaxNode: require("../../node_modules/MathJax-node/lib/mj-single.js"),
	mathjaxNodePage: require("../../node_modules/MathJax-node/lib/mj-page.js"),
	mathJaxNodeOptions: {img:false, mml:true, timeout: 10 * 1000},

	/**
	* Convert to svg and get text description.
	* @param req expected param: mathml/tex
	* @param res returns json
	*/
	convert: function(req, res) {
		var options = MathmlController.mathJaxNodeOptions;
		options.math = req.param('math');
		options.format = req.param('mathType');
		options.svg = req.param('svg');
		options.png = req.param('png');
		options.speakText = req.param('description');

		MathmlController.mathjaxNode.typeset(options, function (data) {
			if (data.errors !== "undefined") {
				//Create record for callback.
				Mathml.create({
				  //altText: "Placeholder until we get chromevox work",
				  asciiMath: req.param("mathType") == "AsciiMath" ? req.param("math") : null,
				  tex: req.param("mathType") === "inline-TeX" ? req.param("math") : null,
				  mathML: data.mml
				}).exec(function(err, mathML) {
				  // Error handling
				  if (err) {
				  	console.log(err);
				  	res.send({errors: err});
				  } else {
					data.cloudUrl = "http://" + req.headers.host + "/mathml/" + mathML.id;
					res.send(data);
				  }
				});
			} else {
				console.log(data.errors);
				res.send(data.errors);
			}

		});
	},

	svg: function(req, res) {
		var options = {};
		options.math = req.param('math');
		options.format = req.param('mathType');
		options.svg = true;
		options.speakText = true;
		MathmlController.mathjaxNode.typeset(options, function (data) {
			if (data.errors !== "undefined") {
				//Create record for callback.
				Mathml.create({
				  //altText: "Placeholder until we get chromevox work",
				  asciiMath: req.param("mathType") == "AsciiMath" ? req.param("math") : null,
				  tex: req.param("mathType") === "inline-TeX" ? req.param("math") : null,
				  mathML: data.mml
				}).exec(function(err, mathML) {
				  // Error handling
				  if (err) {
				  	console.log(err);
				  	res.send({errors: err});
				  } else {
					res.attachment("math.svg");
              		res.end(data.svg, 'UTF-8');
				  }
				});
			} else {
				console.log(data.errors);
				res.send(data.errors);
			}

		});
	},

	/**
	* Upload HTML5 and convert all equations to mathml.
	*/
	upload: function  (req, res) {
		var options = MathmlController.mathJaxNodeOptions;
		req.file('html5').upload(function (err, files) {
	    	if (err)
	        	return res.serverError(err);
	        var html5 = files[0];

	        var fs = require("fs");
	        options.html = fs.readFileSync(html5.fd);
	        options.speakText = true;
	        options.timeout = 10 * 5000;
	        MathmlController.mathjaxNodePage.typeset(options, function (data) {
	          if (data.errors !== "undefined") {
        	  	res.attachment(html5.filename);
              	res.end(data.html, 'UTF-8');
              } else {
				console.log(data.errors);
				res.send(data.errors);
			  }
	        });
	    });
  	},

	find: function(req, res) {
		var mathMLId = req.param('id');
		var wantsjson = req.param('json');
		Mathml.find({ _id: mathMLId }).exec(function (err, mathML) {
			// XXX Error handling
			if (err) {
				return console.log(err);
			} else {
				var dbMathML = mathML[0];
				var options = MathmlController.mathJaxNodeOptions;
				options.math = dbMathML.mathML;
				options.format = "MathML";
				MathmlController.mathjaxNode.typeset(options, function (data) {
					data.mathML = dbMathML.mathML;
					data.asciiMath = dbMathML.asciiMath;
					data.cloudUrl = "http://" + req.headers.host + "/mathml/" + dbMathML.id;
					if (wantsjson !== undefined)
						return res.send(data)
					else
						return res.view({jsonurl: data.cloudUrl + '?json', mathml: dbMathML.mathML, alttext: dbMathML.altText});
				});
			}
		});
	}
}

module.exports =  MathmlController;
<|endoftext|>


<commit_before>class Bird {
    constructor() {
        this.pos = {
            x: -width / 4,
            y: -height / 4
        }
        this.vel = {
            x: 0,
            y: 0
        }
        this.size = height / 10
        this.getHitbox = () => {
            let offset = 0.5
            return {
                pos: {
                    x: this.pos.x - (this.size / 2) * offset,
                    y: this.pos.y - (this.size / 2) * offset
                },
                size: this.size * offset
            }
        }
        this.gravity = height / 700
        this.jumpPower = this.gravity * 12
        this.sprite = sprites.bird
    }

    update(deltaT) {
        this.vel.y += this.gravity * deltaT
        this.pos.y += this.vel.y * deltaT

        //Make sure animation plays only once
        if (this.sprite.playing() && this.sprite.frames().length - 1 == this.sprite.frame()) {
            this.sprite.pause()
            this.sprite.frame(0)
        }
    }

    draw() {

        push()

        translate(this.pos.x, this.pos.y)
        rotateZ((-this.vel.y / this.jumpPower) / 2)
        specularMaterial(0, 0, 0, 0)
        texture(this.sprite)
        plane(this.size, this.size.y)

        pop()

        //draw hitbox
        //not working
        // push()

        // translate(this.hitbox.pos.x() + this.hitbox.size() / 2, this.hitbox.pos.y() + this.hitbox.size() / 2)
        // let t = createGraphics(1, 1)
        // t.background(255)
        // texture(t)
        // plane(this.hitbox.size(), this.hitbox.size())

        // pop()
    }

    jump() {
        //Apply velocity
        this.vel.y = -this.jumpPower

        //Start animation
        this.sprite.frame(0)
        this.sprite.play()

        //Play a random flap sound
        sounds.bird.flap[Math.floor(Math.random() * sounds.bird.flap.length)].play()
    }

    collides(tubes) {
        let collided = false
        let hitbox = this.getHitbox()

        if (hitbox.pos.y + hitbox.size < -height / 2 || hitbox.pos.y > height / 2) {
            collided = true
        }

        tubes.forEach(function(tube) {
            if(tube.collides(hitbox)) {
                collided = true
                sounds.tube.hit[Math.floor(Math.random()*sounds.tube.hit.length)].play()
            }
        })

        return collided
    }

    die() {
        this.sprite.pause()
        this.sprite.frame(0)
    }
}
<commit_msg>Removed upper boundry<commit_after>class Bird {
    constructor() {
        this.pos = {
            x: -width / 4,
            y: -height / 4
        }
        this.vel = {
            x: 0,
            y: 0
        }
        this.size = height / 10
        this.getHitbox = () => {
            let offset = 0.5
            return {
                pos: {
                    x: this.pos.x - (this.size / 2) * offset,
                    y: this.pos.y - (this.size / 2) * offset
                },
                size: this.size * offset
            }
        }
        this.gravity = height / 700
        this.jumpPower = this.gravity * 12
        this.sprite = sprites.bird
    }

    update(deltaT) {
        this.vel.y += this.gravity * deltaT
        this.pos.y += this.vel.y * deltaT

        //Make sure animation plays only once
        if (this.sprite.playing() && this.sprite.frames().length - 1 == this.sprite.frame()) {
            this.sprite.pause()
            this.sprite.frame(0)
        }
    }

    draw() {

        push()

        translate(this.pos.x, this.pos.y)
        rotateZ((-this.vel.y / this.jumpPower) / 2)
        specularMaterial(0, 0, 0, 0)
        texture(this.sprite)
        plane(this.size, this.size.y)

        pop()

        //draw hitbox
        //not working
        // push()

        // translate(this.hitbox.pos.x() + this.hitbox.size() / 2, this.hitbox.pos.y() + this.hitbox.size() / 2)
        // let t = createGraphics(1, 1)
        // t.background(255)
        // texture(t)
        // plane(this.hitbox.size(), this.hitbox.size())

        // pop()
    }

    jump() {
        //Apply velocity
        this.vel.y = -this.jumpPower

        //Start animation
        this.sprite.frame(0)
        this.sprite.play()

        //Play a random flap sound
        sounds.bird.flap[Math.floor(Math.random() * sounds.bird.flap.length)].play()
    }

    collides(tubes) {
        let collided = false
        let hitbox = this.getHitbox()

        if (hitbox.pos.y > height / 2) {
            collided = true
        }

        tubes.forEach(function(tube) {
            if(tube.collides(hitbox)) {
                collided = true
                sounds.tube.hit[Math.floor(Math.random()*sounds.tube.hit.length)].play()
            }
        })

        return collided
    }

    die() {
        this.sprite.pause()
        this.sprite.frame(0)
    }
}
<|endoftext|>


<commit_before>'use strict';

var jwt           = require('jsonwebtoken');

var User          = require('../models/user');
var Trade          = require('../models/trade');
var Book          = require('../models/book');

var Config        = require('../../config/secret');
var Helpers       = require('../helpers');

function TradesController () { }

TradesController.propose = function(req, res) {
  var token = Helpers.getToken(req.headers);
  if (token) {
    var decoded = jwt.decode(token, Config.secret);
    User.findOne({
      'email': decoded._doc.email
    }, function(err, user) {
      if (err)
        throw err;

      if (!user) {
        return res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
      }
      else
      {
        var trade = new Trade( {
                                _book : req.body.bookId,
                                _requester : user._id,
                                completed : false,
                                accepted : false
                              });

        trade.save(function(err){
          if (err)
            throw err;

          Book.findOne({_id : req.body.bookId}, function(err, book){
            book.trades.push(trade);
            book.save(function(err){
              if (err)
                throw err;

                res.json({success: true});
            });
          })
        })
      }
    });
  }
  else
  {
    return res.status(403).send({success: false, msg: 'No token provided.'});
  }
}

TradesController.getOfBook = function(req, res) {
  var token = Helpers.getToken(req.headers);
  if (token) {
    var decoded = jwt.decode(token, Config.secret);
    User.findOne({
      'email': decoded._doc.email
    }, function(err, user) {
      if (err)
        throw err;

      if (!user) {
        return res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
      }
      else
      {
        Trade.find({ _book : req.params.bookId })
        .populate('_requester')
        .exec(function(err, trades){
          if (err)
            throw err;

            res.json({success: true, trades : trades});
        });
      }
    });
  }
  else
  {
    return res.status(403).send({success: false, msg: 'No token provided.'});
  }
}

TradesController.destroy =  function(req, res) {
  var token = Helpers.getToken(req.headers);
  if (token) {
    var decoded = jwt.decode(token, Config.secret);
    User.findOne({
      'email': decoded._doc.email
    }, function(err, user) {
      if (err)
        throw err;

      if (!user) {
        return res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
      }
      else
      {
        Trade.remove({ _id : req.params.tradeId }, function(err){
          if (err)
            throw err;

            return res.json({success: true });
        });
      }
    });
  }
  else
  {
    return res.status(403).send({success: false, msg: 'No token provided.'});
  }
}

TradesController.accept =  function(req, res) {
  var token = Helpers.getToken(req.headers);
  if (token) {
    var decoded = jwt.decode(token, Config.secret);
    User.findOne({
      'email': decoded._doc.email
    }, function(err, user) {
      if (err)
        throw err;

      if (!user) {
        return res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
      }
      else
      {
        Trade.findOne
        (
          { _id : req.params.tradeId },
          function(err, trade){
            if (err)
              throw err;

            trade.completed = true;
            trade.accepted = true;
            trade.save();

            Book.update
            (
              { _id : trade._book},
              { traded : true},
              function(err){
                if (err)
                  throw err;

                return res.json({success: true });
              }
            );
          }
        );
      }
    });
  }
  else
  {
    return res.status(403).send({success: false, msg: 'No token provided.'});
  }
}

module.exports = TradesController;
<commit_msg>Improvements on trades and fixed identation<commit_after>'use strict';

var jwt = require('jsonwebtoken');

var User = require('../models/user');
var Trade = require('../models/trade');
var Book = require('../models/book');

var Config = require('../../config/secret');
var Helpers = require('../helpers');

function TradesController() { }

TradesController.propose = function (req, res) {
  var token = Helpers.getToken(req.headers);
  if (!token)
    return res.status(403).send({
      success: false,
      msg: 'No token provided.'
    });

  var decoded = jwt.decode(token, Config.secret);

  const userQuery = User.findOne({
    'email': decoded._doc.email
  }).exec();

  console.log(userQuery);

  userQuery.then(function (user) {
    if (!user)
      return res.status(403).send({
        success: false,
        msg: 'Authentication failed. User not found.'
      });

    var trade = new Trade({
      _book: req.body.bookId,
      _requester: user._id,
      completed: false,
      accepted: false
    });

    trade.save(function (err) {
      if (err)
        throw err;

      Book.findOne({ _id: req.body.bookId }, function (err, book) {
        book.trades.push(trade);
        book.save(function (err) {
          if (err)
            throw err;

          res.json({ success: true });
        });
      })
    })
  })
  .catch(function(){
    return res.status(500).send({
      success: false,
      msg: error
    });
  });
}

TradesController.getOfBook = function (req, res) {
  var token = Helpers.getToken(req.headers);
  if (token) {
    var decoded = jwt.decode(token, Config.secret);
    User.findOne({
      'email': decoded._doc.email
    }, function (err, user) {
      if (err)
        throw err;

      if (!user) {
        return res.status(403).send({ success: false, msg: 'Authentication failed. User not found.' });
      }
      else {
        Trade.find({ _book: req.params.bookId })
          .populate('_requester')
          .exec(function (err, trades) {
            if (err)
              throw err;

            res.json({ success: true, trades: trades });
          });
      }
    });
  }
  else {
    return res.status(403).send({ success: false, msg: 'No token provided.' });
  }
}

TradesController.destroy = function (req, res) {
  var token = Helpers.getToken(req.headers);
  if (token) {
    var decoded = jwt.decode(token, Config.secret);
    User.findOne({
      'email': decoded._doc.email
    }, function (err, user) {
      if (err)
        throw err;

      if (!user) {
        return res.status(403).send({ success: false, msg: 'Authentication failed. User not found.' });
      }
      else {
        Trade.remove({ _id: req.params.tradeId }, function (err) {
          if (err)
            throw err;

          return res.json({ success: true });
        });
      }
    });
  }
  else {
    return res.status(403).send({ success: false, msg: 'No token provided.' });
  }
}

TradesController.accept = function (req, res) {
  var token = Helpers.getToken(req.headers);
  if (token) {
    var decoded = jwt.decode(token, Config.secret);
    User.findOne({
      'email': decoded._doc.email
    }, function (err, user) {
      if (err)
        throw err;

      if (!user) {
        return res.status(403).send({ success: false, msg: 'Authentication failed. User not found.' });
      }
      else {
        Trade.findOne
          (
            { _id: req.params.tradeId },
            function (err, trade) {
              if (err)
                throw err;

              trade.completed = true;
              trade.accepted = true;
              trade.save();

              Book.update
                (
                  { _id: trade._book },
                  { traded: true },
                  function (err) {
                    if (err)
                      throw err;

                    return res.json({ success: true });
                  }
                );
            }
          );
      }
    });
  }
  else {
    return res.status(403).send({ success: false, msg: 'No token provided.' });
  }
}

module.exports = TradesController;
<|endoftext|>


<commit_before>/**
 * Copyright 2013-2014 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
* @providesModule ReactComponentWithPureRenderMixin
*/

"use strict";

var shallowEqual = require('shallowEqual');

/**
 * If your React component's render function is "pure", e.g. it will render the
 * same result given the same props and state, provide this Mixin for a
 * considerable performance boost.
 *
 * Most React components have pure render functions.
 *
 * Example:
 *
 *   var ReactComponentWithPureRender =
 *     require('ReactComponentWithPureRenderMixin');
 *   React.createClass({
 *     mixins: [ReactComponentWithPureRenderMixin],
 *
 *     render: function() {
 *       return
foo
;
 *     }
 *   });
 *
 * Note: This only checks shallow equality for props and state. If these contain
 * complex data structures this mixin may have false-negatives for deeper
 * differences. Only mixin to components which have simple props and state, or
 * use `forceUpdate()` when you know deep data structures have changed.
 */
var ReactComponentWithPureRenderMixin = {
  shouldComponentUpdate: function(nextProps, nextState) {
    return !shallowEqual(this.props, nextProps) ||
           !shallowEqual(this.state, nextState);
  }
};

module.exports = ReactComponentWithPureRenderMixin;
<commit_msg>Fix docstring typo for pure render mixin<commit_after>/**
 * Copyright 2013-2014 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
* @providesModule ReactComponentWithPureRenderMixin
*/

"use strict";

var shallowEqual = require('shallowEqual');

/**
 * If your React component's render function is "pure", e.g. it will render the
 * same result given the same props and state, provide this Mixin for a
 * considerable performance boost.
 *
 * Most React components have pure render functions.
 *
 * Example:
 *
 *   var ReactComponentWithPureRenderMixin =
 *     require('ReactComponentWithPureRenderMixin');
 *   React.createClass({
 *     mixins: [ReactComponentWithPureRenderMixin],
 *
 *     render: function() {
 *       return
foo
;
 *     }
 *   });
 *
 * Note: This only checks shallow equality for props and state. If these contain
 * complex data structures this mixin may have false-negatives for deeper
 * differences. Only mixin to components which have simple props and state, or
 * use `forceUpdate()` when you know deep data structures have changed.
 */
var ReactComponentWithPureRenderMixin = {
  shouldComponentUpdate: function(nextProps, nextState) {
    return !shallowEqual(this.props, nextProps) ||
           !shallowEqual(this.state, nextState);
  }
};

module.exports = ReactComponentWithPureRenderMixin;
<|endoftext|>


<commit_before>/*
 * Developed by Radu Puspana
 * Date August 2017
 * Version 1.0
 */

console.log("App has started.");
console.log("Loading controllers.");

// module that handles the budget data using the module pattern
// this controller keeps track of all the income, expenses, the budget itself, and
// also the percentage of how much an income/expense represents of the total budget
var budgetController = (function() {

    // private Expense constructor
    var prvExpense = function(id, description, value) {
        this.id = id;
        this.description = description;
        this.value = value;
        this.percentageOfTransOutOfTotalIncome = -1; // -1 = value can't be calculated yet
    };

    // calculate how much in % an expense represents out of the total income and sotre this value in
    // prvExpense.percentageOfTransOutOfTotalIncome
    // totalIncome  Number  the total of the submited income transactions
    prvExpense.prototype.calcPercentageOfSubmitedExpense = function(totalIncome) {

        if (totalIncome > 0) {
            this.percentageOfTransOutOfTotalIncome = roundDecimal((this.value / totalIncome) * 100, 1);
        }
        else {
            // when we delete all of the expense transactions, we need to reset this variable
            this.percentageOfTransOutOfTotalIncome = -1;
        }
    }

    // getter for the prvExpense.percentageOfTransOutOfTotalIncome
    prvExpense.prototype.getPercentageOfSubmitedExpense = function() {

        return this.calcPercentageOfSubmitedExpense();
    }

    // private Income constructor
    var prvIncome = function(id, description, value) {
        this.id = id;
        this.description = description;
        this.value = value;
    };

    // object for storing instances of prvExpense, prvIncome, totalExpenses, totalIncomes
    // every expense entered in the UI will map to an instance of prvExpense
    // every income entered in the UI will map to an instance of prvIncome
    var prvData = {

        // object with all the expenses and all of the incomes
        allItems: {
            // all expense transactions
            exp: [],

            // all income transactions
            inc: []
        },

        // object with expenses total and income total
        totals: {
            // sum of all expense transactions
            exp: 0,

            // sum of all income transactions
            inc: 0
        },

        // total budget: incomes - expenses
        budget: 0,

        // how much of the total income, in %, the total expenses represent
        // -1 = there is no income submited
        expensesPercentageIncome: -1
    };

    // calculate total income or total expenses
    // type  String  type of transaction "exp" or "inc"
    var prvCalculateTotal = function(type) {
        var sum = 0;

        // select between the exp or inc array of the prvDate object to calculate the sum of all the values of each object in the array
        prvData.allItems[type].forEach(function(currElem) {
            sum = sum + currElem.value;
        });

        // add this sum to the prvData.totals[exp] or prvData.totals[inc]
        prvData.totals[type] = sum;
    }

    return {

        // pubic method for creating a new Expense or Income instance based on user input from the UI,
        // add it to the exp or inc array of prvData.allItems, and return the new instance created
        pblAddItem: function(type, descripton, value) {

            // new reference to a new intance of prvExpense or prvIncome
            var newItem;

             // create a unique id to put in each prvExpense or prvIncome item
            var id;

            id = prvData.allItems[type].length > 0 ? prvData.allItems[type][prvData.allItems[type].length - 1].id + 1 : 0;

            // create new instance of prvExpense or prvIncome based on the type parameter
            if(type === "exp") {
                newItem = new prvExpense(id, descripton, value);
            } else if (type === "inc") {
                newItem = new prvIncome(id, descripton, value);
            }

            // object[property]
            // add expense/income to the prvData.allItems[exp] or prvData.allItems[inc] array
            // based on the type parameter
            prvData.allItems[type].push(newItem);

            // return new instance of prvExpense or prvIncome
            return newItem;
        },

        // delete a transaction from the prvData.allItems.exp or prvData.allItems.exp arrays
        // transType  string  the trasaction type "exp" or "inc" in order to extract the element from the appropiate array
        // transId    string  the trasaction id to be extracted from prvData.allItems.exp or prvData.allItems.inc
        pblDeleteItem: function(transType, transId) {

            console.info("transType %s", transType);
            console.info("transId %d", transId);

            var trasactionTypeIds, indexDeleteElem;

            // id = 6
            // [1 2 4 6 8]
            // delete the item at index at which 6 sits

            // create an array with all the ids of the trasactions submited of type transType
            trasactionTypeIds = prvData.allItems[transType].map(function(currentElem) {
                return currentElem.id;
            });
            console.info("trasactionTypeIds %O", trasactionTypeIds);

            // get the index of the element with id transId from the trasactionTypeIds array
            indexDeleteElem = trasactionTypeIds.indexOf(transId);
            console.info("indexDeleteElem %O", indexDeleteElem);

            // if the id that was passed to the function is found in the transaction ids
            if (indexDeleteElem !== -1) {

                // detele the item at index idexDeleteElem from the prvData.allItems[transType] array
                prvData.allItems[transType].splice(indexDeleteElem, 1);
            }
            console.info(" prvData.allItems[%s] %O", transType, prvData.allItems[transType]);

        },

        // to delete
        pblTestGetDataStr: function() {
            console.info("prvData = %O", prvData);
        },

        // calculate the budget after submiting a transaction
        // calculate the % shown in the expenses red row under the available budget. aka the percentage of the budget that we already spent
        pblCalculateBudget: function() {

            // calculate total income
            prvCalculateTotal("inc");

            // calculate total expenses
            prvCalculateTotal("exp");

            // calculate the budget: income - expenses
            prvData.budget = prvData.totals.inc - prvData.totals.exp;
            console.log("budget = %d", prvData.budget);

            // if at least one income transaction was submited
            // else prvData.expensesPercentageIncome = -1 it's default value
            if (prvData.totals.inc > 0){

                // calculate the percent of how much the total expenses represent out of the total income.
                // The result will have 1 decimal and it will be rounded.
                prvData.expensesPercentageIncome = roundDecimal(((prvData.totals.exp * 100) / prvData.totals.inc), 1);
            }
        },

        // calculate percentage for each expense object that is stored in the prvData.allItems.exp array
        pblCalculatePercentageOfSubmitedExpenses: function() {
            // a = 20
            // b = 10
            // c = 40
            // total income 100
            // a = 20/100 * 100 = 20% out of total income
            // b = 10/100 * 100 = 10% out of total income
        },

        // return the budget, total of expenses, of income, and expense percentage
        // it's not a good idea to return the prvData object to be altered by another module
        pblGetBudgetExpIncExpPercentage: function() {
            return {
                budget: prvData.budget,
                totalExpenses : prvData.totals.exp,
                totalIncome : prvData.totals.inc,
                expensesPercentIncome : prvData.expensesPercentageIncome
            };
        },
    }
})();

console.log("Budget controller has finished loading.");
<commit_msg>Rename prvData.percentageOfTransOutOfTotalIncome prop to percentageOfExpenseOutOfTotalIncome  and replace the new name in the script, add calculatePercentageOfExpenseOutOfTotalIncome(), add the pblSetPercentageOfExpenseOutOfTotalIncomeForEachExp() method<commit_after>/*
 * Developed by Radu Puspana
 * Date August 2017
 * Version 1.0
 */

console.log("App has started.");
console.log("Loading controllers.");

// module that handles the budget data using the module pattern
// this controller keeps track of all the income, expenses, the budget itself, and
// also the percentage of how much an income/expense represents of the total budget
var budgetController = (function() {

    // private Expense constructor
    var prvExpense = function(id, description, value) {
        this.id = id;
        this.description = description;
        this.value = value;
        this.percentageOfExpenseOutOfTotalIncome = -1; // -1 = value can't be calculated yet
    };

    // calculate how much in % an expense represents out of the total income and store this value in
    // prvExpense.percentageOfExpenseOutOfTotalIncome
    // totalIncome  Number  the total of the submited income transactions
    prvExpense.prototype.calculatePercentageOfExpenseOutOfTotalIncome = function() {
        if (prvData.totals.inc > 0) {
            this.percentageOfExpenseOutOfTotalIncome = roundDecimal((this.value / prvData.totals.inc) * 100, 1);
        }
        else {
            // when we delete all of the expense transactions, we need to reset this variable
            this.percentageOfExpenseOutOfTotalIncome = -1;
        }
    }

    // getter for the prvExpense.percentageOfExpenseOutOfTotalIncome
    prvExpense.prototype.getPercentageOfSubmitedExpense = function() {
        return this.calcPercentageOfSubmitedExpense();
    }

    // private Income constructor
    var prvIncome = function(id, description, value) {
        this.id = id;
        this.description = description;
        this.value = value;
    };

    // object for storing instances of prvExpense, prvIncome, totalExpenses, totalIncomes
    // every expense entered in the UI will map to an instance of prvExpense
    // every income entered in the UI will map to an instance of prvIncome
    var prvData = {

        // object with all the expenses and all of the incomes
        allItems: {
            // all expense transactions
            exp: [],

            // all income transactions
            inc: []
        },

        // object with expenses total and income total
        totals: {
            // sum of all expense transactions
            exp: 0,

            // sum of all income transactions
            inc: 0
        },

        // total budget: incomes - expenses
        budget: 0,

        // how much of the total income, in %, the total expenses represent
        // -1 = there is no income submited
        expensesPercentageIncome: -1
    };

    // calculate total income or total expenses
    // type  String  type of transaction "exp" or "inc"
    var prvCalculateTotal = function(type) {
        var sum = 0;

        // select between the exp or inc array of the prvDate object to calculate the sum of all the values of each object in the array
        prvData.allItems[type].forEach(function(currElem) {
            sum = sum + currElem.value;
        });

        // add this sum to the prvData.totals[exp] or prvData.totals[inc]
        prvData.totals[type] = sum;
    }

    return {

        // pubic method for creating a new Expense or Income instance based on user input from the UI,
        // add it to the exp or inc array of prvData.allItems, and return the new instance created
        pblAddItem: function(type, descripton, value) {

            // new reference to a new intance of prvExpense or prvIncome
            var newItem;

             // create a unique id to put in each prvExpense or prvIncome item
            var id;

            id = prvData.allItems[type].length > 0 ? prvData.allItems[type][prvData.allItems[type].length - 1].id + 1 : 0;

            // create new instance of prvExpense or prvIncome based on the type parameter
            if(type === "exp") {
                newItem = new prvExpense(id, descripton, value);
            } else if (type === "inc") {
                newItem = new prvIncome(id, descripton, value);
            }

            // object[property]
            // add expense/income to the prvData.allItems[exp] or prvData.allItems[inc] array
            // based on the type parameter
            prvData.allItems[type].push(newItem);

            // return new instance of prvExpense or prvIncome
            return newItem;
        },

        // delete a transaction from the prvData.allItems.exp or prvData.allItems.exp arrays
        // transType  string  the trasaction type "exp" or "inc" in order to extract the element from the appropiate array
        // transId    string  the trasaction id to be extracted from prvData.allItems.exp or prvData.allItems.inc
        pblDeleteItem: function(transType, transId) {

            console.info("transType %s", transType);
            console.info("transId %d", transId);

            var trasactionTypeIds, indexDeleteElem;

            // id = 6
            // [1 2 4 6 8]
            // delete the item at index at which 6 sits

            // create an array with all the ids of the trasactions submited of type transType
            trasactionTypeIds = prvData.allItems[transType].map(function(currentElem) {
                return currentElem.id;
            });
            console.info("trasactionTypeIds %O", trasactionTypeIds);

            // get the index of the element with id transId from the trasactionTypeIds array
            indexDeleteElem = trasactionTypeIds.indexOf(transId);
            console.info("indexDeleteElem %O", indexDeleteElem);

            // if the id that was passed to the function is found in the transaction ids
            if (indexDeleteElem !== -1) {

                // detele the item at index idexDeleteElem from the prvData.allItems[transType] array
                prvData.allItems[transType].splice(indexDeleteElem, 1);
            }
            console.info(" prvData.allItems[%s] %O", transType, prvData.allItems[transType]);

        },

        // to delete
        pblTestGetDataStr: function() {
            console.info("prvData = %O", prvData);
        },

        // calculate the budget after submiting a transaction
        // calculate the % shown in the expenses red row under the available budget. aka the percentage of the budget that we already spent
        pblCalculateBudget: function() {

            // calculate total income
            prvCalculateTotal("inc");

            // calculate total expenses
            prvCalculateTotal("exp");

            // calculate the budget: income - expenses
            prvData.budget = prvData.totals.inc - prvData.totals.exp;
            console.log("budget = %d", prvData.budget);

            // if at least one income transaction was submited
            // else prvData.expensesPercentageIncome = -1 it's default value
            if (prvData.totals.inc > 0){

                // calculate the percent of how much the total expenses represent out of the total income.
                // The result will have 1 decimal and it will be rounded.
                prvData.expensesPercentageIncome = roundDecimal(((prvData.totals.exp * 100) / prvData.totals.inc), 1);
            }
        },

        // calculate percentage for each expense object that is stored in the prvData.allItems.exp array
        pblSetPercentageOfExpenseOutOfTotalIncomeForEachExp: function() {
            prvData.allItems.exp.forEach(function(currentElem) {
                currentElem.calculatePercentageOfExpenseOutOfTotalIncome();
            })
        },

        // return the budget, total of expenses, of income, and expense percentage
        // it's not a good idea to return the prvData object to be altered by another module
        pblGetBudgetExpIncExpPercentage: function() {
            return {
                budget: prvData.budget,
                totalExpenses : prvData.totals.exp,
                totalIncome : prvData.totals.inc,
                expensesPercentIncome : prvData.expensesPercentageIncome
            };
        },
    }
})();

console.log("Budget controller has finished loading.");
<|endoftext|>


<commit_before>// View model for the courses page
function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.title = ko.computed(function(){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("CoursesTitle");}, gvm);
    gvm.pageHeader = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("CoursesHeader");}, gvm);
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectName");}, gvm);

    gvm.isUpdating = false;

    gvm.availableLocations = ko.observableArray([]);
    gvm.availableTrainings = ko.observableArray([]);
    gvm.availableCourses = ko.observableArray([]);

    gvm.addAvailableLocations = function(id, name) {
        var selectObject = {id: id, locationName: name};
        gvm.availableLocations.push(selectObject);
    }
    gvm.addAvailableTrainings = function(id, name) {
        var selectObject = {id: id, trainingName: name};
        gvm.availableTrainings.push(selectObject);
    }

    gvm.addAvailableCourses = function(id, name) {
        var selectObject = {id: id, courseName: name};
        gvm.availableCourses.push(selectObject);
    }

    gvm.clearAll = function() {
        gvm.availableLocations.removeAll();
        gvm.availableTrainings.removeAll();
        gvm.availableCourses.removeAll();
    }
}

function loadAllSelects($locationid, $trainingid)
{
    viewModel.clearAll();
    $.getJSON('/api/courses/' + $locationid + '/' +  $trainingid, function(data) {
        $.each(data[1],function(i, item) {
            viewModel.addAvailableLocations(item.id, item.name);
        });
        $.each(data[2], function(i, item) {
            viewModel.addAvailableTrainings(item.id, item.name);
        });
        $.each(data[3], function(i, item) {
            viewModel.addAvailableCourses(item.id, item.name);
        });
    });
    $("#location").bind("change", function() {
        loadTrainingsAndCourses($("#location").val(), $("#training").val());
    }) ;
    $("#training").bind("change", function() {
       loadCourses($("#location").val(), $("#training").val());
    });
}

function loadTrainingsAndCourses($locationid, $trainingid) {
    viewModel.availableTrainings.removeAll();
    view.availableCourses.removeAll();
    $.getJSON('/api/courses/' + $locationid + '/' +  $trainingid, function(data) {
        $.each(data[2], function(i, item) {
            viewModel.addAvailableTrainings(item.id, item.name);
        });
        $.each(data[3], function(i, item) {
            viewModel.addAvailableCourses(item.id, item.name);
        });
    });
}

function loadCourses($locationid, $trainingid) {
    view.availableCourses.removeAll();
    $.getJSON('/api/courses/' + $locationid + '/' +  $trainingid, function(data) {
        $.each(data[3], function(i, item) {
            viewModel.addAvailableCourses(item.id, item.name);
        });
    });
}

function initPage() {
    loadAllSelects(1,1);
}<commit_msg>courses<commit_after>// View model for the courses page
function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.title = ko.computed(function(){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("CoursesTitle");}, gvm);
    gvm.pageHeader = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("CoursesHeader");}, gvm);
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectName");}, gvm);

    gvm.isUpdating = false;

    gvm.availableLocations = ko.observableArray([]);
    gvm.availableTrainings = ko.observableArray([]);
    gvm.availableCourses = ko.observableArray([]);

    gvm.addAvailableLocations = function(id, name) {
        var selectObject = {id: id, locationName: name};
        gvm.availableLocations.push(selectObject);
    }
    gvm.addAvailableTrainings = function(id, name) {
        var selectObject = {id: id, trainingName: name};
        gvm.availableTrainings.push(selectObject);
    }

    gvm.addAvailableCourses = function(id, name) {
        var selectObject = {id: id, courseName: name};
        gvm.availableCourses.push(selectObject);
    }

    gvm.clearAll = function() {
        gvm.availableLocations.removeAll();
        gvm.availableTrainings.removeAll();
        gvm.availableCourses.removeAll();
    }
}

function loadAllSelects($locationid, $trainingid)
{
    viewModel.clearAll();
    $.getJSON('/api/courses/' + $locationid + '/' +  $trainingid, function(data) {
        $.each(data[1],function(i, item) {
            viewModel.addAvailableLocations(item.id, item.name);
        });
        $.each(data[2], function(i, item) {
            viewModel.addAvailableTrainings(item.id, item.name);
        });
        $.each(data[3], function(i, item) {
            viewModel.addAvailableCourses(item.id, item.name);
        });
    });
    $("#location").bind("change", function() {
        loadTrainingsAndCourses($("#location").val(), $("#training").val());
    }) ;
    $("#training").bind("change", function() {
       loadCourses($("#location").val(), $("#training").val());
    });
}

function loadTrainingsAndCourses($locationid, $trainingid) {
    viewModel.availableTrainings.removeAll();
    viewModel.availableCourses.removeAll();
    $.getJSON('/api/courses/' + $locationid + '/' +  $trainingid, function(data) {
        $.each(data[2], function(i, item) {
            viewModel.addAvailableTrainings(item.id, item.name);
        });
        $.each(data[3], function(i, item) {
            viewModel.addAvailableCourses(item.id, item.name);
        });
    });
}

function loadCourses($locationid, $trainingid) {
    viewModel.availableCourses.removeAll();
    $.getJSON('/api/courses/' + $locationid + '/' +  $trainingid, function(data) {
        $.each(data[3], function(i, item) {
            viewModel.addAvailableCourses(item.id, item.name);
        });
    });
}

function initPage() {
    loadAllSelects(1,1);
}<|endoftext|>


<commit_before>$(function() {

  var Dataset = function(type, name, code, extra, hitTemplate, hintFun) {
  var Dataset = function(type, name, code, extra, hintFun) {
    var self = this;

    self.type = type;
    self.name = name;
    self.id = name.toLowerCase().replace(/[^a-zA-Z0-9-]+/g, "-").replace(/-+/g, "-");

    switch (self.type) {
      case "socrata":
        self.searchUrlTemplate = "https://data.code4sa.org/resource/" + code + ".json?$q={0}";
        self.searchMoreUrlTemplate = "https://data.code4sa.org/" + extra + "/data?q={0}";
        break;
      case "socrata_private":
        self.searchUrlTemplate = "https://backchat.code4sa.org/portalproxy/resource/" + code + ".json?$q={0}";
        self.searchMoreUrlTemplate = "https://backchat.code4sa.org/portalproxy/resource/" + code + ".csv?$q={0}";
        break;
      case "sourceafrica":
        self.searchUrlTemplate = "https://dc.sourceafrica.net/api/search.json?q=projectid%3A404-sens+{0}&page=0&sections=true&mentions=3";
        self.searchMoreUrlTemplate = "https://sourceafrica.net/search.html#q=projectid%3A404-sens%20{0}";
        break;
    }

    self.hitTemplate = hitTemplate || function(hit) {
      var summary = "";

      $.each(hit, function(key, val) {
        summary += " " + Handlebars.escapeExpression(val);
      });

      return summary;
    };

    self.parse_socrata = function(resp) {
      self.total_hits = resp.length;
      self.hits = [];

      if (self.id == "cipc") {
        // clump the same companies together with multiple director entries
        resp = _.map(_.groupBy(resp, 'regno'), function(hits) {
          var company = {};

          // core company info from first entry
          _.each(hits[0], function(value, key) {
            if (key.slice(0, 8) != "director") {
              company[key] = value;
            }
          });

          // directors
          company.directors = _.filter(hits, function(hit) { return !!hit.director_status; });

          return company;
        });
      }

      resp = resp.slice(0, 5);
      resp.forEach(function(hit) {
        self.hits.push({
          summary: self.markText(self.hitTemplate(hit), self.q),
        });
      });
    };
    self.parse_socrata_private = self.parse_socrata;

    self.parse_sourceafrica = function(resp) {
      self.total_hits = resp.total;
      self.hits = [];

      hits = resp.documents.slice(0, 5);

      for (var i = 0; i < hits.length; i++) {
        var hit = hits[i],
            summary = '' + hit.title + '';
            mentions = [];

        if (hit.mentions) {
          for (var j = 0; j < hit.mentions.length; j++) {
            mentions.push(hit.mentions[j].text.replace(/<(\/)?b>/g, "<$1mark>"));
          }
        }

        if (mentions.length) {
          summary += " - " + mentions.join(" ... ");
        }

        self.hits.push({
          summary: summary,
        });
      }
    };

    if (hintFun) {
      self.makeHint = hintFun;
    } else {
      self.makeHint = function(query) { return null; };
    }

    self.markText = function(text, query) {
      var tmp = $.parseHTML("
" + text + "
")[0];
      new Mark(tmp).mark(stemmer(query), {separateWordSearch: true});
      return tmp.innerHTML;
    };

    self.reset = function() {
      self.hint = null;
      self.hits = [];
      self.total_hits = 0;
      self.searching = true;
      self.searchMoreURL = "";
    };
    self.reset();

    self.search = function(query) {
      var result = $.Deferred(),
          escapedQuery = encodeURIComponent(query),
          url = self.searchUrlTemplate.replace("{0}", escapedQuery);

      self.searchMoreURL = self.searchMoreUrlTemplate.replace("{0}", escapedQuery);
      self.q = query;
      self.hint = self.makeHint(query);

      $.ajax(url)
        .done(function(resp) {
          self.error = false;
          self.searching = false;
          self['parse_' + self.type].call(this, resp);
          result.resolve();
        })
        .fail(function(jqXHR, textStatus, errorThrown) {
          self.searching = false;
          self.error = true;
          console.log(textStatus, errorThrown, jqXHR);
          if ('ga' in window) {
            logMsg = self.type + " query: \"" + self.q + "\" " + textStatus + ": " + errorThrown;
            ga('send', 'event', 'corporate-data-search', 'error', logMsg);
          }
          result.resolve();
        });

      return result;
    };
  };

  var cipcHint = function(query) {
    var SAIDMatches = /(\d{6})\d{4}(\d{2})\d/.exec(query);
    var CIDMatches = /(\d{4})\/?(\d{6})\/?(\d{2})/.exec(query);
    var hint = "";
    if (SAIDMatches) {
      hint += ["It looks like you're trying to search for an SA ID ",
               SAIDMatches[0],
               ". SA IDs in this dataset are usually written like ",
               SAIDMatches[1], " XXXX ", SAIDMatches[2], " X or just the date of birth ",
               SAIDMatches[1], ". Try also searching for those. "].join('');
    }
    if (CIDMatches) {
      hint += ["It looks like you're trying to search for a company number ",
               CIDMatches[0],
               ". Company numbers in this dataset are usually written like ",
               CIDMatches[1], " / ", CIDMatches[2], " / ", CIDMatches[3],
               ". Try also searching with spaces and slashes in that form. "].join('');
    }
    return hint;
  };

  var datasets = [
    new Dataset("socrata_private", "CIPC", "5erp-fahs", null, Handlebars.compile($("#cipc-hit-template").html()), cipcHint),
    new Dataset("socrata", "UK Land Registry", "qxgb-avr5", "Business/UK-Land-Registry/n7gy-as2q"),
    new Dataset("socrata", "Tender Awards 2015-2016", "9vmn-5tnb", "Government/Tender-Awards-2015-2016/kvv2-xrvr"),
    new Dataset("sourceafrica", "SENS", "404-sens"),
  ];

  var resultsContainer = $('#corporate-data-search .search-results');
  var datasetTemplate = Handlebars.compile($('#search-dataset-template').html());

  $("#corporate-data-search form").on('submit', function(e) {
    e.preventDefault();

    var q = $(this).find('[name=q]').val(),
        perCol = Math.ceil(datasets.length / 2);

    resultsContainer
      .show()
      .find('.panel').remove();

    datasets.forEach(function(dataset, i) {
      // column it should go in
      var col = Math.floor(i / perCol) + 1,
          $output = resultsContainer.find('.col-' + col);

      dataset.reset();
      $output.append(datasetTemplate(dataset));

      dataset.search(q)
        .then(function() {
          $output.find("#" + dataset.id).replaceWith(datasetTemplate(dataset));
        });
    });

    if ('ga' in window) ga('send', 'event', 'corporate-data-search', 'request', q);
  });

});
<commit_msg>fix rebase fail<commit_after>$(function() {

  var Dataset = function(type, name, code, extra, hitTemplate, hintFun) {
    var self = this;

    self.type = type;
    self.name = name;
    self.id = name.toLowerCase().replace(/[^a-zA-Z0-9-]+/g, "-").replace(/-+/g, "-");

    switch (self.type) {
      case "socrata":
        self.searchUrlTemplate = "https://data.code4sa.org/resource/" + code + ".json?$q={0}";
        self.searchMoreUrlTemplate = "https://data.code4sa.org/" + extra + "/data?q={0}";
        break;
      case "socrata_private":
        self.searchUrlTemplate = "https://backchat.code4sa.org/portalproxy/resource/" + code + ".json?$q={0}";
        self.searchMoreUrlTemplate = "https://backchat.code4sa.org/portalproxy/resource/" + code + ".csv?$q={0}";
        break;
      case "sourceafrica":
        self.searchUrlTemplate = "https://dc.sourceafrica.net/api/search.json?q=projectid%3A404-sens+{0}&page=0&sections=true&mentions=3";
        self.searchMoreUrlTemplate = "https://sourceafrica.net/search.html#q=projectid%3A404-sens%20{0}";
        break;
    }

    self.hitTemplate = hitTemplate || function(hit) {
      var summary = "";

      $.each(hit, function(key, val) {
        summary += " " + Handlebars.escapeExpression(val);
      });

      return summary;
    };

    self.parse_socrata = function(resp) {
      self.total_hits = resp.length;
      self.hits = [];

      if (self.id == "cipc") {
        // clump the same companies together with multiple director entries
        resp = _.map(_.groupBy(resp, 'regno'), function(hits) {
          var company = {};

          // core company info from first entry
          _.each(hits[0], function(value, key) {
            if (key.slice(0, 8) != "director") {
              company[key] = value;
            }
          });

          // directors
          company.directors = _.filter(hits, function(hit) { return !!hit.director_status; });

          return company;
        });
      }

      resp = resp.slice(0, 5);
      resp.forEach(function(hit) {
        self.hits.push({
          summary: self.markText(self.hitTemplate(hit), self.q),
        });
      });
    };
    self.parse_socrata_private = self.parse_socrata;

    self.parse_sourceafrica = function(resp) {
      self.total_hits = resp.total;
      self.hits = [];

      hits = resp.documents.slice(0, 5);

      for (var i = 0; i < hits.length; i++) {
        var hit = hits[i],
            summary = '' + hit.title + '';
            mentions = [];

        if (hit.mentions) {
          for (var j = 0; j < hit.mentions.length; j++) {
            mentions.push(hit.mentions[j].text.replace(/<(\/)?b>/g, "<$1mark>"));
          }
        }

        if (mentions.length) {
          summary += " - " + mentions.join(" ... ");
        }

        self.hits.push({
          summary: summary,
        });
      }
    };

    if (hintFun) {
      self.makeHint = hintFun;
    } else {
      self.makeHint = function(query) { return null; };
    }

    self.markText = function(text, query) {
      var tmp = $.parseHTML("
" + text + "
")[0];
      new Mark(tmp).mark(stemmer(query), {separateWordSearch: true});
      return tmp.innerHTML;
    };

    self.reset = function() {
      self.hint = null;
      self.hits = [];
      self.total_hits = 0;
      self.searching = true;
      self.searchMoreURL = "";
    };
    self.reset();

    self.search = function(query) {
      var result = $.Deferred(),
          escapedQuery = encodeURIComponent(query),
          url = self.searchUrlTemplate.replace("{0}", escapedQuery);

      self.searchMoreURL = self.searchMoreUrlTemplate.replace("{0}", escapedQuery);
      self.q = query;
      self.hint = self.makeHint(query);

      $.ajax(url)
        .done(function(resp) {
          self.error = false;
          self.searching = false;
          self['parse_' + self.type].call(this, resp);
          result.resolve();
        })
        .fail(function(jqXHR, textStatus, errorThrown) {
          self.searching = false;
          self.error = true;
          console.log(textStatus, errorThrown, jqXHR);
          if ('ga' in window) {
            logMsg = self.type + " query: \"" + self.q + "\" " + textStatus + ": " + errorThrown;
            ga('send', 'event', 'corporate-data-search', 'error', logMsg);
          }
          result.resolve();
        });

      return result;
    };
  };

  var cipcHint = function(query) {
    var SAIDMatches = /(\d{6})\d{4}(\d{2})\d/.exec(query);
    var CIDMatches = /(\d{4})\/?(\d{6})\/?(\d{2})/.exec(query);
    var hint = "";
    if (SAIDMatches) {
      hint += ["It looks like you're trying to search for an SA ID ",
               SAIDMatches[0],
               ". SA IDs in this dataset are usually written like ",
               SAIDMatches[1], " XXXX ", SAIDMatches[2], " X or just the date of birth ",
               SAIDMatches[1], ". Try also searching for those. "].join('');
    }
    if (CIDMatches) {
      hint += ["It looks like you're trying to search for a company number ",
               CIDMatches[0],
               ". Company numbers in this dataset are usually written like ",
               CIDMatches[1], " / ", CIDMatches[2], " / ", CIDMatches[3],
               ". Try also searching with spaces and slashes in that form. "].join('');
    }
    return hint;
  };

  var datasets = [
    new Dataset("socrata_private", "CIPC", "5erp-fahs", null, Handlebars.compile($("#cipc-hit-template").html()), cipcHint),
    new Dataset("socrata", "UK Land Registry", "qxgb-avr5", "Business/UK-Land-Registry/n7gy-as2q"),
    new Dataset("socrata", "Tender Awards 2015-2016", "9vmn-5tnb", "Government/Tender-Awards-2015-2016/kvv2-xrvr"),
    new Dataset("sourceafrica", "SENS", "404-sens"),
  ];

  var resultsContainer = $('#corporate-data-search .search-results');
  var datasetTemplate = Handlebars.compile($('#search-dataset-template').html());

  $("#corporate-data-search form").on('submit', function(e) {
    e.preventDefault();

    var q = $(this).find('[name=q]').val(),
        perCol = Math.ceil(datasets.length / 2);

    resultsContainer
      .show()
      .find('.panel').remove();

    datasets.forEach(function(dataset, i) {
      // column it should go in
      var col = Math.floor(i / perCol) + 1,
          $output = resultsContainer.find('.col-' + col);

      dataset.reset();
      $output.append(datasetTemplate(dataset));

      dataset.search(q)
        .then(function() {
          $output.find("#" + dataset.id).replaceWith(datasetTemplate(dataset));
        });
    });

    if ('ga' in window) ga('send', 'event', 'corporate-data-search', 'request', q);
  });

});
<|endoftext|>