<commit_before>// Welcome to dumbai.

// List from campId to map from building to tileKeys where we perform the
// project.
var constructionProjects;
var stallingProjects;   // maps from building to # of attempts.
// List from campId to list of tileKeys.
var destinationProjects;
// List from campId to booleans.
var war;

var buildingValue = {};
var totalValue = 1;

function clear(terrain, humanity) {
  constructionProjects = new Array(humanity.numberOfCamps);
  stallingProjects = new Array(humanity.numberOfCamps);
  destinationProjects = new Array(humanity.numberOfCamps);
  war = new Array(humanity.numberOfCamps);
  for (var i = 0; i < humanity.numberOfCamps; i++) {
    constructionProjects[i] = {};
    stallingProjects[i] = {};
    destinationProjects[i] = [];
    war[i] = false;
  }
  // Building value.
  buildingValue[terrain.tileTypes.farm] = 4;
  buildingValue[terrain.tileTypes.residence] = 30;
  buildingValue[terrain.tileTypes.skyscraper] = 13;
  buildingValue[terrain.tileTypes.factory] = 7;
  buildingValue[terrain.tileTypes.dock] = 7;
  buildingValue[terrain.tileTypes.gunsmith] = 35;
  buildingValue[terrain.tileTypes.airland] = 4;
  buildingValue[terrain.tileTypes.airport] = 15;
  buildingValue[terrain.tileTypes.road] = 1;
  buildingValue[terrain.tileTypes.wall] = 0;
  totalValue = 0;
  for (var building in buildingValue) {
    totalValue += buildingValue[building];
  }
}

// Return a plan, or undefined (if unsuccessful).
function findBuildingPlan(terrain, humanity, humanityData,
    ourTiles, campId, building) {
  // Are there valid construction sites?
  for (var i = 0; i < ourTiles.length; i++) {
    var tile = terrain.tileFromKey(ourTiles[i]);
    var humanityTile = humanityData[ourTiles[i]];
    if (terrain.validConstruction(building, tile)) {
      if (constructionProjects[campId][building] === ourTiles[i]) {
        constructionProjects[campId][building] = null;
        stallingProjects[campId][building] = 0;
      }
      // Usually, we strive for valuable buildings.
      // But old rocks that stand in the way are rubbish.
      if ((humanityTile.b != null)?
          (buildingValue[building] > buildingValue[humanityTile.b]): true) {
        return { at: ourTiles[i], do: terrain.planTypes.build, b: building };
      }
    }
  }
  // Did we work on something?
  if (constructionProjects[campId][building] == null
      || ((humanityTile = humanityData[constructionProjects[campId][building]])
          && humanityTile.c !== campId)
      || (!humanityTile) || (humanityTile.h <= 0)
      || stallingProjects[campId][building] > 10) {
    // Let's make a project!
    var validTiles = [];
    for (var i = 0; i < ourTiles.length; i++) {
      var tile = terrain.tileFromKey(ourTiles[i]);
      if (tile.type !== terrain.tileTypes.water &&
          tile.type !== terrain.tileTypes.mountain) {
        validTiles.push(ourTiles[i]);
      }
    }
    if (validTiles.length > 0) {
      constructionProjects[campId][building] =
        validTiles[(Math.random() * validTiles.length)|0];
      stallingProjects[campId][building] = 0;
    }
  }
  stallingProjects[campId][building]++;
  if (constructionProjects[campId][building] == null) { return; }
  return projectPlan(terrain, humanity, humanityData, ourTiles, campId,
      building, constructionProjects[campId][building]);
}

function projectPlan(terrain, humanity, humanityData, ourTiles, campId,
    building, location) {
  // Find what we require to finish the project.
  var requiredDependencies = terrain.buildingDependencies[building];
  if (!requiredDependencies) { return; }
  var dependencies = new Array(requiredDependencies.length);
  var unoccupied = [];  // neighbors with no people on it.
  var unbuilt = [];  // neighbors with no buildings on it.
  for (var i = 0; i < dependencies.length; i++) { dependencies[i] = 0; }
  var tile = terrain.tileFromKey(location);
  for (var i = 0; i < 6; i++) {
    var neighbor = terrain.neighborFromTile(tile, i);
    var humanityNeighbor = humanity(neighbor);
    var terrainNeighbor = terrain(neighbor);
    if (!humanityNeighbor || humanityNeighbor.c !== campId) {
      unoccupied.push(neighbor);
      continue;
    } else if (humanityNeighbor.b == null) {
      unbuilt.push(neighbor);
      continue;
    }
    for (var j = 0; j < requiredDependencies.length; j++) {
      if (humanityNeighbor.b === requiredDependencies[j][1] ||
          terrainNeighbor.type === requiredDependencies[j][1]) {
        dependencies[j]++;
      }
    }
  }
  for (var j = 0; j < dependencies.length; j++) {
    if (dependencies[j] < requiredDependencies[j][0]) {
      if (requiredDependencies[j][1] === 0) { continue; }
      // We know what to build.
      if (unbuilt.length > 0) {
        // Someone can do it.
        //return projectPlan(terrain, humanity, humanityData,
        //  ourTiles, campId, requiredDependencies[j][1],
        //  terrain.keyFromTile(unbuilt[0]));
        constructionProjects[campId][requiredDependencies[j][1]] = unbuilt[0];
        return {
          at: terrain.keyFromTile(unbuilt[0]),
          do: terrain.planTypes.build,
          b: requiredDependencies[j][1]
        };
      } else if (unoccupied.length > 0) {
        // We can put someone there.
        destinationProjects[campId].push(terrain.keyFromTile(unoccupied[0]));
        return;
      }
    }
  }
}

// a and b are tiles = {q,r}.
function distance(a, b) {
  return (Math.abs(a.q - b.q) +
          Math.abs(a.r - b.r) +
          Math.abs(a.q + a.r - b.q - b.r)) / 2;
}

// Return the closest tileKey we can go to from atTileKey
// in order to go to the target toTileKey.
function closestTowards(terrain, humanity, atTileKey, toTileKey) {
  var accessibleTiles = terrain.humanTravel(terrain.tileFromKey(atTileKey));
  var toTile = terrain.tileFromKey(toTileKey);
  var closest;
  var shortestDistanceYet = Infinity;
  for (var tileKey in accessibleTiles) {
    if (closest === undefined) { closest = tileKey; continue; }
    var tile = terrain.tileFromKey(tileKey);
    var thisDistance = distance(tile, toTile);
    if (thisDistance < shortestDistanceYet) {
      shortestDistanceYet = thisDistance;
      closest = tileKey;
    }
  }
  return closest;
}

// Return a plan to travel towards the enemy. Or a building in construction.
// Or nothing.
function findTravelPlan(terrain, humanity, humanityData, ourTiles, campId) {
  // Find someone to go somewhere.
  var fromTile, fromHumanityTile;
  var travelToBuild = destinationProjects[campId].length > 0;
  if (travelToBuild) {
    var closest = Infinity;   // Will seek the closest humans.
    for (var i = 0; i < ourTiles.length; i++) {
      var humanityTile = humanityData[ourTiles[i]];
      var newDistance = distance(ourTiles[i], destinationProjects[campId][0]);
      if (newDistance < closest && humanityTile.h > 1) {
        closest = newDistance;
        fromTile = ourTiles[i];
        fromHumanityTile = humanityTile;
      }
    }
  }
  if (fromHumanityTile === undefined) {
    var nHumans = 0;   // Will seek the highest number of humans on a tile.
    for (var i = 0; i < ourTiles.length; i++) {
      var humanityTile = humanityData[ourTiles[i]];
      if (humanityTile.h > nHumans &&
          // We leave people on buildings.
          ((humanityTile.b === terrain.tileTypes.residence ||
             humanityTile.b === terrain.tileTypes.skyscraper)?
            humanityTile.h > 1: true)) {
        nHumans = humanityTile.h;
        fromTile = ourTiles[i];
        fromHumanityTile = humanityTile;
      }
    }
    if (fromHumanityTile === undefined) {
      fromTile = ourTiles[0];
      fromHumanityTile = humanityData[fromTile];
    }
  }
  // Are we hungry there?
  if (fromHumanityTile.f < 2) {
    return {
      at: fromTile,
      do: terrain.planTypes.build,
      b: terrain.tileTypes.farm
    };
  }
  // Go somewhere.
  var directionTile;
  if (travelToBuild) {
    directionTile = destinationProjects[campId][0];
  } else {
    // Go towards the enemy!
    var enemyPicked = ((Math.random() * humanity.numberOfCamps)|0);
    for (var tileKey in humanityData) {
      if (humanityData[tileKey].h > 0 && humanityData[tileKey].c === enemyPicked) {
        directionTile = tileKey;
        break;
      }
    }
  }
  var toTile = closestTowards(terrain, humanity, fromTile, directionTile);
  if (toTile === directionTile || Math.random() < 0.2) {
    destinationProjects[campId].shift();
  }
  if (travelToBuild) {
    return {
      at: fromTile,
      do: terrain.planTypes.move,
      to: toTile,
      h: ((fromHumanityTile.h > 1)?
          (fromHumanityTile.h - 1): fromHumanityTile.h)
    };
  } else {
    return {
      at: fromTile,
      do: terrain.planTypes.move,
      to: toTile,
      h: ((fromHumanityTile.b != null && fromHumanityTile.h > 1)?
          (fromHumanityTile.h - 1): fromHumanityTile.h)
    };
  }
}

// Select the building we want to construct.
function selectBuilding() {
  var select = Math.random();
  var acc = 0;
  for (var building in buildingValue) {
    acc += buildingValue[building] / totalValue;
    if (select < acc) { return +building; }
  }
  return +building;
}

// Return a plan. Or undefined.
function run(terrain, humanity) {
  var leastCamp = humanity.campFromId(0);
  for (var i = 1; i < humanity.numberOfCamps; i++) {
    var camp = humanity.campFromId(i);
    if (camp.nActions < leastCamp.nActions) {
      leastCamp = camp;
    }
  }
  var ourTiles = [];
  var humanityData = humanity.data();
  for (var tileKey in humanityData) {
    var humanityTile = humanityData[tileKey];
    if (humanityTile.c === leastCamp.id) {
      // This is the nationality we seek to help.
      ourTiles.push(tileKey);
    }
  }
  if (ourTiles.length <= 0) { return; }
  // Now, pick what we do.
  if (!war[leastCamp.id] && Math.random() < 0.7) {
    // What building do we want to create?
    var building = selectBuilding();
    // Let's pick a location to build that.
    var buildingPlan = findBuildingPlan(terrain, humanity, humanityData,
        ourTiles, leastCamp.id, building);
    if (buildingPlan != null) {
      return buildingPlan;
    }
  }
  if (Math.random() < 0.01 && leastCamp.population > 10) {
    war[leastCamp.id] = !war[leastCamp.id];
  }
  if (war[leastCamp.id]) { console.log('at war!'); }
  // We're going to travel.
  return findTravelPlan(terrain, humanity, humanityData, ourTiles, leastCamp.id);
}

module.exports = run;
module.exports.clear = clear;
<commit_msg>AI: war targets a single enemy.<commit_after>// Welcome to dumbai.

// List from campId to map from building to tileKeys where we perform the
// project.
var constructionProjects;
var stallingProjects;   // maps from building to # of attempts.
// List from campId to list of tileKeys.
var destinationProjects;
// List from campId to booleans.
var war;

var buildingValue = {};
var totalValue = 1;

function clear(terrain, humanity) {
  constructionProjects = new Array(humanity.numberOfCamps);
  stallingProjects = new Array(humanity.numberOfCamps);
  destinationProjects = new Array(humanity.numberOfCamps);
  war = new Array(humanity.numberOfCamps);
  for (var i = 0; i < humanity.numberOfCamps; i++) {
    constructionProjects[i] = {};
    stallingProjects[i] = {};
    destinationProjects[i] = [];
    war[i] = -1;
  }
  // Building value.
  buildingValue[terrain.tileTypes.farm] = 4;
  buildingValue[terrain.tileTypes.residence] = 30;
  buildingValue[terrain.tileTypes.skyscraper] = 13;
  buildingValue[terrain.tileTypes.factory] = 7;
  buildingValue[terrain.tileTypes.dock] = 7;
  buildingValue[terrain.tileTypes.gunsmith] = 35;
  buildingValue[terrain.tileTypes.airland] = 4;
  buildingValue[terrain.tileTypes.airport] = 15;
  buildingValue[terrain.tileTypes.road] = 1;
  buildingValue[terrain.tileTypes.wall] = 0;
  totalValue = 0;
  for (var building in buildingValue) {
    totalValue += buildingValue[building];
  }
}

// Return a plan, or undefined (if unsuccessful).
function findBuildingPlan(terrain, humanity, humanityData,
    ourTiles, campId, building) {
  // Are there valid construction sites?
  for (var i = 0; i < ourTiles.length; i++) {
    var tile = terrain.tileFromKey(ourTiles[i]);
    var humanityTile = humanityData[ourTiles[i]];
    if (terrain.validConstruction(building, tile)) {
      if (constructionProjects[campId][building] === ourTiles[i]) {
        constructionProjects[campId][building] = null;
        stallingProjects[campId][building] = 0;
      }
      // Usually, we strive for valuable buildings.
      // But old rocks that stand in the way are rubbish.
      if ((humanityTile.b != null)?
          (buildingValue[building] > buildingValue[humanityTile.b]): true) {
        return { at: ourTiles[i], do: terrain.planTypes.build, b: building };
      }
    }
  }
  // Did we work on something?
  if (constructionProjects[campId][building] == null
      || ((humanityTile = humanityData[constructionProjects[campId][building]])
          && humanityTile.c !== campId)
      || (!humanityTile) || (humanityTile.h <= 0)
      || stallingProjects[campId][building] > 10) {
    // Let's make a project!
    var validTiles = [];
    for (var i = 0; i < ourTiles.length; i++) {
      var tile = terrain.tileFromKey(ourTiles[i]);
      if (tile.type !== terrain.tileTypes.water &&
          tile.type !== terrain.tileTypes.mountain) {
        validTiles.push(ourTiles[i]);
      }
    }
    if (validTiles.length > 0) {
      constructionProjects[campId][building] =
        validTiles[(Math.random() * validTiles.length)|0];
      stallingProjects[campId][building] = 0;
    }
  }
  stallingProjects[campId][building]++;
  if (constructionProjects[campId][building] == null) { return; }
  return projectPlan(terrain, humanity, humanityData, ourTiles, campId,
      building, constructionProjects[campId][building]);
}

function projectPlan(terrain, humanity, humanityData, ourTiles, campId,
    building, location) {
  // Find what we require to finish the project.
  var requiredDependencies = terrain.buildingDependencies[building];
  if (!requiredDependencies) { return; }
  var dependencies = new Array(requiredDependencies.length);
  var unoccupied = [];  // neighbors with no people on it.
  var unbuilt = [];  // neighbors with no buildings on it.
  for (var i = 0; i < dependencies.length; i++) { dependencies[i] = 0; }
  var tile = terrain.tileFromKey(location);
  for (var i = 0; i < 6; i++) {
    var neighbor = terrain.neighborFromTile(tile, i);
    var humanityNeighbor = humanity(neighbor);
    var terrainNeighbor = terrain(neighbor);
    if (!humanityNeighbor || humanityNeighbor.c !== campId) {
      unoccupied.push(neighbor);
      continue;
    } else if (humanityNeighbor.b == null) {
      unbuilt.push(neighbor);
      continue;
    }
    for (var j = 0; j < requiredDependencies.length; j++) {
      if (humanityNeighbor.b === requiredDependencies[j][1] ||
          terrainNeighbor.type === requiredDependencies[j][1]) {
        dependencies[j]++;
      }
    }
  }
  for (var j = 0; j < dependencies.length; j++) {
    if (dependencies[j] < requiredDependencies[j][0]) {
      if (requiredDependencies[j][1] === 0) { continue; }
      // We know what to build.
      if (unbuilt.length > 0) {
        // Someone can do it.
        //return projectPlan(terrain, humanity, humanityData,
        //  ourTiles, campId, requiredDependencies[j][1],
        //  terrain.keyFromTile(unbuilt[0]));
        constructionProjects[campId][requiredDependencies[j][1]] = unbuilt[0];
        return {
          at: terrain.keyFromTile(unbuilt[0]),
          do: terrain.planTypes.build,
          b: requiredDependencies[j][1]
        };
      } else if (unoccupied.length > 0) {
        // We can put someone there.
        destinationProjects[campId].push(terrain.keyFromTile(unoccupied[0]));
        return;
      }
    }
  }
}

// a and b are tiles = {q,r}.
function distance(a, b) {
  return (Math.abs(a.q - b.q) +
          Math.abs(a.r - b.r) +
          Math.abs(a.q + a.r - b.q - b.r)) / 2;
}

// Return the closest tileKey we can go to from atTileKey
// in order to go to the target toTileKey.
function closestTowards(terrain, humanity, atTileKey, toTileKey) {
  var accessibleTiles = terrain.humanTravel(terrain.tileFromKey(atTileKey));
  var toTile = terrain.tileFromKey(toTileKey);
  var closest;
  var shortestDistanceYet = Infinity;
  for (var tileKey in accessibleTiles) {
    if (closest === undefined) { closest = tileKey; continue; }
    var tile = terrain.tileFromKey(tileKey);
    var thisDistance = distance(tile, toTile);
    if (thisDistance < shortestDistanceYet) {
      shortestDistanceYet = thisDistance;
      closest = tileKey;
    }
  }
  return closest;
}

// Return a plan to travel towards the enemy. Or a building in construction.
// Or nothing.
function findTravelPlan(terrain, humanity, humanityData, ourTiles, campId) {
  // Find someone to go somewhere.
  var fromTile, fromHumanityTile;
  var travelToBuild = destinationProjects[campId].length > 0;
  if (travelToBuild) {
    var closest = Infinity;   // Will seek the closest humans.
    for (var i = 0; i < ourTiles.length; i++) {
      var humanityTile = humanityData[ourTiles[i]];
      var newDistance = distance(ourTiles[i], destinationProjects[campId][0]);
      if (newDistance < closest && humanityTile.h > 1) {
        closest = newDistance;
        fromTile = ourTiles[i];
        fromHumanityTile = humanityTile;
      }
    }
  }
  if (fromHumanityTile === undefined) {
    var nHumans = 0;   // Will seek the highest number of humans on a tile.
    for (var i = 0; i < ourTiles.length; i++) {
      var humanityTile = humanityData[ourTiles[i]];
      if (humanityTile.h > nHumans &&
          // We leave people on buildings.
          ((humanityTile.b === terrain.tileTypes.residence ||
             humanityTile.b === terrain.tileTypes.skyscraper)?
            humanityTile.h > 1: true)) {
        nHumans = humanityTile.h;
        fromTile = ourTiles[i];
        fromHumanityTile = humanityTile;
      }
    }
    if (fromHumanityTile === undefined) {
      fromTile = ourTiles[0];
      fromHumanityTile = humanityData[fromTile];
    }
  }
  // Are we hungry there?
  if (fromHumanityTile.f < 2) {
    return {
      at: fromTile,
      do: terrain.planTypes.build,
      b: terrain.tileTypes.farm
    };
  }
  // Go somewhere.
  var directionTile;
  if (travelToBuild) {
    directionTile = destinationProjects[campId][0];
  } else {
    // Go towards the enemy!
    var enemyPicked = ((Math.random() * humanity.numberOfCamps)|0);
    if (war[campId] >= 0) { enemyPicked = war[campId]; }
    for (var tileKey in humanityData) {
      if (humanityData[tileKey].h > 0 && humanityData[tileKey].c === enemyPicked) {
        directionTile = tileKey;
        break;
      }
    }
  }
  var toTile = closestTowards(terrain, humanity, fromTile, directionTile);
  if (toTile === directionTile || Math.random() < 0.2) {
    destinationProjects[campId].shift();
  }
  if (travelToBuild) {
    return {
      at: fromTile,
      do: terrain.planTypes.move,
      to: toTile,
      h: ((fromHumanityTile.h > 1)?
          (fromHumanityTile.h - 1): fromHumanityTile.h)
    };
  } else {
    return {
      at: fromTile,
      do: terrain.planTypes.move,
      to: toTile,
      h: ((fromHumanityTile.b != null && fromHumanityTile.h > 1)?
          (fromHumanityTile.h - 1): fromHumanityTile.h)
    };
  }
}

// Select the building we want to construct.
function selectBuilding() {
  var select = Math.random();
  var acc = 0;
  for (var building in buildingValue) {
    acc += buildingValue[building] / totalValue;
    if (select < acc) { return +building; }
  }
  return +building;
}

// Return a plan. Or undefined.
function run(terrain, humanity) {
  var leastCamp = humanity.campFromId(0);
  for (var i = 1; i < humanity.numberOfCamps; i++) {
    var camp = humanity.campFromId(i);
    if (camp.nActions < leastCamp.nActions) {
      leastCamp = camp;
    }
  }
  var ourTiles = [];
  var humanityData = humanity.data();
  for (var tileKey in humanityData) {
    var humanityTile = humanityData[tileKey];
    if (humanityTile.c === leastCamp.id) {
      // This is the nationality we seek to help.
      ourTiles.push(tileKey);
    }
  }
  if (ourTiles.length <= 0) { return; }
  // Now, pick what we do.
  if (war[leastCamp.id] < 0 && Math.random() < 0.7) {
    // What building do we want to create?
    var building = selectBuilding();
    // Let's pick a location to build that.
    var buildingPlan = findBuildingPlan(terrain, humanity, humanityData,
        ourTiles, leastCamp.id, building);
    if (buildingPlan != null) {
      return buildingPlan;
    }
  }
  if (Math.random() < 0.01 && leastCamp.population > 10) {
    if (war[leastCamp.id] < 0) {
      war[leastCamp.id] = (Math.random() * humanity.numberOfCamps)|0;
    } else {
      war[leastCamp.id] = -1;
    }
  }
  if (war[leastCamp.id] >= 0) { console.log('at war!'); }
  // We're going to travel.
  return findTravelPlan(terrain, humanity, humanityData, ourTiles, leastCamp.id);
}

module.exports = run;
module.exports.clear = clear;
<|endoftext|>


<commit_before>var $ = require('jquery');
var Backbone = require('backbone');
var _ = require('underscore');
var ConfigModel = require('../../../../../javascripts/cartodb3/data/config-model');
var UserModel = require('../../../../../javascripts/cartodb3/data/user-model');
var ExportImagePane = require('../../../../../javascripts/cartodb3/editor/export-image-pane/export-image-pane');
var ExportImageFormModel = require('../../../../../javascripts/cartodb3/editor/export-image-pane/export-image-form-model');
var MapDefinitionModel = require('../../../../../javascripts/cartodb3/data/map-definition-model');
var FactoryModals = require('../../factories/modals');
var Notifier = require('../../../../../javascripts/cartodb3/components/notifier/notifier');
var EditorModel = require('../../../../../javascripts/cartodb3/data/editor-model');

describe('editor/export-image-pane/export-image-pane', function () {
  beforeEach(function () {
    var style = '[{ "stylers": [ { "saturation": -100 } ] },{ "featureType": "water", "stylers": [ { "gamma": 1.67 }, { "lightness": 27 } ] },{ "elementType": "geometry", "stylers": [ { "gamma": 1.31 }, { "lightness": 12 } ] },{ "featureType": "administrative", "elementType": "labels", "stylers": [ { "lightness": 51 }, { "gamma": 0.94 } ] },{ },{ "featureType": "road", "elementType": "labels", "stylers": [ { "lightness": 57 } ] },{ "featureType": "poi", "elementType": "labels", "stylers": [ { "lightness": 42 } ] }]';

    this.imageExportMetadata = {
      zoom: 10,
      mapType: 'roadmap',
      style: style,
      attribution: 'CARTO',
      provider: 'no-googlemaps',
      title: 'wadus'
    };

    this.getStaticImageURL = jasmine.createSpy('getStaticImageURL');

    this.visDefinitionModel = new Backbone.Model({
      name: 'foo'
    });

    this._exportImageFormModel = new ExportImageFormModel({
      userModel: new Backbone.Model(),
      hasGoogleBasemap: false,
      format: '.png',
      x: 10,
      y: 100,
      width: 300,
      height: 200
    });

    Notifier.init({
      editorModel: new EditorModel(),
      visDefinitionModel: new Backbone.Model()
    });

    this._configModel = new ConfigModel({
      base_url: '/u/pepe'
    });

    this._userModel = new UserModel({
      username: 'perico',
      google_maps_key: 123456
    }, {
      configModel: this._configModel
    });

    this.mapDefinitionModel = new MapDefinitionModel({
      scrollwheel: false
    }, {
      parse: true,
      configModel: this._configModel,
      userModel: this._userModel,
      layerDefinitionsCollection: new Backbone.Collection()
    });

    this.mapDefinitionModel.setImageExportMetadata(this.imageExportMetadata);
    // this.mapDefinitionModel.staticImageURLTemplate = this.getStaticImageURL;
    spyOn(this.mapDefinitionModel, 'staticImageURLTemplate').and.returnValue(this.getStaticImageURL);
    spyOn(this.mapDefinitionModel, 'getMapViewSize').and.returnValue({
      x: 100,
      y: 100
    });

    this.mapDefinitionModel.pixelToLatLng = function (x, y) {
      return { lat: 123, lng: 456 };
    };

    this.mapDefinitionModel.latLngToPixel = function () {
      return { x: 100, y: 20 };
    };

    this.view = new ExportImagePane({
      canvasClassName: 'CDB-Map',
      configModel: this._configModel,
      userModel: this._userModel,
      stackLayoutModel: new Backbone.Collection(),
      editorModel: new Backbone.Model(),
      privacyCollection: new Backbone.Collection(),
      widgetDefinitionsCollection: new Backbone.Collection(),
      mapcapsCollection: new Backbone.Collection(),
      mapStackLayoutModel: new Backbone.Model(),
      stateDefinitionModel: new Backbone.Model(),
      visDefinitionModel: this.visDefinitionModel,
      mapDefinitionModel: this.mapDefinitionModel
    });

    // mocks
    this.view._loadLogo = function () { return true; };
    this.view._loadAttribution = function () { return true; };

    this.view.render();
  });

  it('should render properly', function () {
    expect(this.view.$el.length).toBe(1);
  });

  it('should call the method to generate the image', function () {
    this.view.$('.js-ok').click();

    expect(this.getStaticImageURL).toHaveBeenCalledWith({
      zoom: 10,
      width: 300,
      height: 200,
      lat: 123,
      lng: 456,
      format: 'png'
    });
  });

  describe('with google maps', function () {
    beforeEach(function () {
      this._exportImageFormModel = new ExportImageFormModel({
        userModel: new Backbone.Model(),
        hasGoogleBasemap: true,
        format: '.png',
        x: 10,
        y: 100,
        width: 300,
        height: 200
      });

      this.mapDefinitionModel.setImageExportMetadata(_.extend({}, this.imageExportMetadata, {
        provider: 'googlemaps'
      }));

      this.view = new ExportImagePane({
        canvasClassName: 'CDB-Map',
        configModel: this._configModel,
        userModel: this._userModel,
        stackLayoutModel: new Backbone.Collection(),
        editorModel: new Backbone.Model(),
        privacyCollection: new Backbone.Collection(),
        widgetDefinitionsCollection: new Backbone.Collection(),
        mapcapsCollection: new Backbone.Collection(),
        mapStackLayoutModel: new Backbone.Model(),
        stateDefinitionModel: new Backbone.Model(),
        visDefinitionModel: this.visDefinitionModel,
        mapDefinitionModel: this.mapDefinitionModel
      });

      // mocks
      this.view._loadLogo = function () { return true; };
      this.view._loadAttribution = function () { return true; };

      this.view.render();
    });

    it('should generate the gmaps url', function () {
      expect(this.view._getGMapBasemapURL()).toMatch('https:\/\/maps.googleapis.com/maps/api/staticmap\?center=123,456&zoom=10&size=300x200&mapType=roadmap&rnd=.*?&style=feature:all\|element:all\|saturation:-100\|&style=feature:water\|element:all\|gamma:1.67\|lightness:27\|&style=feature:all\|element:geometry\|gamma:1.31\|lightness:12\|&style=feature:administrative\|element:labels\|lightness:51\|gamma:0.94\|&style=feature:road\|element:labels\|lightness:57\|&style=feature:poi\|element:labels\|lightness:42\|&key=123456');
    });

    it('should call the google method to generate the image', function () {
      spyOn(this.view, '_getGMapBasemapURL').and.callThrough();

      this.view.$('.js-ok').click();
      expect(this.view._getGMapBasemapURL).toHaveBeenCalled();
    });
  });

  describe('with errors', function () {
    beforeEach(function () {
      this._exportImageFormModel = new ExportImageFormModel({
        userModel: new Backbone.Model(),
        hasGoogleBasemap: true,
        format: '.png',
        x: 10,
        y: 100,
        width: 300,
        height: 200
      });

      this._vis.map.set('provider', 'googlemaps');

      this.view = new ExportImagePane({
        canvasClassName: 'CDB-Map',
        configModel: this._configModel,
        userModel: this._userModel,
        stackLayoutModel: new Backbone.Collection(),
        editorModel: new Backbone.Model(),
        visDefinitionModel: new Backbone.Model()
      });

      // mocks
      this.view._loadLogo = function () {
        var deferred = $.Deferred();
        deferred.reject(_t('editor.maps.export-image.errors.error-attribution'));
        return deferred.promise();
      };

      this.view.render();
    });

    it('should show notification', function () {
      var notifications = Notifier.getCollection().length;
      this.view.$('.js-ok').click();
      expect(Notifier.getCollection().length).toBe(notifications + 1);
    });
  });

  afterEach(function () {
    this.view.remove();
  });
});
<commit_msg>Fix specs.<commit_after>var $ = require('jquery');
var Backbone = require('backbone');
var _ = require('underscore');
var ConfigModel = require('../../../../../javascripts/cartodb3/data/config-model');
var UserModel = require('../../../../../javascripts/cartodb3/data/user-model');
var ExportImagePane = require('../../../../../javascripts/cartodb3/editor/export-image-pane/export-image-pane');
var ExportImageFormModel = require('../../../../../javascripts/cartodb3/editor/export-image-pane/export-image-form-model');
var MapDefinitionModel = require('../../../../../javascripts/cartodb3/data/map-definition-model');
var Notifier = require('../../../../../javascripts/cartodb3/components/notifier/notifier');
var EditorModel = require('../../../../../javascripts/cartodb3/data/editor-model');

describe('editor/export-image-pane/export-image-pane', function () {
  beforeEach(function () {
    var style = '[{ "stylers": [ { "saturation": -100 } ] },{ "featureType": "water", "stylers": [ { "gamma": 1.67 }, { "lightness": 27 } ] },{ "elementType": "geometry", "stylers": [ { "gamma": 1.31 }, { "lightness": 12 } ] },{ "featureType": "administrative", "elementType": "labels", "stylers": [ { "lightness": 51 }, { "gamma": 0.94 } ] },{ },{ "featureType": "road", "elementType": "labels", "stylers": [ { "lightness": 57 } ] },{ "featureType": "poi", "elementType": "labels", "stylers": [ { "lightness": 42 } ] }]';

    this.imageExportMetadata = {
      zoom: 10,
      mapType: 'roadmap',
      style: style,
      attribution: 'CARTO',
      provider: 'no-googlemaps',
      title: 'wadus'
    };

    this.getStaticImageURL = jasmine.createSpy('getStaticImageURL');

    this.visDefinitionModel = new Backbone.Model({
      name: 'foo'
    });

    this._exportImageFormModel = new ExportImageFormModel({
      userModel: new Backbone.Model(),
      hasGoogleBasemap: false,
      format: '.png',
      x: 10,
      y: 100,
      width: 300,
      height: 200
    });

    Notifier.init({
      editorModel: new EditorModel(),
      visDefinitionModel: new Backbone.Model()
    });

    this._configModel = new ConfigModel({
      base_url: '/u/pepe'
    });

    this._userModel = new UserModel({
      username: 'perico',
      google_maps_key: 123456
    }, {
      configModel: this._configModel
    });

    this.mapDefinitionModel = new MapDefinitionModel({
      scrollwheel: false
    }, {
      parse: true,
      configModel: this._configModel,
      userModel: this._userModel,
      layerDefinitionsCollection: new Backbone.Collection()
    });

    this.mapDefinitionModel.setImageExportMetadata(this.imageExportMetadata);
    // this.mapDefinitionModel.staticImageURLTemplate = this.getStaticImageURL;
    spyOn(this.mapDefinitionModel, 'staticImageURLTemplate').and.returnValue(this.getStaticImageURL);
    spyOn(this.mapDefinitionModel, 'getMapViewSize').and.returnValue({
      x: 100,
      y: 100
    });

    this.mapDefinitionModel.pixelToLatLng = function (x, y) {
      return { lat: 123, lng: 456 };
    };

    this.mapDefinitionModel.latLngToPixel = function () {
      return { x: 100, y: 20 };
    };

    this.view = new ExportImagePane({
      canvasClassName: 'CDB-Map',
      configModel: this._configModel,
      userModel: this._userModel,
      stackLayoutModel: new Backbone.Collection(),
      editorModel: new Backbone.Model(),
      privacyCollection: new Backbone.Collection(),
      widgetDefinitionsCollection: new Backbone.Collection(),
      mapcapsCollection: new Backbone.Collection(),
      mapStackLayoutModel: new Backbone.Model(),
      stateDefinitionModel: new Backbone.Model(),
      visDefinitionModel: this.visDefinitionModel,
      mapDefinitionModel: this.mapDefinitionModel
    });

    // mocks
    this.view._loadLogo = function () { return true; };
    this.view._loadAttribution = function () { return true; };

    this.view.render();
  });

  it('should render properly', function () {
    expect(this.view.$el.length).toBe(1);
  });

  it('should call the method to generate the image', function () {
    this.view.$('.js-ok').click();

    expect(this.getStaticImageURL).toHaveBeenCalledWith({
      zoom: 10,
      width: 300,
      height: 200,
      lat: 123,
      lng: 456,
      format: 'png'
    });
  });

  describe('with google maps', function () {
    beforeEach(function () {
      this._exportImageFormModel = new ExportImageFormModel({
        userModel: new Backbone.Model(),
        hasGoogleBasemap: true,
        format: '.png',
        x: 10,
        y: 100,
        width: 300,
        height: 200
      });

      this.mapDefinitionModel.setImageExportMetadata(_.extend({}, this.imageExportMetadata, {
        provider: 'googlemaps'
      }));

      this.view = new ExportImagePane({
        canvasClassName: 'CDB-Map',
        configModel: this._configModel,
        userModel: this._userModel,
        stackLayoutModel: new Backbone.Collection(),
        editorModel: new Backbone.Model(),
        privacyCollection: new Backbone.Collection(),
        widgetDefinitionsCollection: new Backbone.Collection(),
        mapcapsCollection: new Backbone.Collection(),
        mapStackLayoutModel: new Backbone.Model(),
        stateDefinitionModel: new Backbone.Model(),
        visDefinitionModel: this.visDefinitionModel,
        mapDefinitionModel: this.mapDefinitionModel
      });

      // mocks
      this.view._loadLogo = function () { return true; };
      this.view._loadAttribution = function () { return true; };

      this.view.render();
    });

    it('should generate the gmaps url', function () {
      expect(this.view._getGMapBasemapURL()).toMatch('https:\/\/maps.googleapis.com/maps/api/staticmap\?center=123,456&zoom=10&size=300x200&mapType=roadmap&rnd=.*?&style=feature:all\|element:all\|saturation:-100\|&style=feature:water\|element:all\|gamma:1.67\|lightness:27\|&style=feature:all\|element:geometry\|gamma:1.31\|lightness:12\|&style=feature:administrative\|element:labels\|lightness:51\|gamma:0.94\|&style=feature:road\|element:labels\|lightness:57\|&style=feature:poi\|element:labels\|lightness:42\|&key=123456');
    });

    it('should call the google method to generate the image', function () {
      spyOn(this.view, '_getGMapBasemapURL').and.callThrough();

      this.view.$('.js-ok').click();
      expect(this.view._getGMapBasemapURL).toHaveBeenCalled();
    });
  });

  describe('with errors', function () {
    beforeEach(function () {
      this._exportImageFormModel = new ExportImageFormModel({
        userModel: new Backbone.Model(),
        hasGoogleBasemap: true,
        format: '.png',
        x: 10,
        y: 100,
        width: 300,
        height: 200
      });

      this.view = new ExportImagePane({
        canvasClassName: 'CDB-Map',
        configModel: this._configModel,
        userModel: this._userModel,
        stackLayoutModel: new Backbone.Collection(),
        editorModel: new Backbone.Model(),
        privacyCollection: new Backbone.Collection(),
        widgetDefinitionsCollection: new Backbone.Collection(),
        mapcapsCollection: new Backbone.Collection(),
        mapStackLayoutModel: new Backbone.Model(),
        stateDefinitionModel: new Backbone.Model(),
        visDefinitionModel: this.visDefinitionModel,
        mapDefinitionModel: this.mapDefinitionModel
      });

      // mocks
      this.view._loadLogo = function () {
        var deferred = $.Deferred();
        deferred.reject(_t('editor.maps.export-image.errors.error-attribution'));
        return deferred.promise();
      };

      this.view.render();
    });

    it('should show notification', function () {
      var notifications = Notifier.getCollection().length;
      this.view.$('.js-ok').click();
      expect(Notifier.getCollection().length).toBe(notifications + 1);
    });
  });

  afterEach(function () {
    this.view.remove();
  });
});
<|endoftext|>


<commit_before>import { shallowMount, mount } from '@vue/test-utils';
import QuotaWidget from 'new-dashboard/pages/Home/QuotaSection/QuotaWidget';

describe('QuotaWidget.vue', () => {
  it('should render correct contents', () => {
    const quotaWidget = shallowMount(QuotaWidget, {
      propsData: {
        name: 'Widget test',
        quotaType: 'quota',
        availableQuota: 1234,
        usedQuota: 40,
        unit: 'GB' }
    });

    expect(quotaWidget).toMatchSnapshot();
  });

  it('should render correctly compact widget', () => {
    const quotaWidget = shallowMount(QuotaWidget, {
      propsData: {
        name: 'Another test',
        quotaType: 'credits',
        availableQuota: 1234,
        usedQuota: 12,
        mode: 'compact'
      }
    });

    expect(quotaWidget).toMatchSnapshot();
  });

  it('should return 0 if the storage remaining quota is negative', () => {
    const quotaWidget = mount(QuotaWidget, {
      propsData: {
        name: 'Another test',
        quotaType: 'quota',
        availableQuota: 262144000,
        usedQuota: 289722368,
        unit: 'MB'
      }
    });

    let remainingQuota = quotaWidget.vm.remainingQuota;
    expect(remainingQuota).toBe(0);
  });

  it('should render warning bar if used ammount is over 60% and under 90%', () => {
    const quotaWidget = shallowMount(QuotaWidget, {
      propsData: {
        name: 'My test',
        quotaType: 'test',
        availableQuota: 1000,
        usedQuota: 610
      }
    });

    expect(quotaWidget).toMatchSnapshot();
  });

  it('should render problem bar if used ammount is over 90%', () => {
    const quotaWidget = shallowMount(QuotaWidget, {
      propsData: {
        name: 'My test',
        quotaType: 'test',
        availableQuota: 1000,
        usedQuota: 932
      }
    });

    expect(quotaWidget).toMatchSnapshot();
  });

  describe('Computed', () => {
    it('should return correct percent', () => {
      const quotaWidget = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 350,
          usedQuota: 224
        }
      });

      const percent = quotaWidget.vm.getUsedPercent;

      expect(percent).toBe(64);
    });

    it('should not crash and return 100 when the available capacity is 0', () => {
      const quotaWidget = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 0,
          usedQuota: 0
        }
      });

      const percent = quotaWidget.vm.getUsedPercent;

      expect(percent).toBe(100);
    });

    it('should return correct status of the quota', () => {
      const quotaWidgetGood = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 100,
          usedQuota: 50
        }
      });
      const quotaWidgetWarning = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 100,
          usedQuota: 61.4
        }
      });
      const quotaWidgetProblem = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 100,
          usedQuota: 90.1
        }
      });

      const statusGood = quotaWidgetGood.vm.getStatusBar;
      const statusWarning = quotaWidgetWarning.vm.getStatusBar;
      const statusProblem = quotaWidgetProblem.vm.getStatusBar;

      expect(statusGood).toBe('good');
      expect(statusWarning).toBe('warning');
      expect(statusProblem).toBe('problem');
    });
  });

  describe('Methods', () => {
    it('should round decimals correctly correct percent', () => {
      const quotaWidget = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 350,
          usedQuota: 224
        }
      });

      const value1 = quotaWidget.vm.roundOneDecimal(0);
      const value2 = quotaWidget.vm.roundOneDecimal(0.1234);
      const value3 = quotaWidget.vm.roundOneDecimal(120.2);
      const value4 = quotaWidget.vm.roundOneDecimal(129.0);
      const value5 = quotaWidget.vm.roundOneDecimal(45.8);
      const value6 = quotaWidget.vm.roundOneDecimal(45.26);

      expect(String(value1)).toBe('0');
      expect(String(value2)).toBe('0.1');
      expect(String(value3)).toBe('120.2');
      expect(String(value4)).toBe('129');
      expect(String(value5)).toBe('45.8');
      expect(String(value6)).toBe('45.3');
    });
  });
});
<commit_msg>Remove mount from quota widget tests<commit_after>import { shallowMount } from '@vue/test-utils';
import QuotaWidget from 'new-dashboard/pages/Home/QuotaSection/QuotaWidget';

describe('QuotaWidget.vue', () => {
  it('should render correct contents', () => {
    const quotaWidget = shallowMount(QuotaWidget, {
      propsData: {
        name: 'Widget test',
        quotaType: 'quota',
        availableQuota: 1234,
        usedQuota: 40,
        unit: 'GB' }
    });

    expect(quotaWidget).toMatchSnapshot();
  });

  it('should render correctly compact widget', () => {
    const quotaWidget = shallowMount(QuotaWidget, {
      propsData: {
        name: 'Another test',
        quotaType: 'credits',
        availableQuota: 1234,
        usedQuota: 12,
        mode: 'compact'
      }
    });

    expect(quotaWidget).toMatchSnapshot();
  });

  it('should return 0 if the storage remaining quota is negative', () => {
    const quotaWidget = shallowMount(QuotaWidget, {
      propsData: {
        name: 'Another test',
        quotaType: 'quota',
        availableQuota: 262144000,
        usedQuota: 289722368,
        unit: 'MB'
      }
    });

    let remainingQuota = quotaWidget.vm.remainingQuota;
    expect(remainingQuota).toBe(0);
  });

  it('should render warning bar if used ammount is over 60% and under 90%', () => {
    const quotaWidget = shallowMount(QuotaWidget, {
      propsData: {
        name: 'My test',
        quotaType: 'test',
        availableQuota: 1000,
        usedQuota: 610
      }
    });

    expect(quotaWidget).toMatchSnapshot();
  });

  it('should render problem bar if used ammount is over 90%', () => {
    const quotaWidget = shallowMount(QuotaWidget, {
      propsData: {
        name: 'My test',
        quotaType: 'test',
        availableQuota: 1000,
        usedQuota: 932
      }
    });

    expect(quotaWidget).toMatchSnapshot();
  });

  describe('Computed', () => {
    it('should return correct percent', () => {
      const quotaWidget = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 350,
          usedQuota: 224
        }
      });

      const percent = quotaWidget.vm.getUsedPercent;

      expect(percent).toBe(64);
    });

    it('should not crash and return 100 when the available capacity is 0', () => {
      const quotaWidget = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 0,
          usedQuota: 0
        }
      });

      const percent = quotaWidget.vm.getUsedPercent;

      expect(percent).toBe(100);
    });

    it('should return correct status of the quota', () => {
      const quotaWidgetGood = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 100,
          usedQuota: 50
        }
      });
      const quotaWidgetWarning = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 100,
          usedQuota: 61.4
        }
      });
      const quotaWidgetProblem = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 100,
          usedQuota: 90.1
        }
      });

      const statusGood = quotaWidgetGood.vm.getStatusBar;
      const statusWarning = quotaWidgetWarning.vm.getStatusBar;
      const statusProblem = quotaWidgetProblem.vm.getStatusBar;

      expect(statusGood).toBe('good');
      expect(statusWarning).toBe('warning');
      expect(statusProblem).toBe('problem');
    });
  });

  describe('Methods', () => {
    it('should round decimals correctly correct percent', () => {
      const quotaWidget = shallowMount(QuotaWidget, {
        propsData: {
          name: 'My test',
          quotaType: 'test',
          availableQuota: 350,
          usedQuota: 224
        }
      });

      const value1 = quotaWidget.vm.roundOneDecimal(0);
      const value2 = quotaWidget.vm.roundOneDecimal(0.1234);
      const value3 = quotaWidget.vm.roundOneDecimal(120.2);
      const value4 = quotaWidget.vm.roundOneDecimal(129.0);
      const value5 = quotaWidget.vm.roundOneDecimal(45.8);
      const value6 = quotaWidget.vm.roundOneDecimal(45.26);

      expect(String(value1)).toBe('0');
      expect(String(value2)).toBe('0.1');
      expect(String(value3)).toBe('120.2');
      expect(String(value4)).toBe('129');
      expect(String(value5)).toBe('45.8');
      expect(String(value6)).toBe('45.3');
    });
  });
});
<|endoftext|>


<commit_before>/**
 * @author César Costas Carrera
 *
 * CURRICULUM VITAE
 */

export const cv = {
  name: 'César Costas Carrera',
  role: 'JavaScript Engineer',
  color: '#84425d',
  contact: {
    phone: '+447920158374',
    email: 'costascarrera.cesar@gmail.com',
    sites: [
      '//github.com/gatsbimantico',
      '//stackoverflow.com/users/2308745/gatsbimantico'
    ],
    skype: 'https://join.skype.com/invite/nVxw0Bw8XoyS'
  },
  summary: `
I am a frontend web developer with over ${Math.floor((new Date() - new Date('11/15/2013')) / 3.1536e+9) / 10} years of experience.

My passion for frontend development begun through data visualization working as a data analyst in a research project.
Since then I've worked on high traffic sites for brands like Loveholidays, Volkswagen or Pull&Bear.

I'm fully competent with modern frameworks (like React, Angular and Polymer), frontend utilities (like NodeJs, Gulp, Git, Docker), testing (functional and A/B tests) and agile methodologies (like Scrum or Khanban).
	`,
  skills: [
    'Frontend enthusiast',
    'Fast learner',
    'Team builder',
    'Agile developer'
  ],
  experiences: [{

    is: 'Frontend web developer',
    at: 'Loveholidays',
    on: { start: '2018 May', end: "now" },
    tools: {
      main: [
        { name: 'React', version: 16 },
        { name: 'Redux' },
        { name: 'redux-sagas' },
        { name: 'Sass' },
        { name: 'styled-components' },
        { name: 'Jest' },
        { name: 'Karma' },
        { name: 'Git' },
        { name: 'Optimizely' },
        { name: 'Google Analytics' },
        { name: 'Google Optimize' }
      ],
    },
    portfolio: {
      released: [
        'https://Loveholidays.com'
      ]
    },
    description: `
During my first year in the company, Loveholidays became
one of the UK's top 100 private companies with the fastest-growing profit.

After a couple of moths working at Loveholidays,
two colleagues and I started the experiments team; where we design,
code and excute A/B and MV tests to bring new features
in a lean, continuous & fast paced delivery.

Since the begning my focus was on reinforcing two pilars:
Speed: by updating the Jira workflow to optimize communication and diminish interruptions and dashboards summarizing Github PR's status.
And Quality and standards: by adding and creating linting rules to reinforce standards, producing less conflicts and reducing the code review feedback.

A list to which lately I added: user segmentation, live style guide, server-side rendering and process automation.
Voted internally the best team of 2018, I guided the experiments team since its conception to its current 10 developers.
    `
  }, {

    was: 'Frontend web developer',
    at: 'Tribal worldwide',
    on: { start: '2017 Jan', end: '2018 May' },
    tools: {
      main: [
        { name: 'Polymer', version: '1' },
        { name: 'Redux' },
        { name: 'Mocha' },
        { name: 'Sass' },
        { name: 'Git' },
        { name: 'Gulp', version: '4' },
        { name: 'jQuery', version: '3' }
      ],
      others: [
        { name: 'Node', version: '6' },
        'Handlebars',
        'Wordpress',
        'Aem',
        'Jahia'
      ]
    },
    portfolio: {
      released: [
        'http://Lipton.com',
        'http://CarteDor.co.uk/maisoncartedor',
        'http://Volkswagen.co.uk'
      ]
    },
    description: `
Tribal is an hybrid Ad-Development agency that provides websites to their advertising clients.
Each project is handled by a small team of 2 to 4 specialists.

We use gulp and git for the frontend, then the backend supplies the data.
I managed front-end development using my expertise in Polymer, jQuery and Handlebars, or pure HTML and CSS.
Also had to work with AEM and Jahia.

The workflow is a ticket based board with JIRA and Scrum.
The goal is to provide the maximum value within the estimated time for the ticket.
The time management is done in JIRA and Projector PSA.
    `

  }, {

    was: 'Full stack web developer',
    at: 'Imatia innovation',
    on: { start: '2016 Jun', end: '2016 Dec' },
    tools: {
      main: [
        { name: 'Angular', version: '2' },
        { name: 'TypeScript', version: '2' },
        { name: 'Sass' },
        { name: 'Git' },
        { name: 'Docker' },
        { name: 'Gulp', version: '3' }
      ],
      others: [
        { name: 'Node', version: '6' },
        'Java',
        'Scala',
        'Hadoop'
      ]
    },
    description: `
At Imatia I worked on the design and implementation of a business analytics dashboard for local administrations.

Working with a team of 4 developers, I defined and implemented the data workflow and its visualization.

I used Docker and Jekins for automatic deploy, Java to serve REST decoupled services
and Angular2 with TypeScript for the web components based front-end development.

The workflow was to acomplish the tasks written on Redmine by the project manager,
commit the work every time it reaches a stable status
and to keep documented the time invested in every task through Elastic Projects.
    `

  }, {

    was: 'Full stack web developer',
    at: 'ViewNext',
    on: { start: '2015 Jun', end: '2016 Jun' },
    tools: {
      main: [
        { name: 'JSP' },
        { name: 'HTML', version: '4' },
        { name: 'CSS', version: '2' },
        { name: 'SVN' },
        { name: 'jQuery', version: '1' }
      ],
      others: [
        { name: 'Angular', version: '1' },
        { name: 'Backbone' },
        'Java',
        'DB2',
        'Jira'
      ]
    },
    portfolio: {
      dismissed: [
        '//MassimoDutti.com',
        '//Oysho.com',
        '//PullAndBear.com',
        '//ZaraHome.com',
        '//Stradivarius.com',
        '//Bershka.com',
        '//Uterque.com',
        '//Lefties.com'
      ]
    },
    description: `
I managed web maintenance for a range of international Inditex's websites including
MassimoDutti, ZaraHome, Oysho, Bershka, Stradivarius and PullAndBear.

I worked within a team of about 20 developers, 15 testers and 10 project managers,
with some remote team memebers.

I used IBM tools for the backend and jquery for the frontend.
While I was working there, we were migrating the web
to use a decoupled backend via REST and migrating to Angular1.4

The workflow was to implement the task,
written and assigned in Jira by one of the Projects Managers,
commit the code only once its finished and tested in a local machine,
then review the bugs found by the testers team
and submit the time spent on every task through an internal tool.
    `

  }, {

    was: 'Full stack web developer',
    at: 'University of Vigo',
    on: { start: '2013 Nov', end: '2015 May' },
    tools: {
      main: [
        { name: 'Ruby-on-Rails' },
        { name: 'HTML', version: '5' },
        { name: 'CSS', version: '3' },
        { name: 'D3' },
        { name: 'jQuery', version: '1' }
      ],
      others: [
        'Java',
        'Weka',
        'RapidMiner',
        'Python'
      ]
    },
    description: `
After a brief attempt of being a entrepreneur with a social e-classroom website
and due my background with data mining,

I worked for UVigo on a state-of-the-art
cloud based recommendation and prediction dashboard for e-learning.

We were a small team of 3 developers and 2 researchers,
my job goal was focused on data mining and data visualization.

We used D3js for the graphs and Undescore and Crossfilter to manage the data.

We were using Scrum, with daily and weekly meetings,
so the workflow was to write and select the tasks with the client,
choose my own tasks, commit soon even unfinished
and register the time I spend in every task.
    `

  }],
  education: [{

    titles: [
      'Time Management',
      'How to speak in public',
      'Strength Deployment Inventory'
    ], at: 'Tribal', on: '2018 - 2017'

  }, {

    titles: [
      'Full Stack Web Development especialization'
    ], at: 'Coursera', on: 2016

  }, {

    titles: [
      'Angular',
      'TDD',
      'eCommerce',
      'JavaEE'
    ], at: 'ViewNext', on: 2015

  }, {

    titles: [
      'Intro to Machine Learning'
    ], at: 'Udacity', on: 2012

  }, {

    titles: [
      'BSC. Physics'
    ], at: 'University of Vigo', on: 2005

  }]
};
<commit_msg>Upgrade job descriptions with personal contributions<commit_after>/**
 * @author César Costas Carrera
 *
 * CURRICULUM VITAE
 */

export const cv = {
  name: 'César Costas Carrera',
  role: 'JavaScript Engineer',
  color: '#84425d',
  contact: {
    phone: '+447920158374',
    email: 'costascarrera.cesar@gmail.com',
    sites: [
      '//github.com/gatsbimantico',
      '//stackoverflow.com/users/2308745/gatsbimantico'
    ],
    skype: 'https://join.skype.com/invite/nVxw0Bw8XoyS'
  },
  summary: `
I am a frontend web developer with over ${Math.floor((new Date() - new Date('11/15/2013')) / 3.1536e+9) / 10} years of experience.

My passion for frontend development begun through data visualization working as a data analyst in a research project.
Since then I've worked on high traffic sites for brands like Loveholidays, Volkswagen or Pull&Bear.

I'm fully competent with modern frameworks (like React, Angular and Polymer), frontend utilities (like NodeJs, Gulp, Git, Docker), testing (functional and A/B tests) and agile methodologies (like Scrum or Khanban).
	`,
  skills: [
    'Frontend enthusiast',
    'Fast learner',
    'Team builder',
    'Agile developer'
  ],
  experiences: [{

    is: 'Frontend web developer',
    at: 'Loveholidays',
    on: { start: '2018 May', end: "now" },
    tools: {
      main: [
        { name: 'React', version: 16 },
        { name: 'Redux' },
        { name: 'redux-sagas' },
        { name: 'Sass' },
        { name: 'styled-components' },
        { name: 'Jest' },
        { name: 'Karma' },
        { name: 'Git' },
        { name: 'Optimizely' },
        { name: 'Google Analytics' },
        { name: 'Google Optimize' }
      ],
    },
    portfolio: {
      released: [
        'https://Loveholidays.com'
      ]
    },
    description: `
During my first year in the company, Loveholidays became
one of the UK's top 100 private companies with the fastest-growing profit.

After a couple of moths working at Loveholidays,
two colleagues and I started the experiments team; where we design,
code and excute A/B and MV tests to bring new features
in a lean, continuous & fast paced delivery.

Since the begining my focus was on reinforcing two pilars:
Speed: by updating the Jira workflow to optimize communication and diminish interruptions and dashboards summarizing Github PR's status.
And Quality and standards: by adding and creating linting rules to reinforce standards, producing less conflicts and reducing the code review feedback.

A list to which lately I added: user segmentation, live style guide, server-side rendering and process automation.
Voted internally the best team of 2018, I guided the experiments team since its conception to its current 10 developers.
    `
  }, {

    was: 'Frontend web developer',
    at: 'Tribal worldwide',
    on: { start: '2017 Jan', end: '2018 May' },
    tools: {
      main: [
        { name: 'Polymer', version: '1' },
        { name: 'Redux' },
        { name: 'Mocha' },
        { name: 'Sass' },
        { name: 'Git' },
        { name: 'Gulp', version: '4' },
        { name: 'jQuery', version: '3' }
      ],
      others: [
        { name: 'Node', version: '6' },
        'Handlebars',
        'Wordpress',
        'Aem',
        'Jahia',
        'Jira'
      ]
    },
    portfolio: {
      released: [
        'http://Lipton.com',
        'http://CarteDor.co.uk/maisoncartedor',
        'http://Volkswagen.co.uk'
      ]
    },
    description: `
Tribal is an hybrid Ad-Development agency that provides websites to their advertising clients.
Each project is handled by a small team of 1 to 4 specialists.

At Tribal I contributed to Wordpress, AEM and Polymer proyects, while mentoring a junior and an intern developers.

I took the lead rooting for reusable components, unit tests and understanding the problem and client needs before coding.
    `

  }, {

    was: 'Full stack web developer',
    at: 'Imatia innovation',
    on: { start: '2016 Jun', end: '2016 Dec' },
    tools: {
      main: [
        { name: 'Angular', version: '2' },
        { name: 'TypeScript', version: '2' },
        { name: 'Sass' },
        { name: 'Git' },
        { name: 'Docker' },
        { name: 'Gulp', version: '3' }
      ],
      others: [
        { name: 'Node', version: '6' },
        'Java',
        'Scala',
        'Hadoop'
      ]
    },
    description: `
At Imatia I worked on the design and implementation of a business analytics dashboard for local administrations.

Working with a team of 4 developers, I defined and implemented the data workflow and its visualization.

I used Docker and Jekins for automatic deploy, Java to serve REST decoupled services
and Angular2 with TypeScript for the web components based front-end development.

The workflow was to acomplish the tasks written on Redmine by the project manager,
commit the work every time it reaches a stable status
and to keep documented the time invested in every task through Elastic Projects.
    `

  }, {

    was: 'Full stack web developer',
    at: 'ViewNext',
    on: { start: '2015 Jun', end: '2016 Jun' },
    tools: {
      main: [
        { name: 'JSP' },
        { name: 'HTML', version: '4' },
        { name: 'CSS', version: '2' },
        { name: 'SVN' },
        { name: 'jQuery', version: '1' }
      ],
      others: [
        { name: 'Angular', version: '1' },
        { name: 'Backbone' },
        'Java',
        'DB2',
        'Jira'
      ]
    },
    portfolio: {
      dismissed: [
        '//MassimoDutti.com',
        '//Oysho.com',
        '//PullAndBear.com',
        '//ZaraHome.com',
        '//Stradivarius.com',
        '//Bershka.com',
        '//Uterque.com',
        '//Lefties.com'
      ]
    },
    description: `
I managed web maintenance for a range of international Inditex's websites including
MassimoDutti, ZaraHome, Oysho, Bershka, Stradivarius and PullAndBear.

I worked within a team of about 20 developers, 15 testers and 10 project managers,
with some remote team memebers.

I used IBM tools for the backend and jquery for the frontend.
While I was working there, we were migrating the web
to use a decoupled backend via REST and migrating to Angular1.4

The workflow was to implement the task,
written and assigned in Jira by one of the Projects Managers,
commit the code only once its finished and tested in a local machine,
then review the bugs found by the testers team
and submit the time spent on every task through an internal tool.

During my work at ViewNext, I developed an internal static site meant to share knowledge and scripting tools for the distributed team. I mentored two junior developers and three interns.
    `

  }, {

    was: 'Full stack web developer',
    at: 'University of Vigo',
    on: { start: '2013 Nov', end: '2015 May' },
    tools: {
      main: [
        { name: 'Ruby-on-Rails' },
        { name: 'HTML', version: '5' },
        { name: 'CSS', version: '3' },
        { name: 'D3' },
        { name: 'jQuery', version: '1' }
      ],
      others: [
        'Java',
        'Weka',
        'RapidMiner',
        'Python'
      ]
    },
    description: `
After a brief attempt of being a entrepreneur with a social e-classroom website
and due my background with data mining,

I worked for UVigo on a state-of-the-art
cloud based recommendation and prediction dashboard for e-learning.

We were a small team of 3 developers and 2 researchers,
my job goal was focused on data mining and data visualization.

We used D3js for the graphs and Undescore and Crossfilter to manage the data.

We were using Scrum, with daily meetings and weekly sprints.
    `

  }],
  education: [{

    titles: [
      'Time Management',
      'How to speak in public',
      'Strength Deployment Inventory'
    ], at: 'Tribal', on: '2018 - 2017'

  }, {

    titles: [
      'Full Stack Web Development especialization'
    ], at: 'Coursera', on: 2016

  }, {

    titles: [
      'Angular',
      'TDD',
      'eCommerce',
      'JavaEE'
    ], at: 'ViewNext', on: 2015

  }, {

    titles: [
      'Intro to Machine Learning'
    ], at: 'Udacity', on: 2012

  }, {

    titles: [
      'BSC. Physics'
    ], at: 'University of Vigo', on: 2005

  }]
};
<|endoftext|>


<commit_before>var ui = {};

window.onload = function () {
    ui.main = document.querySelector('main');
    ui.select = document.getElementById('select-template').querySelector('form');
    ui.playlist = document.getElementById('playlist-template').querySelector('div');
    ui.header = document.querySelector('.page-head');

    api.authorize().then(letsParty);
}

function elt(name) {
    if (!(this instanceof elt)) return new elt(name);
    this.node = document.createElement(name);
};

elt.prototype.attr = function (attributes, value) {
    if (value) {
        this.node.setAttribute(attributes, value);
    } else {
        for (var attrName in attributes) {
            this.node.setAttribute(attrName, attributes[attrName]);
        }
    }

    return this;
};

elt.prototype.prop = function (properties, value) {
    if (value) {
        this.node[properties] = value;
    } else {
        for (var propName in properties) {
            this.node[propName] = properties[propName];
        }
    }

    return this;
}

function letsParty(playlists) {
    var form = ui.select.cloneNode(true);
    var previousSelection = JSON.parse(localStorage['player.previousSelection']);

    playlists.forEach(function (playlist) {
        var snippet = playlist.snippet;

        var checkbox = elt('input').attr({
            type: 'checkbox',
            id: playlist.id,
            name: playlist.id
        }).prop('checked', ~previousSelection.indexOf(playlist.id)).node;
        var label = elt('label').prop({
            innerHTML: `<span>${snippet.title}</span>`,
            htmlFor: playlist.id
        }).node;

        form.appendChild(label).appendChild(checkbox);
    });

    form.appendChild(elt('button')
        .attr('type', 'submit')
        .prop('innerHTML', '►').node);
    ui.header.appendChild(form);

    form.onsubmit = function (e) {
        e.preventDefault();

        this.querySelector('button').disabled = true;
        this.classList.add('loading');

        var selected = Array.from(this.querySelectorAll('input')).filter(function (check) {
            return check.checked;
        }).map(function (check) {
            return check.name;
        });

        localStorage['player.previousSelection'] = JSON.stringify(selected);

        startTheMusic(selected);
    };
};

const youtube_uri = 'https://youtube.com/watch?v=';
const embed_uri = 'https://youtube.com/embed/';

function shuffle(array) {
    var counter = array.length, temp, index;

    while (counter > 0) {
        index = Math.floor(Math.random() * counter);
        counter--;

        temp = array[counter];
        array[counter] = array[index];
        array[index] = temp;
    }

    return array;
};

function grouped(array, size) {
    return array.map(function (item, index) {
        return index % size === 0 ? array.slice(index, index + size) : null;
    }).filter(function (item) {
        return !!item;
    });
}

var player;

function ytStateChange(evt, next) {
    console.log(evt);

    if (evt.data !== YT.PlayerState.ENDED)
        return

    var playing = document.querySelector('.is-playing');

    playing.querySelector('span').remove();

    playing.querySelector('.video-wrapper')
        .classList.remove('revealed');

    next = next || document.querySelector('.is-playing')
    .nextElementSibling;

    var wrapper = next.querySelector('.video-wrapper');
    var iframe = document.getElementById('player');
    wrapper.appendChild(iframe);

    iframe.src = iframe.src.replace(
        playing.querySelector('.video-wrapper').dataset.id,
        wrapper.dataset.id);

    player = new YT.Player('player', {
        events: {
            onReady: ytReady,
            onStateChange: ytStateChange
        }
    });
};

function revealPlayer(evt) {
    evt.preventDefault();
    evt.stopPropagation();
    document.querySelector('.is-playing')
        .querySelector('.video-wrapper')
        .classList.toggle('revealed');
}

function ytReady(evt) {
    var playing = document.querySelector('.is-playing');
    playing && playing.classList.remove('is-playing');

    var item = evt.target.f.parentNode.parentNode;
    item.classList.add('is-playing');

    var title = item.querySelector('h2');
    document.title = title.textContent;
    var arrow = elt('span').prop('innerHTML', '▾').node;
    arrow.onclick = revealPlayer;
    title.appendChild(arrow);

    evt.target.playVideo();

}

function startTheMusic(ids) {
    var container = ui.main; //ui.playlist.cloneNode(true);

    api.items(ids).then(function (videoIDs) {

        videoIDs = shuffle(videoIDs);

        videoIDs.forEach(function (id) {
            var wrapper = elt('div').attr('class', 'video-item').node;
            var link = elt('a').attr('href', youtube_uri + id).node;

            var videoWrap = elt('div').attr({
                'class': 'video-wrapper',
                'data-id': id
            }).node;

            wrapper.appendChild(link);
            wrapper.appendChild(videoWrap);
            container.appendChild(wrapper);
        });

        var groups = grouped(videoIDs, 50);
        return Promise.all(groups.map(api.videoInfo));
    }).then(function (videos) {
        videos = api.flatten(videos);

        var titles = [];
        videos.filter(function (video) {
            return video.snippet;
        }).forEach(function (video) {
            titles.push(video.snippet.title);
        });

        var wrappers = container.querySelectorAll('a');
        titles.forEach(function (title, index) {
            var videoTitle = elt('h2').prop('innerHTML', title).node;
            wrappers[index].appendChild(videoTitle);
        });

        //ui.main.appendChild(container);

        var wrapper = document.querySelector('.video-wrapper');
        wrapper.appendChild(document.getElementById('player'));

        player = new YT.Player('player', {
            videoId: wrapper.dataset['id'],
            events: {
                onReady: ytReady,
                onStateChange: ytStateChange
            }
        });

        document.querySelector('form').classList.remove('loading');

        window.addEventListener('click', function (evt) {
            if (evt.target.parentNode.parentNode == document.querySelector('is-playing'))
                return;

            var self = evt.target;
            if (self.nodeName === 'H2') {
                if (~[0, 1].indexOf(evt.button)) {
                    evt.preventDefault();
                    evt.stopPropagation();

                    if (evt.button === 0) {
                        ytStateChange({ data: YT.PlayerState.ENDED }, self.parentNode.parentNode);
                    } else if (evt.button === 1) {
                        window.open(self.parentNode.href, '_blank');
                    }
                }

            }
        }, true);

    });
}<commit_msg>remove console.log<commit_after>var ui = {};

window.onload = function () {
    ui.main = document.querySelector('main');
    ui.select = document.getElementById('select-template').querySelector('form');
    ui.playlist = document.getElementById('playlist-template').querySelector('div');
    ui.header = document.querySelector('.page-head');

    api.authorize().then(letsParty);
}

function elt(name) {
    if (!(this instanceof elt)) return new elt(name);
    this.node = document.createElement(name);
};

elt.prototype.attr = function (attributes, value) {
    if (value) {
        this.node.setAttribute(attributes, value);
    } else {
        for (var attrName in attributes) {
            this.node.setAttribute(attrName, attributes[attrName]);
        }
    }

    return this;
};

elt.prototype.prop = function (properties, value) {
    if (value) {
        this.node[properties] = value;
    } else {
        for (var propName in properties) {
            this.node[propName] = properties[propName];
        }
    }

    return this;
}

function letsParty(playlists) {
    var form = ui.select.cloneNode(true);
    var previousSelection = JSON.parse(localStorage['player.previousSelection']);

    playlists.forEach(function (playlist) {
        var snippet = playlist.snippet;

        var checkbox = elt('input').attr({
            type: 'checkbox',
            id: playlist.id,
            name: playlist.id
        }).prop('checked', ~previousSelection.indexOf(playlist.id)).node;
        var label = elt('label').prop({
            innerHTML: `<span>${snippet.title}</span>`,
            htmlFor: playlist.id
        }).node;

        form.appendChild(label).appendChild(checkbox);
    });

    form.appendChild(elt('button')
        .attr('type', 'submit')
        .prop('innerHTML', '►').node);
    ui.header.appendChild(form);

    form.onsubmit = function (e) {
        e.preventDefault();

        this.querySelector('button').disabled = true;
        this.classList.add('loading');

        var selected = Array.from(this.querySelectorAll('input')).filter(function (check) {
            return check.checked;
        }).map(function (check) {
            return check.name;
        });

        localStorage['player.previousSelection'] = JSON.stringify(selected);

        startTheMusic(selected);
    };
};

const youtube_uri = 'https://youtube.com/watch?v=';
const embed_uri = 'https://youtube.com/embed/';

function shuffle(array) {
    var counter = array.length, temp, index;

    while (counter > 0) {
        index = Math.floor(Math.random() * counter);
        counter--;

        temp = array[counter];
        array[counter] = array[index];
        array[index] = temp;
    }

    return array;
};

function grouped(array, size) {
    return array.map(function (item, index) {
        return index % size === 0 ? array.slice(index, index + size) : null;
    }).filter(function (item) {
        return !!item;
    });
}

var player;

function ytStateChange(evt, next) {
    if (evt.data !== YT.PlayerState.ENDED)
        return

    var playing = document.querySelector('.is-playing');

    playing.querySelector('span').remove();

    playing.querySelector('.video-wrapper')
        .classList.remove('revealed');

    next = next || document.querySelector('.is-playing')
    .nextElementSibling;

    var wrapper = next.querySelector('.video-wrapper');
    var iframe = document.getElementById('player');
    wrapper.appendChild(iframe);

    iframe.src = iframe.src.replace(
        playing.querySelector('.video-wrapper').dataset.id,
        wrapper.dataset.id);

    player = new YT.Player('player', {
        events: {
            onReady: ytReady,
            onStateChange: ytStateChange
        }
    });
};

function revealPlayer(evt) {
    evt.preventDefault();
    evt.stopPropagation();
    document.querySelector('.is-playing')
        .querySelector('.video-wrapper')
        .classList.toggle('revealed');
}

function ytReady(evt) {
    var playing = document.querySelector('.is-playing');
    playing && playing.classList.remove('is-playing');

    var item = evt.target.f.parentNode.parentNode;
    item.classList.add('is-playing');

    var title = item.querySelector('h2');
    document.title = title.textContent;
    var arrow = elt('span').prop('innerHTML', '▾').node;
    arrow.onclick = revealPlayer;
    title.appendChild(arrow);

    evt.target.playVideo();

}

function startTheMusic(ids) {
    var container = ui.main; //ui.playlist.cloneNode(true);

    api.items(ids).then(function (videoIDs) {

        videoIDs = shuffle(videoIDs);

        videoIDs.forEach(function (id) {
            var wrapper = elt('div').attr('class', 'video-item').node;
            var link = elt('a').attr('href', youtube_uri + id).node;

            var videoWrap = elt('div').attr({
                'class': 'video-wrapper',
                'data-id': id
            }).node;

            wrapper.appendChild(link);
            wrapper.appendChild(videoWrap);
            container.appendChild(wrapper);
        });

        var groups = grouped(videoIDs, 50);
        return Promise.all(groups.map(api.videoInfo));
    }).then(function (videos) {
        videos = api.flatten(videos);

        var titles = [];
        videos.filter(function (video) {
            return video.snippet;
        }).forEach(function (video) {
            titles.push(video.snippet.title);
        });

        var wrappers = container.querySelectorAll('a');
        titles.forEach(function (title, index) {
            var videoTitle = elt('h2').prop('innerHTML', title).node;
            wrappers[index].appendChild(videoTitle);
        });

        //ui.main.appendChild(container);

        var wrapper = document.querySelector('.video-wrapper');
        wrapper.appendChild(document.getElementById('player'));

        player = new YT.Player('player', {
            videoId: wrapper.dataset['id'],
            events: {
                onReady: ytReady,
                onStateChange: ytStateChange
            }
        });

        document.querySelector('form').classList.remove('loading');

        window.addEventListener('click', function (evt) {
            if (evt.target.parentNode.parentNode == document.querySelector('is-playing'))
                return;

            var self = evt.target;
            if (self.nodeName === 'H2') {
                if (~[0, 1].indexOf(evt.button)) {
                    evt.preventDefault();
                    evt.stopPropagation();

                    if (evt.button === 0) {
                        ytStateChange({ data: YT.PlayerState.ENDED }, self.parentNode.parentNode);
                    } else if (evt.button === 1) {
                        window.open(self.parentNode.href, '_blank');
                    }
                }

            }
        }, true);

    });
}<|endoftext|>


<commit_before>var daysRemaining=(function(){
    var oneDay = 24*60*60*1000;
    var decision = new Date(2017, 6, 25, 23, 59);
    return Math.floor(Math.abs((Date.now() - decision.getTime())/(oneDay)));
})();

$("#days-remaining").text(daysRemaining);

$(".toggle-button-question .toggle-select").click(function() {
  $(".toggle-button-question .toggle-select").removeClass("selected");
  $(this).addClass("selected");
});

$(".protest-march-answer-box .toggle-select-protest").click(function() {
  $(".protest-march-answer-box .toggle-select-protest").removeClass("selected");
  $(this).addClass("selected");
});

// $("#secret-ballot-preview-message").hide();
// $("#secret-ballot-sent").hide();

$("#previewEmail").click(function(e) {
  e.preventDefault();
  var senderName = $(".name-input").val();
  var senderEmail = $(".email-input").val();
  if ($("#comment").val() != "") {
    var emailContent = "
Other issues that concern me about the future of South Africa are:

" + ($("#comment").val().replace(/\n/g, '
'))
  } else {
    var emailContent = "";
  };
  if ($(".toggle-select.selected").attr("id") == "no") {
    var emailSubject = "I do not support a secret ballot in the vote of no confidence";
  }
  else if ($(".toggle-select.selected").attr("id") == "yes") {
    var emailSubject = "I support a secret ballot in the vote of no confidence";
  };
  $("#name").text(senderName);
  $("#email").text(senderEmail);
  $("#email-title").text(emailSubject);

  $("#comment-preview").html("
Dear Madam Speaker,

I am a citizen of South Africa and I want to let you know that " + emailSubject + " in President Jacob Zuma.
" + emailContent + "

You represent all South Africans, including me. Please choose in favour of good governance - a governance that is best suited to realising my hopes for our future.

Yours sincerely,

" + senderName + "
");
  // $("#secret-ballot-build-message").hide();
  $("#secret-ballot-preview-message").show();
  location.hash = "#secret-ballot-preview-message";
});

$("#editEmail").click(function(e) {
  e.preventDefault();
  $("#secret-ballot-build-message").show();
  // $("#secret-ballot-preview-message").hide();
});

function submitForm() {
  var senderName = $(".name-input").val();
  var senderEmail = $(".email-input").val();
  var emailSubject = $("#email-title").text();
  var emailContent = $("#comment-preview").html();

  jQuery.ajax('/api/v1/email/', {
    type: 'POST',
    data: {
      person: 7720,
      name: senderName,
      email: senderEmail,
      body: emailContent,
      subject: emailSubject,
      // gRecaptchaResponse: grecaptcha.getResponse();,
    },
    success: function(data) {
      console.info("success", data);
    },
    error: function(jqXHR, textStatus, errorThrown) {
      console.error(jqXHR, textStatus, errorThrown, jqXHR.responseText);
    }
  });
};
<commit_msg>right anchor, disabled preview if no choice selected<commit_after>var daysRemaining=(function(){
    var oneDay = 24*60*60*1000;
    var decision = new Date(2017, 6, 25, 23, 59);
    return Math.floor(Math.abs((Date.now() - decision.getTime())/(oneDay)));
})();

$(".days-remaining-number").text(daysRemaining + " days");

$(".toggle-button-question .toggle-select").click(function() {
  $(".toggle-button-question .toggle-select").removeClass("selected");
  $(this).addClass("selected");
  $("#previewEmail").removeClass("disabled");
});

$(".protest-march-answer-box .toggle-select-protest").click(function() {
  $(".protest-march-answer-box .toggle-select-protest").removeClass("selected");
  $(this).addClass("selected");
});

// $("#secret-ballot-preview-message").hide();
// $("#secret-ballot-sent").hide();

$("#previewEmail").click(function(e) {
  e.preventDefault();
  var senderName = $(".name-input").val();
  var senderEmail = $(".email-input").val();
  if ($("#comment").val() != "") {
    var emailContent = "
Other issues that concern me about the future of South Africa are:

" + ($("#comment").val().replace(/\n/g, '
'))
  } else {
    var emailContent = "";
  };
  if ($(".toggle-select.selected").attr("id") == "no") {
    var emailSubject = "I do not support a secret ballot in the vote of no confidence";
  }
  else if ($(".toggle-select.selected").attr("id") == "yes") {
    var emailSubject = "I support a secret ballot in the vote of no confidence";
  };
  $("#name").text(senderName);
  $("#email").text(senderEmail);
  $("#email-title").text(emailSubject);

  $("#comment-preview").html("
Dear Madam Speaker,

I am a citizen of South Africa and I want to let you know that " + emailSubject + " in President Jacob Zuma.
" + emailContent + "

You represent all South Africans, including me. Please choose in favour of good governance - a governance that is best suited to realising my hopes for our future.

Yours sincerely,

" + senderName + "
");
  // $("#secret-ballot-build-message").hide();
  $("#secret-ballot-preview-message").show();
  location.hash = "#secret-ballot-preview-message";
});

$("#editEmail").click(function(e) {
  e.preventDefault();
  $("#secret-ballot-build-message").show();
  // $("#secret-ballot-preview-message").hide();
  location.hash = "#email-secret";
});

function submitForm() {
  var senderName = $(".name-input").val();
  var senderEmail = $(".email-input").val();
  var emailSubject = $("#email-title").text();
  var emailContent = $("#comment-preview").html();

  jQuery.ajax('/api/v1/email/', {
    type: 'POST',
    data: {
      person: 7720,
      name: senderName,
      email: senderEmail,
      body: emailContent,
      subject: emailSubject,
      // gRecaptchaResponse: grecaptcha.getResponse();,
    },
    success: function(data) {
      console.info("success", data);
    },
    error: function(jqXHR, textStatus, errorThrown) {
      console.error(jqXHR, textStatus, errorThrown, jqXHR.responseText);
    }
  });
};
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

sap.ui.define([
	"sap/base/util/includes",
	"sap/base/util/values",
	"sap/base/util/restricted/_CancelablePromise"
], function (
	includes,
	values,
	_CancelablePromise
) {
	"use strict";

	/**
	 * Registry for property editor validators.
	 *
	 * @namespace sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @static
	 * @since 1.81
	 * @private
	 * @experimental Since 1.81
	 * @ui5-restricted
	 */

	var ValidatorRegistry = {};

	var oValidators = {};

	var oLoadingValidators = {};

	/**
	* Registers the given validator types. If a validator type is already registered, it will be skipped and must first be deregistered using the ValidatorRegistry.deregisterValidator function.
	* @param {object<string, string>} mNames - Validator types and paths to register
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.registerValidators
	*/
	ValidatorRegistry.registerValidators = function (mNames) {
		Object.keys(mNames).forEach(function (sName) {
			if (!this.hasValidator(sName)) {
				oLoadingValidators[sName] = new _CancelablePromise(function (resolve, reject, onCancel) {
					onCancel(function () {
						delete oLoadingValidators[sName];
					});

					sap.ui.require(
						[mNames[sName]],
						resolve,
						reject
					);
				});

				oLoadingValidators[sName].then(function (oValidator) {
					oValidators[sName] = oValidator;
					delete oLoadingValidators[sName];
				});
			}
		}.bind(this));
	};

	/**
	* Ready check to make sure that all registered validators were loaded.
	* @returns {Promise} Promise which resolves when all validators are ready
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.ready
	*/
	ValidatorRegistry.ready = function () {
		return Promise.all(values(oLoadingValidators));
	};

	/**
	* Deregisters the given validator type and cancels the loading.
	* @param {string} sName - Validator type to deregister
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.deregisterValidators
	*/
	ValidatorRegistry.deregisterValidator = function (sName) {
		if (oValidators[sName]) {
			delete oValidators[sName];
		}
		if (oLoadingValidators[sName]) {
			oLoadingValidators[sName].cancel();
		}
	};

	/**
	* Deregisters all validators.
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.deregisterAllValidators
	*/
	ValidatorRegistry.deregisterAllValidators = function () {
		Object.keys(oLoadingValidators).forEach(function (sName) {
			this.deregisterValidator(sName);
		}.bind(this));
		oValidators = {};
	};

	/**
	* Returns the validator for the given type if it was loaded.
	* @param {string} sName - Validator type
	* @returns {object} Validator
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.getValidator
	*/
	ValidatorRegistry.getValidator = function (sName) {
		var oValidator = oValidators[sName];
		if (!oValidator) {
			throw new Error("Validator " + sName + " was not registered.");
		}
		return oValidator;
	};

	/**
	* Checks whether the given validator type was already loaded.
	* @param {string} sName - Validator type
	* @returns {boolean} Whether the validator was loaded or not
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validators.ValidatorRegistry.hasValidator
	*/
	ValidatorRegistry.hasValidator = function (sName) {
		return includes(
			Object.keys(oValidators),
			sName
		);
	};

	/**
	* Checks whether the given validator type was registered but not loaded yet.
	* @param {string} sName - Validator type
	* @returns {boolean} Whether the validator was registered
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.isRegistered
	*/
	ValidatorRegistry.isRegistered = function (sName) {
		return includes(
			Object.keys(oLoadingValidators),
			sName
		);
	};

	return ValidatorRegistry;
});<commit_msg>[INTERNAL] sap.ui.integration: Don't reject on cancel in ValidatorRegistry<commit_after>/*!
 * ${copyright}
 */

sap.ui.define([
	"sap/base/util/includes",
	"sap/base/util/values",
	"sap/base/util/restricted/_CancelablePromise"
], function (
	includes,
	values,
	_CancelablePromise
) {
	"use strict";

	/**
	 * Registry for property editor validators.
	 *
	 * @namespace sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @static
	 * @since 1.81
	 * @private
	 * @experimental Since 1.81
	 * @ui5-restricted
	 */

	var ValidatorRegistry = {};

	var oValidators = {};

	var oLoadingValidators = {};

	/**
	* Registers the given validator types. If a validator type is already registered, it will be skipped and must first be deregistered using the ValidatorRegistry.deregisterValidator function.
	* @param {object<string, string>} mNames - Validator types and paths to register
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.registerValidators
	*/
	ValidatorRegistry.registerValidators = function (mNames) {
		Object.keys(mNames).forEach(function (sName) {
			if (!this.hasValidator(sName)) {
				oLoadingValidators[sName] = new _CancelablePromise(function (resolve, reject, onCancel) {
					onCancel(function () {
						delete oLoadingValidators[sName];
					});

					onCancel.shouldReject = false;

					sap.ui.require(
						[mNames[sName]],
						resolve,
						reject
					);
				});

				oLoadingValidators[sName].then(function (oValidator) {
					oValidators[sName] = oValidator;
					delete oLoadingValidators[sName];
				});
			}
		}.bind(this));
	};

	/**
	* Ready check to make sure that all registered validators were loaded.
	* @returns {Promise} Promise which resolves when all validators are ready
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.ready
	*/
	ValidatorRegistry.ready = function () {
		return Promise.all(values(oLoadingValidators));
	};

	/**
	* Deregisters the given validator type and cancels the loading.
	* @param {string} sName - Validator type to deregister
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.deregisterValidators
	*/
	ValidatorRegistry.deregisterValidator = function (sName) {
		if (oValidators[sName]) {
			delete oValidators[sName];
		}
		if (oLoadingValidators[sName]) {
			oLoadingValidators[sName].cancel();
		}
	};

	/**
	* Deregisters all validators.
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.deregisterAllValidators
	*/
	ValidatorRegistry.deregisterAllValidators = function () {
		Object.keys(oLoadingValidators).forEach(function (sName) {
			this.deregisterValidator(sName);
		}.bind(this));
		oValidators = {};
	};

	/**
	* Returns the validator for the given type if it was loaded.
	* @param {string} sName - Validator type
	* @returns {object} Validator
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.getValidator
	*/
	ValidatorRegistry.getValidator = function (sName) {
		var oValidator = oValidators[sName];
		if (!oValidator) {
			throw new Error("Validator " + sName + " was not registered.");
		}
		return oValidator;
	};

	/**
	* Checks whether the given validator type was already loaded.
	* @param {string} sName - Validator type
	* @returns {boolean} Whether the validator was loaded or not
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validators.ValidatorRegistry.hasValidator
	*/
	ValidatorRegistry.hasValidator = function (sName) {
		return includes(
			Object.keys(oValidators),
			sName
		);
	};

	/**
	* Checks whether the given validator type was registered but not loaded yet.
	* @param {string} sName - Validator type
	* @returns {boolean} Whether the validator was registered
	* @public
	* @function
	* @name sap.ui.integration.designtime.baseEditor.validator.ValidatorRegistry.isRegistered
	*/
	ValidatorRegistry.isRegistered = function (sName) {
		return includes(
			Object.keys(oLoadingValidators),
			sName
		);
	};

	return ValidatorRegistry;
});<|endoftext|>