<commit_before>/**
 * Controller for the search tab. pops in sub controllers for geographic/cultural/etc
 *
 * @param $scope
 * @param colorMapService
 * @param searchModelService
 * @param FindSocieties
 * @constructor
 */
function SearchCtrl($scope, $window, $location, colorMapService, searchModelService, FindSocieties) {
    $scope.setActive('search');
    $scope.searchModel = searchModelService.getModel();
    $scope.searchBySocietyButton = {text: 'Search', disabled: false};
    $scope.selectedButton = $scope.searchModel.selectedButton;
    $scope.searchCriteria = "View selected search criteria";

    var rP = 'radioPlaces';
    var rL = 'radioLanguage';
    var rC = 'radioCulture';
    var rE = 'radioEnv';
    // preserve active button style if any
    if($scope.selectedButton) {
        switch($scope.selectedButton.radioClass) {
        case 'radioPlaces':
            rP += ' active';
            break;
        case 'radioLanguage':
            rL += ' active';
            break;
        case 'radioCulture':
            rC += ' active';
            break;
        case 'radioEnv':
            rE += ' active';
            break;
        }
    }
    $scope.buttons = [
        {radioClass: rP, value:'geographic', name:'PLACES', badgeValue:
            function() { return $scope.searchModel.getGeographicRegions().badgeValue; }
        },
        {radioClass: rL, value:'language', name:'LANGUAGE', badgeValue:
            function() { return $scope.searchModel.getLanguageClassifications().badgeValue; }
        },
        {radioClass: rC, value:'cultural', name:'CULTURE', badgeValue:
            function() { return $scope.searchModel.getCulturalTraits().badgeValue; }
        },
        {radioClass: rE, value:'environmental', name:'ENVIRONMENT', badgeValue:
            function() { return $scope.searchModel.getEnvironmentalData().badgeValue; }
        }
    ];

    // All of this needs to move into model
    $scope.disableSearchButton = function () {
        $scope.searchButton.disabled = true;
        $scope.searchButton.text = 'Working...';
        $scope.searchBySocietyButton = {text: 'Working...', disabled: true};
    };

    $scope.enableSearchButton = function () {
        $scope.searchButton.disabled = false;
        $scope.searchButton.text = 'Search';
        $scope.searchBySocietyButton = {text: 'Search', disabled: false};
    };

    $scope.showCriteria = function() {
        $("#selected-criteria").toggleClass('hidden');
        $("#search-panel").toggleClass('col-md-9', 'col-md-12');
        if (!$("#selected-criteria").hasClass('hidden')) $scope.searchCriteria = "Hide selected search criteria";
        else $scope.searchCriteria = "View selected search criteria";
    };

    //removes a variable, language, or region from search parameters
    $scope.removeFromSearch = function(object, searchType) {
        var index = -1;
        switch(searchType) {
            case 'geographic':
                index = $scope.searchModel.getGeographicRegions().selectedRegions.indexOf(object);
                if (index > -1) {
                    $scope.searchModel.getGeographicRegions().selectedRegions.splice(index, 1);
                    $scope.searchModel.getGeographicRegions().badgeValue--;
                }
                break;
            case 'environmental':
                index = $scope.searchModel.getEnvironmentalData().selectedVariables.indexOf(object);
                if (index > -1) {
                    $scope.searchModel.getEnvironmentalData().selectedVariables.splice(index, 1);
                    $scope.searchModel.getEnvironmentalData().badgeValue = $scope.searchModel.getEnvironmentalData().selectedVariables.length;
                }
                break;
            case 'family':
                var langSelectedObjs = $scope.searchModel.getLanguageClassifications().selected;
                if (object in langSelectedObjs) {
                    for (var i = 0; i < langSelectedObjs[object].length; i++) {
                        langSelectedObjs[object][i].isSelected = false;
                        $scope.searchModel.getLanguageClassifications().badgeValue -= langSelectedObjs[object][i].societies.length
                    }
                    delete langSelectedObjs[object];
                }
                break;
            case 'language':
                var langSelectedObjs = $scope.searchModel.getLanguageClassifications().selected;
                if (object.family.name in langSelectedObjs) {
                    for (var i = 0; i < langSelectedObjs[object.family.name].length; i++) {
                        if (langSelectedObjs[object.family.name][i].id == object.id) {
                            langSelectedObjs[object.family.name][i].isSelected = false;
                            index = i;
                            break;
                        }
                    }
                    if (index > -1) {
                        langSelectedObjs[object.family.name].splice(index, 1);
                        $scope.searchModel.getLanguageClassifications().badgeValue -= object.societies.length;
                        if ($scope.searchModel.getLanguageClassifications().allClasses.selectedFamily && object.family.id == $scope.searchModel.getLanguageClassifications().allClasses.selectedFamily.id)
                            $scope.searchModel.getLanguageClassifications().allClasses.languages.allSelected = false;
                    }
                }
                break;
            case 'code':
                object.isSelected = false;
                 if (object.type == 'c') {
                    variable = $scope.searchModel.getCulturalTraits().selectedVariables.map(function(v) { return v.id; }).indexOf(object.variable);
                    if (variable > -1) {
                        $scope.searchModel.getCulturalTraits().selectedVariables[variable].allSelected = false;
                        $scope.searchModel.getCulturalTraits().badgeValue--;
                    }
                } else if (object.type == 'e') {
                    variable = $scope.searchModel.getEnvironmentalData().selectedVariables.map(function(v) { return v.selectedVariable.id; }).indexOf(object.variable);
                    if (variable > -1) {
                        $scope.searchModel.getEnvironmentalData().selectedVariables[variable].selectedVariable.allSelected = false;
                    }
                }
                break;
            case 'variable':
               index = $scope.searchModel.getCulturalTraits().selectedVariables.map(function(v) { return v.id; }).indexOf(object.id);
               if (index > -1) {
                   if (object.data_type.toLowerCase() == 'continuous') {
                       $scope.searchModel.getCulturalTraits().badgeValue--;
                       $scope.searchModel.getCulturalTraits().selectedVariables.splice(index, 1);
                   } else {
                   for (var i = 0; i < $scope.searchModel.getCulturalTraits().selectedVariables[index].codes.length; i++) {
                        if ($scope.searchModel.getCulturalTraits().selectedVariables[index].codes[i].isSelected) {
                            $scope.searchModel.getCulturalTraits().selectedVariables[index].codes[i].isSelected = false;
                            $scope.searchModel.getCulturalTraits().badgeValue--;
                        }
                   }
                    $scope.searchModel.getCulturalTraits().selectedVariables[index].allSelected = false;
                   }
                    $scope.$broadcast('numVars');
               }
        }
        if (!$scope.searchModel.checkSelected()) {
            d3.select("#selected-criteria").classed("hidden", true);
            d3.select("#search-panel").classed("col-md-12", true);
            d3.select("#search-panel").classed("col-md-9", false);
            if (!$("#selected-criteria").hasClass('hidden')) $scope.searchCriteria = "Hide selected search criteria";
            else $scope.searchCriteria = "View selected search criteria";
        }
    };

    var errorCallBack = function() {
        $scope.errors = "Invalid input.";
        $scope.enableSearchButton();
    };

    var searchCompletedCallback = function() {
        $scope.enableSearchButton();
        searchModelService.searchCompletedCallback();
        $scope.switchToResults();
    };

    var searchBySocietyCallBack = function() {
        $scope.enableSearchButton();
        $scope.searchModel.results.searchedByName = true;
        if ($scope.searchModel.results.societies.length == 1)
            $window.location.href = '/society/'+$scope.searchModel.results.societies[0].society.ext_id;
        else
            $scope.switchToResults();
    };

    $scope.searchSocieties = function() {
        $scope.disableSearchButton();
        var query = $scope.searchModel.query;
        $scope.searchModel.results = FindSocieties.find(query, searchCompletedCallback, errorCallBack);
    };

    $scope.searchBySociety = function() {
        $scope.disableSearchButton();
        $scope.searchModel.query = {'name': $scope.model.societyQuery}
        $scope.searchModel.results = FindSocieties.find($scope.searchModel.query, searchBySocietyCallBack);
    };

    $scope.search = function() {
        var i;
        searchModel = searchModelService.getModel();
        searchParams = searchModel.params;
        searchQuery = {};
        for (var propertyName in searchParams) {

            //get selected cultural traits/codes or environmental codes
           if (propertyName == 'culturalTraits' || propertyName == 'environmentalData') {
               searchParams[propertyName].selectedVariables.forEach(function(variable) {
                   filters = [];
                    selectedVariable = (propertyName == 'culturalTraits') ? variable : variable.selectedVariable;
                    if (!selectedVariable) return;
                   if (selectedVariable.data_type.toLowerCase() == 'continuous') {
                       filters = [
                        selectedVariable.id,
                        selectedVariable.selectedFilter.operator,
                        selectedVariable.vals
                       ]
                   } else {
                        selected_codes = selectedVariable.codes.filter(function (c) { return c.isSelected; }).map(function(c) { return c.id; });
                        if (selected_codes.length > 0) {
                            filters = [
                                selectedVariable.id,
                                'categorical',
                                selected_codes
                            ]
                        }
                   }
                   if (filters.length > 0) {
                    if (propertyName.charAt(0) in searchQuery) searchQuery[propertyName.charAt(0)].push(filters);
                    else searchQuery[propertyName.charAt(0)] = [filters]
                   }
               });

           }
            //get selected regions
            if (propertyName == 'geographicRegions') {
                var selectedRegions = searchParams[propertyName].selectedRegions;
                selectedRegions.forEach(function (selectedRegion) {
                    searchParams[propertyName].allRegions.forEach(function(region) {
                        if (region.tdwg_code == selectedRegion.code)
                            selectedRegion.id = region.id;
                    });
                });
                if (selectedRegions.length > 0) {
                    searchQuery['p'] = [];
                    for (i = 0; i < selectedRegions.length; i++) {
                        searchQuery['p'].push(selectedRegions[i].id);
                    }
                }
            }
            //get selected languages
            if (propertyName == 'languageClassifications') {
                var classifications = [];
                for (var family in searchParams[propertyName].selected) {
                    searchParams[propertyName].selected[family].forEach(function(language) { if (language.isSelected) classifications.push(language); });
                }
                if (classifications.length > 0) {
                    searchQuery['l'] = [];
                    for (i = 0; i < classifications.length; i++) {
                        searchQuery['l'].push(classifications[i].id);
                    }
                }
           }
        }
        searchModelService.updateSearchQuery(searchQuery);
        $scope.searchSocieties();
    };

    // resets this object state and the search query.
    $scope.resetSearch = function() {
        $scope.errors = "";
        $scope.enableSearchButton();
        $scope.searchModel.reset();
        // send a notification so that the individual controllers reload their state
        $scope.$broadcast('searchModelReset');

        // hide selected search criteria div if shown
        if (!$("#selected-criteria").hasClass('hidden')) {
            $("#selected-criteria").addClass('hidden');
            $scope.searchCriteria = "View selected search criteria";
            $("#search-panel").toggleClass('col-md-9', 'col-md-12');
        }
    };

}<commit_msg>removing redundant code<commit_after>/**
 * Controller for the search tab. pops in sub controllers for geographic/cultural/etc
 *
 * @param $scope
 * @param colorMapService
 * @param searchModelService
 * @param FindSocieties
 * @constructor
 */
function SearchCtrl($scope, $window, $location, colorMapService, searchModelService, FindSocieties) {
    $scope.setActive('search');
    $scope.searchModel = searchModelService.getModel();
    $scope.searchBySocietyButton = {text: 'Search', disabled: false};
    $scope.selectedButton = $scope.searchModel.selectedButton;
    $scope.searchCriteria = "View selected search criteria";

    var rP = 'radioPlaces';
    var rL = 'radioLanguage';
    var rC = 'radioCulture';
    var rE = 'radioEnv';
    // preserve active button style if any
    if($scope.selectedButton) {
        switch($scope.selectedButton.radioClass) {
        case 'radioPlaces':
            rP += ' active';
            break;
        case 'radioLanguage':
            rL += ' active';
            break;
        case 'radioCulture':
            rC += ' active';
            break;
        case 'radioEnv':
            rE += ' active';
            break;
        }
    }
    $scope.buttons = [
        {radioClass: rP, value:'geographic', name:'PLACES', badgeValue:
            function() { return $scope.searchModel.getGeographicRegions().badgeValue; }
        },
        {radioClass: rL, value:'language', name:'LANGUAGE', badgeValue:
            function() { return $scope.searchModel.getLanguageClassifications().badgeValue; }
        },
        {radioClass: rC, value:'cultural', name:'CULTURE', badgeValue:
            function() { return $scope.searchModel.getCulturalTraits().badgeValue; }
        },
        {radioClass: rE, value:'environmental', name:'ENVIRONMENT', badgeValue:
            function() { return $scope.searchModel.getEnvironmentalData().badgeValue; }
        }
    ];

    // All of this needs to move into model
    $scope.disableSearchButton = function () {
        $scope.searchButton.disabled = true;
        $scope.searchButton.text = 'Working...';
        $scope.searchBySocietyButton = {text: 'Working...', disabled: true};
    };

    $scope.enableSearchButton = function () {
        $scope.searchButton.disabled = false;
        $scope.searchButton.text = 'Search';
        $scope.searchBySocietyButton = {text: 'Search', disabled: false};
    };

    $scope.showCriteria = function() {
        $("#selected-criteria").toggleClass('hidden');
        $("#search-panel").toggleClass('col-md-9', 'col-md-12');
        if (!$("#selected-criteria").hasClass('hidden')) $scope.searchCriteria = "Hide selected search criteria";
        else $scope.searchCriteria = "View selected search criteria";
    };

    //removes a variable, language, or region from search parameters
    $scope.removeFromSearch = function(object, searchType) {
        var index = -1;
        switch(searchType) {
            case 'geographic':
                index = $scope.searchModel.getGeographicRegions().selectedRegions.indexOf(object);
                if (index > -1) {
                    $scope.searchModel.getGeographicRegions().selectedRegions.splice(index, 1);
                    $scope.searchModel.getGeographicRegions().badgeValue--;
                }
                break;
            case 'environmental':
                index = $scope.searchModel.getEnvironmentalData().selectedVariables.indexOf(object);
                if (index > -1) {
                    $scope.searchModel.getEnvironmentalData().selectedVariables.splice(index, 1);
                    $scope.searchModel.getEnvironmentalData().badgeValue = $scope.searchModel.getEnvironmentalData().selectedVariables.length;
                }
                break;
            case 'family':
                var langSelectedObjs = $scope.searchModel.getLanguageClassifications().selected;
                if (object in langSelectedObjs) {
                    for (var i = 0; i < langSelectedObjs[object].length; i++) {
                        langSelectedObjs[object][i].isSelected = false;
                        $scope.searchModel.getLanguageClassifications().badgeValue -= langSelectedObjs[object][i].societies.length
                    }
                    delete langSelectedObjs[object];
                }
                break;
            case 'language':
                var langSelectedObjs = $scope.searchModel.getLanguageClassifications().selected;
                if (object.family.name in langSelectedObjs) {
                    for (var i = 0; i < langSelectedObjs[object.family.name].length; i++) {
                        if (langSelectedObjs[object.family.name][i].id == object.id) {
                            langSelectedObjs[object.family.name][i].isSelected = false;
                            index = i;
                            break;
                        }
                    }
                    if (index > -1) {
                        langSelectedObjs[object.family.name].splice(index, 1);
                        $scope.searchModel.getLanguageClassifications().badgeValue -= object.societies.length;
                        if ($scope.searchModel.getLanguageClassifications().allClasses.selectedFamily && object.family.id == $scope.searchModel.getLanguageClassifications().allClasses.selectedFamily.id)
                            $scope.searchModel.getLanguageClassifications().allClasses.languages.allSelected = false;
                    }
                }
                break;
            case 'code':
                object.isSelected = false;
                 if (object.type == 'c') {
                    variable = $scope.searchModel.getCulturalTraits().selectedVariables.map(function(v) { return v.id; }).indexOf(object.variable);
                    if (variable > -1) {
                        $scope.searchModel.getCulturalTraits().selectedVariables[variable].allSelected = false;
                        $scope.searchModel.getCulturalTraits().badgeValue--;
                    }
                } else if (object.type == 'e') {
                    variable = $scope.searchModel.getEnvironmentalData().selectedVariables.map(function(v) { return v.selectedVariable.id; }).indexOf(object.variable);
                    if (variable > -1) {
                        $scope.searchModel.getEnvironmentalData().selectedVariables[variable].selectedVariable.allSelected = false;
                    }
                }
                break;
            case 'variable':
               index = $scope.searchModel.getCulturalTraits().selectedVariables.map(function(v) { return v.id; }).indexOf(object.id);
               if (index > -1) {
                   if (object.data_type.toLowerCase() == 'continuous') {
                       $scope.searchModel.getCulturalTraits().badgeValue--;
                       $scope.searchModel.getCulturalTraits().selectedVariables.splice(index, 1);
                   } else {
                   for (var i = 0; i < $scope.searchModel.getCulturalTraits().selectedVariables[index].codes.length; i++) {
                        if ($scope.searchModel.getCulturalTraits().selectedVariables[index].codes[i].isSelected) {
                            $scope.searchModel.getCulturalTraits().selectedVariables[index].codes[i].isSelected = false;
                            $scope.searchModel.getCulturalTraits().badgeValue--;
                        }
                   }
                    $scope.searchModel.getCulturalTraits().selectedVariables[index].allSelected = false;
                   }
                    $scope.$broadcast('numVars');
               }
        }
        if (!$scope.searchModel.checkSelected()) {
            d3.select("#selected-criteria").classed("hidden", true);
            d3.select("#search-panel").classed("col-md-12", true);
            d3.select("#search-panel").classed("col-md-9", false);
            if (!$("#selected-criteria").hasClass('hidden')) $scope.searchCriteria = "Hide selected search criteria";
            else $scope.searchCriteria = "View selected search criteria";
        }
    };

    var errorCallBack = function() {
        $scope.errors = "Invalid input.";
        $scope.enableSearchButton();
    };

    var searchCompletedCallback = function() {
        $scope.enableSearchButton();
        searchModelService.searchCompletedCallback();
        $scope.switchToResults();
    };

    var searchBySocietyCallBack = function() {
        $scope.enableSearchButton();
        $scope.searchModel.results.searchedByName = true;
        if ($scope.searchModel.results.societies.length == 1)
            $window.location.href = '/society/'+$scope.searchModel.results.societies[0].society.ext_id;
        else
            $scope.switchToResults();
    };

    $scope.searchSocieties = function() {
        $scope.disableSearchButton();
        var query = $scope.searchModel.query;
        $scope.searchModel.results = FindSocieties.find(query, searchCompletedCallback, errorCallBack);
    };

    $scope.searchBySociety = function() {
        $scope.disableSearchButton();
        $scope.searchModel.query = {'name': $scope.model.societyQuery}
        $scope.searchModel.results = FindSocieties.find($scope.searchModel.query, searchBySocietyCallBack);
    };

    $scope.search = function() {
        var i;
        searchModel = searchModelService.getModel();
        searchParams = searchModel.params;
        searchQuery = {};
        for (var propertyName in searchParams) {

            //get selected cultural traits/codes or environmental codes
           if (propertyName == 'culturalTraits' || propertyName == 'environmentalData') {
               searchParams[propertyName].selectedVariables.forEach(function(variable) {
                   filters = [];
                    selectedVariable = (propertyName == 'culturalTraits') ? variable : variable.selectedVariable;
                    if (!selectedVariable) return;
                   if (selectedVariable.data_type.toLowerCase() == 'continuous') {
                       filters = [
                        selectedVariable.id,
                        selectedVariable.selectedFilter.operator,
                        selectedVariable.vals
                       ]
                   } else {
                        selected_codes = selectedVariable.codes.filter(function (c) { return c.isSelected; }).map(function(c) { return c.id; });
                        if (selected_codes.length > 0) {
                            filters = [
                                selectedVariable.id,
                                'categorical',
                                selected_codes
                            ]
                        }
                   }
                   if (filters.length > 0) {
                    if (propertyName.charAt(0) in searchQuery) searchQuery[propertyName.charAt(0)].push(filters);
                    else searchQuery[propertyName.charAt(0)] = [filters]
                   }
               });

           }
            //get selected regions
            if (propertyName == 'geographicRegions') {
                var selectedRegions = searchParams[propertyName].selectedRegions;
                selectedRegions.forEach(function (selectedRegion) {
                    searchParams[propertyName].allRegions.forEach(function(region) {
                        if (region.tdwg_code == selectedRegion.code)
                            selectedRegion.id = region.id;
                    });
                });
                if (selectedRegions.length > 0) {
                    searchQuery['p'] = [];
                    for (i = 0; i < selectedRegions.length; i++) {
                        searchQuery['p'].push(selectedRegions[i].id);
                    }
                }
            }
            //get selected languages
            if (propertyName == 'languageClassifications') {
                var classifications = [];
                for (var family in searchParams[propertyName].selected) {
                    searchParams[propertyName].selected[family].forEach(function(language) { if (language.isSelected) classifications.push(language); });
                }
                if (classifications.length > 0) {
                    searchQuery['l'] = [];
                    for (i = 0; i < classifications.length; i++) {
                        searchQuery['l'].push(classifications[i].id);
                    }
                }
           }
        }
        searchModelService.updateSearchQuery(searchQuery);
        $scope.searchSocieties();
    };

    // resets this object state and the search query.
    $scope.resetSearch = function() {
        $scope.errors = "";
        $scope.enableSearchButton();
        $scope.searchModel.reset();
        // send a notification so that the individual controllers reload their state
        $scope.$broadcast('searchModelReset');
        // hide selected search criteria div if shown
        if (!$("#selected-criteria").hasClass('hidden')) {
            $scope.showCriteria();
        }
    };

}<|endoftext|>


<commit_before>/**
 * Given a DOM subtree, wraps it in a list, and puts each line into its own
 * list item.
 *
 * @param {Node} node modified in place.  Its content is pulled into an
 *     HTMLOListElement, and each line is moved into a separate list item.
 *     This requires cloning elements, so the input might not have unique
 *     IDs after numbering.
 */
function numberLines(node, opt_startLineNum) {
  var nocode = /(?:^|\s)nocode(?:\s|$)/;
  var lineBreak = /\r\n?|\n/;

  var document = node.ownerDocument;

  var whitespace;
  if (node.currentStyle) {
    whitespace = node.currentStyle.whiteSpace;
  } else if (window.getComputedStyle) {
    whitespace = document.defaultView.getComputedStyle(node, null)
        .getPropertyValue('white-space');
  }
  // If it's preformatted, then we need to split lines on line breaks
  // in addition to
s.
  var isPreformatted = whitespace && 'pre' === whitespace.substring(0, 3);

  var li = document.createElement('LI');
  while (node.firstChild) {
    li.appendChild(node.firstChild);
  }
  // An array of lines.  We split below, so this is initialized to one
  // un-split line.
  var listItems = [li];

  function walk(node) {
    switch (node.nodeType) {
      case 1:  // Element
        if (nocode.test(node.className)) { console.log('nocode'); break; }
console.log('  el=' + node.nodeName);
        if ('BR' === node.nodeName) {
          breakAfter(node);
          // Discard the
 since it is now flush against a
.
          if (node.parentNode) {
            node.parentNode.removeChild(node);
          }
        } else {
          for (var child = node.firstChild; child; child = child.nextSibling) {
            walk(child);
          }
        }
        break;
      case 3: case 4:  // Text
        if (isPreformatted) {
          var text = node.nodeValue;
          var match = text.match(lineBreak);
          if (match) {
            var firstLine = text.substring(0, match.index);
            node.nodeValue = firstLine;
            var tail = text.substring(match.index + match[0].length);
console.log('split "' + text.replace(/\r\n?|\n/g, '\\n') + '" into "' + firstLine + '" and "' + tail.replace(/\r\n?|\n/g, '\\n') + '"');
            if (tail) {
              var parent = node.parentNode;
              parent.insertBefore(
                  document.createTextNode(tail), node.nextSibling);
            }
            breakAfter(node);
            if (!firstLine) {
              // Don't leave blank text nodes in the DOM.
              node.parentNode.removeChild(node);
            }
          }
        }
        break;
    }
  }

  // Split a line after the given node.
  function breakAfter(lineEndNode) {
    // If there's nothing to the right, then we can skip ending the line
    // here, and move root-wards since splitting just before an end-tag
    // would require us to create a bunch of empty copies.
    while (!lineEndNode.nextSibling) {
      lineEndNode = lineEndNode.parentNode;
      if (!lineEndNode) { return; }
    }

    function breakLeftOf(limit, copy) {
      // Clone shallowly if this node needs to be on both sides of the break.
      var rightSide = copy ? limit.cloneNode(false) : limit;
      var parent = limit.parentNode;
      if (parent) {
        // We clone the parent chain.
        // This helps us resurrect important styling elements that cross lines.
        // E.g. in Foo
Bar
        // should be rewritten to
Foo

Bar
.
        var parentClone = breakLeftOf(parent, 1);
        // Move the clone and everything to the right of the original
        // onto the cloned parent.
        var next = limit.nextSibling;
        parentClone.appendChild(rightSide);
        for (var sibling = next; sibling; sibling = next) {
          next = sibling.nextSibling;
          parentClone.appendChild(sibling);
        }
      }
      return rightSide;
    }

    var split = breakLeftOf(lineEndNode.nextSibling, 0);

    // Walk the parent chain until we reach an unattached LI.
    for (var parent; (parent = split.parentNode);) { split = parent; }
    // Put it on the list of lines for later processing.
    listItems.push(split);
console.log('pushing listItem length=' + listItems.length);
  }

  // Split lines while there are lines left to split.
  for (var i = 0;  // Number of lines that have been split so far.
       i < listItems.length;  // length updated by breakAfter calls.
       ++i) {
console.log('walking list item ' + i + ' : ' + listItems[i].innerHTML.replace(/[\r\n]+/, '\\n'));
    walk(listItems[i]);
  }

  // Make sure numeric indices show correctly.
  if (opt_startLineNum === (opt_startLineNum|0)) {
    listItems[0].setAttribute('value', opt_startLineNum);
  }

  var ol = document.createElement('OL');
  ol.className = 'linenums';
  var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0;
  for (var i = 0, n = listItems.length; i < n; ++i) {
    li = listItems[i];
    // Stick a class on the LIs so that stylesheets can
    // color odd/even rows, or any other row pattern that
    // is co-prime with 10.
    li.className = 'L' + ((i + offset) % 10);
    if (!li.firstChild) {
      li.appendChild(document.createTextNode('\xA0'));
    }
    ol.appendChild(li);
  }

  node.appendChild(ol);
}<commit_msg>logging for IE<commit_after>/**
 * Given a DOM subtree, wraps it in a list, and puts each line into its own
 * list item.
 *
 * @param {Node} node modified in place.  Its content is pulled into an
 *     HTMLOListElement, and each line is moved into a separate list item.
 *     This requires cloning elements, so the input might not have unique
 *     IDs after numbering.
 */
function numberLines(node, opt_startLineNum) {
  var nocode = /(?:^|\s)nocode(?:\s|$)/;
  var lineBreak = /\r\n?|\n/;

  var document = node.ownerDocument;

  var whitespace;
  if (node.currentStyle) {
    whitespace = node.currentStyle.whiteSpace;
  } else if (window.getComputedStyle) {
    whitespace = document.defaultView.getComputedStyle(node, null)
        .getPropertyValue('white-space');
  }
  // If it's preformatted, then we need to split lines on line breaks
  // in addition to
s.
  var isPreformatted = whitespace && 'pre' === whitespace.substring(0, 3);

  var li = document.createElement('LI');
  while (node.firstChild) {
    li.appendChild(node.firstChild);
  }
  // An array of lines.  We split below, so this is initialized to one
  // un-split line.
  var listItems = [li];

  function walk(node) {
    switch (node.nodeType) {
      case 1:  // Element
        if (nocode.test(node.className)) { console.log('nocode'); break; }
console.log('  el=' + node.nodeName);
        if ('BR' === node.nodeName) {
          breakAfter(node);
          // Discard the
 since it is now flush against a
.
          if (node.parentNode) {
            node.parentNode.removeChild(node);
          }
        } else {
          for (var child = node.firstChild; child; child = child.nextSibling) {
            walk(child);
          }
        }
        break;
      case 3: case 4:  // Text
        if (isPreformatted) {
          var text = node.nodeValue;
          var match = text.match(lineBreak);
          if (match) {
            var firstLine = text.substring(0, match.index);
            node.nodeValue = firstLine;
            var tail = text.substring(match.index + match[0].length);
console.log('split "' + text.replace(/\r\n?|\n/g, '\\n') + '" into "' + firstLine + '" and "' + tail.replace(/\r\n?|\n/g, '\\n') + '"');
            if (tail) {
              var parent = node.parentNode;
              parent.insertBefore(
                  document.createTextNode(tail), node.nextSibling);
            }
            breakAfter(node);
            if (!firstLine) {
              // Don't leave blank text nodes in the DOM.
              node.parentNode.removeChild(node);
            }
          }
        }
        break;
    }
  }

  // Split a line after the given node.
  function breakAfter(lineEndNode) {
    // If there's nothing to the right, then we can skip ending the line
    // here, and move root-wards since splitting just before an end-tag
    // would require us to create a bunch of empty copies.
    while (!lineEndNode.nextSibling) {
      lineEndNode = lineEndNode.parentNode;
      if (!lineEndNode) { return; }
    }

    function breakLeftOf(limit, copy) {
      // Clone shallowly if this node needs to be on both sides of the break.
      var rightSide = copy ? limit.cloneNode(false) : limit;
      var parent = limit.parentNode;
      if (parent) {
        // We clone the parent chain.
        // This helps us resurrect important styling elements that cross lines.
        // E.g. in Foo
Bar
        // should be rewritten to
Foo

Bar
.
        var parentClone = breakLeftOf(parent, 1);
        // Move the clone and everything to the right of the original
        // onto the cloned parent.
        var next = limit.nextSibling;
        parentClone.appendChild(rightSide);
        for (var sibling = next; sibling; sibling = next) {
          next = sibling.nextSibling;
          parentClone.appendChild(sibling);
        }
      }
      return rightSide;
    }

    var copiedListItem = breakLeftOf(lineEndNode.nextSibling, 0);

    // Walk the parent chain until we reach an unattached LI.
    for (var parent; (parent = copiedListItem.parentNode);) { copiedListItem = parent; }
    // Put it on the list of lines for later processing.
    listItems.push(copiedListItem);
console.log('pushing listItem length=' + listItems.length + ', copy=' + copiedListItem.nodeName);
  }

  // Split lines while there are lines left to split.
  for (var i = 0;  // Number of lines that have been split so far.
       i < listItems.length;  // length updated by breakAfter calls.
       ++i) {
console.log('walking list item ' + i + ' : ' + listItems[i].innerHTML.replace(/[\r\n]+/, '\\n'));
    walk(listItems[i]);
  }

  // Make sure numeric indices show correctly.
  if (opt_startLineNum === (opt_startLineNum|0)) {
    listItems[0].setAttribute('value', opt_startLineNum);
  }

  var ol = document.createElement('OL');
  ol.className = 'linenums';
  var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0;
  for (var i = 0, n = listItems.length; i < n; ++i) {
    li = listItems[i];
    // Stick a class on the LIs so that stylesheets can
    // color odd/even rows, or any other row pattern that
    // is co-prime with 10.
    li.className = 'L' + ((i + offset) % 10);
    if (!li.firstChild) {
      li.appendChild(document.createTextNode('\xA0'));
    }
    ol.appendChild(li);
  }

  node.appendChild(ol);
}<|endoftext|>


<commit_before>import { sortBy, take, merge } from 'lodash';
import { browserHistory } from 'react-router';
import fetch from 'isomorphic-fetch';
import * as localforage from 'localforage';
const moment = require('moment');
import {
  REQUEST_DPS,
  REQUEST_DPS_DONE,
  SHOW_WELCOME,
  MAP_DATE_CHANGE
} from '../constants/ActionTypes';

function fetchData(dispatch) {
  dispatch({ type: REQUEST_DPS });
  return fetch('/app.json').then((x) => x.json());
}

export function updateWelcomeMessage() {
  return async (dispatch) => {
    const welcomeMessage = await localforage.getItem('showWelcomeMessage');
    dispatch({
      type: SHOW_WELCOME,
      data: welcomeMessage
    });
  };
}

export function closeWelcomeMessage() {
  return async (dispatch) => {
    await localforage.setItem('showWelcomeMessage', false);
    dispatch({
      type: SHOW_WELCOME,
      data: false
    });
  };
}

export function mapDateChange(value) {
  const date = moment(value).format('YYYY-MM-DD');
  browserHistory.push(`/date/${date}`);
  return {
    type: MAP_DATE_CHANGE,
    data: value
  };
}

export function fetchPostsIfNeeded() {
  return async (dispatch) => {
    const lastUpdate = await localforage.getItem('last-update');
    let data = !lastUpdate || !moment().isSame(lastUpdate, 'day') ?
      await fetchData(dispatch) :
      await localforage.getItem('data');
    await localforage.setItem('last-update', moment().format());
    await localforage.setItem('data', data);
    data = data.map((x) => Object.assign(x, { date: moment(x.date) }));
    dispatch({ type: REQUEST_DPS_DONE, data });
  };
}
<commit_msg>A date change always results in a moment object<commit_after>import { sortBy, take, merge } from 'lodash';
import { browserHistory } from 'react-router';
import fetch from 'isomorphic-fetch';
import * as localforage from 'localforage';
const moment = require('moment');
import {
  REQUEST_DPS,
  REQUEST_DPS_DONE,
  SHOW_WELCOME,
  MAP_DATE_CHANGE
} from '../constants/ActionTypes';

function fetchData(dispatch) {
  dispatch({ type: REQUEST_DPS });
  return fetch('/app.json').then((x) => x.json());
}

export function updateWelcomeMessage() {
  return async (dispatch) => {
    const welcomeMessage = await localforage.getItem('showWelcomeMessage');
    dispatch({
      type: SHOW_WELCOME,
      data: welcomeMessage
    });
  };
}

export function closeWelcomeMessage() {
  return async (dispatch) => {
    await localforage.setItem('showWelcomeMessage', false);
    dispatch({
      type: SHOW_WELCOME,
      data: false
    });
  };
}

export function mapDateChange(value) {
  const date = moment(value);
  browserHistory.push(`/date/${date.format('YYYY-MM-DD')}`);
  return {
    type: MAP_DATE_CHANGE,
    data: value
  };
}

export function fetchPostsIfNeeded() {
  return async (dispatch) => {
    const lastUpdate = await localforage.getItem('last-update');
    let data = !lastUpdate || !moment().isSame(lastUpdate, 'day') ?
      await fetchData(dispatch) :
      await localforage.getItem('data');
    await localforage.setItem('last-update', moment().format());
    await localforage.setItem('data', data);
    data = data.map((x) => Object.assign(x, { date: moment(x.date) }));
    dispatch({ type: REQUEST_DPS_DONE, data });
  };
}
<|endoftext|>


<commit_before>let faker = require('faker')
let actualData = require('./mock-data-actual.json')

faker.seed(42)

function luckRatio(ratio) {
	let number = (faker.random.number() % 10 + 1) / 10
	return number <= ratio
}

// AUTHORS
let authors = []
for (let i = 0; i < 250; i++) {
	let lname = faker.name.lastName()
	let fname = faker.name.firstName()
	let author = {
		id: i,
		name: lname,
		firstName: fname,
		identifyingName: `${fname} ${lname}`
	}
	if (luckRatio(0.7)) {
		let nick = faker.name.jobTitle()
		author.nickname = nick
		author.identifyingName = `${fname} '${nick}' ${lname}`
	}
	if (luckRatio(0.1)) {
		author.biography = `<p>${faker.lorem.paragraph()}</p>`
	}
	authors.push(author)
}
authors.sort((a, b) => a.name.localeCompare(b.name))

module.exports = () => {
	return {
		authors: authors,
		translations_en: actualData.translations_en,
		translations_fr: actualData.translations_fr
	}
}
<commit_msg>Made fake data more realistic<commit_after>let faker = require('faker')
let actualData = require('./mock-data-actual.json')

faker.seed(42)

function luckRatio(ratio) {
	let number = (faker.random.number() % 10 + 1) / 10
	return number <= ratio
}

function paragraphs(maxNum) {
	let count = faker.random.number() % maxNum
	let paragraphs = ''
	do {
		paragraphs += `<p>${faker.lorem.paragraph()}</p>`
	} while (count-- >= 0)
	return paragraphs
}

// AUTHORS
let authors = []
for (let i = 0; i < 250; i++) {
	let lname = faker.name.lastName()
	let fname = faker.name.firstName()
	let author = {
		id: i,
		name: lname,
		firstName: fname,
		identifyingName: `${fname} ${lname}`
	}
	if (luckRatio(0.2)) {
		let nick = faker.name.firstName()
		author.nickname = nick
		author.identifyingName = `${fname} '${nick}' ${lname}`
	}
	if (luckRatio(0.5)) {
		author.biography = paragraphs(2)
	}
	authors.push(author)
}
authors.sort((a, b) => a.name.localeCompare(b.name))

module.exports = () => {
	return {
		authors: authors,
		translations_en: actualData.translations_en,
		translations_fr: actualData.translations_fr
	}
}
<|endoftext|>


<commit_before>import 'isomorphic-fetch';

import { Actions } from '../constants';

const SERVER_URL = 'http://localhost:9090';

const TodoActions = {
  addTodo(label) {
    return dispatch => {
      fetch(`${SERVER_URL}/todos`, {
        method: 'POST',
        headers: {
          'Accepts': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          isComplete: false,
          label
        })
      })
      .then(res => res.json())
      .then(todo => dispatch({
        type: Actions.ADD_TODO,
        payload: { todo }
      }))
      .catch(() => dispatch({
        type: Actions.ADD_TODO,
        payload: new Error(),
        error: true
      }));
    };
  },

  // FIXME: this is just a stub--does nothing on the server.
  clearCompleteTodos() {
    return {
      type: Actions.CLEAR_COMPLETE_TODOS
    };
  },

  deleteTodo(id) {
    return dispatch => {
      fetch(`${SERVER_URL}/todos/${id}`, {
        method: 'DELETE'
      })
      .then(() => dispatch({
        type: Actions.DELETE_TODO,
        payload: { id }
      }))
      .catch(() => dispatch({
        type: Actions.DELETE_TODO,
        payload: new Error(),
        error: true
      }));
    };
  },

  editTodo(id, label) {
    return dispatch => {
      fetch(`${SERVER_URL}/todos/${id}`, {
        method: 'PATCH',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ label })
      })
      .then(res => res.json())
      .then(todo => dispatch({
        type: Actions.EDIT_TODO,
        payload: {
          id: todo.id,
          label: todo.label
        }
      }))
      .catch(() => dispatch({
        type: Actions.EDIT_TODO,
        payload: new Error(),
        error: true
      }));
    };
  },

  fetchAllTodos() {
    return dispatch => {
      fetch(`${SERVER_URL}/todos`, {
        method: 'GET'
      })
      .then(res => res.json())
      .then(todos => dispatch({
        type: Actions.FETCH_ALL_TODOS,
        payload: { todos }
      }))
      .catch(() => dispatch({
        type: Actions.FETCH_ALL_TODOS,
        payload: new Error(),
        error: true
      }));
    };
  },

  markTodo(id, isComplete) {
    return dispatch => {
      fetch(`${SERVER_URL}/todos/${id}`, {
        method: 'PATCH',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ isComplete })
      })
      .then(res => res.json())
      .then(todo => dispatch({
        type: Actions.MARK_TODO,
        payload: {
          id: todo.id,
          isComplete: todo.isComplete
        }
      }))
      .catch(() => dispatch({
        type: Actions.MARK_TODO,
        payload: new Error(),
        error: true
      }));
    };
  },

  // FIXME: this is just a stub--does nothing on the server.
  markAllTodos(isComplete) {
    return {
      type: Actions.MARK_ALL_TODOS,
      payload: { isComplete }
    };
  },

  // FIXME: this is just a stub--does nothing on the server.
  moveTodo(at, to) {
    return {
      type: Actions.MOVE_TODO,
      payload: { at, to }
    };
  }
};

export default TodoActions;
<commit_msg>Refactor actions<commit_after>import 'isomorphic-fetch';

import { Actions } from '../constants';

const SERVER_URL = 'http://localhost:9090';

/**
 * Check for HTTP error responses.
 */
function check(response) {
  const { status, statusText } = response;

  if (status >= 200 && status < 300) return response;

  const error = new Error(statusText);
  error.response = response;
  return error;
}

/**
 * Parse the response's JSON.
 */
function parse(response) {
  return response.json();
}

const TodoActions = {
  addTodo(label) {
    return dispatch => {
      return fetch(`${SERVER_URL}/todos`, {
        method: 'POST',
        headers: {
          'Accepts': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          isComplete: false,
          label
        })
      })
      .then(check)
      .then(parse)
      .then(todo => dispatch({
        type: Actions.ADD_TODO,
        payload: { todo }
      }))
      .catch(err => dispatch({
        type: Actions.ADD_TODO,
        payload: err,
        error: true
      }));
    };
  },

  // FIXME: this is just a stub--does nothing on the server.
  clearCompleteTodos() {
    return {
      type: Actions.CLEAR_COMPLETE_TODOS
    };
  },

  deleteTodo(id) {
    return dispatch => {
      return fetch(`${SERVER_URL}/todos/${id}`, {
        method: 'DELETE'
      })
      .then(check)
      .then(() => dispatch({
        type: Actions.DELETE_TODO,
        payload: { id }
      }))
      .catch(err => dispatch({
        type: Actions.DELETE_TODO,
        payload: err,
        error: true
      }));
    };
  },

  editTodo(id, label) {
    return dispatch => {
      return fetch(`${SERVER_URL}/todos/${id}`, {
        method: 'PATCH',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ label })
      })
      .then(check)
      .then(parse)
      .then(todo => dispatch({
        type: Actions.EDIT_TODO,
        payload: {
          id: todo.id,
          label: todo.label
        }
      }))
      .catch(err => dispatch({
        type: Actions.EDIT_TODO,
        payload: err,
        error: true
      }));
    };
  },

  fetchAllTodos() {
    return dispatch => {
      return fetch(`${SERVER_URL}/todos`, {
        method: 'GET'
      })
      .then(check)
      .then(parse)
      .then(todos => dispatch({
        type: Actions.FETCH_ALL_TODOS,
        payload: { todos }
      }))
      .catch(err => dispatch({
        type: Actions.FETCH_ALL_TODOS,
        payload: err,
        error: true
      }));
    };
  },

  markTodo(id, isComplete) {
    return dispatch => {
      return fetch(`${SERVER_URL}/todos/${id}`, {
        method: 'PATCH',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ isComplete })
      })
      .then(check)
      .then(parse)
      .then(todo => dispatch({
        type: Actions.MARK_TODO,
        payload: {
          id: todo.id,
          isComplete: todo.isComplete
        }
      }))
      .catch(err => dispatch({
        type: Actions.MARK_TODO,
        payload: err,
        error: true
      }));
    };
  },

  // FIXME: this is just a stub--does nothing on the server.
  markAllTodos(isComplete) {
    return {
      type: Actions.MARK_ALL_TODOS,
      payload: { isComplete }
    };
  },

  // FIXME: this is just a stub--does nothing on the server.
  moveTodo(at, to) {
    return {
      type: Actions.MOVE_TODO,
      payload: { at, to }
    };
  }
};

export default TodoActions;
<|endoftext|>


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

/**
 * Model element that represents a burner in the simulation.  The burner can heat and also cool other model elements.
 *
 * @author John Blanco
 * @author Jesse Greenberg
 * @author Andrew Adare
 */

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

  // modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EnergyChunk = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunk' );
  var EnergyChunkWanderController = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunkWanderController' );
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  var HorizontalSurface = require( 'ENERGY_FORMS_AND_CHANGES/common/model/HorizontalSurface' );
  var inherit = require( 'PHET_CORE/inherit' );
  var ModelElement = require( 'ENERGY_FORMS_AND_CHANGES/common/model/ModelElement' );
  var ObservableArray = require( 'AXON/ObservableArray' );
  var Property = require( 'AXON/Property' );
  var Range = require( 'DOT/Range' );
  var Rectangle = require( 'DOT/Rectangle' );
  var Vector2 = require( 'DOT/Vector2' );

  // constants
  var SIDE_LENGTH = 0.075; // in meters
  var MAX_ENERGY_GENERATION_RATE = 5000; // joules/sec, empirically chosen
  var CONTACT_DISTANCE = 0.001; // in meters
  var ENERGY_CHUNK_CAPTURE_DISTANCE = 0.2; // in meters, empirically chosen

  // Because of the way that energy chunks are exchanged between thermal modeling elements within this simulation,
  // things can end up looking a bit odd if a burner is turned on with nothing on it.  To account for this, a separate
  // energy generation rate is used when a burner is exchanging energy directly with the air.
  // Units: joules/sec, multiplier empirically chosen.
  var MAX_ENERGY_GENERATION_RATE_INTO_AIR = MAX_ENERGY_GENERATION_RATE * 0.3;

  // counter used by constructor to create unique IDs for each burner
  var idCounter = 0;

  /**
   * @param {Vector2} position - the position in model space where this burner exists
   * @param {Property.<boolean>} energyChunksVisibleProperty - controls whether the energy chunks are visible
   * @constructor
   */
  function Burner( position, energyChunksVisibleProperty ) {

    var self = this;
    ModelElement.call( this, position );

    // @public (read-only) {string} - unique ID, used for debug
    this.id = 'burner-' + idCounter++;

    // @public {Property<number>}
    this.heatCoolLevelProperty = new Property( 0 );

    // @public (read-only) {ObservableArray.<EnergyChunk>}
    this.energyChunkList = new ObservableArray();

    // @private {Vector2}
    this.position = position;

    // @private {Object[]} - motion strategies that control the movement of the energy chunks owned by this burner
    this.energyChunkMotionStrategies = [];

    // @private {Range} - used to keep incoming energy chunks from wandering very far to the left or right
    this.incomingEnergyChunkWanderBounds = new Range( position.x - SIDE_LENGTH / 4, position.x + SIDE_LENGTH / 4 );

    // @private {Property.<boolean>}
    this.energyChunksVisibleProperty = energyChunksVisibleProperty;

    // add position test bounds (see definition in base class for more info)
    this.relativePositionTestingBoundsList.push( new Bounds2( -SIDE_LENGTH / 2, 0, SIDE_LENGTH / 2, SIDE_LENGTH ) );

    // Create and add the top surface.  Some compensation for perspective is necessary in order to avoid problems with
    // edge overlap when dropping objects on top of burner.
    var compositeBounds = self.getCompositeBounds();
    var perspectiveCompensation = compositeBounds.height * EFACConstants.BURNER_EDGE_TO_HEIGHT_RATIO *
                                  Math.cos( EFACConstants.BURNER_PERSPECTIVE_ANGLE ) / 2;

    // @public - see base class for description
    this.topSurface = new HorizontalSurface(
      new Vector2( this.position.x, compositeBounds.maxY ),
      compositeBounds.maxX + perspectiveCompensation - ( compositeBounds.minX - perspectiveCompensation ),
      this
    );
  }

  energyFormsAndChanges.register( 'Burner', Burner );

  return inherit( ModelElement, Burner, {

    /**
     * Get a rectangle that defines the outline of the burner.  In the model the burner is essentially a 2D rectangle.
     * @returns {Rectangle} - rectangle that defines the outline in model space.
     * @public
     */
    getCompositeBounds: function() {
      // TODO: This is wasteful to reconstruct this every time, since burners don't move, should be optimized.
      var minX = this.position.x - SIDE_LENGTH / 2;
      var minY = this.position.y;
      return new Bounds2( minX, minY, minX + SIDE_LENGTH, minY + SIDE_LENGTH
      );
    },

    /**
     * @returns {HorizontalSurface}
     * @public
     */
    getTopSurface: function() {
      return this.topSurface;
    },

    /**
     * Interact with a thermal energy container, adding or removing energy based on the current heat/cool setting.
     * NOTE: this shouldn't be used for air - there is a specific method for that
     * @param {ThermalEnergyContainer} thermalEnergyContainer - model object that will get or give energy
     * @param {number} dt - amount of time (delta time)
     * @returns {number} - amount of energy transferred in joules, can be negative if energy was drawn from object
     * @public
     */
    addOrRemoveEnergyToFromObject: function( thermalEnergyContainer, dt ) {

      var deltaEnergy = 0;

      if ( this.inContactWith( thermalEnergyContainer ) ) {
        if ( thermalEnergyContainer.getTemperature() > EFACConstants.WATER_FREEZING_POINT_TEMPERATURE ) {
          deltaEnergy = MAX_ENERGY_GENERATION_RATE * this.heatCoolLevelProperty.value * dt;

          // make sure we don't cool the object below its minimum allowed energy level
          if ( this.heatCoolLevelProperty.value < 0 ) {
            if ( Math.abs( deltaEnergy ) > thermalEnergyContainer.getEnergyAboveMinimum() ) {
              deltaEnergy = -thermalEnergyContainer.getEnergyAboveMinimum();
            }
          }
        }
        thermalEnergyContainer.changeEnergy( deltaEnergy );
      }
      return deltaEnergy;
    },

    /**
     * Exchange energy with the air.  This has specific behavior that is different from addOrRemoveEnergyToFromObject,
     * defined elsewhere in this type.
     * @param {Air} air - air as a thermal energy container
     * @param dt - amount of time (delta time)
     * @returns {number} - energy, in joules, transferred to the air, negative if energy was absorbed
     * @public
     */
    addOrRemoveEnergyToFromAir: function( air, dt ) {
      var deltaEnergy = MAX_ENERGY_GENERATION_RATE_INTO_AIR * this.heatCoolLevelProperty.value * dt;
      air.changeEnergy( deltaEnergy );
      return deltaEnergy;
    },

    /**
     * determine if the burner is in contact with a thermal energy container
     * @param {ThermalEnergyContainer} thermalEnergyContainer
     * @returns {boolean}
     * @public
     */
    inContactWith: function( thermalEnergyContainer ) {
      var burnerRect = this.getCompositeBounds();
      var area = thermalEnergyContainer.thermalContactArea;
      var xContact = ( area.centerX > burnerRect.minX && area.centerX < burnerRect.maxX );
      var yContact = ( Math.abs( area.minY - burnerRect.maxY ) < CONTACT_DISTANCE );
      return ( xContact && yContact );
    },

    /**
     * @param {EnergyChunk} energyChunk
     * @public
     */
    addEnergyChunk: function( energyChunk ) {

      // make sure the chunk is at the front (which makes it fully opaque in the view)
      energyChunk.zPositionProperty.value = 0;

      // create a motion strategy that will move this energy chunk
      var motionStrategy = new EnergyChunkWanderController(
        energyChunk,
        new Property( this.getCenterPoint() ),
        { horizontalWanderConstraint: this.incomingEnergyChunkWanderBounds, wanderAngleVariation: Math.PI * 0.15 }
      );

      energyChunk.zPosition = 0;

      // add the chunk and its motion strategy to this model
      this.energyChunkList.add( energyChunk );
      this.energyChunkMotionStrategies.push( motionStrategy );
    },

    /**
     * request an energy chunk from the burner
     * @param {Vector2} point - point from which to search for closest chunk
     * @returns {EnergyChunk} - closest energy chunk, null if none are contained
     * @public
     */
    extractEnergyChunkClosestToPoint: function( point ) {
      var self = this;
      var closestEnergyChunk = null;
      if ( this.energyChunkList.length > 0 ) {
        this.energyChunkList.forEach( function( energyChunk ) {
          if ( energyChunk.positionProperty.value.distance( self.position ) > ENERGY_CHUNK_CAPTURE_DISTANCE &&
               ( closestEnergyChunk === null ||
                 energyChunk.positionProperty.value.distance( point ) <
                 closestEnergyChunk.positionProperty.value.distance( point ) ) ) {

            // found a closer chunk
            closestEnergyChunk = energyChunk;
          }
        } );

        this.energyChunkList.remove( closestEnergyChunk );
        this.energyChunkMotionStrategies.forEach( function( energyChunkWanderController, index ) {
          if ( energyChunkWanderController.energyChunk === closestEnergyChunk ) {
            self.energyChunkMotionStrategies.splice( index, 1 );
          }
        } );
      }

      if ( closestEnergyChunk === null && this.heatCoolLevelProperty.value > 0 ) {

        // create an energy chunk
        closestEnergyChunk = new EnergyChunk(
          EnergyType.THERMAL,
          this.getCenterPoint(), // will originate from the center of this burner
          new Vector2( 0, 0 ),
          this.energyChunksVisibleProperty
        );
      }

      if ( closestEnergyChunk === null ) {

        // This probably shouldn't ever occur, but it doesn't really warrant an assert, so log a warning that will
        // encourage investigation if it DOES happen.
        console.warn( 'Burner was unable to supply energy chunks.' );
      }
      return closestEnergyChunk;
    },

    /**
     * request an energy chunk from the burner based on provided bounds
     * @param {Bounds2} bounds - bounds to which the energy chunks will be heading
     * @returns {EnergyChunk} - closest energy chunk
     * @public
     */
    extractEnergyChunkClosestToBounds: function( bounds ) {

      // verify that the bounds where the energy chunk is going are above this burner
      var burnerBounds = this.getCompositeBounds();
      assert && assert(
      bounds.minY >= burnerBounds.maxY && bounds.centerX > burnerBounds.minX && bounds.centerX < burnerBounds.maxX,
        'items should only be on top of burner when getting ECs'
      );
      return this.extractEnergyChunkClosestToPoint( new Vector2( bounds.centerX, bounds.minY ) );
    },

    /**
     * @returns {Vector2}
     * @public
     */
    getCenterPoint: function() {
      return new Vector2( this.position.x, this.position.y + SIDE_LENGTH / 2 );
    },

    /**
     * @returns {Vector2}
     * @public
     */
    getCenterTopPoint: function() {
      return new Vector2( this.position.x, this.position.y + SIDE_LENGTH );
    },

    /**
     * @public
     */
    reset: function() {
      ModelElement.prototype.reset.call( this );
      this.energyChunkList.clear();
      this.heatCoolLevelProperty.reset();
    },

    /**
     * @param {ThermalEnergyContainer[]} thermalEnergyContainers
     * @returns {boolean}
     * @public
     */
    areAnyOnTop: function( thermalEnergyContainers ) {
      var self = this; // Provide a handle for use in nested callback
      var onTop = false;
      thermalEnergyContainers.forEach( function( thermalEnergyContainer ) {
        if ( self.inContactWith( thermalEnergyContainer ) ) {
          onTop = true;
        }
      } );
      return onTop;
    },

    /**
     * animate the energy chunks
     * @param {number} dt
     * @private
     */
    step: function( dt ) {
      var self = this;
      var controllers = this.energyChunkMotionStrategies.slice();

      controllers.forEach( function( controller ) {

        controller.updatePosition( dt );

        if ( controller.isDestinationReached() ) {
          self.energyChunkList.remove( controller.energyChunk );
          _.pull( self.energyChunkMotionStrategies, controller );
        }
      } );
    },

    /**
     * get a rectangle in model space the corresponds to where the flame and ice exist
     * @returns {Rectangle}
     * @public
     */
    getFlameIceRect: function() {

      // word of warning: this needs to stay consistent with the view
      var outlineRect = this.getCompositeBounds();
      var width = outlineRect.width;
      var height = outlineRect.height;
      return new Rectangle( outlineRect.centerX - width / 4, outlineRect.centerY, width / 2, height / 2 );
    },

    /**
     * get burner temperature, clamped at minimum to the freezing point of water
     * @returns {number}
     * @public
     */
    getTemperature: function() {
      var temperature = EFACConstants.ROOM_TEMPERATURE + this.heatCoolLevelProperty.value * 100;
      return Math.max( temperature, EFACConstants.WATER_FREEZING_POINT_TEMPERATURE );
    }

  } );
} );

<commit_msg>reduced bounds allocations in the burner, see #191<commit_after>// Copyright 2014-2018, University of Colorado Boulder

/**
 * Model element that represents a burner in the simulation.  The burner can heat and also cool other model elements.
 *
 * @author John Blanco
 * @author Jesse Greenberg
 * @author Andrew Adare
 */

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

  // modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EnergyChunk = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunk' );
  var EnergyChunkWanderController = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunkWanderController' );
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  var HorizontalSurface = require( 'ENERGY_FORMS_AND_CHANGES/common/model/HorizontalSurface' );
  var inherit = require( 'PHET_CORE/inherit' );
  var ModelElement = require( 'ENERGY_FORMS_AND_CHANGES/common/model/ModelElement' );
  var ObservableArray = require( 'AXON/ObservableArray' );
  var Property = require( 'AXON/Property' );
  var Range = require( 'DOT/Range' );
  var Rectangle = require( 'DOT/Rectangle' );
  var Vector2 = require( 'DOT/Vector2' );

  // constants
  var SIDE_LENGTH = 0.075; // in meters
  var MAX_ENERGY_GENERATION_RATE = 5000; // joules/sec, empirically chosen
  var CONTACT_DISTANCE = 0.001; // in meters
  var ENERGY_CHUNK_CAPTURE_DISTANCE = 0.2; // in meters, empirically chosen

  // Because of the way that energy chunks are exchanged between thermal modeling elements within this simulation,
  // things can end up looking a bit odd if a burner is turned on with nothing on it.  To account for this, a separate
  // energy generation rate is used when a burner is exchanging energy directly with the air.
  // Units: joules/sec, multiplier empirically chosen.
  var MAX_ENERGY_GENERATION_RATE_INTO_AIR = MAX_ENERGY_GENERATION_RATE * 0.3;

  // counter used by constructor to create unique IDs for each burner
  var idCounter = 0;

  /**
   * @param {Vector2} position - the position in model space where this burner exists
   * @param {Property.<boolean>} energyChunksVisibleProperty - controls whether the energy chunks are visible
   * @constructor
   */
  function Burner( position, energyChunksVisibleProperty ) {

    var self = this;
    ModelElement.call( this, position );

    // @public (read-only) {string} - unique ID, used for debug
    this.id = 'burner-' + idCounter++;

    // @public {Property<number>}
    this.heatCoolLevelProperty = new Property( 0 );

    // @public (read-only) {ObservableArray.<EnergyChunk>}
    this.energyChunkList = new ObservableArray();

    // @private {Vector2}
    this.position = position;

    // @private {Object[]} - motion strategies that control the movement of the energy chunks owned by this burner
    this.energyChunkMotionStrategies = [];

    // @private {Range} - used to keep incoming energy chunks from wandering very far to the left or right
    this.incomingEnergyChunkWanderBounds = new Range( position.x - SIDE_LENGTH / 4, position.x + SIDE_LENGTH / 4 );

    // @private {Property.<boolean>}
    this.energyChunksVisibleProperty = energyChunksVisibleProperty;

    // @private {Bounds2} - bounds of the burner in model space
    this.bounds = new Bounds2(
      position.x - SIDE_LENGTH / 2,
      position.y,
      position.x + SIDE_LENGTH / 2,
      position.y + SIDE_LENGTH
    );

    // add position test bounds (see definition in base class for more info)
    this.relativePositionTestingBoundsList.push( new Bounds2( -SIDE_LENGTH / 2, 0, SIDE_LENGTH / 2, SIDE_LENGTH ) );

    // Create and add the top surface.  Some compensation for perspective is necessary in order to avoid problems with
    // edge overlap when dropping objects on top of burner.
    var perspectiveCompensation = this.bounds.height * EFACConstants.BURNER_EDGE_TO_HEIGHT_RATIO *
                                  Math.cos( EFACConstants.BURNER_PERSPECTIVE_ANGLE ) / 2;

    // @public - see base class for description
    this.topSurface = new HorizontalSurface(
      new Vector2( this.position.x, this.bounds.maxY ),
      this.bounds.maxX + perspectiveCompensation - ( this.bounds.minX - perspectiveCompensation ),
      this
    );
  }

  energyFormsAndChanges.register( 'Burner', Burner );

  return inherit( ModelElement, Burner, {

    /**
     * Get a rectangle that defines the outline of the burner.  In the model the burner is essentially a 2D rectangle.
     * @returns {Rectangle} - rectangle that defines the outline in model space.
     * @public
     */
    getCompositeBounds: function() {
      return this.bounds;
    },

    /**
     * @returns {HorizontalSurface}
     * @public
     */
    getTopSurface: function() {
      return this.topSurface;
    },

    /**
     * Interact with a thermal energy container, adding or removing energy based on the current heat/cool setting.
     * NOTE: this shouldn't be used for air - there is a specific method for that
     * @param {ThermalEnergyContainer} thermalEnergyContainer - model object that will get or give energy
     * @param {number} dt - amount of time (delta time)
     * @returns {number} - amount of energy transferred in joules, can be negative if energy was drawn from object
     * @public
     */
    addOrRemoveEnergyToFromObject: function( thermalEnergyContainer, dt ) {

      var deltaEnergy = 0;

      if ( this.inContactWith( thermalEnergyContainer ) ) {
        if ( thermalEnergyContainer.getTemperature() > EFACConstants.WATER_FREEZING_POINT_TEMPERATURE ) {
          deltaEnergy = MAX_ENERGY_GENERATION_RATE * this.heatCoolLevelProperty.value * dt;

          // make sure we don't cool the object below its minimum allowed energy level
          if ( this.heatCoolLevelProperty.value < 0 ) {
            if ( Math.abs( deltaEnergy ) > thermalEnergyContainer.getEnergyAboveMinimum() ) {
              deltaEnergy = -thermalEnergyContainer.getEnergyAboveMinimum();
            }
          }
        }
        thermalEnergyContainer.changeEnergy( deltaEnergy );
      }
      return deltaEnergy;
    },

    /**
     * Exchange energy with the air.  This has specific behavior that is different from addOrRemoveEnergyToFromObject,
     * defined elsewhere in this type.
     * @param {Air} air - air as a thermal energy container
     * @param dt - amount of time (delta time)
     * @returns {number} - energy, in joules, transferred to the air, negative if energy was absorbed
     * @public
     */
    addOrRemoveEnergyToFromAir: function( air, dt ) {
      var deltaEnergy = MAX_ENERGY_GENERATION_RATE_INTO_AIR * this.heatCoolLevelProperty.value * dt;
      air.changeEnergy( deltaEnergy );
      return deltaEnergy;
    },

    /**
     * determine if the burner is in contact with a thermal energy container
     * @param {ThermalEnergyContainer} thermalEnergyContainer
     * @returns {boolean}
     * @public
     */
    inContactWith: function( thermalEnergyContainer ) {
      var burnerRect = this.getCompositeBounds();
      var area = thermalEnergyContainer.thermalContactArea;
      var xContact = ( area.centerX > burnerRect.minX && area.centerX < burnerRect.maxX );
      var yContact = ( Math.abs( area.minY - burnerRect.maxY ) < CONTACT_DISTANCE );
      return ( xContact && yContact );
    },

    /**
     * @param {EnergyChunk} energyChunk
     * @public
     */
    addEnergyChunk: function( energyChunk ) {

      // make sure the chunk is at the front (which makes it fully opaque in the view)
      energyChunk.zPositionProperty.value = 0;

      // create a motion strategy that will move this energy chunk
      var motionStrategy = new EnergyChunkWanderController(
        energyChunk,
        new Property( this.getCenterPoint() ),
        { horizontalWanderConstraint: this.incomingEnergyChunkWanderBounds, wanderAngleVariation: Math.PI * 0.15 }
      );

      energyChunk.zPosition = 0;

      // add the chunk and its motion strategy to this model
      this.energyChunkList.add( energyChunk );
      this.energyChunkMotionStrategies.push( motionStrategy );
    },

    /**
     * request an energy chunk from the burner
     * @param {Vector2} point - point from which to search for closest chunk
     * @returns {EnergyChunk} - closest energy chunk, null if none are contained
     * @public
     */
    extractEnergyChunkClosestToPoint: function( point ) {
      var self = this;
      var closestEnergyChunk = null;
      if ( this.energyChunkList.length > 0 ) {
        this.energyChunkList.forEach( function( energyChunk ) {
          if ( energyChunk.positionProperty.value.distance( self.position ) > ENERGY_CHUNK_CAPTURE_DISTANCE &&
               ( closestEnergyChunk === null ||
                 energyChunk.positionProperty.value.distance( point ) <
                 closestEnergyChunk.positionProperty.value.distance( point ) ) ) {

            // found a closer chunk
            closestEnergyChunk = energyChunk;
          }
        } );

        this.energyChunkList.remove( closestEnergyChunk );
        this.energyChunkMotionStrategies.forEach( function( energyChunkWanderController, index ) {
          if ( energyChunkWanderController.energyChunk === closestEnergyChunk ) {
            self.energyChunkMotionStrategies.splice( index, 1 );
          }
        } );
      }

      if ( closestEnergyChunk === null && this.heatCoolLevelProperty.value > 0 ) {

        // create an energy chunk
        closestEnergyChunk = new EnergyChunk(
          EnergyType.THERMAL,
          this.getCenterPoint(), // will originate from the center of this burner
          new Vector2( 0, 0 ),
          this.energyChunksVisibleProperty
        );
      }

      if ( closestEnergyChunk === null ) {

        // This probably shouldn't ever occur, but it doesn't really warrant an assert, so log a warning that will
        // encourage investigation if it DOES happen.
        console.warn( 'Burner was unable to supply energy chunks.' );
      }
      return closestEnergyChunk;
    },

    /**
     * request an energy chunk from the burner based on provided bounds
     * @param {Bounds2} bounds - bounds to which the energy chunks will be heading
     * @returns {EnergyChunk} - closest energy chunk
     * @public
     */
    extractEnergyChunkClosestToBounds: function( bounds ) {

      // verify that the bounds where the energy chunk is going are above this burner
      var burnerBounds = this.getCompositeBounds();
      assert && assert(
      bounds.minY >= burnerBounds.maxY && bounds.centerX > burnerBounds.minX && bounds.centerX < burnerBounds.maxX,
        'items should only be on top of burner when getting ECs'
      );
      return this.extractEnergyChunkClosestToPoint( new Vector2( bounds.centerX, bounds.minY ) );
    },

    /**
     * @returns {Vector2}
     * @public
     */
    getCenterPoint: function() {
      return new Vector2( this.position.x, this.position.y + SIDE_LENGTH / 2 );
    },

    /**
     * @returns {Vector2}
     * @public
     */
    getCenterTopPoint: function() {
      return new Vector2( this.position.x, this.position.y + SIDE_LENGTH );
    },

    /**
     * @public
     */
    reset: function() {
      ModelElement.prototype.reset.call( this );
      this.energyChunkList.clear();
      this.heatCoolLevelProperty.reset();
    },

    /**
     * @param {ThermalEnergyContainer[]} thermalEnergyContainers
     * @returns {boolean}
     * @public
     */
    areAnyOnTop: function( thermalEnergyContainers ) {
      var self = this; // Provide a handle for use in nested callback
      var onTop = false;
      thermalEnergyContainers.forEach( function( thermalEnergyContainer ) {
        if ( self.inContactWith( thermalEnergyContainer ) ) {
          onTop = true;
        }
      } );
      return onTop;
    },

    /**
     * animate the energy chunks
     * @param {number} dt
     * @private
     */
    step: function( dt ) {
      var self = this;
      var controllers = this.energyChunkMotionStrategies.slice();

      controllers.forEach( function( controller ) {

        controller.updatePosition( dt );

        if ( controller.isDestinationReached() ) {
          self.energyChunkList.remove( controller.energyChunk );
          _.pull( self.energyChunkMotionStrategies, controller );
        }
      } );
    },

    /**
     * get a rectangle in model space the corresponds to where the flame and ice exist
     * @returns {Rectangle}
     * @public
     */
    getFlameIceRect: function() {

      // word of warning: this needs to stay consistent with the view
      var outlineRect = this.getCompositeBounds();
      var width = outlineRect.width;
      var height = outlineRect.height;
      return new Rectangle( outlineRect.centerX - width / 4, outlineRect.centerY, width / 2, height / 2 );
    },

    /**
     * get burner temperature, clamped at minimum to the freezing point of water
     * @returns {number}
     * @public
     */
    getTemperature: function() {
      var temperature = EFACConstants.ROOM_TEMPERATURE + this.heatCoolLevelProperty.value * 100;
      return Math.max( temperature, EFACConstants.WATER_FREEZING_POINT_TEMPERATURE );
    }

  } );
} );

<|endoftext|>