<commit_before>maphub.AnnotationView = function(parameters) {

  /* Callbacks for added / removed features */
  function featureSelected(evt) {
    var class_name = evt.feature.geometry.CLASS_NAME;
    if (!evt.feature.tooltip) {
      if (class_name == "OpenLayers.Geometry.Point") {
        evt.feature.tooltip = new maphub.ControlPointTooltip(evt.feature.control_point, self.user_id);
      } else {
        evt.feature.tooltip = new maphub.AnnotationTooltip(evt.feature.annotation, self.user_id);
      }
      self.tooltips.push(evt.feature.tooltip);
    }
    // get the screen coordinates
    var lonlat = evt.feature.geometry.getBounds().getCenterLonLat();
    var coords = this.map.getPixelFromLonLat(lonlat);
    evt.feature.tooltip.show(coords.x - 50, coords.y);
  }

  function featureUnselected(evt) {
    evt.feature.tooltip.hide();
  }

  // This function is called when a feature was added to the Edit layer
  function featureAdded(evt) {

    // deactivate keyboard shortcuts
    self.keyboard_shortcuts.deactivate();

    // hide all tooltips
    for (var i in self.tooltips) {
      self.tooltips[i].hide();
    }

    // is this a Control Point or an Annotation?
    var class_name = evt.feature.geometry.CLASS_NAME;
    if (class_name == "OpenLayers.Geometry.Point") {
      controlPointAdded(evt);
    } else {
      annotationAdded(evt);
    }
  }

  // This function is called when an annotation was drawn
  function annotationAdded(evt) {
    var wkt_data = evt.feature.geometry.toString();

    // reinitialize title and body, and copy WKT data as well as bounds
    $("#annotation_body").attr("value", "Add your annotation here!");
    $("#annotation_wkt_data").attr("value", wkt_data);

    $("#annotation_boundary_attributes_ne_x").attr("value", evt.feature.geometry.bounds.right);
    $("#annotation_boundary_attributes_ne_y").attr("value", evt.feature.geometry.bounds.top);
    $("#annotation_boundary_attributes_sw_x").attr("value", evt.feature.geometry.bounds.left);
    $("#annotation_boundary_attributes_sw_y").attr("value", evt.feature.geometry.bounds.bottom);

    // clear the existing tags
    $("#modal-annotation-tags").empty();
    $("#tagging-help").hide();
    $("#no-tags").show();

    // show the popup
    $("#modal-annotation").modal({
      'backdrop': 'static'
    });
    $("#annotation_body").focus();
    $("#annotation_body").select();
  }

  function controlPointAdded(evt) {
    var wkt_data = evt.feature.geometry.toString();

    // set x/y in form
    $("#control_point_wkt_data").attr("value", wkt_data);
    $("#control_point_x").attr("value", evt.feature.geometry.x);
    $("#control_point_y").attr("value", evt.feature.geometry.y);

    // reset the place search box and slide up panel
    $("#place-search").attr("value", "");
    $("#modal-control-point").modal({
      'backdrop': 'static'
    });
    $("#place-search").focus();
  }

  /* ============================================================================== */

  this.zoomify_width  = parameters.width;        // pixel width ...
  this.zoomify_height = parameters.height;       // ... and height of map
  this.zoomify_url    = parameters.zoomify_url;  // remote zoomify tileset

  this.annotations_url          = parameters.annotations_url;      // JSON request URL for annotations
  this.control_points_url       = parameters.control_points_url;   // JSON request URL for control points
  this.editable                 = parameters.editable;             // whether to show the control panel
  this.user_id                  = parameters.user_id;              // the currently logged in user ID
  this.features_annotations     = [];   // all annotation features
  this.features_control_points  = [];   // all control point features
  this.annotations              = [];   // all annotations on this map
  this.control_points           = [];   // all control points on this map
  this.tooltips                 = [];   // all tooltips on this map

  // ================================================================================

  var annotationStyleMap = new OpenLayers.StyleMap({
    'default': new OpenLayers.Style({
      'strokeColor': "#cc4400",
      'fillOpacity': "0.4",
      'fillColor': "#cc4400"
    }),
    'select': new OpenLayers.Style({
      'strokeColor': "#ff0000",
    }),
    'temporary': new OpenLayers.Style({
      'strokeColor': "#005580",
      'fillColor': "#00A9FF",
      'fillOpacity': "0.4",
    })
  });

  var controlPointStyleMap = new OpenLayers.StyleMap({
    'default': new OpenLayers.Style({
      'externalGraphic': "/assets/openlayers/pin.png",
      'graphicWidth': '15',
      'graphicWidth': '25',
      'graphicXOffset': -13,
      'graphicYOffset': -25
    }),
    'select': new OpenLayers.Style({
      'externalGraphic': "/assets/openlayers/pin.png",
      'graphicWidth': '15',
      'graphicWidth': '25',
      'graphicXOffset': -13,
      'graphicYOffset': -25
    }),
    'temporary': new OpenLayers.Style({
      'externalGraphic': "/assets/openlayers/pin.png",
      'graphicWidth': '15',
      'graphicWidth': '25',
      'graphicXOffset': -13,
      'graphicYOffset': -25
    })
  });

  // ================================================================================

  /* The zoomify layer */
  this.baseLayer = new OpenLayers.Layer.Zoomify(
    "Zoomify",
    this.zoomify_url,
    new OpenLayers.Size( this.zoomify_width, this.zoomify_height ),
    { displayInLayerSwitcher: false }
  );

  /* The editing controls layer */
  this.editLayer = new OpenLayers.Layer.Vector(
    "Editable",
    { styleMap: annotationStyleMap, displayInLayerSwitcher: false }
  );
  this.editLayer.events.register("featureadded", this.editLayer, featureAdded);

  this.controlPointEditLayer = new OpenLayers.Layer.Vector(
    "Control Point Editable",
    { styleMap: controlPointStyleMap, displayInLayerSwitcher: false }
  );
  this.controlPointEditLayer.events.register("featureadded", this.controlPointEditLayer, featureAdded);

  // if the close button is clicked, we remove all temporary features
  $("a.close").click(function() {
    self.editLayer.removeAllFeatures();
    self.controlPointEditLayer.removeAllFeatures();
  });

  /* The annotation layer */
  this.annotationLayer = new OpenLayers.Layer.Vector(
    "Annotations",
    { styleMap: annotationStyleMap }
  );

  /* The control points layer */
  this.controlPointsLayer = new OpenLayers.Layer.Vector(
    "Control Points",
    { styleMap: controlPointStyleMap }
  );

  /* Display options */
  var bounds = new OpenLayers.Bounds(0, 0, this.zoomify_width, this.zoomify_height)
  var options = {
      controls: [],
      maxExtent: bounds,
      restrictedExtent: bounds,
      maxResolution: Math.pow(2, this.baseLayer.numberOfTiers-1),
      numZoomLevels: this.baseLayer.numberOfTiers,
      units: 'pixels'
  };

  this.map = new OpenLayers.Map("viewer", options);

  // add all layers to the map
  this.map.addLayer(this.baseLayer);
  this.map.addLayer(this.editLayer);
  this.map.addLayer(this.controlPointEditLayer);
  this.map.addLayer(this.annotationLayer);
  this.map.addLayer(this.controlPointsLayer);

  // remotely load already existing annotations and control points via JSON
  this.remoteLoadAnnotations();
  this.remoteLoadControlPoints();

  // add autocomplete
  this.initAutoComplete();

  // MouseDefaults is deprecated, see: http://trac.osgeo.org/openlayers/wiki/Control/MouseDefaults
  this.map.addControl(new OpenLayers.Control.Navigation());
  this.map.addControl(new OpenLayers.Control.MousePosition());
  this.map.addControl(new OpenLayers.Control.PanZoomBar());
  this.keyboard_shortcuts = new OpenLayers.Control.KeyboardDefaults();
  this.map.addControl(this.keyboard_shortcuts);

  this.map.addControl(new OpenLayers.Control.LayerSwitcher(
    {'div':OpenLayers.Util.getElement('layerswitcher')})
    );

  // ================================================================================

  var select = new OpenLayers.Control.SelectFeature(
    [this.annotationLayer, this.controlPointsLayer], {
      hover: true,
      renderIntent: "temporary"
      }
  );
  this.map.addControl(select);
  select.activate();

  this.annotationLayer.events.on({
    "featureselected": featureSelected,
    "featureunselected": featureUnselected
  });

  this.controlPointsLayer.events.on({
    "featureselected": featureSelected,
    "featureunselected": featureUnselected
  });

  // ================================================================================

  /* Allow creation of features */
  // http://stackoverflow.com/questions/10572005/
  if (parameters.editable) {
    this.drawControls = {
        point: new OpenLayers.Control.DrawFeature(this.controlPointEditLayer,
            OpenLayers.Handler.Point),
        line: new OpenLayers.Control.DrawFeature(this.editLayer,
            OpenLayers.Handler.Path),
        polygon: new OpenLayers.Control.DrawFeature(this.editLayer,
            OpenLayers.Handler.Polygon),
        box: new OpenLayers.Control.DrawFeature(this.editLayer,
            OpenLayers.Handler.RegularPolygon, {
                handlerOptions: {
                    sides: 4,
                    irregular: true
                }
            }
        )
    };

    // add controls to map
    for(var key in this.drawControls) {
        this.map.addControl(this.drawControls[key]);
    }

    // hide the types for an annotation
    $("#control-toggle-annotation-types").hide();
    $("#control-toggle-annotation").click(function() {
      $("#control-toggle-annotation-types").slideDown();
    });
    $("#control-toggle-control-point, #control-toggle-navigate").click(function(){
      $("#control-toggle-annotation-types").slideUp();
    });

    // check for toggled types
    var self=this;
    $("#control-toggle button").click(function(){
      for(key in self.drawControls) {
          var control = self.drawControls[key];
          if(this.value == key) {
              control.activate();
          } else {
              control.deactivate();
          }
      }
    });

  }

  // ================================================================================

  this.map.setBaseLayer(this.baseLayer);
  this.map.zoomToMaxExtent();

}

// ----------------------------------------------------------------------------

maphub.AnnotationView.prototype.initAutoComplete = function() {
  $('input#place-search').autocomplete({
      source: function( request, response ) {
          $.ajax({
            url: "http://ws.geonames.org/searchJSON",
            dataType: "jsonp",
            data: {
              featureClass: "P",
              style: "full",
              maxRows: 12,
              name_startsWith: request.term
            },
            success: function( data ) {
              response( $.map( data.geonames, function( item ) {
                return {
                  label: item.name + (item.adminName1 ? ", " + item.adminName1 : "") + ", " + item.countryName,
                  value: item.name,
                  short_name: item.name,
                  lat: item.lat,
                  lng: item.lng,
                  geonameId: item.geonameId
                }
              }));
            }
          });
        },
        minLength: 2,
        select: function( event, ui ) {
          // what to do when it's selected
          $("#control_point_name").attr("value", ui.item.short_name);
          $("#control_point_geonames_label").attr("value", ui.item.label);
          $("#control_point_lat").attr("value", ui.item.lat);
          $("#control_point_lng").attr("value", ui.item.lng);
          $("#control_point_geonames_id").attr("value", ui.item.geonameId);
        },
        open: function() {
          $( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" );
        },
        close: function() {
          $( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" );
        }
    });
}

// ----------------------------------------------------------------------------

/* Loads the annotations for this map via a JSON request */
maphub.AnnotationView.prototype.remoteLoadAnnotations = function() {
  var wkt_parser = new OpenLayers.Format.WKT();
  var self = this;

  $.getJSON(this.annotations_url, function(data) {
    $.each(data, function(key, val) {
      var feature = wkt_parser.read(val.wkt_data);
      feature.annotation = val;
      self.features_annotations.push(feature);
      self.annotations.push(val);
    });
    self.annotationLayer.addFeatures(self.features_annotations);
  });
}

/* Loads the annotations for this map via a JSON request */
maphub.AnnotationView.prototype.remoteLoadControlPoints = function() {
  var wkt_parser = new OpenLayers.Format.WKT();
  var self = this;

  $.getJSON(this.control_points_url, function(data) {
    $.each(data, function(key, val) {
      var feature = wkt_parser.read(val.wkt_data);
      feature.control_point = val;
      self.features_control_points.push(feature);
      self.control_points.push(val);
    });
    self.controlPointsLayer.addFeatures(self.features_control_points);
  });
}

<commit_msg>Don't make Escape close an open annotation / control point window<commit_after>maphub.AnnotationView = function(parameters) {

  /* Callbacks for added / removed features */
  function featureSelected(evt) {
    var class_name = evt.feature.geometry.CLASS_NAME;
    if (!evt.feature.tooltip) {
      if (class_name == "OpenLayers.Geometry.Point") {
        evt.feature.tooltip = new maphub.ControlPointTooltip(evt.feature.control_point, self.user_id);
      } else {
        evt.feature.tooltip = new maphub.AnnotationTooltip(evt.feature.annotation, self.user_id);
      }
      self.tooltips.push(evt.feature.tooltip);
    }
    // get the screen coordinates
    var lonlat = evt.feature.geometry.getBounds().getCenterLonLat();
    var coords = this.map.getPixelFromLonLat(lonlat);
    evt.feature.tooltip.show(coords.x - 50, coords.y);
  }

  function featureUnselected(evt) {
    evt.feature.tooltip.hide();
  }

  // This function is called when a feature was added to the Edit layer
  function featureAdded(evt) {

    // deactivate keyboard shortcuts
    self.keyboard_shortcuts.deactivate();

    // hide all tooltips
    for (var i in self.tooltips) {
      self.tooltips[i].hide();
    }

    // is this a Control Point or an Annotation?
    var class_name = evt.feature.geometry.CLASS_NAME;
    if (class_name == "OpenLayers.Geometry.Point") {
      controlPointAdded(evt);
    } else {
      annotationAdded(evt);
    }
  }

  // This function is called when an annotation was drawn
  function annotationAdded(evt) {
    var wkt_data = evt.feature.geometry.toString();

    // reinitialize title and body, and copy WKT data as well as bounds
    $("#annotation_body").attr("value", "Add your annotation here!");
    $("#annotation_wkt_data").attr("value", wkt_data);

    $("#annotation_boundary_attributes_ne_x").attr("value", evt.feature.geometry.bounds.right);
    $("#annotation_boundary_attributes_ne_y").attr("value", evt.feature.geometry.bounds.top);
    $("#annotation_boundary_attributes_sw_x").attr("value", evt.feature.geometry.bounds.left);
    $("#annotation_boundary_attributes_sw_y").attr("value", evt.feature.geometry.bounds.bottom);

    // clear the existing tags
    $("#modal-annotation-tags").empty();
    $("#tagging-help").hide();
    $("#no-tags").show();

    // show the popup
    $("#modal-annotation").modal({
      'backdrop': 'static',
      keyboard: false
    });
    $("#annotation_body").focus();
    $("#annotation_body").select();
  }

  function controlPointAdded(evt) {
    var wkt_data = evt.feature.geometry.toString();

    // set x/y in form
    $("#control_point_wkt_data").attr("value", wkt_data);
    $("#control_point_x").attr("value", evt.feature.geometry.x);
    $("#control_point_y").attr("value", evt.feature.geometry.y);

    // reset the place search box and slide up panel
    $("#place-search").attr("value", "");
    $("#modal-control-point").modal({
      'backdrop': 'static',
      keyboard: false
    });
    $("#place-search").focus();
  }

  /* ============================================================================== */

  this.zoomify_width  = parameters.width;        // pixel width ...
  this.zoomify_height = parameters.height;       // ... and height of map
  this.zoomify_url    = parameters.zoomify_url;  // remote zoomify tileset

  this.annotations_url          = parameters.annotations_url;      // JSON request URL for annotations
  this.control_points_url       = parameters.control_points_url;   // JSON request URL for control points
  this.editable                 = parameters.editable;             // whether to show the control panel
  this.user_id                  = parameters.user_id;              // the currently logged in user ID
  this.features_annotations     = [];   // all annotation features
  this.features_control_points  = [];   // all control point features
  this.annotations              = [];   // all annotations on this map
  this.control_points           = [];   // all control points on this map
  this.tooltips                 = [];   // all tooltips on this map

  // ================================================================================

  var annotationStyleMap = new OpenLayers.StyleMap({
    'default': new OpenLayers.Style({
      'strokeColor': "#cc4400",
      'fillOpacity': "0.4",
      'fillColor': "#cc4400"
    }),
    'select': new OpenLayers.Style({
      'strokeColor': "#ff0000",
    }),
    'temporary': new OpenLayers.Style({
      'strokeColor': "#005580",
      'fillColor': "#00A9FF",
      'fillOpacity': "0.4",
    })
  });

  var controlPointStyleMap = new OpenLayers.StyleMap({
    'default': new OpenLayers.Style({
      'externalGraphic': "/assets/openlayers/pin.png",
      'graphicWidth': '15',
      'graphicWidth': '25',
      'graphicXOffset': -13,
      'graphicYOffset': -25
    }),
    'select': new OpenLayers.Style({
      'externalGraphic': "/assets/openlayers/pin.png",
      'graphicWidth': '15',
      'graphicWidth': '25',
      'graphicXOffset': -13,
      'graphicYOffset': -25
    }),
    'temporary': new OpenLayers.Style({
      'externalGraphic': "/assets/openlayers/pin.png",
      'graphicWidth': '15',
      'graphicWidth': '25',
      'graphicXOffset': -13,
      'graphicYOffset': -25
    })
  });

  // ================================================================================

  /* The zoomify layer */
  this.baseLayer = new OpenLayers.Layer.Zoomify(
    "Zoomify",
    this.zoomify_url,
    new OpenLayers.Size( this.zoomify_width, this.zoomify_height ),
    { displayInLayerSwitcher: false }
  );

  /* The editing controls layer */
  this.editLayer = new OpenLayers.Layer.Vector(
    "Editable",
    { styleMap: annotationStyleMap, displayInLayerSwitcher: false }
  );
  this.editLayer.events.register("featureadded", this.editLayer, featureAdded);

  this.controlPointEditLayer = new OpenLayers.Layer.Vector(
    "Control Point Editable",
    { styleMap: controlPointStyleMap, displayInLayerSwitcher: false }
  );
  this.controlPointEditLayer.events.register("featureadded", this.controlPointEditLayer, featureAdded);

  // if the close button is clicked, we remove all temporary features
  $("a.close").click(function() {
    self.editLayer.removeAllFeatures();
    self.controlPointEditLayer.removeAllFeatures();
  });

  /* The annotation layer */
  this.annotationLayer = new OpenLayers.Layer.Vector(
    "Annotations",
    { styleMap: annotationStyleMap }
  );

  /* The control points layer */
  this.controlPointsLayer = new OpenLayers.Layer.Vector(
    "Control Points",
    { styleMap: controlPointStyleMap }
  );

  /* Display options */
  var bounds = new OpenLayers.Bounds(0, 0, this.zoomify_width, this.zoomify_height)
  var options = {
      controls: [],
      maxExtent: bounds,
      restrictedExtent: bounds,
      maxResolution: Math.pow(2, this.baseLayer.numberOfTiers-1),
      numZoomLevels: this.baseLayer.numberOfTiers,
      units: 'pixels'
  };

  this.map = new OpenLayers.Map("viewer", options);

  // add all layers to the map
  this.map.addLayer(this.baseLayer);
  this.map.addLayer(this.editLayer);
  this.map.addLayer(this.controlPointEditLayer);
  this.map.addLayer(this.annotationLayer);
  this.map.addLayer(this.controlPointsLayer);

  // remotely load already existing annotations and control points via JSON
  this.remoteLoadAnnotations();
  this.remoteLoadControlPoints();

  // add autocomplete
  this.initAutoComplete();

  // MouseDefaults is deprecated, see: http://trac.osgeo.org/openlayers/wiki/Control/MouseDefaults
  this.map.addControl(new OpenLayers.Control.Navigation());
  this.map.addControl(new OpenLayers.Control.MousePosition());
  this.map.addControl(new OpenLayers.Control.PanZoomBar());
  this.keyboard_shortcuts = new OpenLayers.Control.KeyboardDefaults();
  this.map.addControl(this.keyboard_shortcuts);

  this.map.addControl(new OpenLayers.Control.LayerSwitcher(
    {'div':OpenLayers.Util.getElement('layerswitcher')})
    );

  // ================================================================================

  var select = new OpenLayers.Control.SelectFeature(
    [this.annotationLayer, this.controlPointsLayer], {
      hover: true,
      renderIntent: "temporary"
      }
  );
  this.map.addControl(select);
  select.activate();

  this.annotationLayer.events.on({
    "featureselected": featureSelected,
    "featureunselected": featureUnselected
  });

  this.controlPointsLayer.events.on({
    "featureselected": featureSelected,
    "featureunselected": featureUnselected
  });

  // ================================================================================

  /* Allow creation of features */
  // http://stackoverflow.com/questions/10572005/
  if (parameters.editable) {
    this.drawControls = {
        point: new OpenLayers.Control.DrawFeature(this.controlPointEditLayer,
            OpenLayers.Handler.Point),
        line: new OpenLayers.Control.DrawFeature(this.editLayer,
            OpenLayers.Handler.Path),
        polygon: new OpenLayers.Control.DrawFeature(this.editLayer,
            OpenLayers.Handler.Polygon),
        box: new OpenLayers.Control.DrawFeature(this.editLayer,
            OpenLayers.Handler.RegularPolygon, {
                handlerOptions: {
                    sides: 4,
                    irregular: true
                }
            }
        )
    };

    // add controls to map
    for(var key in this.drawControls) {
        this.map.addControl(this.drawControls[key]);
    }

    // hide the types for an annotation
    $("#control-toggle-annotation-types").hide();
    $("#control-toggle-annotation").click(function() {
      $("#control-toggle-annotation-types").slideDown();
    });
    $("#control-toggle-control-point, #control-toggle-navigate").click(function(){
      $("#control-toggle-annotation-types").slideUp();
    });

    // check for toggled types
    var self=this;
    $("#control-toggle button").click(function(){
      for(key in self.drawControls) {
          var control = self.drawControls[key];
          if(this.value == key) {
              control.activate();
          } else {
              control.deactivate();
          }
      }
    });

  }

  // ================================================================================

  this.map.setBaseLayer(this.baseLayer);
  this.map.zoomToMaxExtent();

}

// ----------------------------------------------------------------------------

maphub.AnnotationView.prototype.initAutoComplete = function() {
  $('input#place-search').autocomplete({
      source: function( request, response ) {
          $.ajax({
            url: "http://ws.geonames.org/searchJSON",
            dataType: "jsonp",
            data: {
              featureClass: "P",
              style: "full",
              maxRows: 12,
              name_startsWith: request.term
            },
            success: function( data ) {
              response( $.map( data.geonames, function( item ) {
                return {
                  label: item.name + (item.adminName1 ? ", " + item.adminName1 : "") + ", " + item.countryName,
                  value: item.name,
                  short_name: item.name,
                  lat: item.lat,
                  lng: item.lng,
                  geonameId: item.geonameId
                }
              }));
            }
          });
        },
        minLength: 2,
        select: function( event, ui ) {
          // what to do when it's selected
          $("#control_point_name").attr("value", ui.item.short_name);
          $("#control_point_geonames_label").attr("value", ui.item.label);
          $("#control_point_lat").attr("value", ui.item.lat);
          $("#control_point_lng").attr("value", ui.item.lng);
          $("#control_point_geonames_id").attr("value", ui.item.geonameId);
        },
        open: function() {
          $( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" );
        },
        close: function() {
          $( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" );
        }
    });
}

// ----------------------------------------------------------------------------

/* Loads the annotations for this map via a JSON request */
maphub.AnnotationView.prototype.remoteLoadAnnotations = function() {
  var wkt_parser = new OpenLayers.Format.WKT();
  var self = this;

  $.getJSON(this.annotations_url, function(data) {
    $.each(data, function(key, val) {
      var feature = wkt_parser.read(val.wkt_data);
      feature.annotation = val;
      self.features_annotations.push(feature);
      self.annotations.push(val);
    });
    self.annotationLayer.addFeatures(self.features_annotations);
  });
}

/* Loads the annotations for this map via a JSON request */
maphub.AnnotationView.prototype.remoteLoadControlPoints = function() {
  var wkt_parser = new OpenLayers.Format.WKT();
  var self = this;

  $.getJSON(this.control_points_url, function(data) {
    $.each(data, function(key, val) {
      var feature = wkt_parser.read(val.wkt_data);
      feature.control_point = val;
      self.features_control_points.push(feature);
      self.control_points.push(val);
    });
    self.controlPointsLayer.addFeatures(self.features_control_points);
  });
}

<|endoftext|>


<commit_before>// AutoComplete plugin
// Dependencies: jQuery

(function($){
  $.fn.AutoComplete = function( options ) {

    var self = this;

    this.init = function(select) {

      var input = Awesomplete.$.create('input');
      $(input).addClass('form-control');
      input.disabled = $(select).attr('disabled')? true : false;
      select.parentElement.insertBefore(input, select);
      select.setAttribute("hidden", "");

      var dataList = [],
        list = select.options;

      for(var i = 0;i<list.length;i++){
        var item = {label: list[i].text, value: list[i].text};
        dataList.push(list[i].text);
      }

      this.copySelectedValue(input, select);

      var awesompleteElement = new Awesomplete(input, {
        list: dataList,
        autoFirst: true
      });

      Awesomplete.$.bind(input, {
        'awesomplete-selectcomplete': function(){
          var selectedItems = Awesomplete.$$("option", select).filter(function(elem){
            return  elem.text === input.value;
          });
          if(selectedItems.length > 0){
            $(select).val(selectedItems[0].value).change()
          }
        },
        'blur': function(){
          self.copySelectedValue(input, select);
        }
      });

    };

    this.copySelectedValue = function(input, select){

      if(select.selectedIndex > -1){
        input.value = $(select).find('option:selected').text();
      }

    };

    this.init(this[0]);

  };
}(jQuery));

$(function (){
  $('select.autocomplete').each(function(i,select){
    $(select).AutoComplete();
  });
});
<commit_msg>Add another semicolon<commit_after>// AutoComplete plugin
// Dependencies: jQuery

(function($){
  $.fn.AutoComplete = function( options ) {

    var self = this;

    this.init = function(select) {

      var input = Awesomplete.$.create('input');
      $(input).addClass('form-control');
      input.disabled = $(select).attr('disabled')? true : false;
      select.parentElement.insertBefore(input, select);
      select.setAttribute("hidden", "");

      var dataList = [],
        list = select.options;

      for(var i = 0;i<list.length;i++){
        var item = {label: list[i].text, value: list[i].text};
        dataList.push(list[i].text);
      }

      this.copySelectedValue(input, select);

      var awesompleteElement = new Awesomplete(input, {
        list: dataList,
        autoFirst: true
      });

      Awesomplete.$.bind(input, {
        'awesomplete-selectcomplete': function(){
          var selectedItems = Awesomplete.$$("option", select).filter(function(elem){
            return  elem.text === input.value;
          });
          if(selectedItems.length > 0){
            $(select).val(selectedItems[0].value).change();
          }
        },
        'blur': function(){
          self.copySelectedValue(input, select);
        }
      });

    };

    this.copySelectedValue = function(input, select){

      if(select.selectedIndex > -1){
        input.value = $(select).find('option:selected').text();
      }

    };

    this.init(this[0]);

  };
}(jQuery));

$(function (){
  $('select.autocomplete').each(function(i,select){
    $(select).AutoComplete();
  });
});
<|endoftext|>


<commit_before>import React, { Component, Children } from 'react';
import { withRouter } from 'olymp-router';
import { withGateway } from 'olymp-utils';
import { Form, Menu, Icon } from 'antd';
import { Modal, createComponent, border } from 'olymp-fela';
import { withCollection, withItem } from '../decorators';
import { getFormSchema } from './detail';
import { DetailForm } from '../components';

const RightMenuItem = createComponent(
  () => ({
    float: 'right !important',
  }),
  p => <Menu.Item {...p} />,
  p => Object.keys(p)
);

const Content = createComponent(
  ({ theme }) => ({
    padding: theme.space3,
    paddingBottom: 0,
  }),
  'div',
  p => Object.keys(p)
);

const HiddenForm = createComponent(
  ({ visible }) => ({
    display: visible ? 'block' : 'none',
  }),
  p => <DetailForm {...p} />,
  p => Object.keys(p)
);

const AntMenu = createComponent(
  ({ theme }) => ({
    borderY: border(theme, theme.dark3),
    boxShadow: 'inset 0 0 10px 0 rgba(0, 0, 0, 0.2)',
    backgroundColor: theme.dark1,
    hasFlex: {
      display: 'flex',
      justifyContent: 'flex-end',
    },
    '> li': {
      hasFlex: {
        flex: '1 1 auto',
        textAlign: 'center',
      },
      padding: theme.space0,
      '> div > div': {
        padding: theme.space0,
      },
      '& i': {
        paddingX: theme.space2,
      },
    },
    '> li:nth-last-of-type(-n+2)': {
      hasFlex: {
        flex: 'none',
      },
    },
  }),
  ({ keys, ...p }) =>
    <Menu theme="dark" selectedKeys={keys} mode="horizontal" {...p} />,
  p => Object.keys(p)
);

@withRouter
@withCollection
@withItem
@withGateway('toolbar')
@Form.create()
export default class CollectionModal extends Component {
  state = {
    tab: undefined,
  };

  render() {
    const {
      collection,
      open,
      id,
      typeName,
      onSave,
      onClose,
      item,
      toolbar,
    } = this.props;
    const schema = getFormSchema(collection);
    const keys = Object.keys(schema);
    const currentTab = this.state.tab || keys[0];
    const header = (

        <Menu mode="horizontal" theme="dark">
          <Menu.Item key="brand">
            {typeName} {id === 'new' ? 'anlegen' : 'bearbeiten'}
          </Menu.Item>

          {keys.reverse().map(tab =>
            <RightMenuItem key={tab}>
               this.setState({ tab })}>
                {tab}

            </RightMenuItem>
          )}
        </Menu>

        <AntMenu>
          {toolbar}

          <Menu.Item key="save">

              <Icon type="save" />

          </Menu.Item>
          <Menu.Item key="close">

              <Icon type="close" />

          </Menu.Item>
        </AntMenu>

    );

    return (
      <Modal width={900} open={open} header={header} onClose={onClose}>
        <Content>
          {keys.map(tab =>
            <HiddenForm
              {...this.props}
              item={item || {}}
              fields={schema[tab]}
              key={tab}
              visible={currentTab === tab}
              onCreate={onSave}
            />
          )}
        </Content>
      </Modal>
    );
  }
}
<commit_msg>collection modal toolbar only on "Text"<commit_after>import React, { Component, Children } from 'react';
import { withRouter } from 'olymp-router';
import { withGateway } from 'olymp-utils';
import { Form, Menu, Icon } from 'antd';
import { Modal, createComponent, border } from 'olymp-fela';
import { withCollection, withItem } from '../decorators';
import { getFormSchema } from './detail';
import { DetailForm } from '../components';

const RightMenuItem = createComponent(
  () => ({
    float: 'right !important',
  }),
  p => <Menu.Item {...p} />,
  p => Object.keys(p)
);

const Content = createComponent(
  ({ theme }) => ({
    padding: theme.space3,
    paddingBottom: 0,
  }),
  'div',
  p => Object.keys(p)
);

const HiddenForm = createComponent(
  ({ visible }) => ({
    display: visible ? 'block' : 'none',
  }),
  p => <DetailForm {...p} />,
  p => Object.keys(p)
);

const AntMenu = createComponent(
  ({ theme }) => ({
    borderY: border(theme, theme.dark3),
    boxShadow: 'inset 0 0 10px 0 rgba(0, 0, 0, 0.2)',
    backgroundColor: theme.dark1,
    hasFlex: {
      display: 'flex',
      justifyContent: 'flex-end',
    },
    '> li': {
      hasFlex: {
        flex: '1 1 auto',
        textAlign: 'center',
      },
      padding: theme.space0,
      '> div > div': {
        padding: theme.space0,
      },
      '& i': {
        paddingX: theme.space2,
      },
    },
    '> li:nth-last-of-type(-n+2)': {
      hasFlex: {
        flex: 'none',
      },
    },
  }),
  ({ keys, ...p }) =>
    <Menu theme="dark" selectedKeys={keys} mode="horizontal" {...p} />,
  p => Object.keys(p)
);

@withRouter
@withCollection
@withItem
@withGateway('toolbar')
@Form.create()
export default class CollectionModal extends Component {
  state = {
    tab: undefined,
  };

  render() {
    const {
      collection,
      open,
      id,
      typeName,
      onSave,
      onClose,
      item,
      toolbar,
    } = this.props;
    const schema = getFormSchema(collection);
    const keys = Object.keys(schema);
    const currentTab = this.state.tab || keys[0];
    const header = (

        <Menu mode="horizontal" theme="dark">
          <Menu.Item key="brand">
            {typeName} {id === 'new' ? 'anlegen' : 'bearbeiten'}
          </Menu.Item>

          {keys.reverse().map(tab =>
            <RightMenuItem key={tab}>
               this.setState({ tab })}>
                {tab}

            </RightMenuItem>
          )}
        </Menu>

        {currentTab === 'Text' &&
          <AntMenu>
            {toolbar}

            <Menu.Item key="close">

                <Icon type="close" />

            </Menu.Item>
          </AntMenu>}

    );
    const footer = (
      <Menu mode="horizontal" theme="dark">
        <Menu.Item key="save">

            <Icon type="save" /> Speichern

        </Menu.Item>
        <Menu.Item key="close">

            Abbrechen

        </Menu.Item>
      </Menu>
    );

    return (
      <Modal
        width={900}
        open={open}
        header={header}
        footer={footer}
        onClose={onClose}
      >
        <Content>
          {keys.map(tab =>
            <HiddenForm
              {...this.props}
              item={item || {}}
              fields={schema[tab]}
              key={tab}
              visible={currentTab === tab}
              onCreate={onSave}
            />
          )}
        </Content>
      </Modal>
    );
  }
}
<|endoftext|>


<commit_before>/**
 * A regex to match css `:host` selector.
 * @type {RegExp}
 * @private
 */
const HOST_REGEX = /:host(\(([^({)]+(\([^)]*\))?)+\))?/g;
/**
 * A regex to match css comments.
 * @type {RegExp}
 * @private
 */
const CSS_COMMENTS_REGEX = /\s*(?!<")\/\*[^*]+\*\/(?!")\s*/g;
/**
 * A regex to match css rules.
 * @type {RegExp}
 * @private
 */
const CSS_RULES_REGEX = /(#|\*|\.|@|\[|[a-zA-Z])([^{;}]*){/g;

/**
 * Convert a shadowDOM css string into a normal scoped css.
 * @private
 *
 * @param {String} css The css string to convert.
 * @param {String} is The component name for scoping.
 * @return {String} The scoped css.
 */
export function convertShadowCSS(css, is) {
    let scope = `.${is}`;
    return css
        .replace(CSS_COMMENTS_REGEX, '')
        .replace(HOST_REGEX, (fullMatch, mod) => `${scope}${mod ? mod.slice(1, -1) : ''}`)
        .replace(CSS_RULES_REGEX, (match) => {
            match = match.trim();
            if (match[0] === '@') {
                return match;
            }
            return match
                .split(',')
                .map((selector) => {
                    selector = selector.trim();
                    if (selector.indexOf(scope) === 0) {
                        return selector;
                    }
                    return `${scope} ${selector}`;
                })
                .join(',');
        });
}
<commit_msg>fix: changed regex for css comments<commit_after>/**
 * A regex to match css `:host` selector.
 * @type {RegExp}
 * @private
 */
const HOST_REGEX = /:host(\(([^({)]+(\([^)]*\))?)+\))?/g;
/**
 * A regex to match css comments.
 * @type {RegExp}
 * @private
 */
const CSS_COMMENTS_REGEX = /\/\*[^*]*\*+([^\/*][^*]*\*+)*\//g;
/**
 * A regex to match css rules.
 * @type {RegExp}
 * @private
 */
const CSS_RULES_REGEX = /(#|\*|\.|@|\[|[a-zA-Z])([^{;}]*){/g;

/**
 * Convert a shadowDOM css string into a normal scoped css.
 * @private
 *
 * @param {String} css The css string to convert.
 * @param {String} is The component name for scoping.
 * @return {String} The scoped css.
 */
export function convertShadowCSS(css, is) {
    let scope = `.${is}`;
    return css
        .replace(CSS_COMMENTS_REGEX, '')
        .replace(HOST_REGEX, (fullMatch, mod) => `${scope}${mod ? mod.slice(1, -1) : ''}`)
        .replace(CSS_RULES_REGEX, (match) => {
            match = match.trim();
            if (match[0] === '@') {
                return match;
            }
            return match
                .split(',')
                .map((selector) => {
                    selector = selector.trim();
                    if (selector.indexOf(scope) === 0) {
                        return selector;
                    }
                    return `${scope} ${selector}`;
                })
                .join(',');
        });
}
<|endoftext|>


<commit_before>//= require jquery-2.1.4.min

DomElements = {
  matchingTerm: 'table.matching-term'
};

function clearAndHideResultTable() {
  var header = $('table.matching-term .header');
  $(DomElements.matchingTerm).html(header);
  $(DomElements.matchingTerm).hide();
}

function hideNoResults() {
  $('.no-results').hide();
}

function showNoResults(searchTerm) {
  $('.no-results').html('No results match "' + searchTerm + '"').show();
}

function showResultTable() {
  hideNoResults();
  $(DomElements.matchingTerm).show();
}

function searchResults(query) {
  clearAndHideResultTable();
  var terms = query.split(' ');
  var parents = [];

  $.each(terms, function(index, el) {
    if (parents.length == 0) {
      parents = $('[data-search*=' + el + ']').parent('tr');
    }
    else if(el.length > 0) {
      $.each(parents, function(index, parent) {
        if($(parent).find($('[data-search*=' + el + ']')).length == 0) {
          parents[index] = null;
        }
      });
    }
  });

  if (query == 'users a') { debugger }

  return parents
}

function addToResultTable(result, searchTerms){
  showResultTable();
  var table = $('.matching-term');
  var lastChild = table.find('tr:last');

  if(lastChild.length == 0) {
    table.html(result);
  }
  else {
    lastChild.after(result);
  }
}

$(document).on('ready', function(){
  $('input[name=search]').keyup(function(event){
    var searchTerm = event.target.value;
    if(searchTerm.length > 0) {
      hideNoResults();
      var results = searchResults(searchTerm.toLowerCase());
      if (results.length > 0) {
        $.each(results, function(index, value){
          addToResultTable($(value).clone(), searchTerm);
        });
      }
      else {
        showNoResults(searchTerm);
      }
    }
    else {
      hideNoResults();
      clearAndHideResultTable();
    }
  });
});
<commit_msg>Working multiple term highlighter<commit_after>//= require jquery-2.1.4.min

DomElement = {
  matchingTerm: 'table.matching-term'
};

function clearAndHideResultTable() {
  var header = $('table.matching-term .header');
  $(DomElement.matchingTerm).html(header);
  $(DomElement.matchingTerm).hide();
}

function hideNoResults() {
  $('.no-results').hide();
}

function showNoResults(searchTerm) {
  $('.no-results').html('No results match "' + searchTerm + '"').show();
}

function showResultTable() {
  hideNoResults();
  $(DomElement.matchingTerm).show();
}

function searchResults(query) {
  clearAndHideResultTable();
  var terms = query.split(' ');
  var parents = [];

  $.each(terms, function(index, el) {
    if (parents.length == 0) {
      parents = $('[data-search*=' + el + ']').parent('tr');
    }
    else if(el.length > 0) {
      $.each(parents, function(index, parent) {
        if($(parent).find($('[data-search*=' + el + ']')).length == 0) {
          parents[index] = null;
        }
      });
    }
  });

  return parents
}

function addToResultTable(result, searchTerms){
  showResultTable();
  var table = $('.matching-term');
  var lastChild = table.find('tr:last');

  $.each(searchTerms.split(' '), function(index, term) {
    if(term.length > 0 ) {
      $.each(result.find('td'), function(index, element) {
        if ($(element).find('span').length == 0){
          var existingHtml = $(element).html();
          var replacedHtml = existingHtml.replace(new RegExp("(" + term + ")", "i"), "$1");
          $(element).html(replacedHtml);
        }
        else {
          $.each($('element').contents(), function(index, content) {
            if(content.nodeType == 3) {
              $(content).html($(content).html().replace(new RegExp("(" + term + ")", "i"), "$1"));
            }
          });
        }
      });
    }
  });

  if(lastChild.length == 0) {
    table.html(result);
  }
  else {
    lastChild.after(result);
  }
}

$(document).on('ready', function(){
  $('input[name=search]').keyup(function(event){
    var searchTerm = event.target.value;
    if(searchTerm.length > 0) {
      hideNoResults();
      var results = searchResults(searchTerm.toLowerCase());
      if (results.length > 0) {
        $.each(results, function(index, value){
          addToResultTable($(value).clone(), searchTerm);
        });
      }
      else {
        showNoResults(searchTerm);
      }
    }
    else {
      hideNoResults();
      clearAndHideResultTable();
    }
  });
});
<|endoftext|>


<commit_before>$(document).ready(function(){

    //initiate event for discussion
    set_show_hide_discussion();

    $('#submit-btn, #test-next-btn').click(function(evt){
        $(this).addClass('disabled');
        var form = $("#training-step-form");
        var update_url = form.children("input[name=update_url]").val();
        var qid = form.children("input[name=qid]").val();
        var checkboxes = form.find("input.choices");
        var choices = [];
        var aids = [];
        var step = form.children("input[name=step]").val();
        var btn_id = $(this).attr('id');
        var sq_id = $('#session_question_id').val();
        var submission_id = $('#submission_id').val();

        $.each(checkboxes, function(i, cb) {
            choices.push($(cb).val());
            if ($(cb).is(":checked")) {
                aids.push($(cb).val());
            }
        });

        if (aids.length > 0) {
            var data = {
                'step':step,
                'qid': qid,
                'aid': aids,
                'choices': choices,
                'sq_id' : sq_id,
                'sid' :submission_id
            };
            // send ajax request to get result
            // update result form
            // change submit to continue if the answer is correct
            $.get(update_url, data, function(resp) {
                //#new code for normal training
                $('#submit-btn, #test-next-btn').removeClass('disabled');

                $('#explanation .result').html(resp.result);
                $('#explanation .reason').html(resp.explanation);
                $('#explanation').removeClass('hidden');
                $('#explanation').removeClass('mcq-ans-incorrect');
                $('#explanation').removeClass('mcq-ans-correct');

                if (resp.is_correct) {
                    $('#continue-btn').removeClass('disabled');
                    $('#continue-btn').addClass('btn-primary');
                    $('#submit-btn').removeClass('btn-primary');
                    $('#explanation').addClass('mcq-ans-correct');
                } else {
                    $('#continue-btn').removeClass('disabled');
                    $('#explanation').addClass('mcq-ans-incorrect');
                }
                //To next question right after answering pre one
                if(btn_id=='test-next-btn') {
                    window.location.href = $('#test-next-btn').attr('href');
                }
            }, 'json');
        }
        if(btn_id=='submit-btn') {
            return false; // prevent default
        }
    });

    $('#continue-btn').click(function(evt) {
        if ($(this).hasClass('disabled')) {
            evt.preventDefault();
        }
    });

    $("#pathrun").bind("click",submitCode);
    $(document).keydown(function(evt){
        if(evt.altKey && evt.which == 82){
            submitCode();
        }
    });

    var running = false;
    function submitCode(){
        if(running) return;
        running = true;
        $("#pathrun").attr("disabled",true);
        var form = $("#training-step-form");
        var update_url = form.children("input[name=update_url]").val();
        var qid = form.children("input[name=qid]").val();
        var step = form.children("input[name=step]").val();

        var failcolor = {backgroundColor: "#e1c1b1"}
        var animateOpt = {duration: 1000, queue: false};
        var passcolor = {backgroundColor: "#008000"};
        $.get(update_url,
            {
                code: $("#ans").val(),
                step: step,
                qid: qid
            },
            function(resp){
                var $er = $("#eval_result");
                $(document.body).scrollTop(($er.offset().top + $er.height()) - $("#ruler").show().height() + 100);
                $("#ruler").hide();
                if(resp.errors.length > 0){
//                $er.text(resp.errors).animate({backgroundColor: "#3C2502",color:"#FF0000"}, animateOpt);
//                console.log("error")
                    $er.html(escapeHtml(resp.errors)).animate({backgroundColor: "#e1c1b1"}, animateOpt);
                }else{
                    var publicTestFlag = true;
                    $("#publicTestTable tbody tr").each(function(index, e){
//                    console.log("change table")
                        if(resp.public[index]){
                            var temp = $("td:last",e);
                            if(temp.hasClass("pathTestFail")){
                                temp.switchClass("pathTestFail","pathTestPass").animate(passcolor,animateOpt);
                            }else if(!temp.hasClass("pathTestPass")){
                                temp.addClass("pathTestPass").animate(passcolor,animateOpt);;
                            }
                        }else{
                            publicTestFlag = false;
                            var temp = $("td:last",e);
                            if(temp.hasClass("pathTestPass")){

                                temp.switchClass("pathTestPass", "pathTestFail").animate(failcolor, animateOpt);
                            }else if(!temp.hasClass("pathTestFail")){
                                temp.addClass("pathTestFail", 1000).animate(failcolor,animateOpt);
                            }
                        }
                    });
                    var privateTestFlag = resp.private.length == 0 ? true : resp.private.reduce(function(a,b){return a && b});
                    if(publicTestFlag){
                        if(resp.private == null || !privateTestFlag){
                            $er.html("Your answer failed to pass one or more of the private test cases."  + (resp.hint ? "
Hint: " + resp.hint : "")).animate(failcolor, animateOpt);
                        }else{
                            $er.html("You have successfully completed this step!").animate(passcolor, animateOpt);
                            $('#continue-btn').removeClass('disabled');
                            $("#pathrun").attr("disabled",true);
                            return; // we do not undisable the run
                        }
                    }else{
                        $er.html("Your answer failed to pass one or more of the public test cases.").animate(failcolor,animateOpt);
                    }
                }

                running = false;
                $("#pathrun").attr("disabled",false);
            }, 'json')
    }
});

<commit_msg>fixed show math in explanation<commit_after>$(document).ready(function(){

    //initiate event for discussion
    set_show_hide_discussion();

    $('#submit-btn, #test-next-btn').click(function(evt){
        $(this).addClass('disabled');
        var form = $("#training-step-form");
        var update_url = form.children("input[name=update_url]").val();
        var qid = form.children("input[name=qid]").val();
        var checkboxes = form.find("input.choices");
        var choices = [];
        var aids = [];
        var step = form.children("input[name=step]").val();
        var btn_id = $(this).attr('id');
        var sq_id = $('#session_question_id').val();
        var submission_id = $('#submission_id').val();

        $.each(checkboxes, function(i, cb) {
            choices.push($(cb).val());
            if ($(cb).is(":checked")) {
                aids.push($(cb).val());
            }
        });

        if (aids.length > 0) {
            var data = {
                'step':step,
                'qid': qid,
                'aid': aids,
                'choices': choices,
                'sq_id' : sq_id,
                'sid' :submission_id
            };
            // send ajax request to get result
            // update result form
            // change submit to continue if the answer is correct
            $.get(update_url, data, function(resp) {
                //#new code for normal training
                $('#submit-btn, #test-next-btn').removeClass('disabled');

                $('#explanation .result').html(resp.result);
                $('#explanation .reason').html(resp.explanation);
                $('#explanation').removeClass('hidden');
                $('#explanation').removeClass('mcq-ans-incorrect');
                $('#explanation').removeClass('mcq-ans-correct');

                var el = document.getElementById("explanation");
                MathJax.Hub.Queue(["Typeset", MathJax.Hub, el]);

                if (resp.is_correct) {
                    $('#continue-btn').removeClass('disabled');
                    $('#continue-btn').addClass('btn-primary');
                    $('#submit-btn').removeClass('btn-primary');
                    $('#explanation').addClass('mcq-ans-correct');
                } else {
                    $('#continue-btn').removeClass('disabled');
                    $('#explanation').addClass('mcq-ans-incorrect');
                }
                //To next question right after answering pre one
                if(btn_id=='test-next-btn') {
                    window.location.href = $('#test-next-btn').attr('href');
                }
            }, 'json');
        }
        if(btn_id=='submit-btn') {
            return false; // prevent default
        }
    });

    $('#continue-btn').click(function(evt) {
        if ($(this).hasClass('disabled')) {
            evt.preventDefault();
        }
    });

    $("#pathrun").bind("click",submitCode);
    $(document).keydown(function(evt){
        if(evt.altKey && evt.which == 82){
            submitCode();
        }
    });

    var running = false;
    function submitCode(){
        if(running) return;
        running = true;
        $("#pathrun").attr("disabled",true);
        var form = $("#training-step-form");
        var update_url = form.children("input[name=update_url]").val();
        var qid = form.children("input[name=qid]").val();
        var step = form.children("input[name=step]").val();

        var failcolor = {backgroundColor: "#e1c1b1"}
        var animateOpt = {duration: 1000, queue: false};
        var passcolor = {backgroundColor: "#008000"};
        $.get(update_url,
            {
                code: $("#ans").val(),
                step: step,
                qid: qid
            },
            function(resp){
                var $er = $("#eval_result");
                $(document.body).scrollTop(($er.offset().top + $er.height()) - $("#ruler").show().height() + 100);
                $("#ruler").hide();
                if(resp.errors.length > 0){
//                $er.text(resp.errors).animate({backgroundColor: "#3C2502",color:"#FF0000"}, animateOpt);
//                console.log("error")
                    $er.html(escapeHtml(resp.errors)).animate({backgroundColor: "#e1c1b1"}, animateOpt);
                }else{
                    var publicTestFlag = true;
                    $("#publicTestTable tbody tr").each(function(index, e){
//                    console.log("change table")
                        if(resp.public[index]){
                            var temp = $("td:last",e);
                            if(temp.hasClass("pathTestFail")){
                                temp.switchClass("pathTestFail","pathTestPass").animate(passcolor,animateOpt);
                            }else if(!temp.hasClass("pathTestPass")){
                                temp.addClass("pathTestPass").animate(passcolor,animateOpt);;
                            }
                        }else{
                            publicTestFlag = false;
                            var temp = $("td:last",e);
                            if(temp.hasClass("pathTestPass")){

                                temp.switchClass("pathTestPass", "pathTestFail").animate(failcolor, animateOpt);
                            }else if(!temp.hasClass("pathTestFail")){
                                temp.addClass("pathTestFail", 1000).animate(failcolor,animateOpt);
                            }
                        }
                    });
                    var privateTestFlag = resp.private.length == 0 ? true : resp.private.reduce(function(a,b){return a && b});
                    if(publicTestFlag){
                        if(resp.private == null || !privateTestFlag){
                            $er.html("Your answer failed to pass one or more of the private test cases."  + (resp.hint ? "
Hint: " + resp.hint : "")).animate(failcolor, animateOpt);
                        }else{
                            $er.html("You have successfully completed this step!").animate(passcolor, animateOpt);
                            $('#continue-btn').removeClass('disabled');
                            $("#pathrun").attr("disabled",true);
                            return; // we do not undisable the run
                        }
                    }else{
                        $er.html("Your answer failed to pass one or more of the public test cases.").animate(failcolor,animateOpt);
                    }
                }

                running = false;
                $("#pathrun").attr("disabled",false);
            }, 'json')
    }
});

<|endoftext|>


<commit_before>'use strict'
var Path = require('path')
var WebTorrent = require('webtorrent')

var Log = require(Path.join(__base, 'src/worker/log.js'))

/**
 * Torrent Client.
 * @constructor
 */
function Client () {
  this.client = new WebTorrent()
  this.torrentLink = ''
  this.torrent = {}
  this.timeout = new Date().getTime()

  this.startFunction = function () {}
  this.updateFunction = function () {}
  this.doneFunction = function () {}
}

/**
 * Download a torrent.
 * @param {string} torrentLink - The link or magnet of the torrent.
*/
Client.prototype.download = function (torrentLink) {
  var self = this

  self.torrentLink = torrentLink
  Log.echo('Start: ' + torrentLink)

  var timeout = setTimeout(function () {
    self.client.destroy(function () {
      self.doneFunction(true, null, null)
    })
  }, __config.client.timeout)
  // download the torrent
  self.client.add(torrentLink, {
    path: __config.torrent.downloads
  }, function (torrent) {
    clearTimeout(timeout)
    // On torrent start
    self.torrent = torrent
    Log.print('Start torrent: ' + self.torrent.name)
    // emit start function with infoHash
    self.startFunction(torrent.infoHash)

    self.torrent.on('download', function (chunkSize) {
      var currentTime = new Date().getTime()
      if ((currentTime - self.timeout) > __config.client.updateTimeout) {
        // emit update function with torrent infos
        self.updateFunction(self.getTorrent())
        self.timeout = currentTime
      }
    })

    self.torrent.on('done', function () {
      Log.print('Finish torrent: ' + self.torrent.name)
      // emit done function with torrent hash and name
      self.doneFunction(false, self.torrent.infoHash, self.torrent.name)
    })

    self.torrent.on('noPeers', function () {
      Log.print('No peers: ' + self.torrent.name)
      // emit done function with torrent hash and name
      self.doneFunction(false, self.torrent.infoHash, self.torrent.name)
    })

    self.torrent.on('error', function (err) {
      Log.print('Error: ' + err)
    })
  })
}

/**
 * Stop the current torrent of the client.
*/
Client.prototype.stop = function () {
  var self = this
  if (self.torrent) {
    self.torrent.pause()
    setTimeout(function () {
      self.torrent.destroy()
    }, 1000)
  }
}

/**
 * Get information about the current torrent of the client.
 * @return {object} - Torrent informations.
*/
Client.prototype.getTorrent = function () {
  var t = {}
  if (this.torrent) {
    if (!this.torrent.client.destroyed) {
      t.name = this.torrent.name
      t.size = this.torrent.length
      t.hash = this.torrent.infoHash
      t.sdown = this.torrent.downloadSpeed
      t.sup = this.torrent.uploadSpeed
      t.down = this.torrent.downloaded
      t.up = this.torrent.uploaded
      t.seed = this.torrent._peersLength
      t.progress = this.torrent.progress
      t.timeRemaining = this.torrent.timeRemaining
    }
  }

  return t
}

/**
 * Set function for the client events.
 * @param {string} what - Name of the event (start, download, done).
 * @param {function} f - Function to execute.
*/
Client.prototype.on = function (what, f) {
  switch (what) {
    case 'start':
      this.startFunction = f
      break
    case 'download':
      // function(download){}
      this.updateFunction = f
      break
    case 'done':
      // function(torrentHash, torrentName){}
      this.doneFunction = f
      break
  }
}

module.exports = Client
<commit_msg>improve visual<commit_after>'use strict'
var Path = require('path')
var WebTorrent = require('webtorrent')

var Log = require(Path.join(__base, 'src/worker/log.js'))

/**
 * Torrent Client.
 * @constructor
 */
function Client () {
  this.client = new WebTorrent()
  this.torrentLink = ''
  this.torrent = {}
  this.timeout = new Date().getTime()

  this.startFunction = function () {}
  this.updateFunction = function () {}
  this.doneFunction = function () {}
}

/**
 * Download a torrent.
 * @param {string} torrentLink - The link or magnet of the torrent.
*/
Client.prototype.download = function (torrentLink) {
  var self = this

  self.torrentLink = torrentLink
  Log.echo('Start: ' + torrentLink)

  var timeout = setTimeout(function () {
    self.client.destroy(function () {
      self.doneFunction(true, null, null)
    })
  }, __config.client.timeout)
  // download the torrent
  self.client.add(torrentLink, {
    path: __config.torrent.downloads
  }, function (torrent) {
    clearTimeout(timeout)
    // On torrent start
    self.torrent = torrent
    Log.print('Start torrent: ' + torrent.name)
    // emit start function with infoHash
    self.startFunction(torrent.infoHash)

    torrent.on('download', function (chunkSize) {
      var currentTime = new Date().getTime()
      if ((currentTime - self.timeout) > __config.client.updateTimeout) {
        // emit update function with torrent infos
        self.updateFunction(self.getTorrent())
        self.timeout = currentTime
      }
    })

    torrent.on('done', function () {
      Log.print('Finish torrent: ' + self.torrent.name)
      // emit done function with torrent hash and name
      self.doneFunction(false, torrent.infoHash, torrent.name)
    })

    torrent.on('noPeers', function () {
      Log.print('No peers: ' + torrent.name)
      // emit done function with torrent hash and name
      self.doneFunction(false, torrent.infoHash, torrent.name)
    })

    torrent.on('error', function (err) {
      Log.print('Error: ' + err)
    })
  })
}

/**
 * Stop the current torrent of the client.
*/
Client.prototype.stop = function () {
  var self = this
  if (self.torrent) {
    self.torrent.pause()
    setTimeout(function () {
      self.torrent.destroy()
    }, 1000)
  }
}

/**
 * Get information about the current torrent of the client.
 * @return {object} - Torrent informations.
*/
Client.prototype.getTorrent = function () {
  var t = {}
  if (this.torrent) {
    if (!this.torrent.client.destroyed) {
      t.name = this.torrent.name
      t.size = this.torrent.length
      t.hash = this.torrent.infoHash
      t.sdown = this.torrent.downloadSpeed
      t.sup = this.torrent.uploadSpeed
      t.down = this.torrent.downloaded
      t.up = this.torrent.uploaded
      t.seed = this.torrent._peersLength
      t.progress = this.torrent.progress
      t.timeRemaining = this.torrent.timeRemaining
    }
  }

  return t
}

/**
 * Set function for the client events.
 * @param {string} what - Name of the event (start, download, done).
 * @param {function} f - Function to execute.
*/
Client.prototype.on = function (what, f) {
  switch (what) {
    case 'start':
      this.startFunction = f
      break
    case 'download':
      // function(download){}
      this.updateFunction = f
      break
    case 'done':
      // function(torrentHash, torrentName){}
      this.doneFunction = f
      break
  }
}

module.exports = Client
<|endoftext|>


<commit_before>/*
Copyright (c) 2014, EDINA.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this
   list of conditions and the following disclaimer in the documentation and/or
   other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
   display the following acknowledgement: This product includes software
   developed by the EDINA.
4. Neither the name of the EDINA nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific prior
   written permission.

THIS SOFTWARE IS PROVIDED BY EDINA ''AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL EDINA BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/

"use strict";

/* global XMLSerializer, OpenLayers */

define(['map', 'records', 'utils','config'], function(// jshint ignore:line
    map, records, utils, config){
    var GPS_ACCURACY = 50;
    var GPS_AUTO_SAVE_THRESHOLD = 5;
    var GPS_ACCURACY_FLAG = false;

    var debugGPS = false;
    var gpsLastRecorded;
    var gpsReceiveCount = 0;
    var gpsTrackWatchID;

    // add new layer for real-time tracks
    var gpsTrackLayer = map.addLayer({
        id: 'gpsTrack',
        style:{
            colour: 'red',
            strokeWidth: 5
        },
        visible: false
    });

    // create new style for tracks
    map.addRecordStyle({
        type: 'track',
        image: 'css/images/routemarker.png'
    });

    // listen for clicks on tracks
    map.addRecordClickListener(function(feature){
        var isTrack = false;
        if(feature.attributes.type === 'track'){
            isTrack = true;
            showGPSTrack(feature.attributes.id,
                         records.getSavedRecord(feature.attributes.id));
        }

        return isTrack;
    });

    /**
     * Save current GPS position to GPX doc. Periodically auto save the doc to file.
     * @param position Contains position coordinates and timestamp, created by the
     * geolocation API.
     */
    var gpsCaptureAutoSave = function(position){
        var trkseg = _this.currentTrack.doc.getElementsByTagName('trkseg')[0];
        var trkpt = _this.currentTrack.doc.createElement('trkpt');

        trkpt.setAttribute('lat', parseFloat(position.coords.latitude).toFixed(6));
        trkpt.setAttribute('lon', parseFloat(position.coords.longitude).toFixed(6));

        var ele = _this.currentTrack.doc.createElement('ele');
        ele.appendChild(_this.currentTrack.doc.createTextNode(position.coords.altitude));
        trkpt.appendChild(ele);

        var time = _this.currentTrack.doc.createElement('time');

        time.appendChild(_this.currentTrack.doc.createTextNode(
            utils.isoDate(new Date(position.timestamp))));
        trkpt.appendChild(time);
        trkseg.appendChild(trkpt);

        ++gpsReceiveCount;

        if(gpsReceiveCount === GPS_AUTO_SAVE_THRESHOLD){
            gpsCaptureSave();
            gpsReceiveCount = 0;
        }
    };

    /**
     * Save current track as a GPX file.
     * @param callback Function executed after a sucessful save.
     */
    var gpsCaptureSave = function(callback){
        var sXML = new XMLSerializer().serializeToString(_this.currentTrack.doc);

        var assetsDir = records.getAssetsDir();
        if(assetsDir){
            var fileName = _this.currentTrack.file;
            assetsDir.getFile(
                fileName,
                {create: true, exclusive: false},
                function(fileEntry){
                    fileEntry.createWriter(
                        function(writer){
                            writer.onwrite = function(evt) {
                                console.debug('GPX file ' + fileName + ' written to ' + assetsDir.fullPath);
                            };
                            writer.write(sXML);

                            if(callback){
                                callback();
                            }
                        },
                        function(error){
                            console.error("Failed to write to gpx file:" + fileName + ". errcode = " + error.code);
                        });
                },
                function(error){
                    console.error("Failed to create gpx file: " + fileName + ". errcode = " + error.code);
                }
            );
        }
    };

    /**
     * @return Start position of current GPS track.
     */
    var getGpsTrackStart = function(){
        var coords;
        var track = gpsTrackLayer.features[0];

        if(track !== undefined){
            var features = track.geometry.components;
            if(features.length > 0){
                coords = {
                    'lon': features[0].x,
                    'lat': features[0].y,
                };
            }
            else{
                console.debug("No components in geometry");
            }
        }
        else{
            console.debug("No track defined");
        }

        return coords;
    };

    /**
     * Draw GPS track on map.
     * @param interval Time gap between updates. Must be more than 0.
     * @param callback Function to be executed on each good GPS coordinate, taking
     * into account the interval.
     * @param debug Use dummy GPS tracking.
     */
    var gpsTrackOnMap = function(interval, callback, debug){
        debugGPS = debug;
        map.hideRecordsLayer();

        if(!gpsLastRecorded){
            gpsLastRecorded = new Date();
        }

        gpsTrackLayer.setVisibility(true);
        gpsTrackLayer.style.strokeColor = localStorage.getItem(_this.COLOUR_INDEX);
        map.getLocateLayer().setVisibility(false);

        if(gpsTrackLayer.features.length === 0){
            var line = new OpenLayers.Geometry.LineString([]);
            gpsTrackLayer.addFeatures([new OpenLayers.Feature.Vector(line)]);
        }

        // found location
        var onSuccess = function(position){
            var next = new Date(gpsLastRecorded.getTime() + interval);
            var timestamp = position.timestamp;

            if(typeof(timestamp) === 'number'){
                timestamp = new Date(timestamp);
            }

            if((position.coords.accuracy < GPS_ACCURACY) && timestamp > next){
                gpsLastRecorded = timestamp;

                var lonLat = map.toInternal(
                    new OpenLayers.LonLat(
                        position.coords.longitude,
                        position.coords.latitude)
                );

                var point = new OpenLayers.Geometry.Point(lonLat.lon, lonLat.lat);
                gpsTrackLayer.features[0].geometry.addPoint(point);

                // only redraw if map page is active
                if($.mobile.activePage.attr('id') === 'gpscapture-page' ||
                   $.mobile.activePage.attr('id') === 'map-page'){
                    gpsTrackLayer.redraw();
                }

                console.debug("add point: " + position.coords.longitude + ' ' +
                              position.coords.latitude);
                callback(position);
            }
            else{
                console.debug("ignore point: " + position.coords.accuracy);
                console.debug(timestamp + " : " + next);
            }
        };

        // timeout has been reached
        var onError = function(position){
            utils.inform('Waiting for GPS signal');
        };

        // clear watch if already defined
        _this.gpsTrackPause();

        if(debugGPS){
            // for testing
            console.debug("GPS track debug mode");

            var lon = -3.188889;
            var lat = 55.936;

            var points = gpsTrackLayer.features[0].geometry.components;
            if(points.length > 0){
                // use last records point
                var coords = points[points.length - 1].clone().transform(
                    map.INTERNAL_PROJECTION, // from national grid
                    map.EXTERNAL_PROJECTION); // to WGS 1984

                lon = coords.x;
                lat = coords.y;
            }

            gpsTrackWatchID = setInterval(function(){
                var now = new Date();
                var position = {
                    'coords': {
                        'longitude': lon += Math.random() / 10000,
                        'latitude': lat += Math.random() / 10000,
                        'altitude': Math.random() * 100,
                        'accuracy': Math.random() * 100
                    },
                    'timestamp': now.getTime()
                };
                onSuccess(position);
            }, 1000);
        }
        else{
            gpsTrackWatchID = navigator.geolocation.watchPosition(
                onSuccess,
                onError,
                {
                    enableHighAccuracy: map.GPS_ACCURACY_FLAG,
                    maximumAge: interval,
                    timeout: 30000,
                }
            );
        }
    };

    /**
     * hide a single track.
     * @param id Annotation id.
     **/
    var hideGPSTrack = function(id){
        // prepend layer name with gps-track
        var name = "gps-track-" + id;

        var layer = map.getLayer(name);
        if(layer){
            map.removeLayer(layer);
        }

    };

    /**
     * Display a single GPS Track.
     * @param id Annotation id.
     * @param track Annotation object.
     */
    var showGPSTrack = function(id, track){
        // prepend layer name with gps-track
        var name = "gps-track-" + id;
        var layer = map.getLayer(name);
        if(layer){
            // its possible for different tracks to have the same name so remove
            // layer existing layer and replace it with current
            map.removeLayer(layer);
        }

        // TODO:
        // track URL found in the second element of the fields array in the
        // record, this may not always be the case
        var trackField = track.record.fields[1];

        var colour = 'red';
        if(typeof(trackField.style) !== 'undefined'){
            colour = trackField.style.strokeColor;
        }

        // create layer with the GPX track
        var gpxLayer = map.addGPXLayer({
            id: name,
            style:{
                colour: colour,
            },
            url: trackField.val
        });

        gpxLayer.setVisibility(true);
        gpxLayer.events.register("loadend", this, function() {
            var extent = gpxLayer.getDataExtent();
            if(extent !== null){
                map.zoomToExtent(extent);
            }
        });
    };

    /************************** public interface  ******************************/

var _this = {

    COLOUR_INDEX: 'gps-track-color',

    /**
     * Start GPX track.
     * @param annotation Annotation metadata object
     * @param debug Use dummy GPS tracking.
     */
    gpsTrack: function(annotation, debug){
        if(!this.currentTrack){
            var now = utils.isoDate();
            var fileName  = (now + '.gpx').replace(/\s|:/g, '_');
            var fullName;

            var assetsDir = records.getAssetsDir();

            if(assetsDir){
                fullName = utils.getFilePath(assetsDir) + '/' + fileName;
            }

            // initialise record point with user's current location
            var start = map.getUserCoords();
            annotation.record.point = {
                'lon': start.lon,
                'lat': start.lat,
                'alt': start.gpsPosition.altitude
            };

            annotation.record.fields[1].val = fullName;
            var id = records.saveAnnotation(undefined, annotation);

            // create XML doc first using jquery's parseXML function,
            // then use standard dom methods for building the XML
            var doc = $.parseXML('<?xml version="1.0" encoding="UTF-8"?><gpx></gpx>');
            var gpx = doc.getElementsByTagName('gpx')[0];
            gpx.setAttribute('xmlns', 'http://www.topografix.com/GPX/1/1');
            gpx.setAttribute('version', '1.1');
            gpx.setAttribute('creator', 'fieldtripGB');

            // create metadata header
            var meta = doc.createElement('metadata');
            var time = doc.createElement('time');
            time.appendChild(doc.createTextNode(now));
            meta.appendChild(time);
            doc.documentElement.appendChild(meta);

            var trk = doc.createElement('trk');
            var trkseg = doc.createElement('trkseg');
            trk.appendChild(trkseg);

            doc.documentElement.appendChild(trk);

            this.currentTrack = {
                'id': id,
                'file': fileName,
                'doc': doc
            };

            // kick off tracking
            this.gpsTrackPlay(annotation.rate, debug);
        }
    },

    /**
     * Complete current GPS capture.
     */
    gpsCaptureComplete: function(){
        if(typeof(this.currentTrack) !== 'undefined'){
            var annotation = records.getSavedRecord(this.currentTrack.id);
            this.gpsTrackPause();

            // populate record with start location
            var startPoint = getGpsTrackStart();

            if(startPoint !== undefined){
                annotation.record.point = startPoint;
                // save the annotation to local storage
                records.saveAnnotation(this.currentTrack.id, annotation);

                var id = this.currentTrack.id;

                // save the GPX file
                gpsCaptureSave(function(){
                    // removing all annotation will force a refresh
                    map.getRecordsLayer().removeAllFeatures();

                    // display saved track on map
                    showGPSTrack(id, annotation);
                });
            }
            else{
                console.debug('Track ' + this.currentTrack.title +
                              ' has no points');
                records.deleteAnnotation(this.currentTrack.id);
            }

            this.gpsTrackStop();
            this.currentTrack = undefined;
        }
    },

    /**
     * Discard current GPS capture.
     */
    gpsCaptureDiscard: function(){
        this.gpsTrackStop();

        //cleanup temp track
        if(this.currentTrack){
            // delete GPX file and record
            records.deleteFile(this.currentTrack.file);
            records.deleteAnnotation(this.currentTrack.id);
        }

        this.currentTrack = undefined;
    },

    /**
     * Resume GPS track after pause.
     */
    gpsTrackPause: function(){
        if(gpsTrackWatchID){
            if(debugGPS){
                clearInterval(gpsTrackWatchID);
            }
            else{
                navigator.geolocation.clearWatch(gpsTrackWatchID);
            }

            gpsTrackWatchID = undefined;
        }
    },

    /**
     * Is there a GPS track currently paused?
     */
    gpsTrackPaused: function(){
        return this.gpsTrackStarted() && !this.gpsTrackRunning();
    },

    /**
     * Start/Resume GPS track.
     * @param captureRate How often, in seconds, a track point should be recorded.
     * @param debug Use debug mode?
     */
    gpsTrackPlay: function(captureRate, debug){
        var cr = captureRate * 1000; // in milliseconds

        // continue tracking
        gpsTrackOnMap(cr,
                      function(position){
                          gpsCaptureAutoSave(position);
                      },
                      debug);
    },

    /**
     * Is a GPS track currently running?
     */
    gpsTrackRunning: function(){
        return gpsTrackWatchID !== undefined;
    },

    /**
     * Is a GPS track in progress?
     */
    gpsTrackStarted: function(){
        return this.currentTrack !== undefined;
    },

    /**
     * Clear GPS track watch.
     */
    gpsTrackStop: function(){
        this.gpsTrackPause();
        gpsTrackLayer.removeAllFeatures();
    },

    /**
     * Switch gps track layer on/off.
     */
    gpsTrackToggle: function(){
        gpsTrackLayer.setVisibility(!gpsTrackLayer.visibility);
    },

    /**
     * Switch gps track layer on/off.
     * @param id Annotation id.
     */
    hideTrack: function(id){
        hideGPSTrack(id);
    },

    /**
     * Display specified GPS track.
     * @param id Annotation id.
     */
    showTrack: function(id){
        showGPSTrack(id, records.getSavedRecord(id));
    },
};

return _this;

});
<commit_msg>edina/fieldtrip-open#62: changed utils to file for file transactions<commit_after>/*
Copyright (c) 2014, EDINA.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this
   list of conditions and the following disclaimer in the documentation and/or
   other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
   display the following acknowledgement: This product includes software
   developed by the EDINA.
4. Neither the name of the EDINA nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific prior
   written permission.

THIS SOFTWARE IS PROVIDED BY EDINA ''AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL EDINA BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/

"use strict";

/* global XMLSerializer, OpenLayers */

define(['map', 'records', 'utils','config', 'file'], function(// jshint ignore:line
    map, records, utils, config, file){
    var GPS_ACCURACY = 50;
    var GPS_AUTO_SAVE_THRESHOLD = 5;
    var GPS_ACCURACY_FLAG = false;

    var debugGPS = false;
    var gpsLastRecorded;
    var gpsReceiveCount = 0;
    var gpsTrackWatchID;

    // add new layer for real-time tracks
    var gpsTrackLayer = map.addLayer({
        id: 'gpsTrack',
        style:{
            colour: 'red',
            strokeWidth: 5
        },
        visible: false
    });

    // create new style for tracks
    map.addRecordStyle({
        type: 'track',
        image: 'css/images/routemarker.png'
    });

    // listen for clicks on tracks
    map.addRecordClickListener(function(feature){
        var isTrack = false;
        if(feature.attributes.type === 'track'){
            isTrack = true;
            showGPSTrack(feature.attributes.id,
                         records.getSavedRecord(feature.attributes.id));
        }

        return isTrack;
    });

    /**
     * Save current GPS position to GPX doc. Periodically auto save the doc to file.
     * @param position Contains position coordinates and timestamp, created by the
     * geolocation API.
     */
    var gpsCaptureAutoSave = function(position){
        var trkseg = _this.currentTrack.doc.getElementsByTagName('trkseg')[0];
        var trkpt = _this.currentTrack.doc.createElement('trkpt');

        trkpt.setAttribute('lat', parseFloat(position.coords.latitude).toFixed(6));
        trkpt.setAttribute('lon', parseFloat(position.coords.longitude).toFixed(6));

        var ele = _this.currentTrack.doc.createElement('ele');
        ele.appendChild(_this.currentTrack.doc.createTextNode(position.coords.altitude));
        trkpt.appendChild(ele);

        var time = _this.currentTrack.doc.createElement('time');

        time.appendChild(_this.currentTrack.doc.createTextNode(
            utils.isoDate(new Date(position.timestamp))));
        trkpt.appendChild(time);
        trkseg.appendChild(trkpt);

        ++gpsReceiveCount;

        if(gpsReceiveCount === GPS_AUTO_SAVE_THRESHOLD){
            gpsCaptureSave();
            gpsReceiveCount = 0;
        }
    };

    /**
     * Save current track as a GPX file.
     * @param callback Function executed after a sucessful save.
     */
    var gpsCaptureSave = function(callback){
        var sXML = new XMLSerializer().serializeToString(_this.currentTrack.doc);

        var assetsDir = records.getAssetsDir();
        if(assetsDir){
            var fileName = _this.currentTrack.file;
            assetsDir.getFile(
                fileName,
                {create: true, exclusive: false},
                function(fileEntry){
                    fileEntry.createWriter(
                        function(writer){
                            writer.onwrite = function(evt) {
                                console.debug('GPX file ' + fileName + ' written to ' + assetsDir.fullPath);
                            };
                            writer.write(sXML);

                            if(callback){
                                callback();
                            }
                        },
                        function(error){
                            console.error("Failed to write to gpx file:" + fileName + ". errcode = " + error.code);
                        });
                },
                function(error){
                    console.error("Failed to create gpx file: " + fileName + ". errcode = " + error.code);
                }
            );
        }
    };

    /**
     * @return Start position of current GPS track.
     */
    var getGpsTrackStart = function(){
        var coords;
        var track = gpsTrackLayer.features[0];

        if(track !== undefined){
            var features = track.geometry.components;
            if(features.length > 0){
                coords = {
                    'lon': features[0].x,
                    'lat': features[0].y,
                };
            }
            else{
                console.debug("No components in geometry");
            }
        }
        else{
            console.debug("No track defined");
        }

        return coords;
    };

    /**
     * Draw GPS track on map.
     * @param interval Time gap between updates. Must be more than 0.
     * @param callback Function to be executed on each good GPS coordinate, taking
     * into account the interval.
     * @param debug Use dummy GPS tracking.
     */
    var gpsTrackOnMap = function(interval, callback, debug){
        debugGPS = debug;
        map.hideRecordsLayer();

        if(!gpsLastRecorded){
            gpsLastRecorded = new Date();
        }

        gpsTrackLayer.setVisibility(true);
        gpsTrackLayer.style.strokeColor = localStorage.getItem(_this.COLOUR_INDEX);
        map.getLocateLayer().setVisibility(false);

        if(gpsTrackLayer.features.length === 0){
            var line = new OpenLayers.Geometry.LineString([]);
            gpsTrackLayer.addFeatures([new OpenLayers.Feature.Vector(line)]);
        }

        // found location
        var onSuccess = function(position){
            var next = new Date(gpsLastRecorded.getTime() + interval);
            var timestamp = position.timestamp;

            if(typeof(timestamp) === 'number'){
                timestamp = new Date(timestamp);
            }

            if((position.coords.accuracy < GPS_ACCURACY) && timestamp > next){
                gpsLastRecorded = timestamp;

                var lonLat = map.toInternal(
                    new OpenLayers.LonLat(
                        position.coords.longitude,
                        position.coords.latitude)
                );

                var point = new OpenLayers.Geometry.Point(lonLat.lon, lonLat.lat);
                gpsTrackLayer.features[0].geometry.addPoint(point);

                // only redraw if map page is active
                if($.mobile.activePage.attr('id') === 'gpscapture-page' ||
                   $.mobile.activePage.attr('id') === 'map-page'){
                    gpsTrackLayer.redraw();
                }

                console.debug("add point: " + position.coords.longitude + ' ' +
                              position.coords.latitude);
                callback(position);
            }
            else{
                console.debug("ignore point: " + position.coords.accuracy);
                console.debug(timestamp + " : " + next);
            }
        };

        // timeout has been reached
        var onError = function(position){
            utils.inform('Waiting for GPS signal');
        };

        // clear watch if already defined
        _this.gpsTrackPause();

        if(debugGPS){
            // for testing
            console.debug("GPS track debug mode");

            var lon = -3.188889;
            var lat = 55.936;

            var points = gpsTrackLayer.features[0].geometry.components;
            if(points.length > 0){
                // use last records point
                var coords = points[points.length - 1].clone().transform(
                    map.INTERNAL_PROJECTION, // from national grid
                    map.EXTERNAL_PROJECTION); // to WGS 1984

                lon = coords.x;
                lat = coords.y;
            }

            gpsTrackWatchID = setInterval(function(){
                var now = new Date();
                var position = {
                    'coords': {
                        'longitude': lon += Math.random() / 10000,
                        'latitude': lat += Math.random() / 10000,
                        'altitude': Math.random() * 100,
                        'accuracy': Math.random() * 100
                    },
                    'timestamp': now.getTime()
                };
                onSuccess(position);
            }, 1000);
        }
        else{
            gpsTrackWatchID = navigator.geolocation.watchPosition(
                onSuccess,
                onError,
                {
                    enableHighAccuracy: map.GPS_ACCURACY_FLAG,
                    maximumAge: interval,
                    timeout: 30000,
                }
            );
        }
    };

    /**
     * hide a single track.
     * @param id Annotation id.
     **/
    var hideGPSTrack = function(id){
        // prepend layer name with gps-track
        var name = "gps-track-" + id;

        var layer = map.getLayer(name);
        if(layer){
            map.removeLayer(layer);
        }

    };

    /**
     * Display a single GPS Track.
     * @param id Annotation id.
     * @param track Annotation object.
     */
    var showGPSTrack = function(id, track){
        // prepend layer name with gps-track
        var name = "gps-track-" + id;
        var layer = map.getLayer(name);
        if(layer){
            // its possible for different tracks to have the same name so remove
            // layer existing layer and replace it with current
            map.removeLayer(layer);
        }

        // TODO:
        // track URL found in the second element of the fields array in the
        // record, this may not always be the case
        var trackField = track.record.fields[1];

        var colour = 'red';
        if(typeof(trackField.style) !== 'undefined'){
            colour = trackField.style.strokeColor;
        }

        // create layer with the GPX track
        var gpxLayer = map.addGPXLayer({
            id: name,
            style:{
                colour: colour,
            },
            url: trackField.val
        });

        gpxLayer.setVisibility(true);
        gpxLayer.events.register("loadend", this, function() {
            var extent = gpxLayer.getDataExtent();
            if(extent !== null){
                map.zoomToExtent(extent);
            }
        });
    };

    /************************** public interface  ******************************/

var _this = {

    COLOUR_INDEX: 'gps-track-color',

    /**
     * Start GPX track.
     * @param annotation Annotation metadata object
     * @param debug Use dummy GPS tracking.
     */
    gpsTrack: function(annotation, debug){
        if(!this.currentTrack){
            var now = utils.isoDate();
            var fileName  = (now + '.gpx').replace(/\s|:/g, '_');
            var fullName;

            var assetsDir = records.getAssetsDir();

            if(assetsDir){
                fullName = file.getFilePath(assetsDir) + '/' + fileName;
            }

            // initialise record point with user's current location
            var start = map.getUserCoords();
            annotation.record.point = {
                'lon': start.lon,
                'lat': start.lat,
                'alt': start.gpsPosition.altitude
            };

            annotation.record.fields[1].val = fullName;
            var id = records.saveAnnotation(undefined, annotation);

            // create XML doc first using jquery's parseXML function,
            // then use standard dom methods for building the XML
            var doc = $.parseXML('<?xml version="1.0" encoding="UTF-8"?><gpx></gpx>');
            var gpx = doc.getElementsByTagName('gpx')[0];
            gpx.setAttribute('xmlns', 'http://www.topografix.com/GPX/1/1');
            gpx.setAttribute('version', '1.1');
            gpx.setAttribute('creator', 'fieldtripGB');

            // create metadata header
            var meta = doc.createElement('metadata');
            var time = doc.createElement('time');
            time.appendChild(doc.createTextNode(now));
            meta.appendChild(time);
            doc.documentElement.appendChild(meta);

            var trk = doc.createElement('trk');
            var trkseg = doc.createElement('trkseg');
            trk.appendChild(trkseg);

            doc.documentElement.appendChild(trk);

            this.currentTrack = {
                'id': id,
                'file': fileName,
                'doc': doc
            };

            // kick off tracking
            this.gpsTrackPlay(annotation.rate, debug);
        }
    },

    /**
     * Complete current GPS capture.
     */
    gpsCaptureComplete: function(){
        if(typeof(this.currentTrack) !== 'undefined'){
            var annotation = records.getSavedRecord(this.currentTrack.id);
            this.gpsTrackPause();

            // populate record with start location
            var startPoint = getGpsTrackStart();

            if(startPoint !== undefined){
                annotation.record.point = startPoint;
                // save the annotation to local storage
                records.saveAnnotation(this.currentTrack.id, annotation);

                var id = this.currentTrack.id;

                // save the GPX file
                gpsCaptureSave(function(){
                    // removing all annotation will force a refresh
                    map.getRecordsLayer().removeAllFeatures();

                    // display saved track on map
                    showGPSTrack(id, annotation);
                });
            }
            else{
                console.debug('Track ' + this.currentTrack.title +
                              ' has no points');
                records.deleteAnnotation(this.currentTrack.id);
            }

            this.gpsTrackStop();
            this.currentTrack = undefined;
        }
    },

    /**
     * Discard current GPS capture.
     */
    gpsCaptureDiscard: function(){
        this.gpsTrackStop();

        //cleanup temp track
        if(this.currentTrack){
            // delete GPX file and record
            records.deleteFile(this.currentTrack.file);
            records.deleteAnnotation(this.currentTrack.id);
        }

        this.currentTrack = undefined;
    },

    /**
     * Resume GPS track after pause.
     */
    gpsTrackPause: function(){
        if(gpsTrackWatchID){
            if(debugGPS){
                clearInterval(gpsTrackWatchID);
            }
            else{
                navigator.geolocation.clearWatch(gpsTrackWatchID);
            }

            gpsTrackWatchID = undefined;
        }
    },

    /**
     * Is there a GPS track currently paused?
     */
    gpsTrackPaused: function(){
        return this.gpsTrackStarted() && !this.gpsTrackRunning();
    },

    /**
     * Start/Resume GPS track.
     * @param captureRate How often, in seconds, a track point should be recorded.
     * @param debug Use debug mode?
     */
    gpsTrackPlay: function(captureRate, debug){
        var cr = captureRate * 1000; // in milliseconds

        // continue tracking
        gpsTrackOnMap(cr,
                      function(position){
                          gpsCaptureAutoSave(position);
                      },
                      debug);
    },

    /**
     * Is a GPS track currently running?
     */
    gpsTrackRunning: function(){
        return gpsTrackWatchID !== undefined;
    },

    /**
     * Is a GPS track in progress?
     */
    gpsTrackStarted: function(){
        return this.currentTrack !== undefined;
    },

    /**
     * Clear GPS track watch.
     */
    gpsTrackStop: function(){
        this.gpsTrackPause();
        gpsTrackLayer.removeAllFeatures();
    },

    /**
     * Switch gps track layer on/off.
     */
    gpsTrackToggle: function(){
        gpsTrackLayer.setVisibility(!gpsTrackLayer.visibility);
    },

    /**
     * Switch gps track layer on/off.
     * @param id Annotation id.
     */
    hideTrack: function(id){
        hideGPSTrack(id);
    },

    /**
     * Display specified GPS track.
     * @param id Annotation id.
     */
    showTrack: function(id){
        showGPSTrack(id, records.getSavedRecord(id));
    },
};

return _this;

});
<|endoftext|>