<commit_before>require(
  [
  'jQuery',
  'Moment'
  ],
  function($, Moment) {

  // hello to another happy developer
  console.log('hello fellow developer :)');
  console.log('if you have suggestions for this site, do buzz me and say hi @sayanee_');

  // Github API call for Repos
  var Repo = {
    totalList: [],
    fetch: function(config) {
      var len = config.length,
      self = this,
      count = 0;
      config.forEach( function(configEl) {
        var url = 'https://api.github.com/repos/' + configEl + '?callback=?';
        $.getJSON( url, function( json ) {
          var repo = {
            html_url: json.data.html_url,
            name: json.data.name,
            pushed_at: json.data.pushed_at,
            description: json.data.description,
            stars: json.data.watchers_count
          };
          self.totalList.push(repo);
          count++;
          if (count === len) {
            self.sortByDate();
            self.insertRepo();
          }
        });
      });
    },
    sortHelper: function(field, reverse, primer){
      var key = function (x) {
        return primer ? primer(x[field]) : x[field];
      };
      return function (a,b) {
        var A = key(a), B = key(b);
        if ( A  B ) {
          return +1 * [-1,1][+!!reverse];
        } else {
          return 0 * [-1,1][+!!reverse];
        }
      };
    },
    sortByDate: function () {
      // console.log(this.totalList);
      this.totalList.sort(
        this.sortHelper('pushed_at', false, function(a) {
          return new Date(a);
        })
        );
    },
    insertRepo: function (  ) {
      var arr = this.totalList;

      for( var arrayIndex = 0; arrayIndex < arr.length; arrayIndex++ ) {
        $("ul.open-list.second li:nth-child(" + (arrayIndex + 1) + ")").html(
          '
' +
          arr[arrayIndex].name +
          'updated ' +
          moment(arr[arrayIndex].pushed_at).endOf('hour').fromNow() +
          '★ ' +
          arr[arrayIndex].stars +
          '

' +
          arr[arrayIndex].description +
          '
'
          );
      }
    }
  };

  // click live section to go to the live website
  $('.live').click(function() {
    window.parent.location.href = 'http://live.webuild.sg';
  });

  // countdown
  countdown();
  setInterval(countdown, 1000);
  function countdown () {
    var now = moment(),
    podcastDate = "2014-04-05 11:00 +0800",
    dateFormat = "YYYY-MM-DD HH:mm Z",
    livedate = moment(podcastDate, dateFormat),
    then = moment(podcastDate, dateFormat);

    ms = then.diff(now, 'milliseconds', true);
    days = Math.floor(moment.duration(ms).asDays());

    if (days >= 0) {
      then.subtract('days', days);
      ms = then.diff(now, 'milliseconds', true);
      hours = Math.floor(moment.duration(ms).asHours());

      then.subtract('hours', hours);
      ms = then.diff(now, 'milliseconds', true);
      minutes = Math.floor(moment.duration(ms).asMinutes());

      then.subtract('minutes', minutes);
      ms = then.diff(now, 'milliseconds', true);
      seconds = Math.floor(moment.duration(ms).asSeconds());

      diff = 'in ' + days + ' days ' + hours + ' hours ' + minutes + ' minutes ' + seconds + ' seconds';

      $('.countdown').html(diff);
      $('#livetime').html( livedate.format('D MMM YYYY, ddd @h:mm a Z' ) + ' GMT' );
    } else {
      $('.countdown').html('');
    }

  }

  // Non Github Repo
  // Repo.totalList.push({
  //   html_url: "http://www.sketchkit.com/",
  //   name: "Sketch Kit Wireframes",
  //   pushed_at: "2012-10-01",
  //   description: "Create quick iPhone app wireframes in Keynote"
  // });

  // Github Repo updated in the last 2 months
  Repo.fetch([
    'lxcid/LXReorderableCollectionViewFlowLayout',
    'cheeaun/hackerweb',
    'specta/specta',
    'Luracast/Restler',
    'MugunthKumar/MKNetworkKit',
    'opauth/opauth',
    // 'MugunthKumar/MKStoreKit',
    // 'honcheng/PaperFold-for-iOS',
    'laktek/punch',
    // 'laktek/jQuery-Smart-Auto-Complete',
    'CoderKungfu/php-queue',
    'cheeaun/life'
    // 'zz85/sparks.js'
    // 'honcheng/RTLabel',
    // 'mbrochh/vim-as-a-python-ide'
    // 'honcheng/PaperFoldMenuController',
    // 'hboon/GlassButtons',
    ]);

});
<commit_msg>added repo<commit_after>require(
  [
  'jQuery',
  'Moment'
  ],
  function($, Moment) {

  // hello to another happy developer
  console.log('hello fellow developer :)');
  console.log('if you have suggestions for this site, do buzz me and say hi @sayanee_');

  // Github API call for Repos
  var Repo = {
    totalList: [],
    fetch: function(config) {
      var len = config.length,
      self = this,
      count = 0;
      config.forEach( function(configEl) {
        var url = 'https://api.github.com/repos/' + configEl + '?callback=?';
        $.getJSON( url, function( json ) {
          var repo = {
            html_url: json.data.html_url,
            name: json.data.name,
            pushed_at: json.data.pushed_at,
            description: json.data.description,
            stars: json.data.watchers_count
          };
          self.totalList.push(repo);
          count++;
          if (count === len) {
            self.sortByDate();
            self.insertRepo();
          }
        });
      });
    },
    sortHelper: function(field, reverse, primer){
      var key = function (x) {
        return primer ? primer(x[field]) : x[field];
      };
      return function (a,b) {
        var A = key(a), B = key(b);
        if ( A  B ) {
          return +1 * [-1,1][+!!reverse];
        } else {
          return 0 * [-1,1][+!!reverse];
        }
      };
    },
    sortByDate: function () {
      // console.log(this.totalList);
      this.totalList.sort(
        this.sortHelper('pushed_at', false, function(a) {
          return new Date(a);
        })
        );
    },
    insertRepo: function (  ) {
      var arr = this.totalList;

      for( var arrayIndex = 0; arrayIndex < arr.length; arrayIndex++ ) {
        $("ul.open-list.second li:nth-child(" + (arrayIndex + 1) + ")").html(
          '
' +
          arr[arrayIndex].name +
          'updated ' +
          moment(arr[arrayIndex].pushed_at).endOf('hour').fromNow() +
          '★ ' +
          arr[arrayIndex].stars +
          '

' +
          arr[arrayIndex].description +
          '
'
          );
      }
    }
  };

  // click live section to go to the live website
  $('.live').click(function() {
    window.parent.location.href = 'http://live.webuild.sg';
  });

  // countdown
  countdown();
  setInterval(countdown, 1000);
  function countdown () {
    var now = moment(),
    podcastDate = "2014-04-05 11:00 +0800",
    dateFormat = "YYYY-MM-DD HH:mm Z",
    livedate = moment(podcastDate, dateFormat),
    then = moment(podcastDate, dateFormat);

    ms = then.diff(now, 'milliseconds', true);
    days = Math.floor(moment.duration(ms).asDays());

    if (days >= 0) {
      then.subtract('days', days);
      ms = then.diff(now, 'milliseconds', true);
      hours = Math.floor(moment.duration(ms).asHours());

      then.subtract('hours', hours);
      ms = then.diff(now, 'milliseconds', true);
      minutes = Math.floor(moment.duration(ms).asMinutes());

      then.subtract('minutes', minutes);
      ms = then.diff(now, 'milliseconds', true);
      seconds = Math.floor(moment.duration(ms).asSeconds());

      diff = 'in ' + days + ' days ' + hours + ' hours ' + minutes + ' minutes ' + seconds + ' seconds';

      $('.countdown').html(diff);
      $('#livetime').html( livedate.format('D MMM YYYY, ddd @h:mm a Z' ) + ' GMT' );
    } else {
      $('.countdown').html('');
    }

  }

  // Non Github Repo
  // Repo.totalList.push({
  //   html_url: "http://www.sketchkit.com/",
  //   name: "Sketch Kit Wireframes",
  //   pushed_at: "2012-10-01",
  //   description: "Create quick iPhone app wireframes in Keynote"
  // });

  // Github Repo updated in the last 2 months
  Repo.fetch([
    'lxcid/LXReorderableCollectionViewFlowLayout',
    'cheeaun/hackerweb',
    'specta/specta',
    'Luracast/Restler',
    'MugunthKumar/MKNetworkKit',
    'opauth/opauth',
    'jf/rbenv-gemset',
    // 'MugunthKumar/MKStoreKit',
    // 'honcheng/PaperFold-for-iOS',
    'laktek/punch',
    // 'laktek/jQuery-Smart-Auto-Complete',
    'CoderKungfu/php-queue',
    'cheeaun/life'
    // 'zz85/sparks.js'
    // 'honcheng/RTLabel',
    // 'mbrochh/vim-as-a-python-ide'
    // 'honcheng/PaperFoldMenuController',
    // 'hboon/GlassButtons',
    ]);

});
<|endoftext|>


<commit_before>var Player = require('../../Broadway-master/Player/Player');
var io = require('socket.io-client');

$(function () {
  'use strict';

  var toUint8Array = function (parStr) {
    var raw = atob(parStr);
    var array = new Uint8Array(new ArrayBuffer(raw.length));

    Array.prototype.forEach.call(raw, function (data, index) {
      array[index] = raw.charCodeAt(index);
    });

    // console.log(array);
    return array;
  };

  var player = new Player({
    useWorker: true,
    workerFile: '/js/Decoder.js',
  });

  var videoWindow = document.getElementById('video-stream');

  // document.body.appendChild(player.canvas);
  videoWindow.appendChild(player.canvas);

  var socket = io('http://localhost:8000/');

  socket.on('data', function (data) {
    console.log('receiving video data...');
    player.decode(toUint8Array(data));
  });

  var toTitleCase = require('titlecase');
  var moment = require('moment');
  var _ = require('lodash');
  var MAX_ZOOM = 14.9;
  var ZOOM = MAX_ZOOM;
  var THEME = 'standard';

  var layers = {

    // Airport Airspaces
    airports_recreational: { icon: 'fa fa-plane' },

    // Cautions
    tfrs: { icon: 'fa fa-exclamation-triangle' },
    sua_prohibited: { icon: 'fa fa-user-secret' },
    sua_restricted: { icon: 'fa fa-fighter-jet' },
    noaa: { icon: 'fa fa-ship' },
    national_parks: { icon: 'fa fa-tree' },

    // Advisories
    hospitals: { icon: 'fa fa-ambulance' },
    parcels: { icon: 'fa fa-home' },
    power_plants: { icon: 'fa fa-bolt' },
    heliports: { icon: 'fa fa-h-square' },
    schools: { icon: 'fa fa-graduation-cap' },
  };

  var layerNames = {
    airports_commercial: 'Airports (Commercial Rules)',
    airports_commercial_private: 'Private Airports (Commercial Rules)',
    airports_recreational: 'Airports (Recreational Rules)',
    airports_recreational_private: 'Private Airports (Recreational Rules)',
    class_b: 'Controlled Airspace (Class B)',
    class_c: 'Controlled Airspace (Class C)',
    class_d: 'Controlled Airspace (Class D)',
    class_e0: 'Controlled Airspace (Class E to Ground)',
    heliports: 'Heliports',
    hospitals: 'Hospitals',
    national_parks: 'National Parks',
    noaa: 'NOAA Marine Protection Areas',
    parcels: 'Private Property',
    power_plants: 'Power Plants',
    prisons: 'Prisons',
    schools: 'Schools',
    stadiums: 'Stadiums',
    sua_prohibited: 'Prohibited Special Use Airspace',
    sua_restricted: 'Restricted Special Use Airspace',
    tfrs: 'Temporary Flight Restrictions',
  };

  var advisoryLevels = {
    red: 3,
    yellow: 2,
    green: 1,
  };

  var labelColors = {
    SAFE: 'success',
    CAUTION: 'warning',
    ADVISORY: 'danger',
  };

  var url = 'https://api.airmap.io/maps/v3/tilejson/' +
    Object.keys(layers).join() + '?apikey=' + AIRMAP_TOKEN +
    '&token=' + AIRMAP_TOKEN +
    '&theme=' + THEME;

  // Initialize the Mapbox map with Airmap overlayed on top
  mapboxgl.accessToken = MAPBOX_TOKEN;
  var map = new mapboxgl.Map({
    container: 'map', // container id
    style: url, //stylesheet location
    center: [-73.9957915, 40.7285429], // starting position
    zoom: MAX_ZOOM, // starting zoom
    maxZoom: ZOOM,
  });

  map.addControl(new mapboxgl.Geolocate());
  map.addControl(new mapboxgl.Navigation());

  var icons = {
    school: 'graduation-cap',
  };

  function buildPopupMarkup(layers) {
    var html = '';
    var grouped = _.groupBy(layers, function (p) {
      return p.type;
    });

    var keys = _.keys(grouped);
    for (var g in keys) {
      var group = keys[g];
      var groupHtml = "
" +
        layerNames[group] + '
';
      for (var i in grouped[group]) {
        var item = grouped[group][i];
        if (item.url) {
          groupHtml += "

" + item.name + '
';
        } else {
          groupHtml += "

" +
          item.name + '
';
        }

        if (item.phone) {
          groupHtml += "
" + item.phone + '
';
        }

        if (item.date_issued) {
          var start = moment(item.date_issued);
          var end = moment(item.date_expire);
          var now = moment();
          if (now.isBefore(start)) {
            groupHtml += "
Starts " + start.fromNow() + '
';
          } else {
            groupHtml += "
Started " + start.fromNow() + '
';
          }

          if (item.date_expire != '+010000-01-01T00:00:00.000Z') {
            groupHtml += "
Ends " +
              end.format('MMMM Do YYYY, h:mm:ss a') + '
';
          }
        }

        groupHtml += '

';
      }

      html += groupHtml;
    }

    return html;
  }

  // Code modified from the AirMap SDK
  function sdkHandleClick(data) {
    var map = data.target;
    var features = map.queryRenderedFeatures(data.point);
    var properties = _.map(features, function (feature) {
      return feature.properties;
    });

    properties = _.filter(properties, function (p) {
      return p.type &&
        (_.includes(Object.keys(layerNames), p.type.replace('layer_', '')) || p.type == 'tfrs');
    });

    if (properties.length > 0) {
      var markup = buildPopupMarkup(properties);
      var popups = document.getElementsByClassName('mapboxgl-popup-content');
      if (popups.length > 0) {
        var markupElement = document.createElement('div');
        markupElement.innerHTML = markup;
        popups[0].insertBefore(markupElement, popups[0].firstChild);
      } else {
        var tooltip = new mapboxgl.Popup({
          closeOnClick: true,
        }).setLngLat(data.lngLat).setHTML(markup);
        tooltip.addTo(map);
      }
    }
  }

  map.on('click', sdkHandleClick);

  function clearRegulations() {
    $('#regulations').empty();
  }

  function addRegulation(name, type, color) {
    // console.log(type);
    // console.log(layers[type]);
    var label = 'SAFE';
    if (color == 'red') {
      label = 'ADVISORY';
    } else if (color == 'yellow') {
      label = 'CAUTION';
    }

    var html = '
 ' +
      toTitleCase(name.toLowerCase()) + '' + label + '
';
    $('#regulations').append(html);
  }

  function updateRegulations(regulations) {
    clearRegulations();

    // Sort the regulations first
    var sortedRegulations = regulations.sort(function (a, b) {
      if (a.advisory_level != b.advisory_level) {
        return (advisoryLevels[a.advisory_level] >
          advisoryLevels[b.advisory_level] ? -1 : 1);
      } else {
        if (a.name == b.name) return 0;
        return (a.name < b.name ? -1 : 1);
      }
    });

    sortedRegulations.forEach(function (regulation) {
      var color = regulation.advisory_level;
      var type = regulation.type;
      var name = regulation.name;
      addRegulation(name, type, color);
    });
  }

  function clearWeather() {
    $('#weather').empty();
  }

  function addWeather(weather, icon, label) {
    var html = '
 ' +
      weather + '' + label + '
';
    $('#weather').append(html);
  }

  function getConditionIcon(condition) {
    condition = condition.toLowerCase();
    if (condition.indexOf('snow') > -1) { // Snowy
      return 'wi wi-day-snow';
    } else if (condition.indexOf('rain') > -1 ||
      condition.indexOf('showers') > -1) {
      return 'wi wi-day-rain';
    } else if (condition.indexOf('cloudy') > -1) { // Cloudy
      return 'wi wi-day-cloudy';
    } else { // Sunny
      return 'wi wi-day-sunny';
    }
  }

  function updateWeather(weather) {
    clearWeather();

    // Add each row with the weather report
    var condition = weather.condition;
    var conditionIcon = getConditionIcon(condition);
    var conditionLabel = 'SAFE';
    var conditionLower = condition.toLowerCase();
    if (conditionLower.indexOf('wind') > -1 ||
      conditionLower.indexOf('rain') > -1 ||
      conditionLower.indexOf('snow') > -1 ||
      conditionLower.indexOf('showers') > -1) conditionLabel = 'CAUTION';
    addWeather(condition, conditionIcon, conditionLabel);

    var visibility = weather.visibility * 0.621371;
    var visLabel = (visibility < 2 ? 'ADVISORY' :
      (visibility < 5) ? 'CAUTION' : 'SAFE');
    addWeather((visibility.toFixed(2)) + ' mi', 'fa fa-eye', visLabel);

    var precipitation = weather.precipitation * 100;
    var precLabel = (precipitation > 50 ? 'ADVISORY' :
      (precipitation > 20) ? 'CAUTION' : 'SAFE');
    addWeather((precipitation) + ' %', 'wi wi-raindrop', precLabel);

    var temperature = (weather.temperature * 9 / 5 + 32);
    var tempLabel = (temperature < 10 || temperature > 100 ? 'ADVISORY' :
      (temperature < 20 || temperature > 90) ? 'CAUTION' : 'SAFE');
    addWeather((temperature) + ' °F', 'wi wi-thermometer-exterior', tempLabel);

    var windspeed = weather.wind.speed * 0.621371;
    var speedLabel = (windspeed > 15 ? 'ADVISORY' :
      (windspeed > 10) ? 'CAUTION' : 'SAFE');
    addWeather((windspeed.toFixed(2)) + ' mph', 'wi wi-windy', speedLabel);

    var gusts = weather.wind.gusting * 0.621371;
    var gustsLabel = (gusts > 25 ? 'ADVISORY' :
      (gusts > 15) ? 'CAUTION' : 'SAFE');
    addWeather((gusts.toFixed(2)) + ' mph', 'wi wi-strong-wind', gustsLabel);
  }

  function getDroneAltitude() {
    return 100 + Math.random() * 10;
  }

  function updateAltitudeRegulation() {
    var currentAltitude = getDroneAltitude();
    var label = (currentAltitude >= 400 ? 'ADVISORY' :
      (currentAltitude >= 250 ? 'CAUTION' : 'SAFE'));

    var html = '
 ' +
      currentAltitude.toFixed(2) + ' ft' + label + '
';
    $('#altregulation').empty();
    $('#altregulation').append(html);
  }

  // point = [long, lat]
  function getRegulationsForPoint(point, cb) {
    var longitude = point[0];
    var latitude = point[1];
    var url = 'https://api.airmap.io/data/v1/status?unique_id=drone_regulations' +
      '&latitude=' + latitude + '&longitude=' + longitude + '&weather=true' +
      '&apikey=' + AIRMAP_TOKEN + '&types=' + Object.keys(layers).join();
    $.ajax({
      method: 'GET',
      url: url,
      success: cb,
    });
  }

  // Data to determine what weather conditions are available from the API
  // Here is what I gathered from running it 1000 times:
  // ["Sunny", "Partly Cloudy/Wind", "Mostly Cloudy", "Snow Showers/Wind",
  // "Mostly Cloudy/Wind", "Sunny/Wind", "Cloudy/Wind", "Cloudy", "Showers/Wind",
  //  "Partly Cloudy", "Rain/Snow/Wind", "Light Rain/Wind", "Few Showers/Wind",
  // "Few Snow Showers/Wind", "Mostly Sunny/Wind", "Snow/Wind", "Mostly Sunny",
  // "Snow", "Snow Showers", "Rain/Snow Showers/Wind", "Light Snow/Wind",
  // "Heavy Snow/Wind", "Few Snow Showers", "Rain/Wind"]
  function gatherConditions(n, cb) {
    var conditions = [];
    var returned = 0;
    var regulationCallback = function (response) {
      var condition = response.weather.condition;
      if (conditions.indexOf(condition) == -1) {
        conditions.push(condition);
      }

      returned++;
      if (returned == n) {
        console.log(conditions);
        cb();
      }
    };

    for (var i = 0; i < n; i++) {
      getRegulationsForPoint(
        [
          -73.997087 + 5 - (Math.random() * 10),
          40.728059 + 5 - (Math.random() * 10),
        ], regulationCallback);
    }
  }

  // gatherConditions(1000);

  function getDroneLocation() {
    return {
      type: 'Point',
      coordinates: [-73.997087 + Math.random() * 0.01, 40.728059 + Math.random() * 0.01],
    };
  }

  map.on('load', function () {
    // Add a source and layer displaying a point which will be animated in a circle.
    map.addSource('drone-source', {
      type: 'geojson',
      data: getDroneLocation(),
    });

    map.addLayer({
      id: 'drone',
      source: 'drone-source',
      type: 'circle',
      paint: {
        'circle-radius': 10,
        'circle-color': '#007cbf',
      },
    });

    // Update the drone location
    setInterval(function () {
      var location = getDroneLocation();
      map.getSource('drone-source').setData(location);
      getRegulationsForPoint(location.coordinates, function (response) {
        updateRegulations(response.nearest_advisories);
        updateWeather(response.weather);
        updateAltitudeRegulation();
      });
    }, 1500);
  });

});
<commit_msg>Added icon tooltips<commit_after>var Player = require('../../Broadway-master/Player/Player');
var io = require('socket.io-client');

$(function () {
  'use strict';

  var toUint8Array = function (parStr) {
    var raw = atob(parStr);
    var array = new Uint8Array(new ArrayBuffer(raw.length));

    Array.prototype.forEach.call(raw, function (data, index) {
      array[index] = raw.charCodeAt(index);
    });

    // console.log(array);
    return array;
  };

  var player = new Player({
    useWorker: true,
    workerFile: '/js/Decoder.js',
  });

  var videoWindow = document.getElementById('video-stream');

  // document.body.appendChild(player.canvas);
  videoWindow.appendChild(player.canvas);

  var socket = io('http://localhost:8000/');

  socket.on('data', function (data) {
    console.log('receiving video data...');
    player.decode(toUint8Array(data));
  });

  var toTitleCase = require('titlecase');
  var moment = require('moment');
  var _ = require('lodash');
  var MAX_ZOOM = 14.9;
  var ZOOM = MAX_ZOOM;
  var THEME = 'standard';

  var layers = {

    // Airport Airspaces
    airports_recreational: { icon: 'fa fa-plane' },

    // Cautions
    tfrs: { icon: 'fa fa-exclamation-triangle' },
    sua_prohibited: { icon: 'fa fa-user-secret' },
    sua_restricted: { icon: 'fa fa-fighter-jet' },
    noaa: { icon: 'fa fa-ship' },
    national_parks: { icon: 'fa fa-tree' },

    // Advisories
    hospitals: { icon: 'fa fa-ambulance' },
    parcels: { icon: 'fa fa-home' },
    power_plants: { icon: 'fa fa-bolt' },
    heliports: { icon: 'fa fa-h-square' },
    schools: { icon: 'fa fa-graduation-cap' },
  };

  var layerNames = {
    airports_commercial: 'Airports (Commercial Rules)',
    airports_commercial_private: 'Private Airports (Commercial Rules)',
    airports_recreational: 'Airports (Recreational Rules)',
    airports_recreational_private: 'Private Airports (Recreational Rules)',
    class_b: 'Controlled Airspace (Class B)',
    class_c: 'Controlled Airspace (Class C)',
    class_d: 'Controlled Airspace (Class D)',
    class_e0: 'Controlled Airspace (Class E to Ground)',
    heliports: 'Heliports',
    hospitals: 'Hospitals',
    national_parks: 'National Parks',
    noaa: 'NOAA Marine Protection Areas',
    parcels: 'Private Property',
    power_plants: 'Power Plants',
    prisons: 'Prisons',
    schools: 'Schools',
    stadiums: 'Stadiums',
    sua_prohibited: 'Prohibited Special Use Airspace',
    sua_restricted: 'Restricted Special Use Airspace',
    tfrs: 'Temporary Flight Restrictions',
  };

  var advisoryLevels = {
    red: 3,
    yellow: 2,
    green: 1,
  };

  var labelColors = {
    SAFE: 'success',
    CAUTION: 'warning',
    ADVISORY: 'danger',
  };

  var url = 'https://api.airmap.io/maps/v3/tilejson/' +
    Object.keys(layers).join() + '?apikey=' + AIRMAP_TOKEN +
    '&token=' + AIRMAP_TOKEN +
    '&theme=' + THEME;

  // Initialize the Mapbox map with Airmap overlayed on top
  mapboxgl.accessToken = MAPBOX_TOKEN;
  var map = new mapboxgl.Map({
    container: 'map', // container id
    style: url, //stylesheet location
    center: [-73.9957915, 40.7285429], // starting position
    zoom: MAX_ZOOM, // starting zoom
    maxZoom: ZOOM,
  });

  map.addControl(new mapboxgl.Geolocate());
  map.addControl(new mapboxgl.Navigation());

  var icons = {
    school: 'graduation-cap',
  };

  function buildPopupMarkup(layers) {
    var html = '';
    var grouped = _.groupBy(layers, function (p) {
      return p.type;
    });

    var keys = _.keys(grouped);
    for (var g in keys) {
      var group = keys[g];
      var groupHtml = "
" +
        layerNames[group] + '
';
      for (var i in grouped[group]) {
        var item = grouped[group][i];
        if (item.url) {
          groupHtml += "

" + item.name + '
';
        } else {
          groupHtml += "

" +
          item.name + '
';
        }

        if (item.phone) {
          groupHtml += "
" + item.phone + '
';
        }

        if (item.date_issued) {
          var start = moment(item.date_issued);
          var end = moment(item.date_expire);
          var now = moment();
          if (now.isBefore(start)) {
            groupHtml += "
Starts " + start.fromNow() + '
';
          } else {
            groupHtml += "
Started " + start.fromNow() + '
';
          }

          if (item.date_expire != '+010000-01-01T00:00:00.000Z') {
            groupHtml += "
Ends " +
              end.format('MMMM Do YYYY, h:mm:ss a') + '
';
          }
        }

        groupHtml += '

';
      }

      html += groupHtml;
    }

    return html;
  }

  // Code modified from the AirMap SDK
  function sdkHandleClick(data) {
    var map = data.target;
    var features = map.queryRenderedFeatures(data.point);
    var properties = _.map(features, function (feature) {
      return feature.properties;
    });

    properties = _.filter(properties, function (p) {
      return p.type &&
        (_.includes(Object.keys(layerNames), p.type.replace('layer_', '')) || p.type == 'tfrs');
    });

    if (properties.length > 0) {
      var markup = buildPopupMarkup(properties);
      var popups = document.getElementsByClassName('mapboxgl-popup-content');
      if (popups.length > 0) {
        var markupElement = document.createElement('div');
        markupElement.innerHTML = markup;
        popups[0].insertBefore(markupElement, popups[0].firstChild);
      } else {
        var tooltip = new mapboxgl.Popup({
          closeOnClick: true,
        }).setLngLat(data.lngLat).setHTML(markup);
        tooltip.addTo(map);
      }
    }
  }

  map.on('click', sdkHandleClick);

  function clearRegulations() {
    $('#regulations').empty();
  }

  function addRegulation(name, type, color) {
    // console.log(type);
    // console.log(layers[type]);
    var label = 'SAFE';
    if (color == 'red') {
      label = 'ADVISORY';
    } else if (color == 'yellow') {
      label = 'CAUTION';
    }

    var html = '
 ' +
      toTitleCase(name.toLowerCase()) + '' + label + '
';
    $('#regulations').append(html);
  }

  function updateRegulations(regulations) {
    clearRegulations();

    // Sort the regulations first
    var sortedRegulations = regulations.sort(function (a, b) {
      if (a.advisory_level != b.advisory_level) {
        return (advisoryLevels[a.advisory_level] >
          advisoryLevels[b.advisory_level] ? -1 : 1);
      } else {
        if (a.name == b.name) return 0;
        return (a.name < b.name ? -1 : 1);
      }
    });

    sortedRegulations.forEach(function (regulation) {
      var color = regulation.advisory_level;
      var type = regulation.type;
      var name = regulation.name;
      addRegulation(name, type, color);
    });
  }

  function clearWeather() {
    $('#weather').empty();
  }

  function addWeather(weather, icon, label, tooltip) {
    var html = '
 ' +
      weather + '' + label + '
';
    $('#weather').append(html);
  }

  function getConditionIcon(condition) {
    condition = condition.toLowerCase();
    if (condition.indexOf('snow') > -1) { // Snowy
      return 'wi wi-day-snow';
    } else if (condition.indexOf('rain') > -1 ||
      condition.indexOf('showers') > -1) {
      return 'wi wi-day-rain';
    } else if (condition.indexOf('cloudy') > -1) { // Cloudy
      return 'wi wi-day-cloudy';
    } else { // Sunny
      return 'wi wi-day-sunny';
    }
  }

  function updateWeather(weather) {
    clearWeather();

    // Add each row with the weather report
    var condition = weather.condition;
    var conditionIcon = getConditionIcon(condition);
    var conditionLabel = 'SAFE';
    var conditionLower = condition.toLowerCase();
    if (conditionLower.indexOf('wind') > -1 ||
      conditionLower.indexOf('rain') > -1 ||
      conditionLower.indexOf('snow') > -1 ||
      conditionLower.indexOf('showers') > -1) conditionLabel = 'CAUTION';
    addWeather(condition, conditionIcon, conditionLabel, 'Weather Condition');

    var visibility = weather.visibility * 0.621371;
    var visLabel = (visibility < 2 ? 'ADVISORY' :
      (visibility < 5) ? 'CAUTION' : 'SAFE');
    addWeather((visibility.toFixed(2)) + ' mi', 'fa fa-eye', visLabel, 'Visibility');

    var precipitation = weather.precipitation * 100;
    var precLabel = (precipitation > 50 ? 'ADVISORY' :
      (precipitation > 20) ? 'CAUTION' : 'SAFE');
    addWeather((precipitation) + ' %', 'wi wi-raindrop', precLabel, 'Chance of Precipitation');

    var temperature = (weather.temperature * 9 / 5 + 32);
    var tempLabel = (temperature < 10 || temperature > 100 ? 'ADVISORY' :
      (temperature < 20 || temperature > 90) ? 'CAUTION' : 'SAFE');
    addWeather((temperature) + ' °F', 'wi wi-thermometer-exterior', tempLabel, 'Temperature');

    var windspeed = weather.wind.speed * 0.621371;
    var speedLabel = (windspeed > 15 ? 'ADVISORY' :
      (windspeed > 10) ? 'CAUTION' : 'SAFE');
    addWeather((windspeed.toFixed(2)) + ' mph', 'wi wi-windy', speedLabel, 'Wind Speed');

    var gusts = weather.wind.gusting * 0.621371;
    var gustsLabel = (gusts > 25 ? 'ADVISORY' :
      (gusts > 15) ? 'CAUTION' : 'SAFE');
    addWeather((gusts.toFixed(2)) + ' mph', 'wi wi-strong-wind', gustsLabel, 'Wind Gust Speed');
  }

  function getDroneAltitude() {
    return 100 + Math.random() * 10;
  }

  function updateAltitudeRegulation() {
    var currentAltitude = getDroneAltitude();
    var label = (currentAltitude >= 400 ? 'ADVISORY' :
      (currentAltitude >= 250 ? 'CAUTION' : 'SAFE'));

    var html = '
 ' +
      currentAltitude.toFixed(2) + ' ft' + label + '
';
    $('#altregulation').empty();
    $('#altregulation').append(html);
  }

  // point = [long, lat]
  function getRegulationsForPoint(point, cb) {
    var longitude = point[0];
    var latitude = point[1];
    var url = 'https://api.airmap.io/data/v1/status?unique_id=drone_regulations' +
      '&latitude=' + latitude + '&longitude=' + longitude + '&weather=true' +
      '&apikey=' + AIRMAP_TOKEN + '&types=' + Object.keys(layers).join();
    $.ajax({
      method: 'GET',
      url: url,
      success: cb,
    });
  }

  // Data to determine what weather conditions are available from the API
  // Here is what I gathered from running it 1000 times:
  // ["Sunny", "Partly Cloudy/Wind", "Mostly Cloudy", "Snow Showers/Wind",
  // "Mostly Cloudy/Wind", "Sunny/Wind", "Cloudy/Wind", "Cloudy", "Showers/Wind",
  //  "Partly Cloudy", "Rain/Snow/Wind", "Light Rain/Wind", "Few Showers/Wind",
  // "Few Snow Showers/Wind", "Mostly Sunny/Wind", "Snow/Wind", "Mostly Sunny",
  // "Snow", "Snow Showers", "Rain/Snow Showers/Wind", "Light Snow/Wind",
  // "Heavy Snow/Wind", "Few Snow Showers", "Rain/Wind"]
  function gatherConditions(n, cb) {
    var conditions = [];
    var returned = 0;
    var regulationCallback = function (response) {
      var condition = response.weather.condition;
      if (conditions.indexOf(condition) == -1) {
        conditions.push(condition);
      }

      returned++;
      if (returned == n) {
        console.log(conditions);
        cb();
      }
    };

    for (var i = 0; i < n; i++) {
      getRegulationsForPoint(
        [
          -73.997087 + 5 - (Math.random() * 10),
          40.728059 + 5 - (Math.random() * 10),
        ], regulationCallback);
    }
  }

  // gatherConditions(1000);

  function getDroneLocation() {
    return {
      type: 'Point',
      coordinates: [-73.997087 + Math.random() * 0.01, 40.728059 + Math.random() * 0.01],
    };
  }

  map.on('load', function () {
    // Add a source and layer displaying a point which will be animated in a circle.
    map.addSource('drone-source', {
      type: 'geojson',
      data: getDroneLocation(),
    });

    map.addLayer({
      id: 'drone',
      source: 'drone-source',
      type: 'circle',
      paint: {
        'circle-radius': 10,
        'circle-color': '#007cbf',
      },
    });

    // Update the drone location
    setInterval(function () {
      var location = getDroneLocation();
      map.getSource('drone-source').setData(location);
      getRegulationsForPoint(location.coordinates, function (response) {
        updateRegulations(response.nearest_advisories);
        updateWeather(response.weather);
        updateAltitudeRegulation();
        $('i').tooltip({
          animation: false,
          placement: 'left',
        });
      });
    }, 1500);
  });

});
<|endoftext|>


<commit_before>// Get the HERE Maps platform object
var platform = new H.service.Platform({
  'app_id': window.HERE_APP_ID,
  'app_code': window.HERE_APP_CODE
});

// Initialize the map
var mapContainerEl = document.getElementById('mapContainer');
var defaultLayers = platform.createDefaultLayers();
var map = new H.Map(mapContainerEl, defaultLayers.terrain.map,
  { zoom: 10, /*center: { lat: 52.51, lng: 13.4 },*/ imprint: null });
var ui = H.ui.UI.createDefault(map, defaultLayers, 'fi-FI');
var mapEvents = new H.mapevents.MapEvents(map);
var behavior = new H.mapevents.Behavior(mapEvents);

// Create the parameters for the routing request:
var routingParameters = {
  // The routing mode:
  'mode': 'fastest;car',
  // The start point of the route:
  'waypoint0': 'geo!50.1120423728813,8.68340740740811',
  // The end point of the route:
  'waypoint1': 'geo!52.5309916298853,13.3846220493377',
  // To retrieve the shape of the route we choose the route
  // representation mode 'display'
  'representation': 'display'
};

// Define a callback function to process the routing response:
var onResult = function(result) {
  var route,
    routeShape,
    startPoint,
    endPoint,
    strip;
  if(result.response.route) {
    // Pick the first route from the response:
    route = result.response.route[0];
    // Pick the route's shape:
    routeShape = route.shape;

    // Create a strip to use as a point source for the route line
    strip = new H.geo.Strip();

    // Push all the points in the shape into the strip:
    routeShape.forEach(function(point) {
      var parts = point.split(',');
      strip.pushLatLngAlt(parts[0], parts[1]);
    });

    // Retrieve the mapped positions of the requested waypoints:
    startPoint = route.waypoint[0].mappedPosition;
    endPoint = route.waypoint[1].mappedPosition;

    // Create a polyline to display the route:
    var routeLine = new H.map.Polyline(strip, {
      style: { strokeColor: 'blue', lineWidth: 10 }
    });

    // Add the route polyline and the two markers to the map:
    map.addObjects([routeLine]);
  }
};

function addMarkerAndBubble(lat, lon, text) {
  var coords = { lat: lat, lng: lon };
  map.addObject(new H.map.Marker(coords));
  ui.addBubble(new H.ui.InfoBubble(coords,
    { content: '' + text + '' }));
}

var crucialPoints = new H.geo.Strip();
function addMarkerForCrucialPoint(lat, lon, text) {
  addMarkerAndBubble(lat, lon, text);
  crucialPoints.pushLatLngAlt(lat, lon);
}

// Add known start and end locations as well as my current location
var lastEntry = window.locationEntries[window.locationEntries.length - 1];
//addMarkerForCrucialPoint(lastEntry.latitude, lastEntry.longitude, lastEntry.place);
addMarkerForCrucialPoint(42.605556, -5.57, 'Temp place (Leon)'); // TEMP
addMarkerForCrucialPoint(43.165, -1.2356, 'Saint-Jean-Pied-de-Port');
addMarkerForCrucialPoint(42.877778, -8.544444, 'Santiago de Compostela');

// Center map to those crucial points
function recenterMap() {
  map.getViewPort().resize();
  map.setViewBounds(crucialPoints.getBounds());
  map.setCenter(crucialPoints.getBounds().getCenter());
}
recenterMap();
// Redraw the map if the container resizes
window.addEventListener('resize', recenterMap);

/*
// Get an instance of the routing service:
var router = platform.getRoutingService();

// Call calculateRoute() with the routing parameters,
// the callback and an error callback function (called if a
// communication error occurs):
router.calculateRoute(routingParameters, onResult,
  function(error) {
    alert(error.message);
  });
*/<commit_msg>Add French Route to the map<commit_after>// Get the HERE Maps platform object
var platform = new H.service.Platform({
  'app_id': window.HERE_APP_ID,
  'app_code': window.HERE_APP_CODE
});

// Initialize the map
var mapContainerEl = document.getElementById('mapContainer');
var defaultLayers = platform.createDefaultLayers();
var map = new H.Map(mapContainerEl, defaultLayers.terrain.map,
  { zoom: 10, /*center: { lat: 52.51, lng: 13.4 },*/ imprint: null });
var ui = H.ui.UI.createDefault(map, defaultLayers, 'fi-FI');
var mapEvents = new H.mapevents.MapEvents(map);
var behavior = new H.mapevents.Behavior(mapEvents);

// Create the parameters for the routing request:
var routingParameters = {
  // The routing mode:
  'mode': 'fastest;car',
  // The start point of the route:
  'waypoint0': 'geo!50.1120423728813,8.68340740740811',
  // The end point of the route:
  'waypoint1': 'geo!52.5309916298853,13.3846220493377',
  // To retrieve the shape of the route we choose the route
  // representation mode 'display'
  'representation': 'display'
};

var frenchWayRoutingParameters = {
  'mode': 'fastest;pedestrian;traffic:disabled;motorway:-2',
  'representation': 'display',

  'waypoint0': 'geo!43.165,-1.2356',
  'waypoint1': 'geo!43,-1.316667',
  'waypoint2': 'geo!42.93113,-1.50437',
  'waypoint3': 'geo!42.816667,-1.65',
  'waypoint4': 'geo!42.671117,-1.818736',
  'waypoint5': 'geo!42.666667,-2.016667',
  'waypoint6': 'geo!42.5683,-2.19218',
  'waypoint7': 'geo!42.465,-2.445556',
  'waypoint8': 'geo!42.416667,-2.733333',
  'waypoint9': 'geo!42.441944,-2.9525',
  'waypoint10': 'geo!42.41884,-3.19125',
  'waypoint11': 'geo!42.37576,-3.43634',
  'waypoint12': 'geo!42.35,-3.706667',
  'waypoint13': 'geo!42.287778,-4.138889',
  'waypoint14': 'geo!42.26786,-4.40502',
  'waypoint15': 'geo!42.333333,-4.6',
  'waypoint16': 'geo!42.371944,-5.030278',
  'waypoint17': 'geo!42.422778,-5.221389',
  'waypoint18': 'geo!42.605556,-5.57',
  'waypoint19': 'geo!42.51721,-5.76632',
  'waypoint20': 'geo!42.458889,-6.063333',
  'waypoint21': 'geo!42.48084,-6.28398',
  'waypoint22': 'geo!42.55,-6.583333',
  'waypoint23': 'geo!42.6075,-6.8075',
  'waypoint24': 'geo!42.72599,-7.01938',
  'waypoint25': 'geo!42.7802,-7.41375',
  'waypoint26': 'geo!42.80623,-7.61817',
  'waypoint27': 'geo!42.873611,-7.869444',
  'waypoint28': 'geo!42.92676,-8.16359',
  'waypoint29': 'geo!42.877778,-8.544444'
};

// Define a callback function to process the routing response:
var onResult = function(result) {
  var route,
    routeShape,
    startPoint,
    endPoint,
    strip;
  if(result.response.route) {
    // Pick the first route from the response:
    route = result.response.route[0];
    // Pick the route's shape:
    routeShape = route.shape;

    // Create a strip to use as a point source for the route line
    strip = new H.geo.Strip();

    // Push all the points in the shape into the strip:
    routeShape.forEach(function(point) {
      var parts = point.split(',');
      strip.pushLatLngAlt(parts[0], parts[1]);
    });

    // Retrieve the mapped positions of the requested waypoints:
    startPoint = route.waypoint[0].mappedPosition;
    endPoint = route.waypoint[1].mappedPosition;

    // Create a polyline to display the route:
    var routeLine = new H.map.Polyline(strip, {
      style: { strokeColor: 'yellow', lineWidth: 2 }
    });

    // Add the route polyline and the two markers to the map:
    map.addObjects([routeLine]);
  }
};

function addMarkerAndBubble(lat, lon, text) {
  var coords = { lat: lat, lng: lon };
  map.addObject(new H.map.Marker(coords));
  ui.addBubble(new H.ui.InfoBubble(coords,
    { content: '' + text + '' }));
}

var crucialPoints = new H.geo.Strip();
function addMarkerForCrucialPoint(lat, lon, text) {
  addMarkerAndBubble(lat, lon, text);
  crucialPoints.pushLatLngAlt(lat, lon);
}

// Add known start and end locations as well as my current location
var lastEntry = window.locationEntries[window.locationEntries.length - 1];
//addMarkerForCrucialPoint(lastEntry.latitude, lastEntry.longitude, lastEntry.place);
addMarkerForCrucialPoint(42.605556, -5.57, 'Temp place (Leon)'); // TEMP
addMarkerForCrucialPoint(43.165, -1.2356, 'Saint-Jean-Pied-de-Port');
addMarkerForCrucialPoint(42.877778, -8.544444, 'Santiago de Compostela');

// Center map to those crucial points
function recenterMap() {
  map.getViewPort().resize();
  map.setViewBounds(crucialPoints.getBounds());
  map.setCenter(crucialPoints.getBounds().getCenter());
}
recenterMap();
// Redraw the map if the container resizes
window.addEventListener('resize', recenterMap);

// Get an instance of the routing service:
var router = platform.getRoutingService();

// Call calculateRoute() with the routing parameters,
// the callback and an error callback function (called if a
// communication error occurs):
router.calculateRoute(frenchWayRoutingParameters, onResult,
  function(error) {
    alert(error.message);
  });
<|endoftext|>


<commit_before>/**
 * main.js assumes the core libs (which should be present on page render), like
 * jQuery and Bootstrap.js, have been loaded already (in application.js)
 */
//= require jquery.timeago
//= require jquery.bootstrap-growl
//= require bootstrap.tags
//= require bootstrap.select
//= require bootstrap.typeahead
//= require form-validator

$(function() {

  // Handle modal & dropdown links for graceful no-js support
  $('a[data-toggle="modal"]').attr('href', "#");
  $('a[data-toggle="dropdown"]').attr('href', "#");

  // Make modal dialogs inside a modal-draggable class draggable
  $(".modal-draggable .modal-dialog").draggable({
      handle: ".modal-header"
  });

  // Process timeago and make timeago elements visible
  // NB: They are hidden on load if the html element has the class "js"
  $("time.timeago").timeago();
  $("time.timeago").css({ visibility: 'visible' });

  // Handle displaying flash alerts on a page as Growl style alerts on load
  $("#flash-messages .alert").each(function() {
    var type = 'info';
    if ($(this).hasClass('alert-success'))
      type = 'success';
    if ($(this).hasClass('alert-warning'))
      type = 'warning';
    if ($(this).hasClass('alert-danger'))
      type = 'danger';

    $.bootstrapGrowl( $(this).html(), {
      type: type,
      align: 'center',
      width: 'auto',
      allow_dismiss: false,
      offset: {
        from: "top",
        amount: $(".navbar").height() + 10
      }
    });
  });

  // Handle keyboard shortcuts
  $(document).keyup(function(e) {
      // Ignore any shortcut keys if the Ctrl modifier key was depressed
      // e.g. So that the "C" key doesn't trigger a dialog when someone
      // uses "Ctrl+C" to copy text on the page!
      if (e.ctrlKey)
          return;

      if (e.keyCode === 27)// 27 = 'ESC' key
        return;

      // Check to see if an element has focus (if so, return)
      if ($("input:focus") != null && $("input:focus").length > 0)
          return;

      if ($("textarea:focus") != null && $("textarea:focus").length > 0)
          return;

      if ($("select:focus") != null && $("select:focus").length > 0)
          return;

      // Escape button behaviour
      if (e.keyCode === 67 || e.keyCode === 78) { // 67 = 'c' key, 78 = 'n' key
        $('*[data-shortcut-key="n"]')[0].click();
      } else if (e.keyCode === 69) { // The 'e' key (for edit).
        $('*[data-shortcut-key="e"]')[0].click();
      } else if (e.keyCode === 83) { // The 's' key (for search).
        $('#search').focus();
      } else if (e.keyCode === 80) { // The 'p' key (for profile).
        $('*[data-shortcut-key="p"]')[0].click();
      } else if (e.keyCode === 72) { // The 'h' key (for home).
        $('*[data-shortcut-key="h"]')[0].click();
      }

      return;
  });

  // Control for the typeahead input for the search box
  // @todo Hook into the render event to improve how results are displayed
  $('#search').typeahead({
    displayField: 'title',
    valueField: 'url',
    onSelect: function(item) {
      window.location = item.value;
    },
    ajax: {
      url: '/posts/search',
      preDispatch: function(query) {
        return {
            q: query
        };
      },
      preProcess: function(data) {
        return data.posts;
      }
    }
  });

  // Shim to make any element with a valid "href" value clickable
  // (This exists under standards like XHTML 2.0 but not natively in browsers.)
  $("*[href]").bind('touch click', function() {
    if ($(this).attr('href') != "" && !$(this).attr('href').match(/^#/))
      window.location = $(this).attr('href');
  });

  // Make textarea's with the 'autoresize' class shink/grow based con content
  $(document).on('keyup', 'textarea.autoresize', function() {
    resizeTextarea($(this));
  });
  // Trigger textarea resizing (to correct size to fit content) on page load
  $('textarea.autoresize').each(function() {
    resizeTextarea($(this));
  });
  // Trigger resizing of textarea elements inside modals when modals are shown
  $('.modal').on('shown.bs.modal', function() {
    $('textarea.autoresize', $(this)).each(function() {
      resizeTextarea($(this));
    });
  });

  // @todo refactor out to seperate JS file
  // Make the entire form containing the voting buttons clickable for better UX
  $(document).on('submit', 'form.btn-vote', function(event) {
    // Prevent the form from submitting normally
    event.preventDefault();
    return false;
  });
  $("form[data-upvote]").bind('touchstart click', function() {
    upvote(this);
  });
  $("form[data-downvote]").bind('touchstart click', function() {
    downvote(this);
  });

});

function upvote(form) {
  var postId = $(form).attr('data-upvote');
  $.post(
    $(form).attr("action"),
    $(form).serialize(),
    function(response) {
      if ($(form).hasClass("btn-default")) {
        $('form[data-upvote="'+postId+'"]').each(function() {
          $(this).removeClass("btn-default").addClass("btn-success");
          $(".btn", this).removeClass("text-default").addClass("text-white");
          this.action = this.action.replace(/upvote/, 'unvote');

          // On upvote, set any "active" downvote buttons to a neutral state
          $('form[data-downvote="'+postId+'"]').each(function() {
            if (!$(this).hasClass("btn-default")) {
              $(this).removeClass("btn-danger").addClass("btn-default");
              $(".btn", this).removeClass("text-white").addClass("text-default");
              this.action = this.action.replace(/unvote/, 'downvote');
            };
          });
        });
      } else {
        $('form[data-upvote="'+postId+'"]').each(function() {
          $(this).removeClass("btn-success").addClass("btn-default");
          $(".btn", this).removeClass("text-white").addClass("text-default");
          this.action = this.action.replace(/unvote/, 'upvote');
        });
      }
      $('*[data-score="'+postId+'"]').each(function() {
        $(this).html(response.score);
      });
    }
  );
};

function downvote(form) {
  var postId = $(form).attr('data-downvote');
  $.post(
    $(form).attr("action"),
    $(form).serialize(),
    function(response) {
      if ($(form).hasClass("btn-default")) {
        $('form[data-downvote="'+postId+'"]').each(function() {
          $(this).removeClass("btn-default").addClass("btn-danger");
          $(".btn", this).removeClass("text-default").addClass("text-white");
          this.action = this.action.replace(/downvote/, 'unvote');
        });

        // On downvote, set any "active" upvote buttons to a neutral state
        $('form[data-upvote="'+postId+'"]').each(function() {
          if (!$(this).hasClass("btn-default")) {
            $(this).removeClass("btn-success").addClass("btn-default");
            $(".btn", this).removeClass("text-white").addClass("text-default");
            this.action = this.action.replace(/unvote/, 'upvote');
          }
        });
      } else {
        $('form[data-downvote="'+postId+'"]').each(function() {
          $(this).removeClass("btn-danger").addClass("btn-default");
          $(".btn", this).removeClass("text-white").addClass("text-default");
          this.action = this.action.replace(/unvote/, 'downvote');
        });
      }
      $('*[data-score="'+postId+'"]').each(function() {
        $(this).html(response.score);
      });
    }
  );
};

function checkIfAnyInputElementHasFocus() {
  var focusedInputs = $("input:focus");
  if (focusedInputs != null && focusedInputs.length > 0)
      return true;

  var focusedInputs = $("textarea:focus");
  if (focusedInputs != null && focusedInputs.length > 0)
      return true;

  var focusedInputs = $("select:focus");
  if (focusedInputs != null && focusedInputs.length > 0)
      return true;

  return false;
}

function resizeTextarea(textarea) {
  var s = $(window).scrollTop();
  if (textarea.parents(".modal").length)
    s = $(textarea.parents(".modal")[0]).scrollTop();

  if (!textarea.data('height'))
    textarea.data('height',textarea.height());

  textarea.height( textarea.data('height') ).height(textarea[0].scrollHeight);

  if (textarea.parents(".modal").length) {
      $(textarea.parents(".modal")[0]).scrollTop(s);
  } else if (s > 0) {
      $(window).scrollTop(s);
  }
}<commit_msg>Changing vote btn to bind on touch<commit_after>/**
 * main.js assumes the core libs (which should be present on page render), like
 * jQuery and Bootstrap.js, have been loaded already (in application.js)
 */
//= require jquery.timeago
//= require jquery.bootstrap-growl
//= require bootstrap.tags
//= require bootstrap.select
//= require bootstrap.typeahead
//= require form-validator

$(function() {

  // Handle modal & dropdown links for graceful no-js support
  $('a[data-toggle="modal"]').attr('href', "#");
  $('a[data-toggle="dropdown"]').attr('href', "#");

  // Make modal dialogs inside a modal-draggable class draggable
  $(".modal-draggable .modal-dialog").draggable({
      handle: ".modal-header"
  });

  // Process timeago and make timeago elements visible
  // NB: They are hidden on load if the html element has the class "js"
  $("time.timeago").timeago();
  $("time.timeago").css({ visibility: 'visible' });

  // Handle displaying flash alerts on a page as Growl style alerts on load
  $("#flash-messages .alert").each(function() {
    var type = 'info';
    if ($(this).hasClass('alert-success'))
      type = 'success';
    if ($(this).hasClass('alert-warning'))
      type = 'warning';
    if ($(this).hasClass('alert-danger'))
      type = 'danger';

    $.bootstrapGrowl( $(this).html(), {
      type: type,
      align: 'center',
      width: 'auto',
      allow_dismiss: false,
      offset: {
        from: "top",
        amount: $(".navbar").height() + 10
      }
    });
  });

  // Handle keyboard shortcuts
  $(document).keyup(function(e) {
      // Ignore any shortcut keys if the Ctrl modifier key was depressed
      // e.g. So that the "C" key doesn't trigger a dialog when someone
      // uses "Ctrl+C" to copy text on the page!
      if (e.ctrlKey)
          return;

      if (e.keyCode === 27)// 27 = 'ESC' key
        return;

      // Check to see if an element has focus (if so, return)
      if ($("input:focus") != null && $("input:focus").length > 0)
          return;

      if ($("textarea:focus") != null && $("textarea:focus").length > 0)
          return;

      if ($("select:focus") != null && $("select:focus").length > 0)
          return;

      // Escape button behaviour
      if (e.keyCode === 67 || e.keyCode === 78) { // 67 = 'c' key, 78 = 'n' key
        $('*[data-shortcut-key="n"]')[0].click();
      } else if (e.keyCode === 69) { // The 'e' key (for edit).
        $('*[data-shortcut-key="e"]')[0].click();
      } else if (e.keyCode === 83) { // The 's' key (for search).
        $('#search').focus();
      } else if (e.keyCode === 80) { // The 'p' key (for profile).
        $('*[data-shortcut-key="p"]')[0].click();
      } else if (e.keyCode === 72) { // The 'h' key (for home).
        $('*[data-shortcut-key="h"]')[0].click();
      }

      return;
  });

  // Control for the typeahead input for the search box
  // @todo Hook into the render event to improve how results are displayed
  $('#search').typeahead({
    displayField: 'title',
    valueField: 'url',
    onSelect: function(item) {
      window.location = item.value;
    },
    ajax: {
      url: '/posts/search',
      preDispatch: function(query) {
        return {
            q: query
        };
      },
      preProcess: function(data) {
        return data.posts;
      }
    }
  });

  // Shim to make any element with a valid "href" value clickable
  // (This exists under standards like XHTML 2.0 but not natively in browsers.)
  $("*[href]").bind('touch click', function() {
    if ($(this).attr('href') != "" && !$(this).attr('href').match(/^#/))
      window.location = $(this).attr('href');
  });

  // Make textarea's with the 'autoresize' class shink/grow based con content
  $(document).on('keyup', 'textarea.autoresize', function() {
    resizeTextarea($(this));
  });
  // Trigger textarea resizing (to correct size to fit content) on page load
  $('textarea.autoresize').each(function() {
    resizeTextarea($(this));
  });
  // Trigger resizing of textarea elements inside modals when modals are shown
  $('.modal').on('shown.bs.modal', function() {
    $('textarea.autoresize', $(this)).each(function() {
      resizeTextarea($(this));
    });
  });

  // @todo refactor out to seperate JS file
  // Make the entire form containing the voting buttons clickable for better UX
  $(document).on('submit', 'form.btn-vote', function(event) {
    // Prevent the form from submitting normally
    event.preventDefault();
    return false;
  });
  $("form[data-upvote]").bind('touch click', function() {
    upvote(this);
  });
  $("form[data-downvote]").bind('touch click', function() {
    downvote(this);
  });

});

function upvote(form) {
  var postId = $(form).attr('data-upvote');
  $.post(
    $(form).attr("action"),
    $(form).serialize(),
    function(response) {
      if ($(form).hasClass("btn-default")) {
        $('form[data-upvote="'+postId+'"]').each(function() {
          $(this).removeClass("btn-default").addClass("btn-success");
          $(".btn", this).removeClass("text-default").addClass("text-white");
          this.action = this.action.replace(/upvote/, 'unvote');

          // On upvote, set any "active" downvote buttons to a neutral state
          $('form[data-downvote="'+postId+'"]').each(function() {
            if (!$(this).hasClass("btn-default")) {
              $(this).removeClass("btn-danger").addClass("btn-default");
              $(".btn", this).removeClass("text-white").addClass("text-default");
              this.action = this.action.replace(/unvote/, 'downvote');
            };
          });
        });
      } else {
        $('form[data-upvote="'+postId+'"]').each(function() {
          $(this).removeClass("btn-success").addClass("btn-default");
          $(".btn", this).removeClass("text-white").addClass("text-default");
          this.action = this.action.replace(/unvote/, 'upvote');
        });
      }
      $('*[data-score="'+postId+'"]').each(function() {
        $(this).html(response.score);
      });
    }
  );
};

function downvote(form) {
  var postId = $(form).attr('data-downvote');
  $.post(
    $(form).attr("action"),
    $(form).serialize(),
    function(response) {
      if ($(form).hasClass("btn-default")) {
        $('form[data-downvote="'+postId+'"]').each(function() {
          $(this).removeClass("btn-default").addClass("btn-danger");
          $(".btn", this).removeClass("text-default").addClass("text-white");
          this.action = this.action.replace(/downvote/, 'unvote');
        });

        // On downvote, set any "active" upvote buttons to a neutral state
        $('form[data-upvote="'+postId+'"]').each(function() {
          if (!$(this).hasClass("btn-default")) {
            $(this).removeClass("btn-success").addClass("btn-default");
            $(".btn", this).removeClass("text-white").addClass("text-default");
            this.action = this.action.replace(/unvote/, 'upvote');
          }
        });
      } else {
        $('form[data-downvote="'+postId+'"]').each(function() {
          $(this).removeClass("btn-danger").addClass("btn-default");
          $(".btn", this).removeClass("text-white").addClass("text-default");
          this.action = this.action.replace(/unvote/, 'downvote');
        });
      }
      $('*[data-score="'+postId+'"]').each(function() {
        $(this).html(response.score);
      });
    }
  );
};

function checkIfAnyInputElementHasFocus() {
  var focusedInputs = $("input:focus");
  if (focusedInputs != null && focusedInputs.length > 0)
      return true;

  var focusedInputs = $("textarea:focus");
  if (focusedInputs != null && focusedInputs.length > 0)
      return true;

  var focusedInputs = $("select:focus");
  if (focusedInputs != null && focusedInputs.length > 0)
      return true;

  return false;
}

function resizeTextarea(textarea) {
  var s = $(window).scrollTop();
  if (textarea.parents(".modal").length)
    s = $(textarea.parents(".modal")[0]).scrollTop();

  if (!textarea.data('height'))
    textarea.data('height',textarea.height());

  textarea.height( textarea.data('height') ).height(textarea[0].scrollHeight);

  if (textarea.parents(".modal").length) {
      $(textarea.parents(".modal")[0]).scrollTop(s);
  } else if (s > 0) {
      $(window).scrollTop(s);
  }
}<|endoftext|>


<commit_before>"use strict";

var ol = window.onload;
window.onload = function() {
    ol();
    var tractor = new Tractor(
        new Point(40, 25), // center
        new Size(60, 40), // size
        10000, // weight
        new Vector(0, 0), // velocity
        new Vector(0, 0) // orientation
    );
    addTractor(tractor, 'fill:blue;stroke:rgb(0,0,200);stroke-width:2;');
    tractor.accelerator.setValue(1.0);
    scene = new BodyFollowScene(tractor.body);

    setInterval(tick, 20);
}
<commit_msg>Proof-of-concept: I can write a proportional controller<commit_after>"use strict";

var ol = window.onload;
window.onload = function() {
    ol();
    var tractor = new Tractor(
        new Point(40, 25), // center
        new Size(60, 40), // size
        10000, // weight
        new Vector(0, 0), // velocity
        new Vector(0, 0) // orientation
    );
    addTractor(tractor, 'fill:blue;stroke:rgb(0,0,200);stroke-width:2;');
    scene = new BodyFollowScene(tractor.body);

    setInterval(function() {
        if(tractor.body.center.x > 500 + tractor.body.size.x/2) {
            // coast to a stop
            tractor.accelerator.setValue(0);
        } else {
            // proportional control
            var tV = 1.0;
            var error = (tV - tractor.body.velocity.x) / tV;
            tractor.accelerator.setValue(error*10);
        }
    }, 20);
    setInterval(tick, 20);
}
<|endoftext|>


<commit_before>$(document).ready(function() {
    var socket = io();
    var gender;
    $('#gender').material_select();

    $("#go1").click(function() {
        socket.emit("getFoodSuggestions", { food: $("#food").val() });
        gender = $("#gender").val();
    });

    socket.on("suggestions", function(packet) {
        for (i in packet.output) {
            $("#more-options").fadeIn("slow");
            $("#foods").append('<option value="' + packet.output[i][2] + '">' + packet.output[i][1] + "</option>");
        }
        $("#foods").material_select();
        $("#go1").off("click");
        $("#go1").click(function() {
            var age = $("#age").val();
            var weight = $("#weight").val();
            if (age == "") age = 42;
            if (weight == "") weight = 184;
            socket.emit("getFood", { food: $("#foods").val(), gender: $("#gender").val(), age: age, weight: weight });
        });
    });

    socket.on("receiveCalories", function(packet) {
        $("main > .container > .results").append('

Run

' + Math.floor(packet.running * 60) + ' minutes

');
        $("main > .container > .results").append('

Walk

' + Math.floor(packet.walking * 60) + ' minutes

');
        $("main > .container > .results").append('

Swim

' + Math.floor(packet.swimming * 60) + ' minutes

');
        $(".results").slideDown("slow");
        $("#go1").fadeOut("slow", function(){
            $(this).html("Try another").off("click").on("click", function(){
                window.location.reload(false);
            }).fadeIn("slow");
        });
    });
});
<commit_msg>Include Twitter 'intent' button for future use<commit_after>$(document).ready(function() {
    var socket = io();
    var gender;
    $('#gender').material_select();

    $("#go1").click(function() {
        socket.emit("getFoodSuggestions", { food: $("#food").val() });
        gender = $("#gender").val();
    });

    socket.on("suggestions", function(packet) {
        for (i in packet.output) {
            $("#more-options").fadeIn("slow");
            $("#foods").append('<option value="' + packet.output[i][2] + '">' + packet.output[i][1] + "</option>");
        }
        $("#foods").material_select();
        $("#go1").off("click");
        $("#go1").click(function() {
            var age = $("#age").val();
            var weight = $("#weight").val();
            if (age == "") age = 42;
            if (weight == "") weight = 184;
            socket.emit("getFood", { food: $("#foods").val(), gender: $("#gender").val(), age: age, weight: weight });
        });
    });

    socket.on("receiveCalories", function(packet) {
//        $("#share-results").attr("href", "https://twitter.com/intent/tweet?text=I%20just%20used%20%40SubjectRefresh%27s%20%23healthy%20app!%20Try%20it%20out%20at%20http%3A%2F%2Ffood.subjectrefresh.info!");
        $("main > .container > .results").append('

Run

' + Math.floor(packet.running * 60) + ' minutes

');
        $("main > .container > .results").append('

Walk

' + Math.floor(packet.walking * 60) + ' minutes

');
        $("main > .container > .results").append('

Swim

' + Math.floor(packet.swimming * 60) + ' minutes

');
        $(".results").slideDown("slow");
        $("#go1").fadeOut("slow", function(){
            $(this).html("Try another").off("click").on("click", function(){
                window.location.reload(false);
            }).fadeIn("slow");
        });
    });
});
<|endoftext|>


<commit_before>$(main);

function main() {
    $.getJSON('./data.json', function(d) {
        var data = attachColors(groupData(d));
        var state = initializeState(data);
        var update = updateAll.bind(undefined, $('.content.row'), data, state);
        var updateWithRoute, router;

        $(window).on('resize', update);

        router = initializeRouter(state, update);

        updateWithRoute = union(update, updateRoute.bind(undefined, state, router));

        createCdns($('.cdns'), state, data, updateWithRoute);
        createControls($('.allControlsContainer'), state, updateWithRoute);

        initializeControls(state);
    });

    function union() {
        var args = arguments;

        return function() {
            for(var i = 0; i < args.length; i++) {
                args[i]();
            }
        }
    }

    function initializeRouter(state, update) {
        var Router = Backbone.Router.extend({
            routes: {
                ':providers': 'providers',
                ':providers/:type': 'providersWithType',
                ':providers/:type/:amount': 'all'
            },
            providers: function(providers) {
                state.providers = providers.split(',');

                update();
            },
            providersWithType: function(providers, type) {
                state.type = type;

                this.providers(providers);
            },
            all: function(providers, type, amount) {
                state.amount = amount;

                this.providersWithType(providers, type);
            }
        });
        var router = new Router();

        Backbone.history.start();

        return router;
    }

    function updateRoute(state, router) {
        var providers = state.providers.toString();
        var type = state.type;
        var amount = state.amount;

        router.navigate('/' + providers + '/' + type + '/' + amount, {replace: true});
    }

    function initializeState(data) {
        return {
            type: '',
            amount: '',
            providers: Object.keys(data).map(idfy)
        };
    }

    function initializeControls(state) {
        var $e, k, v;

        for(k in state) {
            v = state[k];

            if(Array.isArray(v)) {
                v.forEach(function(a) {
                    $e = $('.cdn.' + a);

                    if($e.length) $e.trigger('click');
                })
            }
            else {
                $e = v && $('.control.' + k + '.' + v) || '';
                if($e.length) $e.trigger('click');
                else $('.control.' + k + ':last').trigger('click');
            }
        }
    }

    function groupData(data) {
        var ret = {};
console.log(data);
        data.providers.forEach(function(v) {
            var name = getProviderName(v.name);

            if(!(name in ret)) ret[name] = {};
            if(!(v.type in ret[name])) ret[name][v.type] = {
                latency: v.latency,
                uptime: v.uptime
            };
        });

        return ret;
    }

    function toFixed(a) {
        return function(v) {
            return parseFloat(v.toFixed(a), 10);
        };
    }

    function attachColors(data) {
        var i = 0;

        for(var name in data) {
            data[name]._color = getColor(i);

            i++;
        }

        return data;
    }

    function createCdns($p, state, data, update) {
        Object.keys(data).forEach(function(name) {
            var $e = $('<a>', {'class': 'cdn ' + idfy(name), href: '#'}).text(name).appendTo($p).on('click', function(e) {
                e.preventDefault();

                $e.toggleClass('selected');

                toggleItem(state.providers, idfy(name), $e.hasClass('selected'));

                update();
            });
        });
    }

    function idfy(val) {
        return val.toLowerCase().replace(/[ \-\(\)]+/g, '_').replace(/\.+/g, '');
    }

    function toggleItem(arr, k, v) {
        var i;

        if(v) {
            if(!within(arr, k)) arr.push(k);
        }
        else {
            i = arr.indexOf(k);

            if(i >= 0) arr.splice(i, 1);
        }
    }

    function createControls($p, state, update) {
        createTypes($p, state, update);
        createAmounts($p, state, update);
    }

    function createTypes($p, state, update) {
        $controls($p, state, update, 'types', 'type', ['ping', 'http', 'https']);
    }

    function createAmounts($p, state, update) {
        // TODO: replace with a slider?
        $controls($p, state, update, 'amounts', 'amount', [7, 14, 30]);
    }

    function $controls($p, state, update, containerClass, itemClass, items) {
        var $e = $('<div>',
            {'class': 'controlsContainer ' + containerClass}).appendTo($p);
        items.forEach($control.bind(undefined, $e, state, itemClass, update));
    }

    function $control($p, state, type, update, name) {
        var $e = $('<a>', {'class': 'control ' + type + ' ' + name, href: '#'}).text(name).on('click', function(e) {
            e.preventDefault();

            $e.siblings().removeClass('selected').removeClass('label');
            $e.addClass('selected label');

            state[type] = name;

            update();
        }).appendTo($p);
    }

    function updateAll($p, data, state) {
        updateCharts($p, data, state);
        updateLegend($p, data, state);
    }

    function updateCharts($p, data, state) {
        var $container = $('.chartContainer');

        updateChart($container, data, state, 'uptime', 100);
        updateChart($container, data, state, 'latency', 300)
    }

    function updateChart($p, data, state, category, height) {
        var $canvas = $('.' + category + 'Chart:first');
        var ctx, width;

        if(!$canvas.length) {
            $('<h2>').text(category).appendTo($p);
            $canvas = $('<canvas>', {'class': category + 'Chart'}).appendTo($p);
        }

        width = $canvas.parent().width();

        $canvas.attr({width: width, height: height});

        var ctx = $canvas[0].getContext('2d');

        chart(ctx, getData(data, state, category))
    }

    function chart(ctx, data) {
        // https://github.com/nnnick/Chart.js/issues/76
        var min = minimum(data.datasets.map(op(minimum, prop('data'))));
        var max = maximum(data.datasets.map(op(maximum, prop('data'))));
        var opts;

        if(max == min) {
            opts = {
                datasetFill: false,
                scaleOverride : true,
                scaleSteps : 3,
                scaleStepWidth : 1,
                scaleStartValue : max - 3
            }
        }
        else {
            opts = {
                datasetFill: false,
                animation: true,
                animationSteps: 45,
    	        pointDot: true,
    	        scaleShowGridLines: true,
                scaleGridLineColor: 'rgba(224,224,224,0.5)',
    	        scaleGridLineWidth: 1,
    	        pointDotRadius: 3,
    	        bezierCurve: false
            };
        }

        new Chart(ctx).Line(data, opts);
    }

    function op(fn, accessor) {
        return function(v) {
            return fn(accessor(v));
        };
    }

    function minimum(a) {
        return a.sort()[0];
    }

    function maximum(a) {
        return a.sort()[a.length - 1];
    }

    function updateLegend($p, data, state) {
        var $table = $('table.legend:first');
        var provider, color, $header, $container;

        if(!$table.length) {
            $container = $('.legendContainer');

            $table = $('<table>', {'class': 'legend'}).appendTo($container);
        }

        $table.empty();

        $header = $('<tr>').appendTo($table);

        $('<th>', {'class': 'colorLegend'}).appendTo($header);
        $('<th>', {'class': 'cdn'}).text('CDN').appendTo($header);
        $('<th>', {'class': 'latency'}).text('latency').appendTo($header);
        $('<th>', {'class': 'uptime'}).text('uptime').appendTo($header);

        for(var name in data) {
            provider = data[name];
            color = provider._color;

            if(within(state.providers, idfy(name))) {
                var $row = $('<tr>').appendTo($table);

                $('<td>', {'class': 'color'}).css('background-color',
                    color).appendTo($row);
                $('<td>', {'class': 'name'}).text(name).appendTo($row);

                ['latency', 'uptime'].forEach(function(category) {
                    var values, value;

                    if(state.type in provider) {
                        values = provider[state.type][category];

                        if(values) value = average(values.slice(-state.amount)).toFixed(1);
                        if(category == 'latency') value += ' ms';
                        if(category == 'uptime') value += ' %';
                    }
                    else {
                        value = 'NA';
                    }

                    $('<td>', {'class': 'value'}).text(value).appendTo($row);
                });
            }
        }
    }

    function average(arr) {
        if(!arr) return;

        return sum(arr) / arr.length;
    }

    function sum(arr) {
        if(!arr) return;

        return arr.reduce(function(a, b) {
            return a + b;
        });
    }

    function getData(data, state, category) {
        return {
            labels: getLabels(state.amount),
            datasets: getDatasets(data, state, category)
        };
    }

    function getLabels(amount) {
        var ret = [];
        var i, d;

        for(i = 0; i < amount; i++) {
            d = new XDate();

            d.addDays(-(amount - i - 1));

            ret.push(d.toString('dd MMM'));
        }

        return ret;
    }

    function getDatasets(data, state, category) {
        var ret = [];
        var provider, color;

        for(var cdn in data) {
            provider = data[cdn];
            color = provider._color;

            if(!within(state.providers, idfy(cdn))) continue;
            if(!(state.type in data[cdn])) continue;
            if(!(category in data[cdn][state.type])) continue;

            ret.push({
                strokeColor: color,
                pointColor: color,
                pointStrokeColor: color,
                data: data[cdn][state.type][category].slice(-state.amount)
            });
        }

        return ret;
    }

    function within(arr, v) {
        return arr.indexOf(v) >= 0;
    }

    function getProviderName(fullname) {
        return fullname.split(' ')[0];
    }

    function getColor(index) {
        return [
            '#d84f44',
            '#aee3d6',
            '#f3d5a2',
            '#5d96d7',
            '#444',
            '#e388eb',
            '#aadd5e'
        ][index];
    }

    function unique(arr) {
        var ret = {};

        arr.forEach(function(v) {
            ret[v] = true;
        });

        return Object.keys(ret);
    }

    function prop(name) {
        return function(v) {
            return v[name];
        };
    }
}
<commit_msg>Hook up lastDate with labels<commit_after>$(main);

function main() {
    $.getJSON('./data.json', function(d) {
        var data = attachColors(groupData(d));
        var state = initializeState(data);
        var update = updateAll.bind(undefined, $('.content.row'), data, state);
        var updateWithRoute, router;

        $(window).on('resize', update);

        router = initializeRouter(state, update);

        updateWithRoute = union(update, updateRoute.bind(undefined, state, router));

        createCdns($('.cdns'), state, data.providers, updateWithRoute);
        createControls($('.allControlsContainer'), state, updateWithRoute);

        initializeControls(state);
    });

    function union() {
        var args = arguments;

        return function() {
            for(var i = 0; i < args.length; i++) {
                args[i]();
            }
        }
    }

    function initializeRouter(state, update) {
        var Router = Backbone.Router.extend({
            routes: {
                ':providers': 'providers',
                ':providers/:type': 'providersWithType',
                ':providers/:type/:amount': 'all'
            },
            providers: function(providers) {
                state.providers = providers.split(',');

                update();
            },
            providersWithType: function(providers, type) {
                state.type = type;

                this.providers(providers);
            },
            all: function(providers, type, amount) {
                state.amount = amount;

                this.providersWithType(providers, type);
            }
        });
        var router = new Router();

        Backbone.history.start();

        return router;
    }

    function updateRoute(state, router) {
        var providers = state.providers.toString();
        var type = state.type;
        var amount = state.amount;

        router.navigate('/' + providers + '/' + type + '/' + amount, {replace: true});
    }

    function initializeState(data) {
        return {
            type: '',
            amount: '',
            providers: Object.keys(data).map(idfy)
        };
    }

    function initializeControls(state) {
        var $e, k, v;

        for(k in state) {
            v = state[k];

            if(Array.isArray(v)) {
                v.forEach(function(a) {
                    $e = $('.cdn.' + a);

                    if($e.length) $e.trigger('click');
                })
            }
            else {
                $e = v && $('.control.' + k + '.' + v) || '';
                if($e.length) $e.trigger('click');
                else $('.control.' + k + ':last').trigger('click');
            }
        }
    }

    function groupData(data) {
        var providers = {};

        data.providers.forEach(function(v) {
            var name = getProviderName(v.name);

            if(!(name in providers)) providers[name] = {};
            if(!(v.type in providers[name])) providers[name][v.type] = {
                latency: v.latency,
                uptime: v.uptime
            };
        });

        return {
            providers: providers,
            firstDate: new XDate(data.firstDate * 1000),
            lastDate: new XDate(data.lastDate * 1000)
        }
    }

    function toFixed(a) {
        return function(v) {
            return parseFloat(v.toFixed(a), 10);
        };
    }

    function attachColors(data) {
        var i = 0;

        for(var name in data.providers) {
            data.providers[name]._color = getColor(i);

            i++;
        }

        return data;
    }

    function createCdns($p, state, providers, update) {
        Object.keys(providers).forEach(function(name) {
            var $e = $('<a>', {'class': 'cdn ' + idfy(name), href: '#'}).text(name).appendTo($p).on('click', function(e) {
                e.preventDefault();

                $e.toggleClass('selected');

                toggleItem(state.providers, idfy(name), $e.hasClass('selected'));

                update();
            });
        });
    }

    function idfy(val) {
        return val.toLowerCase().replace(/[ \-\(\)]+/g, '_').replace(/\.+/g, '');
    }

    function toggleItem(arr, k, v) {
        var i;

        if(v) {
            if(!within(arr, k)) arr.push(k);
        }
        else {
            i = arr.indexOf(k);

            if(i >= 0) arr.splice(i, 1);
        }
    }

    function createControls($p, state, update) {
        createTypes($p, state, update);
        createAmounts($p, state, update);
    }

    function createTypes($p, state, update) {
        $controls($p, state, update, 'types', 'type', ['ping', 'http', 'https']);
    }

    function createAmounts($p, state, update) {
        // TODO: replace with a slider?
        $controls($p, state, update, 'amounts', 'amount', [7, 14, 30]);
    }

    function $controls($p, state, update, containerClass, itemClass, items) {
        var $e = $('<div>',
            {'class': 'controlsContainer ' + containerClass}).appendTo($p);
        items.forEach($control.bind(undefined, $e, state, itemClass, update));
    }

    function $control($p, state, type, update, name) {
        var $e = $('<a>', {'class': 'control ' + type + ' ' + name, href: '#'}).text(name).on('click', function(e) {
            e.preventDefault();

            $e.siblings().removeClass('selected').removeClass('label');
            $e.addClass('selected label');

            state[type] = name;

            update();
        }).appendTo($p);
    }

    function updateAll($p, data, state) {
        updateCharts($p, data, state);
        updateLegend($p, data.providers, state);
    }

    function updateCharts($p, data, state) {
        var $container = $('.chartContainer');

        updateChart($container, data, state, 'uptime', 100);
        updateChart($container, data, state, 'latency', 300)
    }

    function updateChart($p, data, state, category, height) {
        var $canvas = $('.' + category + 'Chart:first');
        var ctx, width;

        if(!$canvas.length) {
            $('<h2>').text(category).appendTo($p);
            $canvas = $('<canvas>', {'class': category + 'Chart'}).appendTo($p);
        }

        width = $canvas.parent().width();

        $canvas.attr({width: width, height: height});

        var ctx = $canvas[0].getContext('2d');

        chart(ctx, getData(data, state, category))
    }

    function chart(ctx, data) {
        // https://github.com/nnnick/Chart.js/issues/76
        var min = minimum(data.datasets.map(op(minimum, prop('data'))));
        var max = maximum(data.datasets.map(op(maximum, prop('data'))));
        var opts;

        if(max == min) {
            opts = {
                datasetFill: false,
                scaleOverride : true,
                scaleSteps : 3,
                scaleStepWidth : 1,
                scaleStartValue : max - 3
            }
        }
        else {
            opts = {
                datasetFill: false,
                animation: true,
                animationSteps: 45,
    	        pointDot: true,
    	        scaleShowGridLines: true,
                scaleGridLineColor: 'rgba(224,224,224,0.5)',
    	        scaleGridLineWidth: 1,
    	        pointDotRadius: 3,
    	        bezierCurve: false
            };
        }

        new Chart(ctx).Line(data, opts);
    }

    function op(fn, accessor) {
        return function(v) {
            return fn(accessor(v));
        };
    }

    function minimum(a) {
        return a.sort()[0];
    }

    function maximum(a) {
        return a.sort()[a.length - 1];
    }

    function updateLegend($p, providers, state) {
        var $table = $('table.legend:first');
        var provider, color, $header, $container;

        if(!$table.length) {
            $container = $('.legendContainer');

            $table = $('<table>', {'class': 'legend'}).appendTo($container);
        }

        $table.empty();

        $header = $('<tr>').appendTo($table);

        $('<th>', {'class': 'colorLegend'}).appendTo($header);
        $('<th>', {'class': 'cdn'}).text('CDN').appendTo($header);
        $('<th>', {'class': 'latency'}).text('latency').appendTo($header);
        $('<th>', {'class': 'uptime'}).text('uptime').appendTo($header);

        for(var name in providers) {
            provider = providers[name];
            color = provider._color;

            if(within(state.providers, idfy(name))) {
                var $row = $('<tr>').appendTo($table);

                $('<td>', {'class': 'color'}).css('background-color',
                    color).appendTo($row);
                $('<td>', {'class': 'name'}).text(name).appendTo($row);

                ['latency', 'uptime'].forEach(function(category) {
                    var values, value;

                    if(state.type in provider) {
                        values = provider[state.type][category];

                        if(values) value = average(values.slice(-state.amount)).toFixed(1);
                        if(category == 'latency') value += ' ms';
                        if(category == 'uptime') value += ' %';
                    }
                    else {
                        value = 'NA';
                    }

                    $('<td>', {'class': 'value'}).text(value).appendTo($row);
                });
            }
        }
    }

    function average(arr) {
        if(!arr) return;

        return sum(arr) / arr.length;
    }

    function sum(arr) {
        if(!arr) return;

        return arr.reduce(function(a, b) {
            return a + b;
        });
    }

    function getData(data, state, category) {
        return {
            labels: getLabels(data.lastDate, state.amount),
            datasets: getDatasets(data.providers, state, category)
        };
    }

    function getLabels(lastDate, amount) {
        var ret = [];
        var i, d;

        for(i = 0; i < amount; i++) {
            d = lastDate.clone();

            d.addDays(-(amount - i - 1));

            ret.push(d.toString('dd MMM'));
        }

        return ret;
    }

    function getDatasets(providers, state, category) {
        var ret = [];
        var provider, color;

        for(var name in providers) {
            provider = providers[name];
            color = provider._color;

            if(!within(state.providers, idfy(name))) continue;
            if(!(state.type in provider)) continue;
            if(!(category in provider[state.type])) continue;

            ret.push({
                strokeColor: color,
                pointColor: color,
                pointStrokeColor: color,
                data: provider[state.type][category].slice(-state.amount)
            });
        }

        return ret;
    }

    function within(arr, v) {
        return arr.indexOf(v) >= 0;
    }

    function getProviderName(fullname) {
        return fullname.split(' ')[0];
    }

    function getColor(index) {
        return [
            '#d84f44',
            '#aee3d6',
            '#f3d5a2',
            '#5d96d7',
            '#444',
            '#e388eb',
            '#aadd5e'
        ][index];
    }

    function unique(arr) {
        var ret = {};

        arr.forEach(function(v) {
            ret[v] = true;
        });

        return Object.keys(ret);
    }

    function prop(name) {
        return function(v) {
            return v[name];
        };
    }
}
<|endoftext|>


<commit_before>var FlashBuilder = function (type, messages) {
  function createFlashContainer () {
    var container = $('

')
      .addClass('alert animated fadeIn alert-dismissable')
      .html(createDismissButton());

      switch(type) {
        case 'success':
          container.addClass('alert-success');
          break;
        case 'info':
          container.addClass('alert-info');
          break;
        case 'error':
          container.addClass('alert-danger');
          break;
      }

      return container;
  }

  function createDismissButton () {
    return $('<button></button>')
      .attr('type', 'button')
      .attr('data-dismiss', 'alert')
      .attr('aria-hidden', true)
      .addClass('close')
      .text('x');
  }

  function createMessage (message) {
    return $('

')
      .text(message);
  }

  function clearFlash () {
    $('#flash').empty();
  }

  function buildMessage () {
    if (messages && messages.length > 0) {
      clearFlash();

      var container = createFlashContainer(type);

      messages.forEach(function (message) {
        container.append(createMessage(message.msg));
      });

      $('#flash').append(container);
    }
  }

  return {
    build: buildMessage
  };
};

$(document).ready(function() {

  if ($("#url").length > 0) {
    $("#title").on('focus', function() {
      var url = $("#url").val();
      $.get("/news/summarize?url=" + url, function(response) {
        if (response) {
      if(typeof response.title !== "undefined"){
			$("#title").val(response.title);
      }

      if(typeof response.source !== "undefined"){
			$("#source").val(response.source);
      }
      else $("#source").val(url);

      if(typeof response.summary !== "undefined"){
			$("#summary").val(response.summary.join(" "));
      }

		}
      });
    });
    $('input[autofocus]').trigger('focus');//force fire it on the autofocus element
  }

  if($(".show-summary").length > 0) {
    $(".show-summary").on('click', function(e) {

      if ($("p.item-summary").length ) {
        $("p.item-summary").toggleClass('hidden');
      }

      if ($(this).siblings("p.summary").length ) {
        $(this).siblings("p").toggleClass('hidden');
      }

      e.preventDefault();
    });
  }

  if ($("label.post-type-btn").length) {
    $("label.post-type-btn").click( function(e) {
      if($(this).hasClass('post-type-url')) {
	$('.form-group-url').slideDown();
	$('.form-group-source').slideDown();
      } else {
	$('.form-group-url').slideUp();
	$('.form-group-source').slideUp();
      }
    });
  }

  $("#copyright").text(new Date().getFullYear());

  (function hideNavMenuOnBodyClick() {
    $('body').on('click', function(e) {
      if (! $('.navbar').has(e.target).length) {
        $('.navbar-collapse.in').collapse('hide');
      }
    });
  }());

  $('form').submit(function(ev) {
    $(this).append(
      '<input type="hidden" name="windowscrolly" value="' + window.scrollY + '">'
    );
  });

  if ($('form.upvote-form').length > 0) {
    $('.upvote-form').on('submit', function (e) {
      var form = $(this);

      $.post(form.attr('action'), form.serialize())
        .done(function (data) {
          if (data.messages) {
            var builder = new FlashBuilder(data.success ? 'success' : 'error', data.messages);

            builder.build();
          }

          if (data.success) {
            $('button.upvote', form).remove();
          }
        })
        .fail(function (data) {
          var builder = new FlashBuilder('error', [{ msg: 'Something went wrong!'}]);
        });

      e.preventDefault();
    });
  }

  var scrollDebouncer,
      $flash = $('#flash'), $pageHeader = $('.page-header:first'),
      initPageHeaderTopMargin = parseFloat($pageHeader.css('margin-top')),
      positionFlash = function() {
        var flashPosition = window.scrollY <= 0 ? 'static' : 'fixed',
            pageHeaderTopMargin = initPageHeaderTopMargin + (window.scrollY <= 0 ? 0 : $flash.height());

        $flash.css('position', flashPosition);
        $pageHeader.css('margin-top', pageHeaderTopMargin + 'px');
        scrollDebouncer = null;
      },
      onWindowScroll = function(ev) {
        if (scrollDebouncer) { clearTimeout(scrollDebouncer); } else { positionFlash(); }
        if (window.scrollY <= 0) { positionFlash(); } else { scrollDebouncer = setTimeout(positionFlash, 50); }
      };

  $(window).scroll(onWindowScroll);
  $flash.find('.alert .close').click(function(ev) { setTimeout(positionFlash, 1) });
  window.scrollTo(0, $('input[name="windowscrollto"]').val() || 0);
  positionFlash();

});
<commit_msg>added semicolon to public/js/main.js to quiet jshint<commit_after>var FlashBuilder = function (type, messages) {
  function createFlashContainer () {
    var container = $('

')
      .addClass('alert animated fadeIn alert-dismissable')
      .html(createDismissButton());

      switch(type) {
        case 'success':
          container.addClass('alert-success');
          break;
        case 'info':
          container.addClass('alert-info');
          break;
        case 'error':
          container.addClass('alert-danger');
          break;
      }

      return container;
  }

  function createDismissButton () {
    return $('<button></button>')
      .attr('type', 'button')
      .attr('data-dismiss', 'alert')
      .attr('aria-hidden', true)
      .addClass('close')
      .text('x');
  }

  function createMessage (message) {
    return $('

')
      .text(message);
  }

  function clearFlash () {
    $('#flash').empty();
  }

  function buildMessage () {
    if (messages && messages.length > 0) {
      clearFlash();

      var container = createFlashContainer(type);

      messages.forEach(function (message) {
        container.append(createMessage(message.msg));
      });

      $('#flash').append(container);
    }
  }

  return {
    build: buildMessage
  };
};

$(document).ready(function() {

  if ($("#url").length > 0) {
    $("#title").on('focus', function() {
      var url = $("#url").val();
      $.get("/news/summarize?url=" + url, function(response) {
        if (response) {
      if(typeof response.title !== "undefined"){
			$("#title").val(response.title);
      }

      if(typeof response.source !== "undefined"){
			$("#source").val(response.source);
      }
      else $("#source").val(url);

      if(typeof response.summary !== "undefined"){
			$("#summary").val(response.summary.join(" "));
      }

		}
      });
    });
    $('input[autofocus]').trigger('focus');//force fire it on the autofocus element
  }

  if($(".show-summary").length > 0) {
    $(".show-summary").on('click', function(e) {

      if ($("p.item-summary").length ) {
        $("p.item-summary").toggleClass('hidden');
      }

      if ($(this).siblings("p.summary").length ) {
        $(this).siblings("p").toggleClass('hidden');
      }

      e.preventDefault();
    });
  }

  if ($("label.post-type-btn").length) {
    $("label.post-type-btn").click( function(e) {
      if($(this).hasClass('post-type-url')) {
	$('.form-group-url').slideDown();
	$('.form-group-source').slideDown();
      } else {
	$('.form-group-url').slideUp();
	$('.form-group-source').slideUp();
      }
    });
  }

  $("#copyright").text(new Date().getFullYear());

  (function hideNavMenuOnBodyClick() {
    $('body').on('click', function(e) {
      if (! $('.navbar').has(e.target).length) {
        $('.navbar-collapse.in').collapse('hide');
      }
    });
  }());

  $('form').submit(function(ev) {
    $(this).append(
      '<input type="hidden" name="windowscrolly" value="' + window.scrollY + '">'
    );
  });

  if ($('form.upvote-form').length > 0) {
    $('.upvote-form').on('submit', function (e) {
      var form = $(this);

      $.post(form.attr('action'), form.serialize())
        .done(function (data) {
          if (data.messages) {
            var builder = new FlashBuilder(data.success ? 'success' : 'error', data.messages);

            builder.build();
          }

          if (data.success) {
            $('button.upvote', form).remove();
          }
        })
        .fail(function (data) {
          var builder = new FlashBuilder('error', [{ msg: 'Something went wrong!'}]);
        });

      e.preventDefault();
    });
  }

  var scrollDebouncer,
      $flash = $('#flash'), $pageHeader = $('.page-header:first'),
      initPageHeaderTopMargin = parseFloat($pageHeader.css('margin-top')),
      positionFlash = function() {
        var flashPosition = window.scrollY <= 0 ? 'static' : 'fixed',
            pageHeaderTopMargin = initPageHeaderTopMargin + (window.scrollY <= 0 ? 0 : $flash.height());

        $flash.css('position', flashPosition);
        $pageHeader.css('margin-top', pageHeaderTopMargin + 'px');
        scrollDebouncer = null;
      },
      onWindowScroll = function(ev) {
        if (scrollDebouncer) { clearTimeout(scrollDebouncer); } else { positionFlash(); }
        if (window.scrollY <= 0) { positionFlash(); } else { scrollDebouncer = setTimeout(positionFlash, 50); }
      };

  $(window).scroll(onWindowScroll);
  $flash.find('.alert .close').click(function(ev) { setTimeout(positionFlash, 1); });
  window.scrollTo(0, $('input[name="windowscrollto"]').val() || 0);
  positionFlash();

});
<|endoftext|>