<commit_before>var Recipe = require('../../db/models/recipe.js');
var util = require('../util.js');

module.exports = {
  findRecipe: function (req, res) {
    var query = {};
    var field = req.params.prop;
    if (field === '_id') field = 'id';
    var value = req.params.query;
    if (field && value) query[field] = value;

    Recipe.find(query).exec()
      .then(function(recipe) {
        res.body = recipe;
        util.sendResponse(req, res, 200);
      })
      .catch(function(err) {
        console.log(err);
        util.sendResponse(req, res, 404);
      });
  },
  createRecipe: function (req, res) {
    var params = {
      title: req.body.title,
      image_url: req.body.image_url || '',
      ingredients: req.body.ingredients || [],
      directions: req.body.directions || '',
      author: req.body.author || ''
    };

    Recipe.find(params).exec()
      .then(function(recipe) {
        if (recipe.length === 0) {
          var newRecipe = new Recipe(params);
          newRecipe.save()
            .then(function(createdRecipe) {
              res.body = 'Successfully created recipe';
              util.sendResponse(req, res, 201);
            });
        } else {
          res.body = 'Recipe already exists';
          util.sendResponse(req, res, 404);
        }
      })
      .catch(function(err) {
        console.log(err);
        util.sendResponse(req, res, 404);
      })
  }
};

// var somethingWithDB = function(req, res) {

//   // mock data, real data would come from db
//   // var data = [{
//   //   id: req.params.id,
//   //   title: 'A Test Recipe',
//   //   ingredients: ['1/2 apple', '1/2 orange', '2 cups spinach'],
//   //   author: { id: 1, username: 'Matt'}
//   // }];
//   // res.body = data;
//   Recipe.find({ 'title': 'Fried Pickles'}, function (err, recipe) {
//     console.log(recipe);
//     res.body = [recipe];
//     util.sendResponse(req, res, 200);
//   });
//   /*console.log('type: ', req.params.type);
//   console.log('prop: ', req.params.prop);
//   console.log('query: ', req.params.query);
//   util.sendResponse(req, res);*/
// };

// var somethingElseWithDB = function (req, res) {
//   console.log(req.params.action); // createRecipe
//   console.log(req.body); // {}
//   util.sendResponse(req, res, 201);
// };<commit_msg>(fix) Fixed create recipe to intialize all fields<commit_after>var Recipe = require('../../db/models/recipe.js');
var util = require('../util.js');

module.exports = {
  findRecipe: function (req, res) {
    var query = {};
    var field = req.params.prop;
    var value = req.params.query;
    if (field && value) query[field] = value;

    Recipe.find(query).exec()
      .then(function(recipe) {
        res.body = recipe;
        util.sendResponse(req, res, 200);
      })
      .catch(function(err) {
        console.log(err);
        util.sendResponse(req, res, 404);
      });
  },
  createRecipe: function (req, res) {
    var params = {
      title: req.body.title,
      image_url: req.body.image_url || '',
      ingredients: req.body.ingredients || [],
      directions: req.body.directions || '',
      author: req.body.author || '',
      likedBy: req.body.likedBy || [],
      dislikedBy: req.body.dislikedBy || [],
      groups: req.body.groups || [],
      reviews: req.body.reviews || []
    };

    Recipe.find(params).exec()
      .then(function(recipe) {
        if (recipe.length === 0) {
          var newRecipe = new Recipe(params);
          newRecipe.save()
            .then(function(createdRecipe) {
              res.body = 'Successfully created recipe';
              util.sendResponse(req, res, 201);
            });
        } else {
          res.body = 'Recipe already exists';
          util.sendResponse(req, res, 404);
        }
      })
      .catch(function(err) {
        console.log(err);
        util.sendResponse(req, res, 404);
      })
  }
};

// var somethingWithDB = function(req, res) {

//   // mock data, real data would come from db
//   // var data = [{
//   //   id: req.params.id,
//   //   title: 'A Test Recipe',
//   //   ingredients: ['1/2 apple', '1/2 orange', '2 cups spinach'],
//   //   author: { id: 1, username: 'Matt'}
//   // }];
//   // res.body = data;
//   Recipe.find({ 'title': 'Fried Pickles'}, function (err, recipe) {
//     console.log(recipe);
//     res.body = [recipe];
//     util.sendResponse(req, res, 200);
//   });
//   /*console.log('type: ', req.params.type);
//   console.log('prop: ', req.params.prop);
//   console.log('query: ', req.params.query);
//   util.sendResponse(req, res);*/
// };

// var somethingElseWithDB = function (req, res) {
//   console.log(req.params.action); // createRecipe
//   console.log(req.body); // {}
//   util.sendResponse(req, res, 201);
// };<|endoftext|>


<commit_before>'use strict';
var superagent = require('superagent');
var config = require('../../config');
var _ = require("lodash");
var moment = require('moment');

exports.index = function(req, res) {
  res.send({data: req.params.fromCountry});
};

exports.searchAirport = function(req, res) {
  superagent.get(config.api + '/picker/en/' + req.params.airport)
    .set('Accept', 'application/json')
    .end(function(err, response) {
      if (err) {
        return res.status(500).send('Something went wrong :(');
      }
      res.send(JSON.parse(response.text));
    });
};

exports.findCheapestFlight = function(travelingInfo, socket) {

  //FIRSTFLIGHT
  if(travelingInfo.stateData === 'firstFlight') {
     //UPDATE THE TRAVELING INFO FOR THE FIRST FLIGHT
     travelingInfo = initFirstFlight(travelingInfo);

     //is it time to go home?
     if (goHome(travelingInfo)) {

      flyHome(travelingInfo, socket);
     }
     else {
     flyNormal(travelingInfo, socket);
    }
  }

  //FIND A NEW FLIGHT
  if (travelingInfo.stateData === 'newFlight') {

    //is it time to go home?
     if(goHome(travelingInfo)){

      flyHome(travelingInfo, socket);
     }
     else {
     flyNormal(travelingInfo, socket);
   }

  }

  if (travelingInfo.stateData === 'homeFlight') {

    //find a way home
    flyHome(travelingInfo);

  }
};

/*function normalDistributionDiff(x, tripLength, stopDuration, dfh) {
  var max = 20000;
  stopDuration = stopDuration * 1.3;
  max = max * dfh;
  var a = Math.pow((x - (tripLength / 2 )), 2);
  return max * Math.exp(a / (2 * Math.pow(stopDuration, 2)));
}*/

function normalDist(x, tripLength, stopDuration, dfh, dfhW) {
  var max = 20000 * dfhW;
  stopDuration = stopDuration;
  max = max * dfh;
  return max * Math.exp(-((Math.pow(x - (tripLength / 2), 2)) / (2 * Math.pow(stopDuration, 2))));
}

function initFirstFlight(travelingInfo){

    var duration = createStopDuration(travelingInfo.departure.from, travelingInfo.departure.to);

     travelingInfo.stopDuration = duration;
     //FLY ON THE DAY THAT HE WANTS
     travelingInfo.departure.to = travelingInfo.departure.from;
     travelingInfo.endDate = travelingInfo.departure.to;

     return travelingInfo;
   }

function goHome(travelingInfo){
    //total amount of days where you should be thinking about going home
    var dayAmount = 2;
    if ((travelingInfo.stopDuration.totalDays - dayAmount) < moment(travelingInfo.departure.from).diff(moment(travelingInfo.endDate), 'days')) {
    //MABY  travelingInfo.departure.to = moment(travelingInfo.departure.to).add(100,'days').format('YYYY-MM-DD')
      console.log("GOHOME");
      return true;
    }

    return false;
}

  function flyHome(travelingInfo, socket) {
    // FIND AWAY HOME BROTHER

    // maby change departur.to here so we deffently get home

    var url = [
      config.api,
      'livestore',
      'en',
      travelingInfo.departure.country,
      'per-airport',
      travelingInfo.departure.airportCode,
      travelingInfo.startingPoint.airportCode,
      travelingInfo.departure.from,
      travelingInfo.departure.to
    ].join('/') + '?currency=USD&stay=1-365&include_split=true&airport-format=full&fare-format=full&id=H4cK3r';

    //DO THE REQUEST
    superagent.get(url)
     .end(function(err, response) {
      if (err) {
        socket.emit('error', 'something went wrong in the socket');
        return;
      }

      //HANDLE THE RESULT
      var responseData = JSON.parse(response.text);
      var fares = responseData.fares;
      var airports = responseData.airports;

      //HERNA VERÐA EITTHVER ÖNNUR SKYLYRÐI því heim er öðruvísi

        var homeDest = fares[0];
        if (homeDest.length > 1) {
          homeDest = homeDest[0];
        }
        //if there is a flight there
        //DO THIS ELSE WHERE
        if (fares.length) {

          var travelInfo = {

            fromAirport: homeDest.a,
            destAirport: homeDest.b,
            price: homeDest.conv_fare,
            departure: homeDest.d1,
            departureCountry: travelingInfo.departure,
            stopDuration: travelingInfo.stopDuration,
            endDate: travelingInfo.endDate,
            stateData: 'homeDest',
          };
          var departureCount = fares[0];

          var travelingAirport = homeDest.b;
          if (airports[travelingAirport]) {
            travelInfo.arrivalCountry = {
              airportCode: airports[travelingAirport].a_i,
              airportName: airports[travelingAirport].a_n,
              countryCode: travelingInfo.de,
              countryName: travelInfo.departure,
              city: airports[travelingAirport].ci_n,
              lat: airports[travelingAirport].lat,
              lon: airports[travelingAirport].lon,
              state: airports[travelingAirport].r_n,
              state_short: airports[travelingAirport].r_c,
            };
          }
          socket.emit('new-flight', travelInfo);
        }

      //á eftir að gera derp aergheariughaeirhg

      //
    });
  }

  function flyNormal(travelingInfo, socket) {
    //FIND AWAY HOME BROTHER

    //maby change departur.to here so we deffently get home

    var url = [
      config.api,
      'livestore',
      'en',
      travelingInfo.departure.country,
      'per-country',
      travelingInfo.departure.airportCode,
      travelingInfo.departure.from,
      travelingInfo.departure.to
    ].join('/') + '?currency=USD&airport-format=full&fare-format=full&id=H4cK3r';

    //DO THE REQUEST
    superagent.get(url)
     .end(function(err, response) {
      if (err) {
        socket.emit('error', 'something went wrong in the socket');
        return;
      }

      //HANDLE THE RESULT----------------------------------------------------------

      var responseData = JSON.parse(response.text);
      var fares = responseData.fares;
      var airports = responseData.airports;

      //Get the countrys we have allreddy been to
      var countries = _.map(travelingInfo.flights, function(item) {
        return item['departureCountry'].country;
      });

      //FIND THE APROPREATE FLIGHT
      var flightIndex = isValidFlight(fares, airports, countries, travelingInfo);

      //
      if(flightIndex > -1) {

        var cheapestFlight = fares[flightIndex];

        //if there is a flight there
        //DO THIS ELSE WHERE
        if (fares.length) {

          var travelInfo = {

            fromAirport: cheapestFlight.a,
            destAirport: cheapestFlight.b,
            price: cheapestFlight.conv_fare,
            departure: cheapestFlight.d1,
            departureCountry: travelingInfo.departure,
            stopDuration: travelingInfo.stopDuration,
            endDate:travelingInfo.endDate,
            stateData:'newDest',
          };
          if (airports[cheapestFlight.b]) {
            travelInfo.arrivalCountry = {
              airportCode: airports[cheapestFlight.b].a_i,
              airportName: airports[cheapestFlight.b].a_n,
              countryCode: airports[cheapestFlight.b].cc_c,
              countryName: airports[cheapestFlight.b].cc_n,
              city: airports[cheapestFlight.b].ci_n,
              lat: airports[cheapestFlight.b].lat,
              lon: airports[cheapestFlight.b].lon,
              state: airports[cheapestFlight.b].r_n,
              state_short: airports[cheapestFlight.b].r_c,
            };
          }
          socket.emit('new-flight', travelInfo);
        }
      };
    });
  };

  function isValidFlight(fares,airports,countries, travelingInfo){

    //ADD VALIDATION HERE --------------------------
    //check if we have travled there before
    var indexCheapest = -1;
    var bestDistance = -1;
    for (var cheapest = 0; cheapest < fares.length; cheapest++) {

      //check if we have travled there before
      if (countries.indexOf(airports[fares[cheapest].b].cc_c)  > -1) {
          continue;
      }

      //check if it is within our travel limits
      var dist = findDistance(travelingInfo.startingPoint.location.lat, travelingInfo.startingPoint.location.lng, airports[fares[cheapest].b].lat, airports[fares[cheapest].b].lon);

      var thisDay = moment(fares[cheapest].d1).diff(moment(travelingInfo.stopDuration.startDate), 'days');
      var totalDays = travelingInfo.stopDuration.totalDays;
      var stopDuration = travelingInfo.stopDuration.highBound;
      var dfhMax = travelingInfo.stopDuration.dfhMax;
      var dfhW = travelingInfo.stopDuration.dfhW;
      var idealDist = normalDist(thisDay, totalDays, stopDuration, dfhMax, dfhW);

      var diff = Math.abs(idealDist - dist);

      if ( diff < bestDistance || indexCheapest === -1) {
          indexCheapest = cheapest;
          bestDistance = dist;
      }
    }

    //If we have option, but not to any country we havn't visited
    if (indexCheapest === -1 && fares.length > 0) {
      indexCheapest = 0;
    }

    return indexCheapest;

  }

function findDistance(lat1, lon1, lat2, lon2){

  //HAVERSIN FORMULA

  var dLat = deg2rad(lat2 - lat1);
  var dLon = deg2rad(lon2 - lon1);

  var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon/2) * Math.sin(dLon/2);

  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

  var d = config.earthRadius * c; //distance in km

  return d;
}

function deg2rad(deg) {
  return deg * (Math.PI/180);
}

//derp
function createStopDuration(startDate, endDate){

  var days = moment(endDate).diff(moment(startDate), 'days');

  var duration = {
    lowBound: '',
    highBound: '',
    dfhMax: 0,
    priceW: 0,
    dfhW: 0,
    totalDays: days,
    startDate: startDate,
  };

  if (days >= 24 * 7) {
    duration.lowBound = 7;
    duration.highBound = 21;
    duration.dfhMax = 1;
    duration.priceW = 0.8;
    duration.dfhW = 0.25;
  } else if (days >= 12 * 7) {
    duration.lowBound = 5;
    duration.highBound = 10;
    duration.dfhMax = 0.75;
    duration.priceW = 0.6;
    duration.dfhW = 0.50;
  } else if (days >= 4 * 7) {
    duration.lowBound = 3;
    duration.highBound = 8;
    duration.dfhMax = 0.5;
    duration.priceW = 0.4;
    duration.dfhW = 0.75;
  } else {
    duration.lowBound = 2;
    duration.highBound = 4;
    duration.dfhMax = 0.25;
    duration.priceW = 0;
    duration.dfhW = 1;
  }

  return duration;
}
<commit_msg>date to get home becomes longer<commit_after>'use strict';
var superagent = require('superagent');
var config = require('../../config');
var _ = require("lodash");
var moment = require('moment');

exports.index = function(req, res) {
  res.send({data: req.params.fromCountry});
};

exports.searchAirport = function(req, res) {
  superagent.get(config.api + '/picker/en/' + req.params.airport)
    .set('Accept', 'application/json')
    .end(function(err, response) {
      if (err) {
        return res.status(500).send('Something went wrong :(');
      }
      res.send(JSON.parse(response.text));
    });
};

exports.findCheapestFlight = function(travelingInfo, socket) {

  //FIRSTFLIGHT
  if(travelingInfo.stateData === 'firstFlight') {
     //UPDATE THE TRAVELING INFO FOR THE FIRST FLIGHT
     travelingInfo = initFirstFlight(travelingInfo);

     //is it time to go home?
     if (goHome(travelingInfo)) {

      flyHome(travelingInfo, socket, travelingInfo.departure.to);
     }
     else {
     flyNormal(travelingInfo, socket);
    }
  }

  //FIND A NEW FLIGHT
  if (travelingInfo.stateData === 'newFlight') {

    //is it time to go home?
     if(goHome(travelingInfo)){

      flyHome(travelingInfo, socket, travelingInfo.departure.to);
     }
     else {
     flyNormal(travelingInfo, socket);
   }

  }

  if (travelingInfo.stateData === 'homeFlight') {

    //find a way home
    flyHome(travelingInfo, socket, travelingInfo.departure.to);

  }
};

/*function normalDistributionDiff(x, tripLength, stopDuration, dfh) {
  var max = 20000;
  stopDuration = stopDuration * 1.3;
  max = max * dfh;
  var a = Math.pow((x - (tripLength / 2 )), 2);
  return max * Math.exp(a / (2 * Math.pow(stopDuration, 2)));
}*/

function normalDist(x, tripLength, stopDuration, dfh, dfhW) {
  var max = 20000 * dfhW;
  stopDuration = stopDuration;
  max = max * dfh;
  return max * Math.exp(-((Math.pow(x - (tripLength / 2), 2)) / (2 * Math.pow(stopDuration, 2))));
}

function initFirstFlight(travelingInfo){

    var duration = createStopDuration(travelingInfo.departure.from, travelingInfo.departure.to);

     travelingInfo.stopDuration = duration;
     //FLY ON THE DAY THAT HE WANTS
     travelingInfo.departure.to = travelingInfo.departure.from;
     travelingInfo.endDate = travelingInfo.departure.to;

     return travelingInfo;
   }

function goHome(travelingInfo){
    //total amount of days where you should be thinking about going home
    var dayAmount = 2;
    if ((travelingInfo.stopDuration.totalDays - dayAmount) < moment(travelingInfo.departure.from).diff(moment(travelingInfo.endDate), 'days')) {
    //MABY  travelingInfo.departure.to = moment(travelingInfo.departure.to).add(100,'days').format('YYYY-MM-DD')
      console.log("GOHOME");
      return true;
    }

    return false;
}

function flyHome(travelingInfo, socket, dateTo) {
  // FIND AWAY HOME BROTHER

  // maby change departur.to here so we deffently get home

  var url = [
    config.api,
    'livestore',
    'en',
    travelingInfo.departure.country,
    'per-airport',
    travelingInfo.departure.airportCode,
    travelingInfo.startingPoint.airportCode,
    travelingInfo.departure.from,
    dateTo
  ].join('/') + '?currency=USD&stay=1-365&include_split=true&airport-format=full&fare-format=full&id=H4cK3r';

  //DO THE REQUEST
  superagent.get(url)
   .end(function(err, response) {
    if (err) {
      socket.emit('error', 'something went wrong in the socket');
      return;
    }

    //HANDLE THE RESULT
    var responseData = JSON.parse(response.text);
    var fares = responseData.fares;
    console.log('fares', dateTo);
    console.log('url', url);
    if(fares.length <= 0) {
      flyHome(travelingInfo, socket, moment(dateTo).add(3, 'days').format('YYYY-MM-DD'));
      return;
    }
    var airports = responseData.airports;
    //HERNA VERÐA EITTHVER ÖNNUR SKYLYRÐI því heim er öðruvísi

      var homeDest = fares[0];
      if (homeDest.length > 1) {
        homeDest = homeDest[0];
      }
      //if there is a flight there
      //DO THIS ELSE WHERE
      if (fares.length) {

        var travelInfo = {

          fromAirport: homeDest.a,
          destAirport: homeDest.b,
          price: homeDest.conv_fare,
          departure: homeDest.d1,
          departureCountry: travelingInfo.departure,
          stopDuration: travelingInfo.stopDuration,
          endDate: travelingInfo.endDate,
          stateData: 'homeDest',
        };
        var departureCount = fares[0];

        var travelingAirport = homeDest.b;
        if (airports[travelingAirport]) {
          travelInfo.arrivalCountry = {
            airportCode: airports[travelingAirport].a_i,
            airportName: airports[travelingAirport].a_n,
            countryCode: travelingInfo.de,
            countryName: travelInfo.departure,
            city: airports[travelingAirport].ci_n,
            lat: airports[travelingAirport].lat,
            lon: airports[travelingAirport].lon,
            state: airports[travelingAirport].r_n,
            state_short: airports[travelingAirport].r_c,
          };
        }
        socket.emit('new-flight', travelInfo);
      }

    //á eftir að gera derp aergheariughaeirhg

    //
  });
}

  function flyNormal(travelingInfo, socket) {
    //FIND AWAY HOME BROTHER

    //maby change departur.to here so we deffently get home

    var url = [
      config.api,
      'livestore',
      'en',
      travelingInfo.departure.country,
      'per-country',
      travelingInfo.departure.airportCode,
      travelingInfo.departure.from,
      travelingInfo.departure.to
    ].join('/') + '?currency=USD&airport-format=full&fare-format=full&id=H4cK3r';

    //DO THE REQUEST
    superagent.get(url)
     .end(function(err, response) {
      if (err) {
        socket.emit('error', 'something went wrong in the socket');
        return;
      }

      //HANDLE THE RESULT----------------------------------------------------------

      var responseData = JSON.parse(response.text);
      var fares = responseData.fares;
      var airports = responseData.airports;

      //Get the countrys we have allreddy been to
      var countries = _.map(travelingInfo.flights, function(item) {
        return item['departureCountry'].country;
      });

      //FIND THE APROPREATE FLIGHT
      var flightIndex = isValidFlight(fares, airports, countries, travelingInfo);

      //
      if(flightIndex > -1) {

        var cheapestFlight = fares[flightIndex];

        //if there is a flight there
        //DO THIS ELSE WHERE
        if (fares.length) {

          var travelInfo = {

            fromAirport: cheapestFlight.a,
            destAirport: cheapestFlight.b,
            price: cheapestFlight.conv_fare,
            departure: cheapestFlight.d1,
            departureCountry: travelingInfo.departure,
            stopDuration: travelingInfo.stopDuration,
            endDate:travelingInfo.endDate,
            stateData:'newDest',
          };
          if (airports[cheapestFlight.b]) {
            travelInfo.arrivalCountry = {
              airportCode: airports[cheapestFlight.b].a_i,
              airportName: airports[cheapestFlight.b].a_n,
              countryCode: airports[cheapestFlight.b].cc_c,
              countryName: airports[cheapestFlight.b].cc_n,
              city: airports[cheapestFlight.b].ci_n,
              lat: airports[cheapestFlight.b].lat,
              lon: airports[cheapestFlight.b].lon,
              state: airports[cheapestFlight.b].r_n,
              state_short: airports[cheapestFlight.b].r_c,
            };
          }
          socket.emit('new-flight', travelInfo);
        }
      };
    });
  };

  function isValidFlight(fares,airports,countries, travelingInfo){

    //ADD VALIDATION HERE --------------------------
    //check if we have travled there before
    var indexCheapest = -1;
    var bestDistance = -1;
    for (var cheapest = 0; cheapest < fares.length; cheapest++) {

      //check if we have travled there before
      if (countries.indexOf(airports[fares[cheapest].b].cc_c)  > -1) {
          continue;
      }

      //check if it is within our travel limits
      var dist = findDistance(travelingInfo.startingPoint.location.lat, travelingInfo.startingPoint.location.lng, airports[fares[cheapest].b].lat, airports[fares[cheapest].b].lon);

      var thisDay = moment(fares[cheapest].d1).diff(moment(travelingInfo.stopDuration.startDate), 'days');
      var totalDays = travelingInfo.stopDuration.totalDays;
      var stopDuration = travelingInfo.stopDuration.highBound;
      var dfhMax = travelingInfo.stopDuration.dfhMax;
      var dfhW = travelingInfo.stopDuration.dfhW;
      var idealDist = normalDist(thisDay, totalDays, stopDuration, dfhMax, dfhW);

      var diff = Math.abs(idealDist - dist);

      if ( diff < bestDistance || indexCheapest === -1) {
          indexCheapest = cheapest;
          bestDistance = dist;
      }
    }

    //If we have option, but not to any country we havn't visited
    if (indexCheapest === -1 && fares.length > 0) {
      indexCheapest = 0;
    }

    return indexCheapest;

  }

function findDistance(lat1, lon1, lat2, lon2){

  //HAVERSIN FORMULA

  var dLat = deg2rad(lat2 - lat1);
  var dLon = deg2rad(lon2 - lon1);

  var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon/2) * Math.sin(dLon/2);

  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

  var d = config.earthRadius * c; //distance in km

  return d;
}

function deg2rad(deg) {
  return deg * (Math.PI/180);
}

//derp
function createStopDuration(startDate, endDate){

  var days = moment(endDate).diff(moment(startDate), 'days');

  var duration = {
    lowBound: '',
    highBound: '',
    dfhMax: 0,
    priceW: 0,
    dfhW: 0,
    totalDays: days,
    startDate: startDate,
  };

  if (days >= 24 * 7) {
    duration.lowBound = 7;
    duration.highBound = 21;
    duration.dfhMax = 1;
    duration.priceW = 0.8;
    duration.dfhW = 0.25;
  } else if (days >= 12 * 7) {
    duration.lowBound = 5;
    duration.highBound = 10;
    duration.dfhMax = 0.75;
    duration.priceW = 0.6;
    duration.dfhW = 0.50;
  } else if (days >= 4 * 7) {
    duration.lowBound = 3;
    duration.highBound = 8;
    duration.dfhMax = 0.5;
    duration.priceW = 0.4;
    duration.dfhW = 0.75;
  } else {
    duration.lowBound = 2;
    duration.highBound = 4;
    duration.dfhMax = 0.25;
    duration.priceW = 0;
    duration.dfhW = 1;
  }

  return duration;
}
<|endoftext|>


<commit_before>/* Making dynamic imports static for modulizer */
/* end faux static imports */
/*
  FIXME(polymer-modulizer): the above comments were extracted
  from HTML and may be out of place here. Review them and
  then delete this comment!
*/
import { PolymerElement } from '@polymer/polymer/polymer-element.js';

import './boardgame-component-animator.js';
import '@polymer/paper-spinner/paper-spinner-lite.js';
import '@polymer/iron-flex-layout/iron-flex-layout.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';

class BoardgameRenderGame extends PolymerElement {
  static get template() {
    return html`

    <boardgame-component-animator id="animator" ancestor-offset-parent="{{\$.container}}"></boardgame-component-animator>

Diagram of {{gameName}}

{{diagram}}

        <paper-spinner-lite active="{{!socketActive}}"></paper-spinner-lite>

`;
  }

  static get is() {
    return "boardgame-render-game"
  }

  static get properties() {
    return {
      state: {
        type: Object,
      },
      chest: {
        type: Object,
        observer: "_chestChanged",
      },
      active: {
        type: Boolean,
        observer: "_activeChanged",
      },
      diagram : {
        type: String,
        observer: "_diagramChanged",
      },
      gameName: {
        type: String,
        observer: "_gameNameChanged",
      },
      renderer: Object,
      rendererLoaded: {
        type: Boolean,
        value: false,
      },
      viewingAsPlayer: {
        type: Number,
        observer: "_viewingAsPlayerChanged",
      },
      currentPlayerIndex: {
        type: Number,
        observer: "_currentPlayerIndexChanged"
      },
      socketActive: {
        type: Boolean,
        value: false,
      },
      //Keep track of the will-animate we've heard.
      _activeAnimations: Object
    }
  }

  ready() {
    super.ready();
    this.addEventListener("will-animate", e => this._componentWillAnimate(e));
    this.addEventListener("animation-done", e => this._componentAnimationDone(e));
  }

  static get observers() {
    return [
      "_stateChanged(state.*)"
    ]
  }

  _diagramChanged(newValue) {
    if (!this.renderer) {
      return;
    }
    this.renderer.diagram = newValue;
  }

  _activeChanged(newValue) {
    if (!newValue) {

      //The game view has gone inactive.

      //Clear out state now so by the time we switch back it will be null
      //and we minimize chance of trying to render state with the wrong
      //renderer.

      //We don't throw out the renderer here because if we come back to a
      //game of the same type we should keep it around.
      this.state = null;
      this.diagram = "";
      this.viewingAsPlayer = 0;
      this.currentPlayerIndex = 0;
    }
  }

  _resetAnimating() {
    this._activeAnimations = new Map();
  }

  _componentWillAnimate(e) {
    this._activeAnimations.set(e.detail.ele, true);
  }

  _componentAnimationDone(e) {
    //If we're already done, don't bother firing again
    if(this._activeAnimations.size == 0) return;
    this._activeAnimations.delete(e.detail.ele);
    if (this._activeAnimations.size == 0) {
      this._notifyAnimationsDone();
    }
  }

  _notifyAnimationsDone() {
    this.dispatchEvent(new CustomEvent('all-animations-done', {composed: true}));
  }

  _stateChanged(record) {
    if (!this.renderer) return;
    var stateWasNull = (this.renderer.state == null);
    if (record.path == "state" && !stateWasNull) {
      this._resetAnimating();
      this.$.animator.prepare();
    }
    this.renderer.set(record.path, record.value);
    //This shiouldn't be necessary... set should have already done
    //notifyPath. Bug in Polymer 2?
    this.renderer.notifyPath(record.path);
    if (record.path == "state" && !stateWasNull) {
      this.$.animator.animate();
      //If nothing was going to animate, then notify right now that we're
      //done. (This should be very rare).
      if (this._activeAnimations.size == 0) this._notifyAnimationsDone();
    }
  }

  _viewingAsPlayerChanged(newValue) {
    if (!this.renderer) return;
    this.renderer.viewingAsPlayer = newValue;
  }

  _currentPlayerIndexChanged(newValue) {
    if (!this.renderer) return;
    this.renderer.currentPlayerIndex = newValue;
  }

  _chestChanged(newValue) {
    if (!this.renderer) return;
    this.renderer.chest = newValue;
  }

  _gameNameChanged(newValue) {

    //If there was a state, it might for a different game type which would
    //cause a render error.
    this.state = null;

    this.rendererLoaded = false

    if (this.renderer) {
      this.$.container.removeChild(this.renderer);
    }
    this.renderer = null;

    import("../game-src/" +newValue + "/boardgame-render-game-" + newValue + ".js").then(this._instantiateRenderer.bind(this), null);
  }

  _instantiateRenderer(e) {
    //The import loaded! Add it!
    this.rendererLoaded = true;

    var ele = document.createElement("boardgame-render-game-" + this.gameName);

    ele.diagram = this.diagram;
    ele.state = this.state;
    ele.viewingAsPlayer = this.viewingAsPlayer;
    ele.currentPlayerIndex = this.currentPlayerIndex;
    ele.chest = this.chest;

    this.renderer = ele;

    this.$.container.appendChild(ele);

  }
}

customElements.define(BoardgameRenderGame.is, BoardgameRenderGame)
<commit_msg>Removed the edge case where if no animations happen we say ready for next state automatically. Didn't work because animator.animate() returns immediately and schedules work in a rAF callback, so we don't yet know if things will animate by that check. Took a TODO in issue #396 to address, it's an edge case for games that have no rendering.<commit_after>/* Making dynamic imports static for modulizer */
/* end faux static imports */
/*
  FIXME(polymer-modulizer): the above comments were extracted
  from HTML and may be out of place here. Review them and
  then delete this comment!
*/
import { PolymerElement } from '@polymer/polymer/polymer-element.js';

import './boardgame-component-animator.js';
import '@polymer/paper-spinner/paper-spinner-lite.js';
import '@polymer/iron-flex-layout/iron-flex-layout.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';

class BoardgameRenderGame extends PolymerElement {
  static get template() {
    return html`

    <boardgame-component-animator id="animator" ancestor-offset-parent="{{\$.container}}"></boardgame-component-animator>

Diagram of {{gameName}}

{{diagram}}

        <paper-spinner-lite active="{{!socketActive}}"></paper-spinner-lite>

`;
  }

  static get is() {
    return "boardgame-render-game"
  }

  static get properties() {
    return {
      state: {
        type: Object,
      },
      chest: {
        type: Object,
        observer: "_chestChanged",
      },
      active: {
        type: Boolean,
        observer: "_activeChanged",
      },
      diagram : {
        type: String,
        observer: "_diagramChanged",
      },
      gameName: {
        type: String,
        observer: "_gameNameChanged",
      },
      renderer: Object,
      rendererLoaded: {
        type: Boolean,
        value: false,
      },
      viewingAsPlayer: {
        type: Number,
        observer: "_viewingAsPlayerChanged",
      },
      currentPlayerIndex: {
        type: Number,
        observer: "_currentPlayerIndexChanged"
      },
      socketActive: {
        type: Boolean,
        value: false,
      },
      //Keep track of the will-animate we've heard.
      _activeAnimations: Object
    }
  }

  ready() {
    super.ready();
    this.addEventListener("will-animate", e => this._componentWillAnimate(e));
    this.addEventListener("animation-done", e => this._componentAnimationDone(e));
  }

  static get observers() {
    return [
      "_stateChanged(state.*)"
    ]
  }

  _diagramChanged(newValue) {
    if (!this.renderer) {
      return;
    }
    this.renderer.diagram = newValue;
  }

  _activeChanged(newValue) {
    if (!newValue) {

      //The game view has gone inactive.

      //Clear out state now so by the time we switch back it will be null
      //and we minimize chance of trying to render state with the wrong
      //renderer.

      //We don't throw out the renderer here because if we come back to a
      //game of the same type we should keep it around.
      this.state = null;
      this.diagram = "";
      this.viewingAsPlayer = 0;
      this.currentPlayerIndex = 0;
    }
  }

  _resetAnimating() {
    this._activeAnimations = new Map();
  }

  _componentWillAnimate(e) {
    this._activeAnimations.set(e.detail.ele, true);
  }

  _componentAnimationDone(e) {
    //If we're already done, don't bother firing again
    if(this._activeAnimations.size == 0) return;
    this._activeAnimations.delete(e.detail.ele);
    if (this._activeAnimations.size == 0) {
      this._notifyAnimationsDone();
    }
  }

  _notifyAnimationsDone() {
    this.dispatchEvent(new CustomEvent('all-animations-done', {composed: true}));
  }

  _stateChanged(record) {
    if (!this.renderer) return;
    var stateWasNull = (this.renderer.state == null);
    if (record.path == "state" && !stateWasNull) {
      this._resetAnimating();
      this.$.animator.prepare();
    }
    this.renderer.set(record.path, record.value);
    //This shiouldn't be necessary... set should have already done
    //notifyPath. Bug in Polymer 2?
    this.renderer.notifyPath(record.path);
    if (record.path == "state" && !stateWasNull) {
      this.$.animator.animate();
      //TODO: technically it's possible that no animations fire, but
      //this.$.animator.animate() returns immediately but schedules work in a
      //rAF call back. We used to check for this._activeAnimations.length == 0
      //and then bail, but that always triggered becauase animate() returns
      //immediately.
    }
  }

  _viewingAsPlayerChanged(newValue) {
    if (!this.renderer) return;
    this.renderer.viewingAsPlayer = newValue;
  }

  _currentPlayerIndexChanged(newValue) {
    if (!this.renderer) return;
    this.renderer.currentPlayerIndex = newValue;
  }

  _chestChanged(newValue) {
    if (!this.renderer) return;
    this.renderer.chest = newValue;
  }

  _gameNameChanged(newValue) {

    //If there was a state, it might for a different game type which would
    //cause a render error.
    this.state = null;

    this.rendererLoaded = false

    if (this.renderer) {
      this.$.container.removeChild(this.renderer);
    }
    this.renderer = null;

    import("../game-src/" +newValue + "/boardgame-render-game-" + newValue + ".js").then(this._instantiateRenderer.bind(this), null);
  }

  _instantiateRenderer(e) {
    //The import loaded! Add it!
    this.rendererLoaded = true;

    var ele = document.createElement("boardgame-render-game-" + this.gameName);

    ele.diagram = this.diagram;
    ele.state = this.state;
    ele.viewingAsPlayer = this.viewingAsPlayer;
    ele.currentPlayerIndex = this.currentPlayerIndex;
    ele.chest = this.chest;

    this.renderer = ele;

    this.$.container.appendChild(ele);

  }
}

customElements.define(BoardgameRenderGame.is, BoardgameRenderGame)
<|endoftext|>


<commit_before>module.exports = /* glsl */`
vec2 getTextureCoordinates(const in PBRData data, in int index) {
  #ifdef USE_TEXCOORD_1
    if (index == 1) return data.texCoord1;
  #endif

  return data.texCoord0;
}
`
<commit_msg>Clean texture-coordinates<commit_after>module.exports = /* glsl */`
vec2 getTextureCoordinates(in PBRData data, in int index) {
  #ifdef USE_TEXCOORD_1
    if (index == 1) return data.texCoord1;
  #endif

  return data.texCoord0;
}
`
<|endoftext|>


<commit_before>/**
 *      @description A simple code snippet for alias variable in JavaScript
 *      @author Xiao Yuze(https://github.com/xiaoyuze88)
 */

(function(global) {
    'use strict';
    /**
     *  Usage: A.fn = "DO STH.";
     *         aliasFor(A, 'fn').is('anotherName').and('secondName').are('moreName', 'AndMoreName', [...])
     *
     *  Notice: functions: "is","and","are" are all the same, it's just a syntactic sugar.
     */

    function aliasFor(obj, oldName) {

        var fn = function () {
            for (var i = 0, l = arguments.length; i < l; i++) {
                obj[arguments[i]] === undefined && (obj[arguments[i]] = obj[oldName]);
            }
            return fn;
        }
        // Just for syntactic sugar
        return (fn.is = fn.and = fn.are = fn);
    }

    global.aliasFor = aliasFor;

})(this);
<commit_msg>update<commit_after>/**
 *      @description A simple code snippet for alias variable in JavaScript
 *      @author Xiao Yuze(https://github.com/xiaoyuze88)
 */

(function(global) {
    'use strict';
    /**
     *  Usage: A.fn = "DO STH.";
     *         aliasFor(A, 'fn').is('anotherName').and('secondName').are('moreName', 'AndMoreName', [...])
     *
     *  Notice: functions: "is","and","are" are all the same, it's just a syntactic sugar.
     */

    function aliasFor(obj, oldName) {

        var fn = function () {
            for (var i = 0, l = arguments.length; i < l; i++) {
                // If the namespace is already been in used, it will not cover the old one.
                obj[arguments[i]] === undefined && (obj[arguments[i]] = obj[oldName]);
            }
            return fn;
        }
        // Just for syntactic sugar
        return (fn.is = fn.and = fn.are = fn);
    }

    global.aliasFor = aliasFor;

})(this);
<|endoftext|>


<commit_before>/* Check LICENSE for details. Developed by Oratio.io */

/* Adapter: Inter-process Messages
   Description: Uses default, built-in child_process messages. */

var Promise = require('bluebird');

function Adapter(child){
  this.child = child;
}

Adapter.prototype.init = function(){}; //Unnecessary.

Adapter.prototype.contact = function(type, message){
  return new Promise(function(resolve, reject){
    this.child.send({type: 'contact', listener: type, message: message});
  }.bind(this));
}

Adapter.prototype.contactHandler = function(){}.toString(); //For the Messages Adapter, handling is done by default.

module.exports = Adapter;
<commit_msg>Adds new message handler.<commit_after>/* Check LICENSE for details. Developed by Oratio.io */

/* Adapter: Inter-process Messages
   Description: Uses default, built-in child_process messages. */

var Promise = require('bluebird');

function Adapter(child){
  this.child = child;
  this.child.process.on('message', function(result){
    this.child.callbacks[result.listener]();
  })
}

Adapter.prototype.init = function(){}; //Unnecessary.

Adapter.prototype.contact = function(type, message){
  return new Promise(function(resolve, reject){
    this.child.process.send({type: 'contact', listener: type, message: message});
  }.bind(this));
}

Adapter.prototype.contactHandler = function(){}.toString(); //For the Messages Adapter, handling is done by default.

module.exports = Adapter;
<|endoftext|>


<commit_before>dojo.provide("dojox.analytics.Urchin");

/*=====
dojo.mixin(djConfig,{
	// urchin: String
	//		Used by dojox.analytics.Urchin as the default UA-123456-7 account
	//		number used when being created. Alternately, you can pass an acct:""
	//		parameter to the constructor a la: new dojox.analytics.Urchin({ acct:"UA-123456-7" });
	urchin: ""
});
=====*/

dojo.declare("dojox.analytics.Urchin", null, {
	// summary: A Google-analytics helper, for post-onLoad inclusion of the tracker
	//
	// description:
	//		A small class object will allows for lazy-loading the Google Analytics API
	//		at any point during a page lifecycle. Most commonly, Google-Analytics is loaded
	//		via a synchronous script tag in the body, which causes `dojo.addOnLoad` to
	//		stall until the external API has been completely loaded. The Urchin helper
	//		will load the API on the fly, and provide a convenient API to use, wrapping
	//		Analytics for Ajaxy or single page applications.
	//
	//		The class can be instantiated two ways: Programatically, by passing an
	//		`acct:` parameter, or via Markup / dojoType and defining a djConfig
	//		parameter `urchin:`
	//
	//		IMPORTANT:
	//		This module will not work simultaneously with the core dojox.analytics
	//		package. If you need the ability to run Google Analytics AND your own local
	//		analytics system, you MUST include dojox.analytics._base BEFORE dojox.analytics.Urchin
	//
	//	example:
	//	|	// create the tracker programatically:
	//	|	var tracker = new dojox.analytics.Urchin({ acct:"UA-123456-7" });
	//
	//	example:
	//	|	// define the urchin djConfig option:
	//	|	var djConfig = { urchin: "UA-123456-7" };
	//	|	// and in markup:
	//	|

	//
	// acct: String
	//		your GA urchin tracker account number.
	acct: dojo.config.urchin,

	// loadInterval: Integer
	//		Time (in ms) to wait before checking for a ready Analytics API
	loadInterval: 420,

	constructor: function(args){
		this.tracker = null;
		dojo.mixin(this, args);
		this._loadGA.apply(this, arguments);
	},

	_loadGA: function(){
		// summary: load the ga.js file and begin initialization process
		var gaHost = ("https:" == document.location.protocol) ? "https://ssl." : "http://www.";
		var s = dojo.doc.createElement('script');
		s.src = gaHost + "google-analytics.com/ga.js";
		dojo.doc.getElementsByTagName("head")[0].appendChild(s);
		setTimeout(dojo.hitch(this, "_checkGA"), this.loadInterval);
	},

	_checkGA: function(){
		// summary: sniff the global _gat variable Google defines and either check again
		//		or fire onLoad if ready.
		setTimeout(dojo.hitch(this, !window["_gat"] ? "_checkGA" : "_gotGA"), this.loadInterval);
	},

	_gotGA: function(){
		// summary: initialize the tracker
		this.tracker = _gat._getTracker(this.acct);
		this.tracker._initData();
		this.GAonLoad.apply(this,arguments);
	},

	GAonLoad: function(){
		// summary: Stub function to fire when urchin is complete
		//	description:
		//		This function is executed when the tracker variable is
		//		complete and initialized. The initial trackPageView (with
		//		no arguments) is called here as well, so remeber to call
		//		manually if overloading this method.
		this.trackPageView();
	},

	trackPageView: function(/* string */url){
		// summary: A public API attached to this widget instance, allowing you
		//		Ajax-like notification of updates.
		//
		//	url: String
		//		A location to tell the tracker to track, eg: "/my-ajaxy-endpoint"
		this.tracker._trackPageview.apply(this, arguments);
	}

});
<commit_msg>refs #7461 - add example and words.<commit_after>dojo.provide("dojox.analytics.Urchin");

/*=====
dojo.mixin(djConfig,{
	// urchin: String
	//		Used by dojox.analytics.Urchin as the default UA-123456-7 account
	//		number used when being created. Alternately, you can pass an acct:""
	//		parameter to the constructor a la: new dojox.analytics.Urchin({ acct:"UA-123456-7" });
	urchin: ""
});
=====*/

dojo.declare("dojox.analytics.Urchin", null, {
	// summary: A Google-analytics helper, for post-onLoad inclusion of the tracker
	//
	// description:
	//		A small class object will allows for lazy-loading the Google Analytics API
	//		at any point during a page lifecycle. Most commonly, Google-Analytics is loaded
	//		via a synchronous script tag in the body, which causes `dojo.addOnLoad` to
	//		stall until the external API has been completely loaded. The Urchin helper
	//		will load the API on the fly, and provide a convenient API to use, wrapping
	//		Analytics for Ajaxy or single page applications.
	//
	//		The class can be instantiated two ways: Programatically, by passing an
	//		`acct:` parameter, or via Markup / dojoType and defining a djConfig
	//		parameter `urchin:`
	//
	//		IMPORTANT:
	//		This module will not work simultaneously with the core dojox.analytics
	//		package. If you need the ability to run Google Analytics AND your own local
	//		analytics system, you MUST include dojox.analytics._base BEFORE dojox.analytics.Urchin
	//
	//	example:
	//	|	// create the tracker programatically:
	//	|	var tracker = new dojox.analytics.Urchin({ acct:"UA-123456-7" });
	//
	//	example:
	//	|	// define the urchin djConfig option:
	//	|	var djConfig = { urchin: "UA-123456-7" };
	//	|	// and in markup:
	//	|

	//
	//	example:
	//	|	// create and define all analytics with one tag.
	//	|

	//
	// acct: String
	//		your GA urchin tracker account number. Overrides `djConfig.urchin`
	acct: dojo.config.urchin,

	// loadInterval: Integer
	//		Time (in ms) to wait before checking for a ready Analytics API
	loadInterval: 420,

	constructor: function(args){
		this.tracker = null;
		dojo.mixin(this, args);
		this._loadGA.apply(this, arguments);
	},

	_loadGA: function(){
		// summary: load the ga.js file and begin initialization process
		var gaHost = ("https:" == document.location.protocol) ? "https://ssl." : "http://www.";
		var s = dojo.doc.createElement('script');
		s.src = gaHost + "google-analytics.com/ga.js";
		dojo.doc.getElementsByTagName("head")[0].appendChild(s);
		setTimeout(dojo.hitch(this, "_checkGA"), this.loadInterval);
	},

	_checkGA: function(){
		// summary: sniff the global _gat variable Google defines and either check again
		//		or fire onLoad if ready.
		setTimeout(dojo.hitch(this, !window["_gat"] ? "_checkGA" : "_gotGA"), this.loadInterval);
	},

	_gotGA: function(){
		// summary: initialize the tracker
		this.tracker = _gat._getTracker(this.acct);
		this.tracker._initData();
		this.GAonLoad.apply(this,arguments);
	},

	GAonLoad: function(){
		// summary: Stub function to fire when urchin is complete
		//	description:
		//		This function is executed when the tracker variable is
		//		complete and initialized. The initial trackPageView (with
		//		no arguments) is called here as well, so remeber to call
		//		manually if overloading this method.
		this.trackPageView();
	},

	trackPageView: function(/* string */url){
		// summary: A public API attached to this widget instance, allowing you
		//		Ajax-like notification of updates.
		//
		//	url: String
		//		A location to tell the tracker to track, eg: "/my-ajaxy-endpoint"
		this.tracker._trackPageview.apply(this, arguments);
	}

});
<|endoftext|>


<commit_before>
/**
 * A Recipe is responsible for specifying the amount of each ingredient that should be used.
 */
var Recipe = function(soylent, ingredientAmounts) {

    this.soylent = soylent;

    if (ingredientAmounts) {
        this.ingredientAmounts = ingredientAmounts;
    }
    else {
        this.ingredientAmounts = [];

        // Initialize the recipe with random amounts of each ingredient.
        for (var i = 0; i < this.soylent.ingredients.length; i++){
            var theMax = this.soylent.ingredients[i].maxAmount;
            var theMin = this.soylent.ingredients[i].minAmount;
            this.ingredientAmounts.push(theMin + (Math.random() * (theMax - theMin)));
        }
    }

    this.calculateCompleteness();
};

/**
 * This function 'mates' two recipes, producing a 'child' recipe.
 * The child recipe contains most of it's parents chromosomes, with a few of them mutated slightly.
 */
Recipe.prototype.createChildWith = function(mate) {

    // Pick random ingredient amounts from each parent.
    var pos = Math.floor(Math.random() * this.soylent.ingredients.length);
    var childIngredientAmounts = [];this.ingredientAmounts.slice(0, pos).concat(mate.ingredientAmounts.slice(pos));
    for (var i=0; i< this.soylent.ingredients.length; i++) {
        var randomParent = Math.random() > 0.5 ? this : mate;
        childIngredientAmounts.push(randomParent.ingredientAmounts[i]);
    }

    // Pick some random ingredient in the recipe to mutate.
    // A mutation is defined as increasing or decreasing the amount of an ingredient by the mutationMultiplier.
    while (Math.random() < this.soylent.mutationProbability){
        var ingredientToMutate = Math.floor(Math.random() * this.soylent.ingredients.length);
        var mutationMultiplier = Math.random() > 0.5 ? (1 - this.soylent.mutationMultiplier) : (1 + this.soylent.mutationMultiplier);
        var theMax = this.soylent.ingredients[ingredientToMutate].maxAmount;
        var theMin = this.soylent.ingredients[ingredientToMutate].minAmount;

        // Set the ingredient to a mutated value within the range of theMin to theMax
        childIngredientAmounts[ingredientToMutate] = Math.min(childIngredientAmounts[ingredientToMutate] * mutationMultiplier, theMax);
        childIngredientAmounts[ingredientToMutate] = Math.max(childIngredientAmounts[ingredientToMutate], theMin);
    }

    return new Recipe(this.soylent, childIngredientAmounts);
};

/**
 * Sets the current nutrient totals for the recipe.
 */
Recipe.prototype.calculateTotalNutrients = function() {

    var nutrients = _.keys(this.soylent.targetNutrients);
    this.nutrientTotals = {};

    _.each(this.soylent.ingredients, function(ingredient, idx) {
        _.each(nutrients, function(nutrient) {
            // this.ingredientAmounts[idx] is rounded to the nearest whole since we assume input
            // is in the form of the smallest measurable unit.
            var ingredientNutrient = ingredient[nutrient] * Math.round(this.ingredientAmounts[idx]);
            this.nutrientTotals[nutrient] = this.nutrientTotals[nutrient] || 0;
            this.nutrientTotals[nutrient] += ingredientNutrient;
        }, this);
    }, this);
};

/**
 * Returns the recipes score. The closer the number is to 0, the better.
 *
 * If the nutrientCompleteness is less than the min, compare to that.
 * If it is higher than the max, compare to that.
 * If it is between them, completeness = 0.
 */
Recipe.prototype.calculateCompleteness = function() {

    var nutrients = _.keys(this.soylent.targetNutrients);
    this.calculateTotalNutrients();
    this.nutrientCompleteness = {};

    var nutrientCompleteness = 0;
    _.each(nutrients, function(nutrient) {
        var completeness = 0;
        if (this.nutrientTotals[nutrient] < this.soylent.targetNutrients[nutrient].min) {
            completeness = 100 - (this.nutrientTotals[nutrient] / this.soylent.targetNutrients[nutrient].min * 100);
        }
        else if (this.nutrientTotals[nutrient] > this.soylent.targetNutrients[nutrient].max) {
            completeness = (this.nutrientTotals[nutrient] / this.soylent.targetNutrients[nutrient].max * 100) - 100;
        }
        else {
            completeness = 0;
        }
        completeness *= this.soylent.targetNutrients[nutrient].importanceFactor;
        //console.log(nutrient + ": " + completeness);
        nutrientCompleteness += completeness;
        this.nutrientCompleteness[nutrient] = completeness;
    }, this);

    this.completenessScore = -nutrientCompleteness;
};

<commit_msg>Removed the slice line from createChildWith, which doesn't do anything<commit_after>
/**
 * A Recipe is responsible for specifying the amount of each ingredient that should be used.
 */
var Recipe = function(soylent, ingredientAmounts) {

    this.soylent = soylent;

    if (ingredientAmounts) {
        this.ingredientAmounts = ingredientAmounts;
    }
    else {
        this.ingredientAmounts = [];

        // Initialize the recipe with random amounts of each ingredient.
        for (var i = 0; i < this.soylent.ingredients.length; i++){
            var theMax = this.soylent.ingredients[i].maxAmount;
            var theMin = this.soylent.ingredients[i].minAmount;
            this.ingredientAmounts.push(theMin + (Math.random() * (theMax - theMin)));
        }
    }

    this.calculateCompleteness();
};

/**
 * This function 'mates' two recipes, producing a 'child' recipe.
 * The child recipe contains most of it's parents chromosomes, with a few of them mutated slightly.
 */
Recipe.prototype.createChildWith = function(mate) {

    // Pick random ingredient amounts from each parent.
    var pos = Math.floor(Math.random() * this.soylent.ingredients.length);
    var childIngredientAmounts = [];
    for (var i=0; i< this.soylent.ingredients.length; i++) {
        var randomParent = Math.random() > 0.5 ? this : mate;
        childIngredientAmounts.push(randomParent.ingredientAmounts[i]);
    }

    // Pick some random ingredient in the recipe to mutate.
    // A mutation is defined as increasing or decreasing the amount of an ingredient by the mutationMultiplier.
    while (Math.random() < this.soylent.mutationProbability){
        var ingredientToMutate = Math.floor(Math.random() * this.soylent.ingredients.length);
        var mutationMultiplier = Math.random() > 0.5 ? (1 - this.soylent.mutationMultiplier) : (1 + this.soylent.mutationMultiplier);
        var theMax = this.soylent.ingredients[ingredientToMutate].maxAmount;
        var theMin = this.soylent.ingredients[ingredientToMutate].minAmount;

        // Set the ingredient to a mutated value within the range of theMin to theMax
        childIngredientAmounts[ingredientToMutate] = Math.min(childIngredientAmounts[ingredientToMutate] * mutationMultiplier, theMax);
        childIngredientAmounts[ingredientToMutate] = Math.max(childIngredientAmounts[ingredientToMutate], theMin);
    }

    return new Recipe(this.soylent, childIngredientAmounts);
};

/**
 * Sets the current nutrient totals for the recipe.
 */
Recipe.prototype.calculateTotalNutrients = function() {

    var nutrients = _.keys(this.soylent.targetNutrients);
    this.nutrientTotals = {};

    _.each(this.soylent.ingredients, function(ingredient, idx) {
        _.each(nutrients, function(nutrient) {
            // this.ingredientAmounts[idx] is rounded to the nearest whole since we assume input
            // is in the form of the smallest measurable unit.
            var ingredientNutrient = ingredient[nutrient] * Math.round(this.ingredientAmounts[idx]);
            this.nutrientTotals[nutrient] = this.nutrientTotals[nutrient] || 0;
            this.nutrientTotals[nutrient] += ingredientNutrient;
        }, this);
    }, this);
};

/**
 * Returns the recipes score. The closer the number is to 0, the better.
 *
 * If the nutrientCompleteness is less than the min, compare to that.
 * If it is higher than the max, compare to that.
 * If it is between them, completeness = 0.
 */
Recipe.prototype.calculateCompleteness = function() {

    var nutrients = _.keys(this.soylent.targetNutrients);
    this.calculateTotalNutrients();
    this.nutrientCompleteness = {};

    var nutrientCompleteness = 0;
    _.each(nutrients, function(nutrient) {
        var completeness = 0;
        if (this.nutrientTotals[nutrient] < this.soylent.targetNutrients[nutrient].min) {
            completeness = 100 - (this.nutrientTotals[nutrient] / this.soylent.targetNutrients[nutrient].min * 100);
        }
        else if (this.nutrientTotals[nutrient] > this.soylent.targetNutrients[nutrient].max) {
            completeness = (this.nutrientTotals[nutrient] / this.soylent.targetNutrients[nutrient].max * 100) - 100;
        }
        else {
            completeness = 0;
        }
        completeness *= this.soylent.targetNutrients[nutrient].importanceFactor;
        //console.log(nutrient + ": " + completeness);
        nutrientCompleteness += completeness;
        this.nutrientCompleteness[nutrient] = completeness;
    }, this);

    this.completenessScore = -nutrientCompleteness;
};

<|endoftext|>


<commit_before>// https://medium.com/@deathmood/how-to-write-your-own-virtual-dom-ee74acc13060
// https://medium.com/@deathmood/write-your-virtual-dom-2-props-events-a957608f5c76

// verbose naming is needed for Google Closure Compiler

function make_node(type, properties, ...children) {
  return {
    'type': type,
    'properties': properties || {},
    'children': children,
  }
}

function make_difference(path, action, ...parameters) {
  return {
    'path': path,
    'action': action,
    'parameters': parameters,
  }
}

function is_different(node_1, node_2) {
  return typeof node_1 !== typeof node_2
    || (typeof node_1 === 'string' && node_1 !== node_2)
    || node_1['type'] !== node_2['type']
}

function compare_property(path, name, old_value, new_value) {
  let difference
  if (!new_value) {
    difference = make_difference(path, 'remove_property', name)
  } else if (!old_value || new_value !== old_value) {
    difference = make_difference(path, 'set_property', name, new_value)
  }

  return difference
}

function compare_properties(path, old_properties, new_properties) {
  const properties = Object.assign({}, old_properties, new_properties)
  return Object.keys(properties)
    .map(name => compare_property(path, name, old_properties[name], new_properties[name]))
    .filter(difference => difference)
}

function compare_nodes(old_node, new_node, index=0, path=[]) {
  let difference = []
  if (!old_node) {
    difference.push(make_difference(path, 'create', new_node))
  } else if (!new_node) {
    difference.push(make_difference(path, 'remove', index))
  } else if (is_different(old_node, new_node)) {
    difference.push(make_difference(path, 'replace', index, new_node))
  } else if (old_node['type']) {
    const child_path = [...path, old_node['type']]
    const properties_difference = compare_properties(child_path, old_node['properties'], new_node['properties'])
    difference.push(...properties_difference)

    const maximal_children_length = Math.max(old_node['children'].length, new_node['children'].length)
    for (let i = 0; i < maximal_children_length; i++) {
      const child_difference = compare_nodes(old_node['children'][i], new_node['children'][i], i, child_path)
      difference.push(...child_difference)
    }
  }

  return difference
}

module['exports'] = {
  'make_node': make_node,
  'compare_nodes': compare_nodes,
}
<commit_msg>Issue #151: fix the comment in JavaScript<commit_after>// https://medium.com/@deathmood/how-to-write-your-own-virtual-dom-ee74acc13060
// https://medium.com/@deathmood/write-your-virtual-dom-2-props-events-a957608f5c76

// Verbose naming is needed for Google Closure Compiler.

function make_node(type, properties, ...children) {
  return {
    'type': type,
    'properties': properties || {},
    'children': children,
  }
}

function make_difference(path, action, ...parameters) {
  return {
    'path': path,
    'action': action,
    'parameters': parameters,
  }
}

function is_different(node_1, node_2) {
  return typeof node_1 !== typeof node_2
    || (typeof node_1 === 'string' && node_1 !== node_2)
    || node_1['type'] !== node_2['type']
}

function compare_property(path, name, old_value, new_value) {
  let difference
  if (!new_value) {
    difference = make_difference(path, 'remove_property', name)
  } else if (!old_value || new_value !== old_value) {
    difference = make_difference(path, 'set_property', name, new_value)
  }

  return difference
}

function compare_properties(path, old_properties, new_properties) {
  const properties = Object.assign({}, old_properties, new_properties)
  return Object.keys(properties)
    .map(name => compare_property(path, name, old_properties[name], new_properties[name]))
    .filter(difference => difference)
}

function compare_nodes(old_node, new_node, index=0, path=[]) {
  let difference = []
  if (!old_node) {
    difference.push(make_difference(path, 'create', new_node))
  } else if (!new_node) {
    difference.push(make_difference(path, 'remove', index))
  } else if (is_different(old_node, new_node)) {
    difference.push(make_difference(path, 'replace', index, new_node))
  } else if (old_node['type']) {
    const child_path = [...path, old_node['type']]
    const properties_difference = compare_properties(child_path, old_node['properties'], new_node['properties'])
    difference.push(...properties_difference)

    const maximal_children_length = Math.max(old_node['children'].length, new_node['children'].length)
    for (let i = 0; i < maximal_children_length; i++) {
      const child_difference = compare_nodes(old_node['children'][i], new_node['children'][i], i, child_path)
      difference.push(...child_difference)
    }
  }

  return difference
}

module['exports'] = {
  'make_node': make_node,
  'compare_nodes': compare_nodes,
}
<|endoftext|>


<commit_before>/**
 * Utility functions for checking bids file structures
 * eg. corresponding files
 */
// dependencies -------------------------------------------------------------------
import type from './type.js'

// implementations ----------------------------------------------------------------
/**
 * Verify that JSON sidecars have corresponding data files
 */
function checkSidecarForDatafiles(file, fileList) {
  const noExt = file.relativePath.replace('.json', '')
  const dictName = noExt.substring(noExt.lastIndexOf('/') + 1, noExt.length)
  const args = {
    dictArgs: dictName.split('_'),
    dictName: dictName,
    dictPath: noExt.substring(0, noExt.lastIndexOf('/') + 1),
    file: file,
    fileList: fileList,
    noExt: noExt,
  }
  const idxs = Object.keys(fileList)
  // Check each file in fileList for potential match - return true on first match
  let dataFile = idxs.some(checkFileListForMatch, args)
  return dataFile
}

/**
 * Check file list for first valid match for sidecar file
 */
function checkFileListForMatch(i) {
  this.path = this.fileList[i].relativePath
  let match = false
  // Only check file if path includes the path to sidecar
  const dictArgs = this.path.includes(this.dictPath) ? this.dictArgs : []
  // Set true if dictArgs and all dictargs exist in file path (except 'coordsystem')
  let pathMatch =
    dictArgs.length > 0
      ? dictArgs.every(arg => arg === 'coordsystem' || this.path.includes(arg))
      : false
  if (pathMatch) {
    match = verifyDatafileMatch(
      this.file.relativePath,
      this.noExt,
      this.fileList[i],
    )
  }
  return match
}

/**
 * Accepts path to sidecar file, the sidecar filename without extension
 * and the datafile that's a potential match
 * Returns boolean indicating if file evaluates as valid datafile
 */
function verifyDatafileMatch(sidecarPath, noExt, matchFile) {
  let match = false
  let folderMatch = false

  // Make sure it's not the data dictionary itself
  const isSelf = matchFile.relativePath === sidecarPath
  if (!isSelf && type.file.isDatafile(matchFile.relativePath)) {
    match = true
  }

  // IEEG and MEG datafiles may be a folder, therefore not contained in fileList, will need to look in paths
  if (
    !isSelf &&
    !match) {

    if (noExt.endsWith('_meg') || noExt.endsWith('_coordsystem')) {
      folderMatch = matchFile.relativePath.includes('_meg.ds')
    }
    if (noExt.endsWith('_ieeg') || noExt.endsWith('_coordsystem')) {
      folderMatch = matchFile.relativePath.includes('_ieeg.mefd')
    }

  }
  if (folderMatch) {
    match = true
  }

  return match
}

export { checkSidecarForDatafiles }

export default {
  checkSidecarForDatafiles,
}
<commit_msg>Update bids-validator/utils/bids_files.js<commit_after>/**
 * Utility functions for checking bids file structures
 * eg. corresponding files
 */
// dependencies -------------------------------------------------------------------
import type from './type.js'

// implementations ----------------------------------------------------------------
/**
 * Verify that JSON sidecars have corresponding data files
 */
function checkSidecarForDatafiles(file, fileList) {
  const noExt = file.relativePath.replace('.json', '')
  const dictName = noExt.substring(noExt.lastIndexOf('/') + 1, noExt.length)
  const args = {
    dictArgs: dictName.split('_'),
    dictName: dictName,
    dictPath: noExt.substring(0, noExt.lastIndexOf('/') + 1),
    file: file,
    fileList: fileList,
    noExt: noExt,
  }
  const idxs = Object.keys(fileList)
  // Check each file in fileList for potential match - return true on first match
  let dataFile = idxs.some(checkFileListForMatch, args)
  return dataFile
}

/**
 * Check file list for first valid match for sidecar file
 */
function checkFileListForMatch(i) {
  this.path = this.fileList[i].relativePath
  let match = false
  // Only check file if path includes the path to sidecar
  const dictArgs = this.path.includes(this.dictPath) ? this.dictArgs : []
  // Set true if dictArgs and all dictargs exist in file path (except 'coordsystem')
  let pathMatch =
    dictArgs.length > 0
      ? dictArgs.every(arg => arg === 'coordsystem' || this.path.includes(arg))
      : false
  if (pathMatch) {
    match = verifyDatafileMatch(
      this.file.relativePath,
      this.noExt,
      this.fileList[i],
    )
  }
  return match
}

/**
 * Accepts path to sidecar file, the sidecar filename without extension
 * and the datafile that's a potential match
 * Returns boolean indicating if file evaluates as valid datafile
 */
function verifyDatafileMatch(sidecarPath, noExt, matchFile) {
  let match = false
  let folderMatch = false

  // Make sure it's not the data dictionary itself
  const isSelf = matchFile.relativePath === sidecarPath
  if (!isSelf && type.file.isDatafile(matchFile.relativePath)) {
    match = true
  }

  // IEEG and MEG datafiles may be a folder, therefore not contained in fileList, will need to look in paths
  if (
    !isSelf &&
    !match) {
    if (noExt.endsWith('_meg') || noExt.endsWith('_coordsystem')) {
      folderMatch = matchFile.relativePath.includes('_meg.ds')
    }
    if (noExt.endsWith('_ieeg') || noExt.endsWith('_coordsystem')) {
      folderMatch = matchFile.relativePath.includes('_ieeg.mefd')
    }
  }
  if (folderMatch) {
    match = true
  }

  return match
}

export { checkSidecarForDatafiles }

export default {
  checkSidecarForDatafiles,
}
<|endoftext|>


<commit_before>/* global define: false */

define(['lodash'], function(_) {
	"use strict";

	// constants for piano key states
	var STATE_KEYUP = 'off';
	var STATE_KEYDN = 'on';

	// constants for sustain flag
	var SUSTAIN_ON = 'on';
	var SUSTAIN_OFF = 'off';
	var SUSTAIN_NEXT = 'next';

	// constants for piano key colors
	var COLOR_BLACK_KEYUP = '90-hsl(0,0,25)-hsl(0,0,0)';
	var COLOR_WHITE_KEYUP = '#fffff0';
	var COLOR_KEYDN = 'hsl(0,0,60)';
	var COLOR_KEYSUSTAIN = '90-hsla(0, 50, 40)-hsl(0,50,25)';

	/**
	 * Piano Key Mixin.
	 *
	 * Encapsulates common functionality for displaying and interacting with piano
	 * keys on the onscreen keyboard.
	 *
	 * These properties and methods should be copied into the white/black piano key
	 * classes.
	 */
	var PianoKeyMixin = {
		/**
		 * Current state of the piano key (up or down).
		 */
		state: STATE_KEYUP,

		/**
		 * Sustain flag used to change the coloring of the key
		 * when it is released to indicate that it is being sustained.
		 */
		sustain: SUSTAIN_OFF,

		/**
		 * Changes the piano key state to "pressed" (down).
		 */
		press: function() {
			this.state = STATE_KEYDN;
			this.updateColor();
		},

		/**
		 * Changes the piano key state to "released" (up).
		 */
		release: function() {
			switch(this.sustain) {
				case SUSTAIN_ON:
					this.setColor(STATE_KEYUP, COLOR_KEYSUSTAIN);
					break;
				case SUSTAIN_NEXT:
					this.sustain = SUSTAIN_ON;
					break;
				case SUSTAIN_OFF:
					this.revertColor(STATE_KEYUP);
					break;
				default:
					this.revertColor(STATE_KEYUP);
			}

			this.state = STATE_KEYUP;
			this.updateColor();
		},

		/**
		 * Clears the key.
		 */
		clear: function() {
			this.revertColor(STATE_KEYUP);
			this.state = STATE_KEYUP;
			this.updateColor();
		},

		/**
		 * Sets the sustain on the key so that when the key is released
		 * it will know to sustain it.
		 *
		 * Note that when the sustain is turned off, this method automatically
		 * calls the release() method to revert it back to the normal release
		 * state.
		 */
		setSustain: function(state) {
			if(state) {
				this.sustain = SUSTAIN_ON;
				if(this.isPressed()) {
					this.sustain = SUSTAIN_NEXT;
				}
			} else {
				this.sustain = SUSTAIN_OFF;
				if(this.isReleased()) {
					this.release();
				}
			}
		},

		/**
		 * Returns true if the key is in the down state.
		 *
		 * @return {boolean}
		 */
		isPressed: function() {
			return this.state === STATE_KEYDN;
		},

		/**
		 * Returns true if the key is in the up state.
		 *
		 * @return {boolean}
		 */
		isReleased: function() {
			return this.state === STATE_KEYUP;
		},

		/**
		 * Initializes the piano key.
		 *
		 * @return {this}
		 */
		init: function(config) {
			if(!config.hasOwnProperty('noteNumber')) {
				throw new Error("Key must have a MIDI note number");
			}

			this.noteNumber = config.noteNumber;
			this.noteName = config.noteName || '';
			this.keyboard = config.keyboard;

			_.bindAll(this, ['onPress', 'onRelease']);
		},

		/**
		 * Renders the piano key on the screen.
		 *
		 * @param {Raphael} paper Paper object from Raphael SVG library.
		 * @param {integer} whiteKeyIndex the index of the white key immediately preceding this one.
		 * @param {number} keyboardWidth the width of the keyboard
		 * @param {number} keyboardHeight the height of the keyboard
		 * @return {this}
		 */
		render: function(paper, whiteKeyIndex, numWhiteKeys, keyboardWidth, keyboardHeight) {
			this.el = paper.rect(
				this.calculateOffsetX(whiteKeyIndex),
				0, // offsetY
				this.calculateWidth(),
				this.calculateHeight(keyboardHeight)
			);
			this.el.mousedown(this.onPress);
			this.el.mouseup(this.onRelease);
			this.el.mouseout(this.onRelease);
			this.rendered = true;
			return this;
		},

		/**
		 * Event handler for key press.
		 */
		onPress: function(e) {
			if(this.isReleased()) {
				this.press();
				this.keyboard.trigger('key', this.state, this.noteNumber);
			}
			return false;
		},

		/**
		 * Event handler for key release.
		 */
		onRelease: function(e) {
			if(this.isPressed()) {
				this.release();
				this.keyboard.trigger('key', this.state, this.noteNumber);
			}
			return false;
		},

		/**
		 * Update the element color.
		 */
		updateColor: function() {
			this.el.attr('fill', this.keyColorMap[this.state]);
		},

		/**
		 * Reverts the color for a particular key state to the default.
		 */
		revertColor: function(keyState) {
			this.keyColorMap[keyState] = this.defaultKeyColorMap[keyState];
		},

		/**
		 * Sets the color for a particular key state.
		 */
		setColor: function(keyState, color) {
			this.keyColorMap[keyState] = color;
		},

		/**
		 * Destroys the key.
		 */
		destroy: function() {
			if(this.rendered) {
				this.el.unmousedown(this.onPress);
				this.el.unmouseup(this.onRelease);
				this.el.unmouseout(this.onRelease);
			}
		}
	};

	/**
	 * White piano key class.
	 *
	 * @constructor
	 * @this {WhitePianoKey}
	 * @param {Object} config
	 */
	var WhitePianoKey = function() {
		this.init.apply(this, arguments);
	};

	_.extend(WhitePianoKey.prototype, PianoKeyMixin, {
		/**
		 * Property to indicate if it's a white or black key.
		 */
		isWhite: true,

		/**
		 * Property for changing the key color when it's pressed or released.
		 */
		keyColorMap: {},

		/**
		 * Returns the width of the key.
		 *
		 * @param {integer} numWhiteKeys The total number of white keys on the keyboard.
		 * @param {number} keyboardWidth The total width of the keyboard.
		 * @return {number}
		 */
		calculateWidth: function() {
			return this.keyboard.keyWidth;
		},

		/**
		 * Returns the height of the key.
		 *
		 * @param {number} keyboardHeight The height of the keyboard.
		 * @return {number}
		 */
		calculateHeight: function(keyboardHeight) {
			return keyboardHeight;
		},

		/**
		 * Returns the horizontal offset of the key on the keyboard.
		 *
		 * @param {integer} whiteKeyIndex The index of the preceding white key
		 * @return {number}
		 */
		calculateOffsetX: function(whiteKeyIndex) {
			return whiteKeyIndex * this.calculateWidth();
		},

		/**
		 * Renders the key.
		 *
		 * @param {Raphael} paper The Raphael paper object.
		 * @param {integer} whiteKeyIndex The index of the preceding white key
		 * @param {number} keyboardWidth The total width of the keyboard.
		 * @param {number} keyboardHeight The height of the keyboard.
		 * @return {this}
		 */
		render: function(paper, whiteKeyIndex, numWhiteKeys, keyboardWidth, keyboardHeight) {
			PianoKeyMixin.render.apply(this, arguments);
			var el = this.el;
			el.attr({'stroke': '#000', 'fill': COLOR_WHITE_KEYUP});
			el.toBack();
			return this;
		}
	});

	WhitePianoKey.prototype.keyColorMap[STATE_KEYUP] = COLOR_WHITE_KEYUP;
	WhitePianoKey.prototype.keyColorMap[STATE_KEYDN] = COLOR_KEYDN;
	WhitePianoKey.prototype.defaultKeyColorMap = _.clone(WhitePianoKey.prototype.keyColorMap);

	/**
	 * Black piano key class.
	 *
	 * @constructor
	 * @this {BlackPianoKey}
	 * @param {Object} config
	 */
	var BlackPianoKey = function() {
		this.init.apply(this, arguments);
	};

	_.extend(BlackPianoKey.prototype, PianoKeyMixin, {
		/**
		 * Property to indicate if it's a white or black key.
		 */
		isWhite: false,

		/**
		 * Property for changing the key color when it's pressed or released.
		 */
		keyColorMap: {},

		/**
		 * Returns the width of the key.
		 *
		 * @param {integer} numWhiteKeys The total number of white keys on the keyboard.
		 * @param {number} keyboardWidth The total width of the keyboard.
		 * @return {number}
		 */
		calculateWidth: function() {
			return this.keyboard.keyWidth / 2;
		},

		/**
		 * Returns the height of the key.
		 *
		 * @param {number} keyboardHeight The height of the keyboard.
		 * @return {number}
		 */
		calculateHeight: function(keyboardHeight) {
			return 0.75 * keyboardHeight;
		},

		/**
		 * Returns the horizontal offset of the key on the keyboard.
		 *
		 * @param {integer} whiteKeyIndex The index of the preceding white key
		 * @return {number}
		 */
		calculateOffsetX: function(whiteKeyIndex) {
			var offset = (whiteKeyIndex * this.keyboard.keyWidth);
			var width = this.calculateWidth();
			return offset - (width / 2);
		},

		/**
		 * Renders the key.
		 *
		 * @param {Raphael} paper The Raphael paper object.
		 * @param {integer} whiteKeyIndex The index of the preceding white key
		 * @param {number} keyboardWidth The total width of the keyboard.
		 * @param {number} keyboardHeight The height of the keyboard.
		 * @return {this}
		 */
		render: function(paper, whiteKeyIndex, numWhiteKeys, keyboardWidth, keyboardHeight) {
			PianoKeyMixin.render.apply(this, arguments);
			var el = this.el;
			el.attr('fill', COLOR_BLACK_KEYUP);
			el.toFront();
			return this;
		}
	});

	BlackPianoKey.prototype.keyColorMap[STATE_KEYUP] = COLOR_BLACK_KEYUP;
	BlackPianoKey.prototype.keyColorMap[STATE_KEYDN] = COLOR_KEYDN;
	BlackPianoKey.prototype.defaultKeyColorMap = _.clone(BlackPianoKey.prototype.keyColorMap);

	var PianoKey = {
		/**
		 * Factory function that returns an instance of a white
		 * or black piano key.
		 *
		 * @param {boolean} isWhite True to create a white key, false for a black key.
		 * @param {string} key The string name of the key (optional).
		 * @return {PianoKey}
		 */
		create: function(config) {
			var KeyConstructor = config.isWhite ? WhitePianoKey : BlackPianoKey;
			return new KeyConstructor(config);
		}
	};

	return PianoKey;
});
<commit_msg>sustain even when pressed and desaturate color of sustained key<commit_after>/* global define: false */

define(['lodash'], function(_) {
	"use strict";

	// constants for piano key states
	var STATE_KEYUP = 'off';
	var STATE_KEYDN = 'on';

	// constants for sustain flag
	var SUSTAIN_ON = 'on';
	var SUSTAIN_OFF = 'off';

	// constants for piano key colors
	var COLOR_BLACK_KEYUP = '90-hsl(0,0,25)-hsl(0,0,0)';
	var COLOR_WHITE_KEYUP = '#fffff0';
	var COLOR_KEYDN = 'hsl(0,0,60)';
	var COLOR_KEYSUSTAIN = '90-hsla(0, 30, 40)-hsl(0,45,20)';

	/**
	 * Piano Key Mixin.
	 *
	 * Encapsulates common functionality for displaying and interacting with piano
	 * keys on the onscreen keyboard.
	 *
	 * These properties and methods should be copied into the white/black piano key
	 * classes.
	 */
	var PianoKeyMixin = {
		/**
		 * Current state of the piano key (up or down).
		 */
		state: STATE_KEYUP,

		/**
		 * Sustain flag used to change the coloring of the key
		 * when it is released to indicate that it is being sustained.
		 */
		sustain: SUSTAIN_OFF,

		/**
		 * Changes the piano key state to "pressed" (down).
		 */
		press: function() {
			this.state = STATE_KEYDN;
			this.updateColor();
		},

		/**
		 * Changes the piano key state to "released" (up).
		 */
		release: function() {
			switch(this.sustain) {
				case SUSTAIN_ON:
					this.setColor(STATE_KEYUP, COLOR_KEYSUSTAIN);
					break;
				case SUSTAIN_OFF:
					this.revertColor(STATE_KEYUP);
					break;
				default:
					this.revertColor(STATE_KEYUP);
			}

			this.state = STATE_KEYUP;
			this.updateColor();
		},

		/**
		 * Clears the key.
		 */
		clear: function() {
			this.revertColor(STATE_KEYUP);
			this.state = STATE_KEYUP;
			this.updateColor();
		},

		/**
		 * Sets the sustain on the key so that when the key is released
		 * it will know to sustain it.
		 *
		 * Note that when the sustain is turned off, this method automatically
		 * calls the release() method to revert it back to the normal release
		 * state.
		 */
		setSustain: function(state) {
			if(state) {
				this.sustain = SUSTAIN_ON;
				if(this.isPressed()) {
					this.sustain = SUSTAIN_ON;
				}
			} else {
				this.sustain = SUSTAIN_OFF;
				if(this.isReleased()) {
					this.release();
				}
			}
		},

		/**
		 * Returns true if the key is in the down state.
		 *
		 * @return {boolean}
		 */
		isPressed: function() {
			return this.state === STATE_KEYDN;
		},

		/**
		 * Returns true if the key is in the up state.
		 *
		 * @return {boolean}
		 */
		isReleased: function() {
			return this.state === STATE_KEYUP;
		},

		/**
		 * Initializes the piano key.
		 *
		 * @return {this}
		 */
		init: function(config) {
			if(!config.hasOwnProperty('noteNumber')) {
				throw new Error("Key must have a MIDI note number");
			}

			this.noteNumber = config.noteNumber;
			this.noteName = config.noteName || '';
			this.keyboard = config.keyboard;

			_.bindAll(this, ['onPress', 'onRelease']);
		},

		/**
		 * Renders the piano key on the screen.
		 *
		 * @param {Raphael} paper Paper object from Raphael SVG library.
		 * @param {integer} whiteKeyIndex the index of the white key immediately preceding this one.
		 * @param {number} keyboardWidth the width of the keyboard
		 * @param {number} keyboardHeight the height of the keyboard
		 * @return {this}
		 */
		render: function(paper, whiteKeyIndex, numWhiteKeys, keyboardWidth, keyboardHeight) {
			this.el = paper.rect(
				this.calculateOffsetX(whiteKeyIndex),
				0, // offsetY
				this.calculateWidth(),
				this.calculateHeight(keyboardHeight)
			);
			this.el.mousedown(this.onPress);
			this.el.mouseup(this.onRelease);
			this.el.mouseout(this.onRelease);
			this.rendered = true;
			return this;
		},

		/**
		 * Event handler for key press.
		 */
		onPress: function(e) {
			if(this.isReleased()) {
				this.press();
				this.keyboard.trigger('key', this.state, this.noteNumber);
			}
			return false;
		},

		/**
		 * Event handler for key release.
		 */
		onRelease: function(e) {
			if(this.isPressed()) {
				this.release();
				this.keyboard.trigger('key', this.state, this.noteNumber);
			}
			return false;
		},

		/**
		 * Update the element color.
		 */
		updateColor: function() {
			this.el.attr('fill', this.keyColorMap[this.state]);
		},

		/**
		 * Reverts the color for a particular key state to the default.
		 */
		revertColor: function(keyState) {
			this.keyColorMap[keyState] = this.defaultKeyColorMap[keyState];
		},

		/**
		 * Sets the color for a particular key state.
		 */
		setColor: function(keyState, color) {
			this.keyColorMap[keyState] = color;
		},

		/**
		 * Destroys the key.
		 */
		destroy: function() {
			if(this.rendered) {
				this.el.unmousedown(this.onPress);
				this.el.unmouseup(this.onRelease);
				this.el.unmouseout(this.onRelease);
			}
		}
	};

	/**
	 * White piano key class.
	 *
	 * @constructor
	 * @this {WhitePianoKey}
	 * @param {Object} config
	 */
	var WhitePianoKey = function() {
		this.init.apply(this, arguments);
	};

	_.extend(WhitePianoKey.prototype, PianoKeyMixin, {
		/**
		 * Property to indicate if it's a white or black key.
		 */
		isWhite: true,

		/**
		 * Property for changing the key color when it's pressed or released.
		 */
		keyColorMap: {},

		/**
		 * Returns the width of the key.
		 *
		 * @param {integer} numWhiteKeys The total number of white keys on the keyboard.
		 * @param {number} keyboardWidth The total width of the keyboard.
		 * @return {number}
		 */
		calculateWidth: function() {
			return this.keyboard.keyWidth;
		},

		/**
		 * Returns the height of the key.
		 *
		 * @param {number} keyboardHeight The height of the keyboard.
		 * @return {number}
		 */
		calculateHeight: function(keyboardHeight) {
			return keyboardHeight;
		},

		/**
		 * Returns the horizontal offset of the key on the keyboard.
		 *
		 * @param {integer} whiteKeyIndex The index of the preceding white key
		 * @return {number}
		 */
		calculateOffsetX: function(whiteKeyIndex) {
			return whiteKeyIndex * this.calculateWidth();
		},

		/**
		 * Renders the key.
		 *
		 * @param {Raphael} paper The Raphael paper object.
		 * @param {integer} whiteKeyIndex The index of the preceding white key
		 * @param {number} keyboardWidth The total width of the keyboard.
		 * @param {number} keyboardHeight The height of the keyboard.
		 * @return {this}
		 */
		render: function(paper, whiteKeyIndex, numWhiteKeys, keyboardWidth, keyboardHeight) {
			PianoKeyMixin.render.apply(this, arguments);
			var el = this.el;
			el.attr({'stroke': '#000', 'fill': COLOR_WHITE_KEYUP});
			el.toBack();
			return this;
		}
	});

	WhitePianoKey.prototype.keyColorMap[STATE_KEYUP] = COLOR_WHITE_KEYUP;
	WhitePianoKey.prototype.keyColorMap[STATE_KEYDN] = COLOR_KEYDN;
	WhitePianoKey.prototype.defaultKeyColorMap = _.clone(WhitePianoKey.prototype.keyColorMap);

	/**
	 * Black piano key class.
	 *
	 * @constructor
	 * @this {BlackPianoKey}
	 * @param {Object} config
	 */
	var BlackPianoKey = function() {
		this.init.apply(this, arguments);
	};

	_.extend(BlackPianoKey.prototype, PianoKeyMixin, {
		/**
		 * Property to indicate if it's a white or black key.
		 */
		isWhite: false,

		/**
		 * Property for changing the key color when it's pressed or released.
		 */
		keyColorMap: {},

		/**
		 * Returns the width of the key.
		 *
		 * @param {integer} numWhiteKeys The total number of white keys on the keyboard.
		 * @param {number} keyboardWidth The total width of the keyboard.
		 * @return {number}
		 */
		calculateWidth: function() {
			return this.keyboard.keyWidth / 2;
		},

		/**
		 * Returns the height of the key.
		 *
		 * @param {number} keyboardHeight The height of the keyboard.
		 * @return {number}
		 */
		calculateHeight: function(keyboardHeight) {
			return 0.75 * keyboardHeight;
		},

		/**
		 * Returns the horizontal offset of the key on the keyboard.
		 *
		 * @param {integer} whiteKeyIndex The index of the preceding white key
		 * @return {number}
		 */
		calculateOffsetX: function(whiteKeyIndex) {
			var offset = (whiteKeyIndex * this.keyboard.keyWidth);
			var width = this.calculateWidth();
			return offset - (width / 2);
		},

		/**
		 * Renders the key.
		 *
		 * @param {Raphael} paper The Raphael paper object.
		 * @param {integer} whiteKeyIndex The index of the preceding white key
		 * @param {number} keyboardWidth The total width of the keyboard.
		 * @param {number} keyboardHeight The height of the keyboard.
		 * @return {this}
		 */
		render: function(paper, whiteKeyIndex, numWhiteKeys, keyboardWidth, keyboardHeight) {
			PianoKeyMixin.render.apply(this, arguments);
			var el = this.el;
			el.attr('fill', COLOR_BLACK_KEYUP);
			el.toFront();
			return this;
		}
	});

	BlackPianoKey.prototype.keyColorMap[STATE_KEYUP] = COLOR_BLACK_KEYUP;
	BlackPianoKey.prototype.keyColorMap[STATE_KEYDN] = COLOR_KEYDN;
	BlackPianoKey.prototype.defaultKeyColorMap = _.clone(BlackPianoKey.prototype.keyColorMap);

	var PianoKey = {
		/**
		 * Factory function that returns an instance of a white
		 * or black piano key.
		 *
		 * @param {boolean} isWhite True to create a white key, false for a black key.
		 * @param {string} key The string name of the key (optional).
		 * @return {PianoKey}
		 */
		create: function(config) {
			var KeyConstructor = config.isWhite ? WhitePianoKey : BlackPianoKey;
			return new KeyConstructor(config);
		}
	};

	return PianoKey;
});
<|endoftext|>


<commit_before>/*
 * Copyright 2009-2013 Roland Huss
 *
 * 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.
 */

if(!this.JSON){this.JSON={};}
(function(){"use strict";function f(n){return n<10?'0'+n:n;}
if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z':null;};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};}
var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}
function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
if(typeof rep==='function'){value=rep.call(holder,key,value);}
switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
v=partial.length===0?'[]':gap?'[\n'+gap+
partial.join(',\n'+gap)+'\n'+
mind+']':'['+partial.join(',')+']';gap=mind;return v;}
if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+
mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
return str('',{'':value});};}
if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
return reviver.call(holder,key,value);}
text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+
('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}
if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
throw new SyntaxError('JSON.parse');};}}());
<commit_msg>Removed copyright notice which slipped in accidentally.<commit_after>if(!this.JSON){this.JSON={};}
(function(){"use strict";function f(n){return n<10?'0'+n:n;}
if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z':null;};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};}
var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}
function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
if(typeof rep==='function'){value=rep.call(holder,key,value);}
switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
v=partial.length===0?'[]':gap?'[\n'+gap+
partial.join(',\n'+gap)+'\n'+
mind+']':'['+partial.join(',')+']';gap=mind;return v;}
if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+
mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
return str('',{'':value});};}
if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
return reviver.call(holder,key,value);}
text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+
('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}
if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
throw new SyntaxError('JSON.parse');};}}());
<|endoftext|>


<commit_before>// ==UserScript==
// @name             Get DLC Info from SteamDB
// @namespace        sak32009-get-dlc-info-from-steamdb
// @description      Get DLC Info from SteamDB
// @author           Sak32009
// @contributor      cs.rin.ru
// @version          3.6.2
// @license          MIT
// @homepageURL      https://github.com/Sak32009/GetDLCInfoFromSteamDB/
// @supportURL       http://cs.rin.ru/forum/viewtopic.php?f=10&t=71837
// @updateURL        https://github.com/Sak32009/GetDLCInfoFromSteamDB/raw/master/sak32009-get-dlc-info-from-steamdb.meta.js
// @downloadURL      https://github.com/Sak32009/GetDLCInfoFromSteamDB/raw/master/sak32009-get-dlc-info-from-steamdb.user.js
// @icon             https://raw.githubusercontent.com/Sak32009/GetDLCInfoFromSteamDB/master/sak32009-get-dlc-info-from-steamdb-32.png
// @icon64           https://raw.githubusercontent.com/Sak32009/GetDLCInfoFromSteamDB/master/sak32009-get-dlc-info-from-steamdb-64.png
// @match            *://steamdb.info/app/*
// @match            *://steamdb.info/search/*
// @require          https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @require          https://raw.githubusercontent.com/zewish/rmodal.js/master/dist/rmodal.min.js
// @require          https://steamdb.info/static/js/tabbable.4f8f7fce.js
// @grant            none
// @run-at           document-end
// ==/UserScript==
<commit_msg>Update 3.6.2f<commit_after>// ==UserScript==
// @name             Get DLC Info from SteamDB
// @namespace        sak32009-get-dlc-info-from-steamdb
// @description      Get DLC Info from SteamDB
// @author           Sak32009
// @contributor      cs.rin.ru
// @version          3.6.2f
// @license          MIT
// @homepageURL      https://github.com/Sak32009/GetDLCInfoFromSteamDB/
// @supportURL       http://cs.rin.ru/forum/viewtopic.php?f=10&t=71837
// @updateURL        https://github.com/Sak32009/GetDLCInfoFromSteamDB/raw/master/sak32009-get-dlc-info-from-steamdb.meta.js
// @downloadURL      https://github.com/Sak32009/GetDLCInfoFromSteamDB/raw/master/sak32009-get-dlc-info-from-steamdb.user.js
// @icon             https://raw.githubusercontent.com/Sak32009/GetDLCInfoFromSteamDB/master/sak32009-get-dlc-info-from-steamdb-32.png
// @icon64           https://raw.githubusercontent.com/Sak32009/GetDLCInfoFromSteamDB/master/sak32009-get-dlc-info-from-steamdb-64.png
// @match            *://steamdb.info/app/*
// @match            *://steamdb.info/search/*
// @require          https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @require          https://raw.githubusercontent.com/zewish/rmodal.js/master/dist/rmodal.min.js
// @require          https://steamdb.info/static/js/tabbable.4f8f7fce.js
// @grant            none
// @run-at           document-end
// ==/UserScript==
<|endoftext|>