<commit_before>window.app.views.SimPage = Backbone.View.extend({

  el: "#view",
  template: JST["app/templates/sim.us"],
  camera: null,
  scene: null,
  renderer: null,
  controls: null,
  transform: null,
  materials: [
    //new THREE.MeshLambertMaterial({ color: 0x000000 }), //Outer Floor Color
    ///new THREE.MeshLambertMaterial({ color: 0x666666 }), //Outer Roof Ceiling Color
    //new THREE.MeshLambertMaterial({ color: 0xcccccc, opacity: 0.1, depthWrite: false, depthTest: false, vertexColors: THREE.VertexColors }),
    //new THREE.MeshLambertMaterial({ color: 0xcccccc, wireframe: true }), //WireFrame or Wall color
    //new THREE.MeshLambertMaterial({ color: 0xe0ffff, opacity: 0.8, depthWrite: false, depthTest: false, vertexColors: THREE.NoColors }) //Window Color etc
  ],

  events: {
    'click #run-button' : 'runSimulation',
    'change .group-disable' : 'handleSliderCheckboxChange'
  },

  // Create a transform matrix that will put the center of a zone at the origin (0, 0, 0)
  buildOriginTransform: function(zone) {
    var dimensions = { x: {}, y: {}, z: {} };
    _.each(zone, function(surface) {
      if (surface.BuildingSurface) {
        _.each(surface.BuildingSurface.vertices, function(vertex) {
          if (dimensions.x.min === undefined || vertex.x < dimensions.x.min) { dimensions.x.min = vertex.x; }
          if (dimensions.x.max === undefined || vertex.x > dimensions.x.max) { dimensions.x.max = vertex.x; }
          if (dimensions.y.min === undefined || vertex.y < dimensions.y.min) { dimensions.y.min = vertex.y; }
          if (dimensions.y.max === undefined || vertex.y > dimensions.y.max) { dimensions.y.max = vertex.y; }
          if (dimensions.z.min === undefined || vertex.z < dimensions.z.min) { dimensions.z.min = vertex.z; }
          if (dimensions.z.max === undefined || vertex.z > dimensions.z.max) { dimensions.z.max = vertex.z; }
        });
      }
    });

    return new THREE.Matrix4().makeTranslation(-dimensions.x.min-(dimensions.x.max-dimensions.x.min)/2,
      -dimensions.y.min-(dimensions.y.max-dimensions.y.min)/2,
      -dimensions.z.min-(dimensions.z.max-dimensions.z.min)/2);
  },

  getMaterialIndex: function(surfaceType) {
    switch (surfaceType) {
      case 'Floor':
        return 0;
      case 'Ceiling':
        return 1;
      case 'Wall':
        return 2;
      case 'Window':
        return 3;
    }
    return -1;
  },

  createGeometry: function(surface, transform) {
    var geometry = new THREE.PlaneGeometry();
    geometry.vertices = [];
    _.each(surface.vertices, function(vertex) {
      var vector = new THREE.Vector3(vertex.x, vertex.y, vertex.z);
      vector.applyMatrix4(transform);
      geometry.vertices.push(vector);
    });
    return geometry;
  },

  createFaces: function(surface) {
    var faces = [];
    var face = new THREE.Face3(0, 1, 2);
    face.materialIndex = this.getMaterialIndex(surface.type);
    faces.push(face);
    face = new THREE.Face3(2, 3, 0);
    face.materialIndex = this.getMaterialIndex(surface.type);
    faces.push(face);
    return faces;
  },

  addPlane: function(scene, surface, transform) {
      var geometry = this.createGeometry(surface, transform);
      geometry.faces = this.createFaces(surface);
      geometry.computeBoundingSphere();
      geometry.computeFaceNormals();
      geometry.computeCentroids();
      geometry.name = surface.name;
      geometry.type = surface.type;

      //var material = new THREE.MeshBasicMaterial({ vertexColors: THREE.FaceColors, overdraw: 0.5 });
      //var plane = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(this.materials));
      //plane.castShadow=true;
      //scene.add(plane);
      var axes = new THREE.AxisHelper (20);
      scene.add(axes);
      var cubeGeometry=new THREE.CubeGeometry(4,3,4);
      var cubeMaterial= new THREE.MeshLambertMaterial({color: 0xf2d478});
      var solarCube = new THREE.Mesh(cubeGeometry, cubeMaterial);
      solarCube.position.x=0;
      solarCube.position.y=0;
      solarCube.position.z=2;
      solarCube.castShadow=true;
      scene.add(solarCube);

      var groundPlaneGeometry = new THREE.PlaneGeometry (100,100);
      var groundPlaneMaterial= new THREE.MeshLambertMaterial ({color: 0xcccccc});
      var groundPlane = new THREE.Mesh (groundPlaneGeometry, groundPlaneMaterial);
      groundPlane.position.x=0;
      groundPlane.position.y=0;
      groundPlane.position.z=-0;
      groundPlane.receiveShadow=true;
      scene.add(groundPlane);

      var sunLight= new THREE.DirectionalLight (0xffffff, 0.1);
      sunLight.position.set(45,45,100);
      sunLight.castShadow=true;
      sunLight.onlyShadow=false;
      scene.add(sunLight);

      //var ambilight = new THREE.AmbientLight( 0x404040 ); // soft white light
      //scene.add( ambilight );
  },

  addPlanes: function(scene, surfaces, transform) {
    var self = this;
    _.each(surfaces, function(surface) {
      if (surface.BuildingSurface) {
        self.addPlane(scene, surface.BuildingSurface, transform);
      } else if (surface.FenestrationSurface) {
        self.addPlane(scene, surface.FenestrationSurface, transform);
      }
    });
  },

  createCamera: function(canvas) {
    var camera = new THREE.PerspectiveCamera(60, canvas.innerWidth() / canvas.innerHeight(), 1, 1000);
    camera.position.x = -10;
    camera.position.y = 10;
    camera.position.z = 5;
    //camera.lookAt(scene.position);
    return camera;
  },

  createCanvasControls: function(canvas) {
    var controls = new THREE.TrackballControls(this.camera, canvas[0]);
    controls.rotateSpeed = 0.1;
    controls.zoomSpeed = 0.5;
    controls.panSpeed = 0.7;
    controls.noZoom = false;
    controls.noPan = false;
    controls.staticMoving = false;
    controls.dynamicDampingFactor = 0.3;
    controls.keys = [ 65, 83, 68 ];
    return controls;
  },

  createManualControls: function() {
    var self = this;
    $("#Length").slider({ min: 1, max: 10, value: 5, slide: self.updateSliderDisplay });
    $("#Depth").slider({ min: 1, max: 10, value: 5, slide: self.updateSliderDisplay });
    $("#Height").slider({ min: 1, max: 10, value: 5, slide: self.updateSliderDisplay });
    $("#NWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#EWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#SWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#WWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#insulation-level").slider({ min: 1, max: 10, value: 5 });
    $("#ventilation-rate").slider({ min: 1, max: 10, value: 5 });
    $("#run-button").button();

    this.setSliderDisplayValue('#Length');
    this.setSliderDisplayValue('#Depth');
    this.setSliderDisplayValue('#Height');
    this.setSliderDisplayValue('#NWinGR');
    this.setSliderDisplayValue('#EWinGR');
    this.setSliderDisplayValue('#SWinGR');
    this.setSliderDisplayValue('#WWinGR');
  },

  handleSliderCheckboxChange: function(event) {
    var grId = '#'+event.target.id+'GR';
    $(grId).slider(event.target.checked ? "enable" : "disable");
    if (event.target.checked) {
      $(grId+'Disp').show();
    } else {
      $(grId+'Disp').hide();
    }
  },

  updateSliderDisplay: function(event, ui) {
    this.setSliderDisplayValue('#'+event.target.id, ui.value);
  },

  setSliderDisplayValue: function(sliderId, val) {
    if (val === undefined) {
      val = $(sliderId).slider('value');
    }
    $(sliderId+'Disp').text(val);
  },

  createRenderer: function(canvas) {
    var renderer = new THREE.WebGLRenderer();
    renderer.setClearColorHex(0xffffff, 1.0);
    renderer.setSize(canvas.innerWidth(), canvas.innerHeight());
    renderer.shadowMapEnabled=true;
    return renderer;
  },

  init: function() {
    var self = this;
    var container = $("#canvas");
    this.scene = new THREE.Scene();
    this.camera = this.createCamera(container);
    this.controls = this.createCanvasControls(container);
    this.createManualControls();
    this.renderer = this.createRenderer(container);
    container[0].appendChild(this.renderer.domElement);

    $.get('simulation/load/25', function(d) {
      if (d.status === 0) {
        transform = self.buildOriginTransform(d.data.zone);
        self.addPlanes(self.scene, d.data.zone, transform);
      }
    });

    window.addEventListener('resize', function() {
      var canvas = $('#canvas');
      var width = canvas.innerWidth();
      var height = canvas.innerHeight();
      renderer.setSize(width, height);
      this.camera.aspect = width / height;
      this.camera.updateProjectionMatrix();
    });
  },

  animate: function(controls, renderer) {
    window.requestAnimationFrame(this.animate);
    this.controls.update();
    this.renderer.render(this.scene, this.camera);
  },

  runSimulation: function() {
    var zone = [];
    var inverseTransform = new THREE.Matrix4();

    var progbar = $('#progressbar');

    $("#run-button").button("option", "disabled", true);
    progbar.progressbar({ value: false });
    progbar.show();

    /* TODO: this needs to be changed after Troy's changes
    inverseTransform.getInverse(transform);
    _.each(this.scene.children, function(child) {
      var result = {};
      result.name = child.geometry.name;
      result.type = child.geometry.type;
      result.vertices = [];

      _.each(child.geometry.vertices, function(vertex) {
        var vert = vertex.clone().applyMatrix4(inverseTransform);
        result.vertices.push({x: vert.x, y: vert.y, z: vert.z});
      });

      zone.push(result);
    });
    */
    // this is a kludge for now
    zone.push({name: "test", type: "roof", vertices: [{x: 10, y: 10, z: 10 }]});

    $.post('simulation/run', { zone: zone }, this.getSimulationResults);
  },

  getSimulationResults: function(data, status) {
    //window.console.log(data);
    //window.console.log(status);
    $.get('simulation/' + data.jobId, this.handleSimulationResults.bind(this));
    // TODO: add some error checking
  },

  handleSimulationResults: function(results, status, xhr) {
    if (xhr.status === 200) {
      //window.console.log('simulation run finished');
      var progbar = $('#progressbar');
      progbar.hide();
      progbar.progressbar('destroy');
      $("#run-button").button("option", "disabled", false);
      $("#results").text(results);
    } else {
      //window.console.log('simulation not finished');
      window.setTimeout(this.getSimulationResults.bind(this, results), 5000);
    }
  },

  initialize: function(options) {
    _.bindAll(this);
    this.model = new window.app.models.Simulation();
  },

  render: function() {
    var self = this;
    this.$el.html(this.template(this.model.attributes));
    window.setTimeout(function() {
      self.init();
      self.animate();
    }, 0);
    return this;
  }

});
<commit_msg>Updates to setting up the scene.<commit_after>window.app.views.SimPage = Backbone.View.extend({

  el: "#view",
  template: JST["app/templates/sim.us"],
  camera: null,
  scene: null,
  renderer: null,
  controls: null,
  transform: null,
  solarCube: null,
  materials: [
    //new THREE.MeshLambertMaterial({ color: 0x000000 }), //Outer Floor Color
    ///new THREE.MeshLambertMaterial({ color: 0x666666 }), //Outer Roof Ceiling Color
    //new THREE.MeshLambertMaterial({ color: 0xcccccc, opacity: 0.1, depthWrite: false, depthTest: false, vertexColors: THREE.VertexColors }),
    //new THREE.MeshLambertMaterial({ color: 0xcccccc, wireframe: true }), //WireFrame or Wall color
    //new THREE.MeshLambertMaterial({ color: 0xe0ffff, opacity: 0.8, depthWrite: false, depthTest: false, vertexColors: THREE.NoColors }) //Window Color etc
  ],

  events: {
    'click #run-button' : 'runSimulation',
    'change .group-disable' : 'handleSliderCheckboxChange'
  },

  // Create a transform matrix that will put the center of a zone at the origin (0, 0, 0)
  buildOriginTransform: function(zone) {
    var dimensions = { x: {}, y: {}, z: {} };
    _.each(zone, function(surface) {
      if (surface.BuildingSurface) {
        _.each(surface.BuildingSurface.vertices, function(vertex) {
          if (dimensions.x.min === undefined || vertex.x < dimensions.x.min) { dimensions.x.min = vertex.x; }
          if (dimensions.x.max === undefined || vertex.x > dimensions.x.max) { dimensions.x.max = vertex.x; }
          if (dimensions.y.min === undefined || vertex.y < dimensions.y.min) { dimensions.y.min = vertex.y; }
          if (dimensions.y.max === undefined || vertex.y > dimensions.y.max) { dimensions.y.max = vertex.y; }
          if (dimensions.z.min === undefined || vertex.z < dimensions.z.min) { dimensions.z.min = vertex.z; }
          if (dimensions.z.max === undefined || vertex.z > dimensions.z.max) { dimensions.z.max = vertex.z; }
        });
      }
    });

    return new THREE.Matrix4().makeTranslation(-dimensions.x.min-(dimensions.x.max-dimensions.x.min)/2,
      -dimensions.y.min-(dimensions.y.max-dimensions.y.min)/2,
      -dimensions.z.min-(dimensions.z.max-dimensions.z.min)/2);
  },

  /*
  getMaterialIndex: function(surfaceType) {
    switch (surfaceType) {
      case 'Floor':
        return 0;
      case 'Ceiling':
        return 1;
      case 'Wall':
        return 2;
      case 'Window':
        return 3;
    }
    return -1;
  },

  createGeometry: function(surface, transform) {
    var geometry = new THREE.PlaneGeometry();
    geometry.vertices = [];
    _.each(surface.vertices, function(vertex) {
      var vector = new THREE.Vector3(vertex.x, vertex.y, vertex.z);
      vector.applyMatrix4(transform);
      geometry.vertices.push(vector);
    });
    return geometry;
  },

  createFaces: function(surface) {
    var faces = [];
    var face = new THREE.Face3(0, 1, 2);
    face.materialIndex = this.getMaterialIndex(surface.type);
    faces.push(face);
    face = new THREE.Face3(2, 3, 0);
    face.materialIndex = this.getMaterialIndex(surface.type);
    faces.push(face);
    return faces;
  },

  addPlane: function(scene, surface, transform) {
    var geometry = this.createGeometry(surface, transform);
    geometry.faces = this.createFaces(surface);
    geometry.computeBoundingSphere();
    geometry.computeFaceNormals();
    geometry.computeCentroids();
    geometry.name = surface.name;
    geometry.type = surface.type;

    //var material = new THREE.MeshBasicMaterial({ vertexColors: THREE.FaceColors, overdraw: 0.5 });
    //var plane = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(this.materials));
    //plane.castShadow=true;
    //scene.add(plane);

    var axes = new THREE.AxisHelper (20);
    scene.add(axes);
    var boxGeometry =new THREE.BoxGeometry(4, 3, 4);
    var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xf2d478});
    var solarCube = new THREE.Mesh(boxGeometry, cubeMaterial);
    solarCube.position.x=0;
    solarCube.position.y=0;
    solarCube.position.z=2;
    solarCube.castShadow=true;
    scene.add(solarCube);

    var groundPlaneGeometry = new THREE.PlaneGeometry (100, 100);
    var groundPlaneMaterial= new THREE.MeshLambertMaterial ({color: 0xcccccc});
    var groundPlane = new THREE.Mesh (groundPlaneGeometry, groundPlaneMaterial);
    groundPlane.position.x=0;
    groundPlane.position.y=0;
    groundPlane.position.z=-0;
    groundPlane.receiveShadow=true;
    scene.add(groundPlane);

    var sunLight= new THREE.DirectionalLight (0xffffff, 0.1);
    sunLight.position.set(45, 45, 100);
    sunLight.castShadow=true;
    sunLight.onlyShadow=false;
    scene.add(sunLight);

    //var ambilight = new THREE.AmbientLight( 0x404040 ); // soft white light
    //scene.add( ambilight );
  },

  addPlanes: function(scene, surfaces, transform) {
    var self = this;
    _.each(surfaces, function(surface) {
      if (surface.BuildingSurface) {
        self.addPlane(scene, surface.BuildingSurface, transform);
      } else if (surface.FenestrationSurface) {
        self.addPlane(scene, surface.FenestrationSurface, transform);
      }
    });
  },
  */

  initializeScene: function(scene, surfaces, transform) {
    var axes = new THREE.AxisHelper(20);
    scene.add(axes);
    var boxGeometry = new THREE.BoxGeometry(1, 1, 1);
    var cubeMaterial = new THREE.MeshBasicMaterial({color: 0xf2d478});
    this.solarCube = new THREE.Mesh(boxGeometry, cubeMaterial);
    this.solarCube.castShadow = true;
    this.solarCube.scale.x = 3;
    this.solarCube.scale.y = 4;
    this.solarCube.scale.z = 4;
    this.solarCube.position.x = 0;
    this.solarCube.position.y = 0;
    this.solarCube.position.z = 2;
    this.scene.add(this.solarCube);

    var groundPlaneGeometry = new THREE.PlaneGeometry (100, 100);
    var groundPlaneMaterial = new THREE.MeshBasicMaterial ({color: 0xcccccc});
    var groundPlane = new THREE.Mesh(groundPlaneGeometry, groundPlaneMaterial);
    groundPlane.position.x = 0;
    groundPlane.position.y = 0;
    groundPlane.position.z = 0;
    groundPlane.receiveShadow = true;
    scene.add(groundPlane);

    var sunLight = new THREE.DirectionalLight (0xffffff, 0.1);
    sunLight.position.set(45, 45, 100);
    sunLight.castShadow = true;
    sunLight.onlyShadow = false;
    scene.add(sunLight);
  },

  createCamera: function(canvas) {
    var camera = new THREE.PerspectiveCamera(60, canvas.innerWidth() / canvas.innerHeight(), 1, 1000);
    camera.position.x = -10;
    camera.position.y = 10;
    camera.position.z = 5;
    //camera.lookAt(scene.position);
    return camera;
  },

  createCanvasControls: function(canvas) {
    var controls = new THREE.TrackballControls(this.camera, canvas[0]);
    controls.rotateSpeed = 0.1;
    controls.zoomSpeed = 0.5;
    controls.panSpeed = 0.7;
    controls.noZoom = false;
    controls.noPan = false;
    controls.staticMoving = false;
    controls.dynamicDampingFactor = 0.3;
    controls.keys = [ 65, 83, 68 ];
    return controls;
  },

  createManualControls: function() {
    var self = this;
    $("#Length").slider({ min: 1, max: 10, value: 3, slide: self.updateLength });
    $("#Depth").slider({ min: 1, max: 10, value: 4, slide: self.updateWidth });
    $("#Height").slider({ min: 1, max: 10, value: 4, slide: self.updateHeight });
    $("#NWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#EWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#SWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#WWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#insulation-level").slider({ min: 1, max: 10, value: 5 });
    $("#ventilation-rate").slider({ min: 1, max: 10, value: 5 });
    $("#run-button").button();

    this.setSliderDisplayValue('#Length');
    this.setSliderDisplayValue('#Depth');
    this.setSliderDisplayValue('#Height');
    this.setSliderDisplayValue('#NWinGR');
    this.setSliderDisplayValue('#EWinGR');
    this.setSliderDisplayValue('#SWinGR');
    this.setSliderDisplayValue('#WWinGR');
  },

  handleSliderCheckboxChange: function(event) {
    var grId = '#'+event.target.id+'GR';
    $(grId).slider(event.target.checked ? "enable" : "disable");
    if (event.target.checked) {
      $(grId+'Disp').show();
    } else {
      $(grId+'Disp').hide();
    }
  },

  updateLength: function(event, ui) {
    this.updateSliderDisplay(event, ui);
    this.solarCube.scale.x = ui.value;
  },

  updateWidth: function(event, ui) {
    this.updateSliderDisplay(event, ui);
    this.solarCube.scale.y = ui.value;
  },

  updateHeight: function(event, ui) {
    this.updateSliderDisplay(event, ui);
    this.solarCube.scale.z = ui.value;
    this.solarCube.position.z = ui.value/2;
  },

  updateSliderDisplay: function(event, ui) {
    this.setSliderDisplayValue('#'+event.target.id, ui.value);
  },

  setSliderDisplayValue: function(sliderId, val) {
    if (val === undefined) {
      val = $(sliderId).slider('value');
    }
    $(sliderId+'Disp').text(val);
  },

  createRenderer: function(canvas) {
    var renderer = new THREE.WebGLRenderer();
    renderer.setClearColorHex(0xffffff, 1.0);
    renderer.setSize(canvas.innerWidth(), canvas.innerHeight());
    renderer.shadowMapEnabled=true;
    return renderer;
  },

  init: function() {
    var self = this;
    var container = $("#canvas");
    this.scene = new THREE.Scene();
    this.camera = this.createCamera(container);
    this.controls = this.createCanvasControls(container);
    this.createManualControls();
    this.renderer = this.createRenderer(container);
    container[0].appendChild(this.renderer.domElement);

    $.get('simulation/load/25', function(d) {
      if (d.status === 0) {
        transform = null; //self.buildOriginTransform(d.data.zone);
        self.initializeScene(self.scene, d.data.zone, transform);
      }
    });

    window.addEventListener('resize', function() {
      var canvas = $('#canvas');
      var width = canvas.innerWidth();
      var height = canvas.innerHeight();
      renderer.setSize(width, height);
      this.camera.aspect = width / height;
      this.camera.updateProjectionMatrix();
    });
  },

  animate: function(controls, renderer) {
    window.requestAnimationFrame(this.animate);
    this.controls.update();
    this.renderer.render(this.scene, this.camera);
  },

  runSimulation: function() {
    var zone = [];
    var inverseTransform = new THREE.Matrix4();

    var progbar = $('#progressbar');

    $("#run-button").button("option", "disabled", true);
    progbar.progressbar({ value: false });
    progbar.show();

    /* TODO: this needs to be changed after Troy's changes
    inverseTransform.getInverse(transform);
    _.each(this.scene.children, function(child) {
      var result = {};
      result.name = child.geometry.name;
      result.type = child.geometry.type;
      result.vertices = [];

      _.each(child.geometry.vertices, function(vertex) {
        var vert = vertex.clone().applyMatrix4(inverseTransform);
        result.vertices.push({x: vert.x, y: vert.y, z: vert.z});
      });

      zone.push(result);
    });
    */
    // this is a kludge for now
    zone.push({name: "test", type: "roof", vertices: [{x: 10, y: 10, z: 10 }]});

    $.post('simulation/run', { zone: zone }, this.getSimulationResults);
  },

  getSimulationResults: function(data, status) {
    //window.console.log(data);
    //window.console.log(status);
    $.get('simulation/' + data.jobId, this.handleSimulationResults.bind(this));
    // TODO: add some error checking
  },

  handleSimulationResults: function(results, status, xhr) {
    if (xhr.status === 200) {
      //window.console.log('simulation run finished');
      var progbar = $('#progressbar');
      progbar.hide();
      progbar.progressbar('destroy');
      $("#run-button").button("option", "disabled", false);
      $("#results").text(results);
    } else {
      //window.console.log('simulation not finished');
      window.setTimeout(this.getSimulationResults.bind(this, results), 5000);
    }
  },

  initialize: function(options) {
    _.bindAll(this);
    this.model = new window.app.models.Simulation();
  },

  render: function() {
    var self = this;
    this.$el.html(this.template(this.model.attributes));
    window.setTimeout(function() {
      self.init();
      self.animate();
    }, 0);
    return this;
  }

});
<|endoftext|>


<commit_before>app.controller('ResponseDataCtrl', function ($scope, $http) {

});<commit_msg>dfasd<commit_after>app.controller('AboutCtrl', function ($scope, $http) {

});<|endoftext|>


<commit_before>// Import and configure the Firebase SDK
// These scripts are made available when the app is served or deployed on Firebase Hosting
// If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup
importScripts('/__/firebase/7.24.0/firebase-app.js')
importScripts('/__/firebase/7.24.0/firebase-messaging.js')
importScripts('/__/firebase/init.js');

const messaging = firebase.messaging()

/**
 * Here is is the code snippet to initialize Firebase Messaging in the Service
 * Worker when your app is not hosted on Firebase Hosting.

 // [START initialize_firebase_in_sw]
 // Give the service worker access to Firebase Messaging.
 // Note that you can only use Firebase Messaging here. Other Firebase libraries
 // are not available in the service worker.
 importScripts('https://www.gstatic.com/firebasejs/7.24.0/firebase-app.js');
 importScripts('https://www.gstatic.com/firebasejs/7.24.0/firebase-messaging.js');

 // Initialize the Firebase app in the service worker by passing in
 // your app's Firebase config object.
 // https://firebase.google.com/docs/web/setup#config-object
 firebase.initializeApp({
   apiKey: 'api-key',
   authDomain: 'project-id.firebaseapp.com',
   databaseURL: 'https://project-id.firebaseio.com',
   projectId: 'project-id',
   storageBucket: 'project-id.appspot.com',
   messagingSenderId: 'sender-id',
   appId: 'app-id',
   measurementId: 'G-measurement-id',
 });

 // Retrieve an instance of Firebase Messaging so that it can handle background
 // messages.
 const messaging = firebase.messaging();
 // [END initialize_firebase_in_sw]
 **/

// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START on_background_message]
messaging.onBackgroundMessage(function (payload) {
  console.log('[firebase-messaging-sw.js] Received background message ', payload)
  // Customize notification here
  const notificationTitle = 'Tamuro'
  const notificationOptions = {
    body: payload,
    icon: '/img/icons/apple-touch-icon-120x120.png'
  }

  self.registration.showNotification(notificationTitle,
    notificationOptions)
})
// [END on_background_message]
<commit_msg>bug fix<commit_after>// Import and configure the Firebase SDK
// These scripts are made available when the app is served or deployed on Firebase Hosting
// If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup
importScripts('/__/firebase/7.24.0/firebase-app.js')
importScripts('/__/firebase/7.24.0/firebase-messaging.js')
importScripts('/__/firebase/init.js');

const messaging = firebase.messaging()

/**
 * Here is is the code snippet to initialize Firebase Messaging in the Service
 * Worker when your app is not hosted on Firebase Hosting.

 // [START initialize_firebase_in_sw]
 // Give the service worker access to Firebase Messaging.
 // Note that you can only use Firebase Messaging here. Other Firebase libraries
 // are not available in the service worker.
 importScripts('https://www.gstatic.com/firebasejs/7.24.0/firebase-app.js');
 importScripts('https://www.gstatic.com/firebasejs/7.24.0/firebase-messaging.js');

 // Initialize the Firebase app in the service worker by passing in
 // your app's Firebase config object.
 // https://firebase.google.com/docs/web/setup#config-object
 firebase.initializeApp({
   apiKey: 'api-key',
   authDomain: 'project-id.firebaseapp.com',
   databaseURL: 'https://project-id.firebaseio.com',
   projectId: 'project-id',
   storageBucket: 'project-id.appspot.com',
   messagingSenderId: 'sender-id',
   appId: 'app-id',
   measurementId: 'G-measurement-id',
 });

 // Retrieve an instance of Firebase Messaging so that it can handle background
 // messages.
 const messaging = firebase.messaging();
 // [END initialize_firebase_in_sw]
 **/

// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START on_background_message]
messaging.onBackgroundMessage(function (payload) {
  // Customize notification here
  const notificationTitle = 'Tamuro'
  const notificationOptions = {
    body: payload.notification.body,
    icon: '/img/icons/apple-touch-icon-120x120.png'
  }

  self.registration.showNotification(
    notificationTitle,
    notificationOptions
  )
})
// [END on_background_message]
<|endoftext|>


<commit_before>/*jslint indent:8, devel:true, browser:true, vars:true*/
/*global jQuery, $, Handlebars, Router*/

//TODO: Añadir soporte para Director.js [https://github.com/flatiron/director]
//Ejemplo en la página del proyecto y en todoMVC [https://github.com/tastejs/todomvc/blob/gh-pages/architecture-examples/jquery/js/app.js]

//Esta es otra forma de escribir '$(document).ready()'
jQuery(function ($) {
        "use strict";

        //La variable informa si hay una petición Ajax en proceso
        var ajaxInProgress = false,
                ENTER_KEY = 13;

        //Objeto contenedor de utilidades. Aquí podría ir el esqueleto de la llamada vía Ajax
        //Estas funciones, si su uso es lo suficientemente común, podrían ir en un archivo '.js' a parte.
        var util = {
                getDiaSemana: function (numDia) {
                        var dias = [{short: "D", complete: "Domingo"},
                                        {short: "L", complete: "Lunes"},
                                        {short: "M", complete: "Martes"},
                                        {short: "X", complete: "Miércoles"},
                                        {short: "J", complete: "Jueves"},
                                        {short: "V", complete: "Viernes"},
                                        {short: "S", complete: "Sábado"}];
                        return dias[numDia];
                }
        };

        var App = {
                init: function () {
                        //Poner el foco en el primer campo de entrada visible
                        $("body").find("input[type=text]:visible:first").focus();
                        this.cacheElements();
                        this.bindEvents();
                        this.route();

                },
                cacheElements: function () {
                        this.forecastTemplate = Handlebars.compile($('#forecast-template').html());
                        this.weatherNode = $("#weatherNode");
                        this.condicionesActuales = this.weatherNode.find("#condicionesActuales");
                        this.cuerpo = this.weatherNode.find(".cuerpo");
                        this.temperatura = this.condicionesActuales.find(".temperatura");
                        this.estado = this.condicionesActuales.find(".estado");
                        this.localidadTemperatura = this.condicionesActuales.find(".localidad-temperatura");
                        this.imagenActual = this.condicionesActuales.find(".imagen-actual");
                        this.localidad = this.cuerpo.find("#localidad");
                        this.indicadorAjaxEnCurso = this.cuerpo.find("#indicadorAjaxEnCurso");
                        this.btoGetWeatherInfo = this.cuerpo.find("#getWeatherInfo");
                        this.forecastContainer = this.cuerpo.find("#forecast-container");
                        this.validacionesContainer = this.cuerpo.find("#validaciones-container");
                        this.errorContainer = this.cuerpo.find("#error-container");
                        this.txtLocalidad = this.cuerpo.find(".localidad");
                },
                bindEvents: function () {
                        //Al clicar el botón se consulta el tiempo para la localidad indicada
                        this.btoGetWeatherInfo.on("click", this.eventWeatherInfo.bind(this));
                        this.localidad.on("keyup", this.eventLocalidad.bind(this));
                        //Al clicar en el campo de entrada quitar la alerta visual de error por campo vacío
                        this.localidad.on("click", function (event) {
                                $(event.target).removeClass("error-input");
                        });
                },
                route: function () {
                        var route = window.location.hash.slice(2);
                        // Si en la URL se informa una localidad buscar directamente la previsión sobre la misma
                        if (route.length) {
                                this.localidad.val(route);
                                this.txtLocalidad.text(route);
                                this.btoGetWeatherInfo.click();
                        }
                },

                /* Métodos utilizados desde 'bindEvents' */

                eventWeatherInfo: function () {
                        //Validaciones sobre los campos de entrada
                        var erroresEntrada = false;
                        if (!this.localidad.val()) {
                                this.muestraValidaciones(["Es necesario especificar una localidad"]);
                                this.localidad.focus().addClass("error-input");
                                erroresEntrada = true;
                        }

                        if (!erroresEntrada) {
                                this.validacionesContainer.hide();
                                this.getWeatherInfo();
                        }
                },
                eventLocalidad: function (event) {
                        //Al comenzar a teclear en el campo de entrada quitar la alerta visual de error por campo vacío
                        this.localidad.removeClass("error-input");

                        //Si la tecla pulsada es un 'Intro' lanzar el evento 'click' del botón
                        if (event.keyCode === ENTER_KEY) {
                                this.btoGetWeatherInfo.click();
                                return true;
                        }

                        //Se utiliza 'event.target' en vez de 'this', ya que este en vez de hacer referencia al objeto
                        //que ha lanzado el evento hace referenci al objeto App porque se ha invocado así: '.bind(this)'?
                        this.txtLocalidad.text($(event.target).val());

                        //Si el campo está vacío
                        if (!$(event.target).val()) {
                                this.txtLocalidad.text("...");
                        }

                        //Si se han introducido más de 3 caracteres se procede a buscar localidades
                        //que coincidan con esos caracteres
                        //FUTURE Descomentar cuando se sepa qué hacer con la lista de localidades devueltas
                        /*if ($(this).val().length >= 4) {
                        getLocalidades(this);
                        }*/
                },
                //Muestra mensajes de validación
                muestraValidaciones: function (arrValidaciones) {
                        //La información meteorológica anteriormente cargada se esconde
                        this.forecastContainer.hide();
                        this.condicionesActuales.hide();

                        this.validacionesContainer.html("" + arrValidaciones[0] + "");

                        if (this.validacionesContainer.is(":visible")) {
                                this.validacionesContainer.slideUp(200).delay(200).slideDown(200);
                        } else {
                                this.validacionesContainer.slideDown(200);
                        }
                },

                //Maneja los errores de la petición Ajax
                //err = { cod, desc }
                errorHandle: function (err) {
                        //La información meteorológica anteriormente cargada se oculta, además de los errores que se estén ya mostrando
                        this.forecastContainer.hide();
                        this.condicionesActuales.hide();
                        this.errorContainer.hide();

                        var errorMsg = err.statusText + "\n Error " + err.status;

                        this.errorContainer.html(errorMsg);
                        this.errorContainer.slideDown(200).delay(3000).slideUp(2000);
                },
                //Recupera la información meteorológica para la localización introducida
                getWeatherInfo: function () {
                        //Si no hay una petición Ajax en curso se realiza una
                        if (!ajaxInProgress) {
                                //URL del servicio RESTful del Backend de la aplicación
                                var targetUrl = "/forecast";

                                //Objecto con los datos de entrada de la petición
                                var datos = {
                                        formato: "json",
                                        localidad: this.localidad.val(),
                                        numDias: 4
                                };

                                ajaxInProgress = true;
                                this.indicadorAjaxEnCurso.show();

                                //Se quiere utilizar 'this' dentro de una función [complete] de un objeto, pero la función tendrá como 'this' el objeto en el que fué creada
                                //por lo que guardo el 'this' actual en una variable alcanzable desde dentro de la función llamada 'that'
                                var that = this;

                                //Configuración y llamada al servicio RESTful vía Ajax
                                $.ajax({
                                        url: targetUrl,
                                        data: datos,
                                        type: "GET",
                                        dataType: "json",
                                        cache: false,
                                        contentType: "application/x-www-form-urlencoded; charset=UTF-8", //por defecto
                                        //En el contexto de una invocación Ajax, 'this' no se refiere al objeto contenedor sino a la propia llamada. Por eso 'bind(this)'
                                        success: this.printWeather.bind(this),
                                        error: this.errorHandle.bind(this),
                                        //Función que se ejecuta sin importar el resultado de la petición Ajax
                                        //CHANGES He comprobado que si se produce una excepción en la función 'success' la función 'complete' no se ejecuta. Quizá
                                        //este no sea el mejor sitio para indicar que la función Ajax ha terminado [ajaxInProgress = false] y sea mejor hacerlo
                                        //tanto en el success como en el error.
                                        complete: function () {
                                                ajaxInProgress = false;
                                                that.indicadorAjaxEnCurso.hide();
                                        }
                                });
                        }// End if
                },
                //Función que ejecuta en caso de que la petición Ajax 'getWeatherInfo' haya sido exitosa
                //Muestra en pantalla la información meteorológica
                printWeather: function (json) {
                        //Si no se ha producido ningún error al tratar el objeto JSON en el Backend
                        if (json.ok) {
                                //Si la respuesta no contiene errores
                                if (json.info.data.error === undefined) {
                                        //Se modifica la URL con la localidad consultada con el fin de que el enlace pueda ser almacenado como marcador/favorito
                                        if (!window.location.hash) {
                                                window.location += "#/"+ this.localidad.val();
                                        } else {
                                                window.location = new String(window.location).replace("/#/" + escape(window.location.hash.slice(2)), "#/"+ this.localidad.val());
                                        }

                                        //Carga del tiempo actual
                                        this.temperatura.text(json.info.data.current_condition[0].temp_C + "Cº");
                                        this.estado.text(json.info.data.current_condition[0].weatherDesc[0].value);
                                        $(this.temperatura).closest("div").show();
                                        this.localidadTemperatura.text(json.info.data.request[0].query);

                                        //FUTURE utilizar los siguientes datos referentes al tiempo actual
                                        /*$("#cloud-cover").text(json.info.data.current_condition[0].cloudcover + "%");
                                        $("#precipitacion").text(json.info.data.current_condition[0].precipMM + "mm");
                                        $("#velocidad-viento").text(json.info.data.current_condition[0].windspeedKmph + "Km/h");*/

                                        //Carga de la imagen asociada al tiempo actual
                                        this.imagenActual.attr("src", json.info.data.current_condition[0].weatherIconUrl[0].value);

                                        //Presentación de la previsión meteorológica (diferentes de temperatura actual)
                                        var jsonForecast = json.info.data.weather;

                                        if (jsonForecast !== undefined) {
                                                //Se quiere utilizar 'this' dentro del bucle, pero los bucles crean sus propios 'this'
                                                //por lo que guardo el 'this' actual en una variable alcanzable desde dentro del bucle llamada 'that'
                                                var that = this;
                                                this.forecastContainer.empty();

                                                $.each(jsonForecast, function (key, value) {

                                                        //Mostrar fecha en formato Español/España
                                                        var date = new Date(value.date);
                                                        var day = date.getDate();
                                                        var month = date.getMonth() + 1;
                                                        var year = date.getFullYear();

                                                        var fechaFormateada = util.getDiaSemana(date.getDay()).complete + " " + day + "/" + month + "/" + year;
                                                        var hoy = new Date();

                                                        if ((hoy.getDate() === day) && (hoy.getMonth() + 1 === month) && (hoy.getFullYear() === year)) {
                                                                fechaFormateada = "Hoy";
                                                        }

                                                        //Creo objeto de configuración de entrada para la plantilla Handlebars 'forecastTemplate'
                                                        var forecast = {
                                                                imagen: value.weatherIconUrl[0].value,
                                                                cabecera: fechaFormateada,
                                                                temperatura: value.tempMaxC + "/" + value.tempMinC + "Cº Max/min",
                                                                estado: value.weatherDesc[0].value,
                                                                precipitacion: value.precipMM + "mm",
                                                                velocidadViento: value.windspeedKmph + "Km/h"
                                                        };
                                                        that.forecastContainer.append(that.forecastTemplate(forecast));
                                                });

                                                this.forecastContainer.append("

");
                                        }

                                        //Animación para la presentación del listado de datos y la imagen
                                        if (this.forecastContainer.is(":visible")) {
                                                this.forecastContainer.slideUp(200).delay(200).slideDown(1000);
                                                this.condicionesActuales.hide().slideDown("1000");
                                        } else {
                                                this.forecastContainer.slideDown(1000);
                                                this.condicionesActuales.hide().slideDown("1000");
                                        }
                                } else {
                                        //Se pasa el error a la función manejadora de errores
                                        this.errorHandle({statusText: json.info.data.error[0].msg, status: 200});
                                }
                        } else {
                                this.errorHandle({statusText: "Ha ocurrido un problema al recuperar la información.", status: 500});
                        }
                }
                //Busca las posibles localizaciones según el critério de búsqueda utilizado
               /* getLocalidades: function (localidad) {
                    //FUTURE Si con una consulta no ha encontrado nada no llamar de nuevo al servicio hasta que la consulta se modifique.
                    //Ej: 'casavo' no encuentra nada. 'casavoo' no buscarlo poque no va a encontrar nada tampoco.
                    //Actualización: Estudiar el comportamiento del servicio, porque creo que no funciona como describo arriba

                    if (!app.ajaxInProgress) {
                        var targetUrl = "/location";

                        //Objecto con los datos de entrada de la petición
                        var datos = {
                            formato: "json",
                            localidad: $(localidad).val()
                        };

                        app.ajaxInProgress = true;
                        $("#indicadorAjaxEnCurso").show();
                        setTimeout(1000);
                        //Configuración y llamada al servicio RESTful vía Ajax
                        $.ajax({
                            url: targetUrl,
                            data: datos,
                            type: "GET",
                            dataType: "json",
                            cache: false,
                            contentType: "application/x-www-form-urlencoded; charset=UTF-8", //por defecto
                            success: printLocationHelper,
                            error: errorHandle,
                            //Función que se ejecuta sin importar el resultado de la petición Ajax
                            //FUTURE He comprobado que si se produce una excepción en la función 'success' la función 'complete' no se ejecuta. Investigar.
                            complete: function () {
                                console.log("Petición Ajax realizada.");
                                this.ajaxInProgress = false;
                                $("#indicadorAjaxEnCurso").hide();
                            }
                        });
                    }
                },

                //Muestra las posibles localizaciones según el critério de búsqueda utilizado
                printLocationHelper: function(json) {
                    //Si no se ha producido ningún error al tratar el objeto JSON en el Backend
                    if (json.ok) {
                        //Si la respuesta no contiene errores
                        if (json.info.data == undefined) {
                            //FUTURE hacer algo con los resultados obtenidos de buscar localidades según se va escribiendo
                            //json.info.search_api.result[0].areaName[0].value);
                        } else {
                            //No se encuentran localidades con los datoa introducidos.
                            //Este error no se maneja (ni muestra)
                        }
                    } else {
                        //Ha habido algún problema en el servidor.
                        //Este error no se maneja (ni muestra)
                    }
                }*/
        };

        App.init();

});
<commit_msg>Clean comments<commit_after>/*jslint indent:8, devel:true, browser:true, vars:true*/
/*global jQuery, $, Handlebars*/

//Esta es otra forma de escribir '$(document).ready()'
jQuery(function ($) {
        "use strict";

        //La variable informa si hay una petición Ajax en proceso
        var ajaxInProgress = false,
                ENTER_KEY = 13;

        //Objeto contenedor de utilidades. Aquí podría ir el esqueleto de la llamada vía Ajax
        //Estas funciones, si su uso es lo suficientemente común, podrían ir en un archivo '.js' a parte.
        var util = {
                getDiaSemana: function (numDia) {
                        var dias = [{short: "D", complete: "Domingo"},
                                        {short: "L", complete: "Lunes"},
                                        {short: "M", complete: "Martes"},
                                        {short: "X", complete: "Miércoles"},
                                        {short: "J", complete: "Jueves"},
                                        {short: "V", complete: "Viernes"},
                                        {short: "S", complete: "Sábado"}];
                        return dias[numDia];
                }
        };

        var App = {
                init: function () {
                        //Poner el foco en el primer campo de entrada visible
                        $("body").find("input[type=text]:visible:first").focus();
                        this.cacheElements();
                        this.bindEvents();
                        this.route();

                },
                cacheElements: function () {
                        this.forecastTemplate = Handlebars.compile($('#forecast-template').html());
                        this.weatherNode = $("#weatherNode");
                        this.condicionesActuales = this.weatherNode.find("#condicionesActuales");
                        this.cuerpo = this.weatherNode.find(".cuerpo");
                        this.temperatura = this.condicionesActuales.find(".temperatura");
                        this.estado = this.condicionesActuales.find(".estado");
                        this.localidadTemperatura = this.condicionesActuales.find(".localidad-temperatura");
                        this.imagenActual = this.condicionesActuales.find(".imagen-actual");
                        this.localidad = this.cuerpo.find("#localidad");
                        this.indicadorAjaxEnCurso = this.cuerpo.find("#indicadorAjaxEnCurso");
                        this.btoGetWeatherInfo = this.cuerpo.find("#getWeatherInfo");
                        this.forecastContainer = this.cuerpo.find("#forecast-container");
                        this.validacionesContainer = this.cuerpo.find("#validaciones-container");
                        this.errorContainer = this.cuerpo.find("#error-container");
                        this.txtLocalidad = this.cuerpo.find(".localidad");
                },
                bindEvents: function () {
                        //Al clicar el botón se consulta el tiempo para la localidad indicada
                        this.btoGetWeatherInfo.on("click", this.eventWeatherInfo.bind(this));
                        this.localidad.on("keyup", this.eventLocalidad.bind(this));
                        //Al clicar en el campo de entrada quitar la alerta visual de error por campo vacío
                        this.localidad.on("click", function (event) {
                                $(event.target).removeClass("error-input");
                        });
                },
                route: function () {
                        var route = window.location.hash.slice(2);
                        // Si en la URL se informa una localidad buscar directamente la previsión sobre la misma
                        if (route.length) {
                                this.localidad.val(route);
                                this.txtLocalidad.text(route);
                                this.btoGetWeatherInfo.click();
                        }
                },

                /* Métodos utilizados desde 'bindEvents' */

                eventWeatherInfo: function () {
                        //Validaciones sobre los campos de entrada
                        var erroresEntrada = false;
                        if (!this.localidad.val()) {
                                this.muestraValidaciones(["Es necesario especificar una localidad"]);
                                this.localidad.focus().addClass("error-input");
                                erroresEntrada = true;
                        }

                        if (!erroresEntrada) {
                                this.validacionesContainer.hide();
                                this.getWeatherInfo();
                        }
                },
                eventLocalidad: function (event) {
                        //Al comenzar a teclear en el campo de entrada quitar la alerta visual de error por campo vacío
                        this.localidad.removeClass("error-input");

                        //Si la tecla pulsada es un 'Intro' lanzar el evento 'click' del botón
                        if (event.keyCode === ENTER_KEY) {
                                this.btoGetWeatherInfo.click();
                                return true;
                        }

                        //Se utiliza 'event.target' en vez de 'this', ya que este en vez de hacer referencia al objeto
                        //que ha lanzado el evento hace referenci al objeto App porque se ha invocado así: '.bind(this)'?
                        this.txtLocalidad.text($(event.target).val());

                        //Si el campo está vacío
                        if (!$(event.target).val()) {
                                this.txtLocalidad.text("...");
                        }

                        //Si se han introducido más de 3 caracteres se procede a buscar localidades
                        //que coincidan con esos caracteres
                        //FUTURE Descomentar cuando se sepa qué hacer con la lista de localidades devueltas
                        /*if ($(this).val().length >= 4) {
                        getLocalidades(this);
                        }*/
                },
                //Muestra mensajes de validación
                muestraValidaciones: function (arrValidaciones) {
                        //La información meteorológica anteriormente cargada se esconde
                        this.forecastContainer.hide();
                        this.condicionesActuales.hide();

                        this.validacionesContainer.html("" + arrValidaciones[0] + "");

                        if (this.validacionesContainer.is(":visible")) {
                                this.validacionesContainer.slideUp(200).delay(200).slideDown(200);
                        } else {
                                this.validacionesContainer.slideDown(200);
                        }
                },

                //Maneja los errores de la petición Ajax
                //err = { cod, desc }
                errorHandle: function (err) {
                        //La información meteorológica anteriormente cargada se oculta, además de los errores que se estén ya mostrando
                        this.forecastContainer.hide();
                        this.condicionesActuales.hide();
                        this.errorContainer.hide();

                        var errorMsg = err.statusText + "\n Error " + err.status;

                        this.errorContainer.html(errorMsg);
                        this.errorContainer.slideDown(200).delay(3000).slideUp(2000);
                },
                //Recupera la información meteorológica para la localización introducida
                getWeatherInfo: function () {
                        //Si no hay una petición Ajax en curso se realiza una
                        if (!ajaxInProgress) {
                                //URL del servicio RESTful del Backend de la aplicación
                                var targetUrl = "/forecast";

                                //Objecto con los datos de entrada de la petición
                                var datos = {
                                        formato: "json",
                                        localidad: this.localidad.val(),
                                        numDias: 4
                                };

                                ajaxInProgress = true;
                                this.indicadorAjaxEnCurso.show();

                                //Se quiere utilizar 'this' dentro de una función [complete] de un objeto, pero la función tendrá como 'this' el objeto en el que fué creada
                                //por lo que guardo el 'this' actual en una variable alcanzable desde dentro de la función llamada 'that'
                                var that = this;

                                //Configuración y llamada al servicio RESTful vía Ajax
                                $.ajax({
                                        url: targetUrl,
                                        data: datos,
                                        type: "GET",
                                        dataType: "json",
                                        cache: false,
                                        contentType: "application/x-www-form-urlencoded; charset=UTF-8", //por defecto
                                        //En el contexto de una invocación Ajax, 'this' no se refiere al objeto contenedor sino a la propia llamada. Por eso 'bind(this)'
                                        success: this.printWeather.bind(this),
                                        error: this.errorHandle.bind(this),
                                        //Función que se ejecuta sin importar el resultado de la petición Ajax
                                        //CHANGES He comprobado que si se produce una excepción en la función 'success' la función 'complete' no se ejecuta. Quizá
                                        //este no sea el mejor sitio para indicar que la función Ajax ha terminado [ajaxInProgress = false] y sea mejor hacerlo
                                        //tanto en el success como en el error.
                                        complete: function () {
                                                ajaxInProgress = false;
                                                that.indicadorAjaxEnCurso.hide();
                                        }
                                });
                        }// End if
                },
                //Función que ejecuta en caso de que la petición Ajax 'getWeatherInfo' haya sido exitosa
                //Muestra en pantalla la información meteorológica
                printWeather: function (json) {
                        //Si no se ha producido ningún error al tratar el objeto JSON en el Backend
                        if (json.ok) {
                                //Si la respuesta no contiene errores
                                if (json.info.data.error === undefined) {
                                        //Se modifica la URL con la localidad consultada con el fin de que el enlace pueda ser almacenado como marcador/favorito
                                        if (!window.location.hash) {
                                                window.location += "#/"+ this.localidad.val();
                                        } else {
                                                window.location = new String(window.location).replace("/#/" + escape(window.location.hash.slice(2)), "#/"+ this.localidad.val());
                                        }

                                        //Carga del tiempo actual
                                        this.temperatura.text(json.info.data.current_condition[0].temp_C + "Cº");
                                        this.estado.text(json.info.data.current_condition[0].weatherDesc[0].value);
                                        $(this.temperatura).closest("div").show();
                                        this.localidadTemperatura.text(json.info.data.request[0].query);

                                        //FUTURE utilizar los siguientes datos referentes al tiempo actual
                                        /*$("#cloud-cover").text(json.info.data.current_condition[0].cloudcover + "%");
                                        $("#precipitacion").text(json.info.data.current_condition[0].precipMM + "mm");
                                        $("#velocidad-viento").text(json.info.data.current_condition[0].windspeedKmph + "Km/h");*/

                                        //Carga de la imagen asociada al tiempo actual
                                        this.imagenActual.attr("src", json.info.data.current_condition[0].weatherIconUrl[0].value);

                                        //Presentación de la previsión meteorológica (diferentes de temperatura actual)
                                        var jsonForecast = json.info.data.weather;

                                        if (jsonForecast !== undefined) {
                                                //Se quiere utilizar 'this' dentro del bucle, pero los bucles crean sus propios 'this'
                                                //por lo que guardo el 'this' actual en una variable alcanzable desde dentro del bucle llamada 'that'
                                                var that = this;
                                                this.forecastContainer.empty();

                                                $.each(jsonForecast, function (key, value) {

                                                        //Mostrar fecha en formato Español/España
                                                        var date = new Date(value.date);
                                                        var day = date.getDate();
                                                        var month = date.getMonth() + 1;
                                                        var year = date.getFullYear();

                                                        var fechaFormateada = util.getDiaSemana(date.getDay()).complete + " " + day + "/" + month + "/" + year;
                                                        var hoy = new Date();

                                                        if ((hoy.getDate() === day) && (hoy.getMonth() + 1 === month) && (hoy.getFullYear() === year)) {
                                                                fechaFormateada = "Hoy";
                                                        }

                                                        //Creo objeto de configuración de entrada para la plantilla Handlebars 'forecastTemplate'
                                                        var forecast = {
                                                                imagen: value.weatherIconUrl[0].value,
                                                                cabecera: fechaFormateada,
                                                                temperatura: value.tempMaxC + "/" + value.tempMinC + "Cº Max/min",
                                                                estado: value.weatherDesc[0].value,
                                                                precipitacion: value.precipMM + "mm",
                                                                velocidadViento: value.windspeedKmph + "Km/h"
                                                        };
                                                        that.forecastContainer.append(that.forecastTemplate(forecast));
                                                });

                                                this.forecastContainer.append("

");
                                        }

                                        //Animación para la presentación del listado de datos y la imagen
                                        if (this.forecastContainer.is(":visible")) {
                                                this.forecastContainer.slideUp(200).delay(200).slideDown(1000);
                                                this.condicionesActuales.hide().slideDown("1000");
                                        } else {
                                                this.forecastContainer.slideDown(1000);
                                                this.condicionesActuales.hide().slideDown("1000");
                                        }
                                } else {
                                        //Se pasa el error a la función manejadora de errores
                                        this.errorHandle({statusText: json.info.data.error[0].msg, status: 200});
                                }
                        } else {
                                this.errorHandle({statusText: "Ha ocurrido un problema al recuperar la información.", status: 500});
                        }
                }
                //Busca las posibles localizaciones según el critério de búsqueda utilizado
               /* getLocalidades: function (localidad) {
                    //FUTURE Si con una consulta no ha encontrado nada no llamar de nuevo al servicio hasta que la consulta se modifique.
                    //Ej: 'casavo' no encuentra nada. 'casavoo' no buscarlo poque no va a encontrar nada tampoco.
                    //Actualización: Estudiar el comportamiento del servicio, porque creo que no funciona como describo arriba

                    if (!app.ajaxInProgress) {
                        var targetUrl = "/location";

                        //Objecto con los datos de entrada de la petición
                        var datos = {
                            formato: "json",
                            localidad: $(localidad).val()
                        };

                        app.ajaxInProgress = true;
                        $("#indicadorAjaxEnCurso").show();
                        setTimeout(1000);
                        //Configuración y llamada al servicio RESTful vía Ajax
                        $.ajax({
                            url: targetUrl,
                            data: datos,
                            type: "GET",
                            dataType: "json",
                            cache: false,
                            contentType: "application/x-www-form-urlencoded; charset=UTF-8", //por defecto
                            success: printLocationHelper,
                            error: errorHandle,
                            //Función que se ejecuta sin importar el resultado de la petición Ajax
                            //FUTURE He comprobado que si se produce una excepción en la función 'success' la función 'complete' no se ejecuta. Investigar.
                            complete: function () {
                                console.log("Petición Ajax realizada.");
                                this.ajaxInProgress = false;
                                $("#indicadorAjaxEnCurso").hide();
                            }
                        });
                    }
                },

                //Muestra las posibles localizaciones según el critério de búsqueda utilizado
                printLocationHelper: function(json) {
                    //Si no se ha producido ningún error al tratar el objeto JSON en el Backend
                    if (json.ok) {
                        //Si la respuesta no contiene errores
                        if (json.info.data == undefined) {
                            //FUTURE hacer algo con los resultados obtenidos de buscar localidades según se va escribiendo
                            //json.info.search_api.result[0].areaName[0].value);
                        } else {
                            //No se encuentran localidades con los datoa introducidos.
                            //Este error no se maneja (ni muestra)
                        }
                    } else {
                        //Ha habido algún problema en el servidor.
                        //Este error no se maneja (ni muestra)
                    }
                }*/
        };

        App.init();

});
<|endoftext|>


<commit_before>$(document).ready(function() {
  $("#writeStory").click(function() {
    var originalStory = $("#originalStory").val();

    var generatedStory = generateStory(originalStory);

    $("#generatedHeader").text("Generated Story:");
    $("#generatedStory").html(generatedStory);
    $("#generatedStory").html($("#generatedStory").html().replace(/ /g, ''));
  });

  function generateStory(originalStory) {
    originalStory = originalStory.replace(/[\n\r]/g, '

');
    var words = originalStory.split(" ");
    var map = {};
    var count = 0;

    for(var i = 0; i < words.length - 1; i++) {
      if(!map[words[i] + " " + words[i + 1]]) {
        count++;
        map[words[i] + " " + words[i + 1]] = new Array();
      }
      if(words[i + 2]) {
        map[words[i] + " " + words[i + 1]].push(words[i + 2]);
      } else {
        map[words[i] + " " + words[i + 1]].push(null);
      }
    }

    var generatedStory = "";
    var pairs = Object.keys(map);
    var currentPair = pairs[Math.floor(Math.random() * pairs.length)];
    var firstWord = currentPair.split(" ")[0];
    var secondWord = currentPair.split(" ")[1];
    generatedStory += "
" + firstWord.charAt(0).toUpperCase() + firstWord.slice(1); + " ";
    generatedStory += " " + secondWord;
    while(map[currentPair][0]) {
      firstWord = secondWord;
      secondWord = map[currentPair][Math.floor(Math.random() * map[currentPair].length)];
      generatedStory += " " + secondWord;
      currentPair = firstWord + " " + secondWord;
    }
    generatedStory += "
";
    return generatedStory;
  }
});
<commit_msg>testing<commit_after>$(document).ready(function() {
  $("#writeStory").click(function() {
    var originalStory = $("#originalStory").val();

    var generatedStory = generateStory(originalStory);

    $("#generatedHeader").text("Generated Story:");
    $("#generatedStory").html(generatedStory);
    $("#generatedStory").html($("#generatedStory").html().replace(/ /g, ''));
  });

  function generateStory(originalStory) {
    originalStory = originalStory.replace(/[\n\r]/g, '

');
    var words = originalStory.split(" ");
    var map = {};
    var count = 0;

    for(var i = 0; i < words.length - 1; i++) {
      if(!map[words[i] + " " + words[i + 1]]) {
        count++;
        map[words[i] + " " + words[i + 1]] = new Array();
      }
      if(words[i + 2]) {
        map[words[i] + " " + words[i + 1]].push(words[i + 2]);
      } else {
        map[words[i] + " " + words[i + 1]].push(null);
      }
    }

    var generatedStory = "";
    var pairs = Object.keys(map);
    var currentPair = pairs[Math.floor(Math.random() * pairs.length)];
    var firstWord = currentPair.split(" ")[0];
    var secondWord = currentPair.split(" ")[1];
    generatedStory += "
" + firstWord.charAt(0).toUpperCase() + firstWord.slice(1); + " ";
    generatedStory += " " + secondWord;
    while(map[currentPair][0]) {
      firstWord = secondWord;
      secondWord = map[currentPair][Math.floor(Math.random() * map[currentPair].length)];
      generatedStory += " " + secondWord;
      currentPair = firstWord + " " + secondWord;
    }
    generatedStory += "
";
    return generatedStory;
  }
});
#
<|endoftext|>


<commit_before>// Set up!
var sad_canvas = document.getElementById("sad");
var happy_canvas = document.getElementById("happy");
var ws = MyWebSocket();
// ws.onopen = function()
		// {
		//    // Web Socket is connected, send data using send()
		//    ws.send("Message to send");
		//    alert("Message is sent...");
		// };

makeHappiness(sad_canvas, false);
makeHappiness(happy_canvas, true);

function calculateHappiness(canvas, happynessLevel){
	var context = canvas.getContext("2d");
	context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clears the canvas
	drawBasicCanvas(context);

	// Draw the mouth
	context.beginPath();
	context.arc(95, 90, 26, (happynessLevel +0.02)*Math.PI, 2*Math.PI, true);
    context.stroke();

}

function makeHappiness(canvas, isHappy){
	var context = canvas.getContext("2d");
	drawBasicCanvas(context);

	// Draw the mouth
	context.beginPath();
	if(!isHappy)
		context.arc(95, 110, 26, Math.PI, 2*Math.PI, false); //sad
	else
		context.arc(95, 90, 26, Math.PI, 2*Math.PI, true); // happy

	context.stroke();

	// Write "Hello, World!"
	context.font = "30px Garamond";
	happynessStr = isHappy ? "Do u feel happy?"  : "Do u feel sad?"
	context.fillText(happynessStr,15,175);
}

function sendHappiness(happinessLevel){ //should be 1 (happy) or 0 (sad) for now
	// $.post(
	// 	"http://localhost:9000/smile?level="+happinessLevel
	// 	).done( function(){
	// 		alert('Your happiness has been submitted.'+happinessLevel);
	// 	});
	console.log(ws);
	ws.send(happinessLevel);

}

function MyWebSocket()
{
	if ("WebSocket" in window)
	{
		// Let us open a web socket
		var ws = new WebSocket("ws://localhost:9000/ws");

		ws.onmessage = function (evt)
		{
			var received_msg = evt.data;
			console.log("Message is received..."+received_msg);
			var level = received_msg.level;
			if (level >= 0)
				calculateHappiness(document.getElementById("result"), data.level);

		};

		ws.onclose = function()
		{
			// websocket is closed.
			console.log("Connection is closed...");
		};

		return ws;
	}
	else
	{
		// The browser doesn't support WebSocket
		alert("WebSocket NOT supported by your Browser!");
	}
}

function drawBasicCanvas(context){
	// Draw the face
	context.fillStyle = "yellow";
	context.beginPath();
	context.arc(95, 85, 40, 0, 2*Math.PI);
	context.closePath();
	context.fill();
	context.lineWidth = 2;
	context.stroke();
	context.fillStyle = "black";

	// Draw the left eye
	context.beginPath();
	context.arc(75, 75, 5, 0, 2*Math.PI);
	context.closePath();
	context.fill();

	// Draw the right eye
	context.beginPath();
	context.arc(114, 75, 5, 0, 2*Math.PI);
	context.closePath();
	context.fill();
}
<commit_msg>Works<commit_after>var sad_canvas = document.getElementById("sad");
var happy_canvas = document.getElementById("happy");
var ws = MyWebSocket();

makeHappiness(sad_canvas, false);
makeHappiness(happy_canvas, true);

function calculateHappiness(canvas, happinessLevel){
	var context = canvas.getContext("2d");
	context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clears the canvas
	drawBasicCanvas(context);

	// Draw the mouth
	context.beginPath();
	context.arc(95, 90, 26, (happinessLevel +0.02)*Math.PI, 2*Math.PI, true);
    context.stroke();
}

function makeHappiness(canvas, isHappy){
	var context = canvas.getContext("2d");
	drawBasicCanvas(context);

	// Draw the mouth
	context.beginPath();
	if(!isHappy)
		context.arc(95, 110, 26, Math.PI, 2*Math.PI, false); //sad
	else
		context.arc(95, 90, 26, Math.PI, 2*Math.PI, true); // happy

	context.stroke();

	context.font = "30px Garamond";
	happynessStr = isHappy ? "Do u feel happy?"  : "Do u feel sad?"
	context.fillText(happynessStr,15,175);
}

function sendHappiness(happinessLevel){ //should be 1 (happy) or 0 (sad) for now
	console.log(ws);
	ws.send(happinessLevel);
}

function MyWebSocket()
{
	if ("WebSocket" in window)
	{
		// Let us open a web socket
		var ws = new WebSocket("ws://localhost:9000/ws");

		ws.onmessage = function (evt)
		{
			var received_msg = evt.data;
			console.log("Message is received..."+received_msg);
			var level = JSON.parse(received_msg).level;
			    console.log(level)
			if (level >= 0) {
			    console.log("hello")
				calculateHappiness(document.getElementById("result"), level);
			}
		};

		ws.onclose = function() {
			// websocket is closed.
			console.log("Connection is closed...");
		};

		return ws;
	}
	else
	{
		alert("WebSocket NOT supported by your Browser!");
	}
}

function drawBasicCanvas(context){
	// Draw the face
	context.fillStyle = "yellow";
	context.beginPath();
	context.arc(95, 85, 40, 0, 2*Math.PI);
	context.closePath();
	context.fill();
	context.lineWidth = 2;
	context.stroke();
	context.fillStyle = "black";

	// Draw the left eye
	context.beginPath();
	context.arc(75, 75, 5, 0, 2*Math.PI);
	context.closePath();
	context.fill();

	// Draw the right eye
	context.beginPath();
	context.arc(114, 75, 5, 0, 2*Math.PI);
	context.closePath();
	context.fill();
}
<|endoftext|>


<commit_before>// register the directive with your app module
var app = angular.module('ddApp', ['ngAnimate', 'ui.bootstrap', 'rzModule']);

// function referenced by the drop target
app.controller('ddController', ['$scope', '$http', '$log', function($scope, $http, $log){

    $scope.sliderOptions = {
        floor: 0,
        ceil: 0,
        showSelectionBar: true,
        showTicksValues: true
    };

    $scope.visType = "slider";
    $scope.z = 0;
    $scope.tiles = {};
    if(typeof runId !== 'undefined'){
        loadNewRun();
    }

    (function launchSocketIo() {
        // launch socket.io
        var socket = io.connect(window.location.origin);
        if(typeof runId !== 'undefined'){
            socket.emit('subscribe', 'runs/' + runId);
            socket.on('data', function(data) {
                $scope.rescuedVictims = data.rescuedVictims;

                for(var i = 0; i < data.tiles.length; i++){
                    $scope.tiles[data.tiles[i].x + ',' +
                                 data.tiles[i].y + ',' +
                                 data.tiles[i].z].scoredItems = data.tiles[i].scoredItems;
                }
                $scope.score = data.score;
                $scope.showedUp = data.showedUp;
                $scope.LoPs = data.LoPs;
                $scope.minutes = data.time.minutes;;
                $scope.seconds = data.time.seconds;
                $scope.$apply();
                console.log("Updated view from socket.io");
            });
        }

        if(typeof fieldId !== 'undefined'){
            socket.emit('subscribe', 'fields/' + fieldId);
            socket.on('data', function(data) {
//                if(typeof runId === 'undefined') || runId != data.newRun){ // TODO: FIX!
                    console.log("Judge changed to a new run");
                    runId = data.newRun;
                    loadNewRun();
//                }
            });

        }

    })();

    function loadNewRun(){
        $http.get("/api/runs/"+runId+"?populate=true").then(function(response){
            $scope.height = response.data.height;
            $scope.sliderOptions.ceil = $scope.height - 1;
            $scope.width = response.data.width;
            $scope.length = response.data.length;
            $scope.team = response.data.team;
            $scope.field = response.data.field;

            $scope.numberOfDropTiles = response.data.numberOfDropTiles;;
            $scope.rescuedVictims = response.data.rescuedVictims;

            for(var i = 0; i < response.data.tiles.length; i++){
                $scope.tiles[response.data.tiles[i].x + ',' +
                             response.data.tiles[i].y + ',' +
                             response.data.tiles[i].z] = response.data.tiles[i];
            }

            $scope.score = response.data.score;
            $scope.showedUp = response.data.showedUp;
            $scope.LoPs = response.data.LoPs;
            // Verified time by timekeeper
            $scope.minutes = response.data.time.minutes;;
            $scope.seconds = response.data.time.seconds;

            console.log($scope.tiles);
        }, function(response){
            console.log("Error: " + response.statusText);
        });
    }

    $scope.range = function(n){
        arr = [];
        for (var i=0; i < n; i++) {
            arr.push(i);
        }
        return arr;
    }

    $scope.getOpacity = function(x,y){
        var stackedTiles = 0;
        for(var z = 0; z < $scope.height; z++){
            if($scope.tiles[x+','+y+','+z])
                stackedTiles++;
        }
        return 1.0/stackedTiles;
    }

}]);

app.directive('tile', function() {
    return {
        scope: {
            tile: '='
        },
        restrict: 'E',
        templateUrl: '/templates/tile.html',
        link : function($scope, element, attrs){

            $scope.tileStatus = function(tile){
                // If this is a non-existent tile
                if(!tile)
                    return;
                var successfully = 0;
                var possible = 0;

                var count = function(list){
                    for(var i = 0; i < list.length; i++){
                        if(list[i])
                            successfully++;
                        possible++;
                    }
                }
                count(tile.scoredItems.gaps);
                count(tile.scoredItems.speedbumps);
                count(tile.scoredItems.intersections);
                count(tile.scoredItems.obstacles);
                if(tile.scoredItems.dropTiles.length > 0)
                    count(tile.scoredItems.dropTiles);

                if(possible > 0 && successfully == possible)
                    return "done";
                else if(successfully > 0)
                    return "halfdone";
                else if(possible > 0)
                    return "undone";
                else
                    return "";
            }

            $scope.rotateRamp = function(direction){
                switch(direction){
                case "bottom":
                    return "rot0";
                case "top":
                    return "rot180";
                case "left":
                    return "rot90";
                case "right":
                    return "rot270";
                }
            }
        }
    };
});
<commit_msg>Update URL for line viewer<commit_after>// register the directive with your app module
var app = angular.module('ddApp', ['ngAnimate', 'ui.bootstrap', 'rzModule']);

// function referenced by the drop target
app.controller('ddController', ['$scope', '$http', '$log', function($scope, $http, $log){

    $scope.sliderOptions = {
        floor: 0,
        ceil: 0,
        showSelectionBar: true,
        showTicksValues: true
    };

    $scope.visType = "slider";
    $scope.z = 0;
    $scope.tiles = {};
    if(typeof runId !== 'undefined'){
        loadNewRun();
    }

    (function launchSocketIo() {
        // launch socket.io
        var socket = io.connect(window.location.origin);
        if(typeof runId !== 'undefined'){
            socket.emit('subscribe', 'runs/' + runId);
            socket.on('data', function(data) {
                $scope.rescuedVictims = data.rescuedVictims;

                for(var i = 0; i < data.tiles.length; i++){
                    $scope.tiles[data.tiles[i].x + ',' +
                                 data.tiles[i].y + ',' +
                                 data.tiles[i].z].scoredItems = data.tiles[i].scoredItems;
                }
                $scope.score = data.score;
                $scope.showedUp = data.showedUp;
                $scope.LoPs = data.LoPs;
                $scope.minutes = data.time.minutes;;
                $scope.seconds = data.time.seconds;
                $scope.$apply();
                console.log("Updated view from socket.io");
            });
        }

        if(typeof fieldId !== 'undefined'){
            socket.emit('subscribe', 'fields/' + fieldId);
            socket.on('data', function(data) {
//                if(typeof runId === 'undefined') || runId != data.newRun){ // TODO: FIX!
                    console.log("Judge changed to a new run");
                    runId = data.newRun;
                    loadNewRun();
//                }
            });

        }

    })();

    function loadNewRun(){
        $http.get("/api/runs/line/"+runId+"?populate=true").then(function(response){
            $scope.height = response.data.height;
            $scope.sliderOptions.ceil = $scope.height - 1;
            $scope.width = response.data.width;
            $scope.length = response.data.length;
            $scope.team = response.data.team;
            $scope.field = response.data.field;

            $scope.numberOfDropTiles = response.data.numberOfDropTiles;;
            $scope.rescuedVictims = response.data.rescuedVictims;

            for(var i = 0; i < response.data.tiles.length; i++){
                $scope.tiles[response.data.tiles[i].x + ',' +
                             response.data.tiles[i].y + ',' +
                             response.data.tiles[i].z] = response.data.tiles[i];
            }

            $scope.score = response.data.score;
            $scope.showedUp = response.data.showedUp;
            $scope.LoPs = response.data.LoPs;
            // Verified time by timekeeper
            $scope.minutes = response.data.time.minutes;;
            $scope.seconds = response.data.time.seconds;

            console.log($scope.tiles);
        }, function(response){
            console.log("Error: " + response.statusText);
        });
    }

    $scope.range = function(n){
        arr = [];
        for (var i=0; i < n; i++) {
            arr.push(i);
        }
        return arr;
    }

    $scope.getOpacity = function(x,y){
        var stackedTiles = 0;
        for(var z = 0; z < $scope.height; z++){
            if($scope.tiles[x+','+y+','+z])
                stackedTiles++;
        }
        return 1.0/stackedTiles;
    }

}]);

app.directive('tile', function() {
    return {
        scope: {
            tile: '='
        },
        restrict: 'E',
        templateUrl: '/templates/tile.html',
        link : function($scope, element, attrs){

            $scope.tileStatus = function(tile){
                // If this is a non-existent tile
                if(!tile)
                    return;
                var successfully = 0;
                var possible = 0;

                var count = function(list){
                    for(var i = 0; i < list.length; i++){
                        if(list[i])
                            successfully++;
                        possible++;
                    }
                }
                count(tile.scoredItems.gaps);
                count(tile.scoredItems.speedbumps);
                count(tile.scoredItems.intersections);
                count(tile.scoredItems.obstacles);
                if(tile.scoredItems.dropTiles.length > 0)
                    count(tile.scoredItems.dropTiles);

                if(possible > 0 && successfully == possible)
                    return "done";
                else if(successfully > 0)
                    return "halfdone";
                else if(possible > 0)
                    return "undone";
                else
                    return "";
            }

            $scope.rotateRamp = function(direction){
                switch(direction){
                case "bottom":
                    return "rot0";
                case "top":
                    return "rot180";
                case "left":
                    return "rot90";
                case "right":
                    return "rot270";
                }
            }
        }
    };
});
<|endoftext|>