<commit_before>window.data = window.data || {};
window.data.tutorials = window.data.tutorials || [];
window.data.tutorials.push({
  name: 'Events',
  steps: [
    {
      name: 'Event Handling',
      index: 1,
      description_html: '
Events are defined with the standard events hash API when using the Backbone or Ampersand adaptor. If a selector is passed in the event key, however, it can only use a js- prefixed class selector. This optimizes performance when delegating events because under the hood, unlike Backbone or Ampersand, Tungsten.js provides its own event delegation system. By default, all events are delegated from the document. Special events can also be handled by an event handler plugin.

All common event types are supported as well as the special cases outlined in the examples in the following sections:

Directional Swipe Events

Intent Events

Document Bindings

Window Bindings

Outside Events

Submit Data

'
    },
    {
      name: 'Directional Swipe Events',
      index: 2,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      template: '
Test events in the colored area below.
\n

\n
captured: {{message}}
',
      description_html: '
Exactly what it sounds like:

swipeup

swipedown

swipeleft

swiperight

In order to test this on a desktop browser, it might be necessary to put the browser dev tools into device mode to register swipes correctly.
',
      js: "var View = tungsten.backbone.View;\nvar Model = tungsten.backbone.Model;\n\nvar AppModel = Model.extend({\n  message: '---'\n});\n\nvar AppView = View.extend({\n  events: {\n    'swipeup .js-event-area': 'swipeUp',\n    'swipedown .js-event-area': 'swipeDown',\n    'swipeleft .js-event-area': 'swipeLeft',\n    'swiperight .js-event-area': 'swipeRight'\n  },\n  swipeUp: function() {\n    this.setEventMessage('swipeup');\n  },\n  swipeDown: function() {\n    this.setEventMessage('swipedown');\n  },\n  swipeLeft: function() {\n    this.setEventMessage('swipeleft');\n  },\n  swipeRight: function() {\n    this.setEventMessage('swiperight');\n  },\n  setEventMessage: function(msg) {\n    var self = this;\n    this.model.set('message', msg);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('message', '---');\n    }, 1200);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    },
    {
      name: 'Intent Events',
      index: 3,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      description_html: '
Limited to a subset of events that can be "cancelled". The handler will be called n milliseconds (default 200ms) after the initial event if it is not "cancelled"

Bindable by appending -intent to one of the following events and configurable using the eventOptions hash:

mouseenter

mouseleave

mousedown

mouseup

keydown

keyup

touchstart

touchend

In the following example, there are two regular events (mouseenter, mousedown) and two counterpart intent events (mouseenter-intent, mousedown-intent). If the mouse enters the event area, mouseenter will be triggered immediately. If the mouse remains in the event area (a mouseleave does not occur) for more than the time required to measure intent (200ms by default), then the mouseenter-intent event will fire. The scenario is similar for mousedown and mousedown-intent.
',
      template: '
Test events in the colored area below.
\n

\n
captured: {{message}}
',
      js: "var View = tungsten.backbone.View;\nvar Model = tungsten.backbone.Model;\n\nvar AppModel = Model.extend({\n  message: '---'\n});\n\nvar AppView = View.extend({\n  events: {\n    'mouseenter .js-event-area': 'mouseenterEvent',\n    'mouseenter-intent .js-event-area': 'mouseenterIntent',\n    'mousedown .js-event-area': 'mousedownEvent',\n    'mousedown-intent .js-event-area': 'mousedownIntent'\n  },\n  mouseenterEvent: function(e) {\n    this.setEventMessage('mouseenter');\n  },\n  mouseenterIntent: function(e) {\n    this.setEventMessage('mouseenter INTENT');\n  },\n  mousedownEvent: function(e) {\n    this.setEventMessage('mousedown');\n  },\n  mousedownIntent: function(e) {\n    this.setEventMessage('mousedown INTENT');\n  },\n  setEventMessage: function(msg) {\n    var self = this;\n    this.model.set('message', msg);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('message', '---');\n    }, 1200);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    },
    {
      name: 'Document Bindings',
      index: 4,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      description_html: '
Adds an event binding to the document with delegation still working as expected

Bindable by prepending doc- to any event type

In the example below, there are four events being listened to. mousedown and mouseenter are regular events, and doc-mouseup and doc-mouseleave are delegated to the document.

Test each of these events below and notice the delegate in the status line below the event area.
',
      template: '
Test events in the colored area below.
\n

\n
captured: {{message}}
',
      js: "var AppView = View.extend({\n  events: {\n    'mousedown .js-event-area': 'mousedownEvent',\n    'doc-mouseup .js-event-area': 'mouseupDelegated',\n    'mouseenter .js-event-area': 'mouseenterEvent',\n    'doc-mouseleave .js-event-area': 'mouseleaveDelegated'\n  },\n  mousedownEvent: function(e) {\n    this.setEventMessage('mousedown | delegate: ' + e.delegateTarget.nodeName);\n  },\n  mouseupDelegated: function(e) {\n    this.setEventMessage('mouseup | delegate: ' + e.delegateTarget.nodeName);\n  },\n  mouseenterEvent: function(e) {\n    this.setEventMessage('mouseenter | delegate: ' + e.delegateTarget.nodeName);\n  },\n  mouseleaveDelegated: function(e) {\n    this.setEventMessage('mouseleave | delegate: ' + e.delegateTarget.nodeName);\n  },\n  setEventMessage: function(msg) {\n    var self = this;\n    this.model.set('message', msg);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('message', '---');\n    }, 1200);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    },
    {
      name: 'Window Bindings',
      index: 5,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      description_html: '
Adds an event binding to the window

Bindable by prepending win- to any event that the window fires (primarily scroll or resize, and height/width/scroll values are cached to prevent repeated reads)

',
      template: '
Resize the window and see the event data below:
\n
captured: {{message}}
',
      js: "var View = tungsten.backbone.View;\nvar Model = tungsten.backbone.Model;\n\nvar AppModel = Model.extend({\n  message: '---'\n});\n\nvar AppView = View.extend({\n  events: {\n    'win-resize': 'winResize'\n  },\n  winResize: function(e) {\n    var w = e.current.width;\n    var h = e.current.height;\n    this.setEventMessage('resize (' + w + ',' + h + ')');\n  },\n  setEventMessage: function(msg) {\n    var self = this;\n    this.model.set('message', msg);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('message', '---');\n    }, 1200);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    },
    {
      name: 'Outside Events',
      index: 6,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      description_html: '
Adds an event binding to events firing outside of the element

Bindable by appending -outside to any event type

In this example swipeup-outside, swipedown-outside, mousedown-outside, and mouseup-outside are shown. The -outside is in relation to the colored .js-event-area. If the listed events occur within the event area, the callbacks will not fire. They will only fire when triggered from outside the event area.
',
      template: '
Test events in the colored area below.
\n

\n
captured: {{message}}
',
      js: "var View = tungsten.backbone.View;\nvar Model = tungsten.backbone.Model;\n\nvar AppModel = Model.extend({\n  message: '---'\n});\n\nvar AppView = View.extend({\n  events: {\n    'swipeup-outside .js-event-area': 'swipeupOutside',\n    'swipeup-outside .js-event-area': 'swipedownOutside',\n    'mousedown-outside .js-event-area': 'mousedownOutside',\n    'mouseup-outside .js-event-area': 'mouseupOutside'\n  },\n  swipeupOutside: function() {\n    this.setEventMessage('swipeup OUTSIDE');\n  },\n  swipedownOutside: function() {\n    this.setEventMessage('swipedown OUTSIDE');\n  },\n  mousedownOutside: function() {\n    this.setEventMessage('mousedown OUTSIDE');\n  },\n  mouseupOutside: function() {\n    this.setEventMessage('mouseup OUTSIDE');\n  },\n  setEventMessage: function(msg) {\n    var self = this;\n    this.model.set('message', msg);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('message', '---');\n    }, 1200);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    },
    {
      name: 'Submit Data',
      index: 7,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      description_html: '
Adds an event binding to form submit events with the form\'s serialized data passed as the second parameter of the callback (uses form-serialize)

Bindable by using the submit-data event type

This example has a short form bound with the submit-data event. This will fire a callback when the form is submitted with the serialized data. Here we are just printing out the data but we could instead do form validation and/or status messaging.
',
      template: '<form class="js-event-form pure-form">\n    <input type="text" name="name" placeholder="Name" value=""/>\n    <input type="text" name="email" placeholder="Email" value=""/>\n    <input class="pure-button pure-button-primary" type="submit" value="Submit"/>\n</form>\n
\n
submitted: {{data}}
',
      js: "var View = tungsten.backbone.View;\nvar Model = tungsten.backbone.Model;\n\nvar AppModel = Model.extend({\n  data: '---'\n});\n\nvar AppView = View.extend({\n  events: {\n    'submit-data .js-event-form': 'formSubmit'\n  },\n  formSubmit: function(e, data) {\n    e.preventDefault();\n    this.setEventMessage(JSON.stringify(data));\n  },\n  setEventMessage: function(data) {\n    var self = this;\n    this.model.set('data', 'submitted: ' + data);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('data', '---');\n    }, 4000);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    }
  ]
});
<commit_msg>Moving extended data to defaults; Adding explicit AppModel declaration.<commit_after>window.data = window.data || {};
window.data.tutorials = window.data.tutorials || [];
window.data.tutorials.push({
  name: 'Events',
  steps: [
    {
      name: 'Event Handling',
      index: 1,
      description_html: '
Events are defined with the standard events hash API when using the Backbone or Ampersand adaptor. If a selector is passed in the event key, however, it can only use a js- prefixed class selector. This optimizes performance when delegating events because under the hood, unlike Backbone or Ampersand, Tungsten.js provides its own event delegation system. By default, all events are delegated from the document. Special events can also be handled by an event handler plugin.

All common event types are supported as well as the special cases outlined in the examples in the following sections:

Directional Swipe Events

Intent Events

Document Bindings

Window Bindings

Outside Events

Submit Data

'
    },
    {
      name: 'Directional Swipe Events',
      index: 2,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      template: '
Test events in the colored area below.
\n

\n
captured: {{message}}
',
      description_html: '
Exactly what it sounds like:

swipeup

swipedown

swipeleft

swiperight

In order to test this on a desktop browser, it might be necessary to put the browser dev tools into device mode to register swipes correctly.
',
      js: "var View = tungsten.backbone.View;\nvar Model = tungsten.backbone.Model;\n\nvar AppModel = Model.extend({\n  defaults: {\n    message: '---'\n  }\n});\n\nvar AppView = View.extend({\n  events: {\n    'swipeup .js-event-area': 'swipeUp',\n    'swipedown .js-event-area': 'swipeDown',\n    'swipeleft .js-event-area': 'swipeLeft',\n    'swiperight .js-event-area': 'swipeRight'\n  },\n  swipeUp: function() {\n    this.setEventMessage('swipeup');\n  },\n  swipeDown: function() {\n    this.setEventMessage('swipedown');\n  },\n  swipeLeft: function() {\n    this.setEventMessage('swipeleft');\n  },\n  swipeRight: function() {\n    this.setEventMessage('swiperight');\n  },\n  setEventMessage: function(msg) {\n    var self = this;\n    this.model.set('message', msg);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('message', '---');\n    }, 1200);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    },
    {
      name: 'Intent Events',
      index: 3,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      description_html: '
Limited to a subset of events that can be "cancelled". The handler will be called n milliseconds (default 200ms) after the initial event if it is not "cancelled"

Bindable by appending -intent to one of the following events and configurable using the eventOptions hash:

mouseenter

mouseleave

mousedown

mouseup

keydown

keyup

touchstart

touchend

In the following example, there are two regular events (mouseenter, mousedown) and two counterpart intent events (mouseenter-intent, mousedown-intent). If the mouse enters the event area, mouseenter will be triggered immediately. If the mouse remains in the event area (a mouseleave does not occur) for more than the time required to measure intent (200ms by default), then the mouseenter-intent event will fire. The scenario is similar for mousedown and mousedown-intent.
',
      template: '
Test events in the colored area below.
\n

\n
captured: {{message}}
',
      js: "var View = tungsten.backbone.View;\nvar Model = tungsten.backbone.Model;\n\nvar AppModel = Model.extend({\n  defaults: {\n    message: '---'\n  }\n});\n\nvar AppView = View.extend({\n  events: {\n    'mouseenter .js-event-area': 'mouseenterEvent',\n    'mouseenter-intent .js-event-area': 'mouseenterIntent',\n    'mousedown .js-event-area': 'mousedownEvent',\n    'mousedown-intent .js-event-area': 'mousedownIntent'\n  },\n  mouseenterEvent: function(e) {\n    this.setEventMessage('mouseenter');\n  },\n  mouseenterIntent: function(e) {\n    this.setEventMessage('mouseenter INTENT');\n  },\n  mousedownEvent: function(e) {\n    this.setEventMessage('mousedown');\n  },\n  mousedownIntent: function(e) {\n    this.setEventMessage('mousedown INTENT');\n  },\n  setEventMessage: function(msg) {\n    var self = this;\n    this.model.set('message', msg);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('message', '---');\n    }, 1200);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    },
    {
      name: 'Document Bindings',
      index: 4,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      description_html: '
Adds an event binding to the document with delegation still working as expected

Bindable by prepending doc- to any event type

In the example below, there are four events being listened to. mousedown and mouseenter are regular events, and doc-mouseup and doc-mouseleave are delegated to the document.

Test each of these events below and notice the delegate in the status line below the event area.
',
      template: '
Test events in the colored area below.
\n

\n
captured: {{message}}
',
      js: "var View = tungsten.backbone.View;\nvar Model = tungsten.backbone.Model;\n\nvar AppModel = Model.extend({\n  defaults: {\n    message: '---'\n  }\n});\n\nvar AppView = View.extend({\n  events: {\n    'mousedown .js-event-area': 'mousedownEvent',\n    'doc-mouseup .js-event-area': 'mouseupDelegated',\n    'mouseenter .js-event-area': 'mouseenterEvent',\n    'doc-mouseleave .js-event-area': 'mouseleaveDelegated'\n  },\n  mousedownEvent: function(e) {\n    this.setEventMessage('mousedown | delegate: ' + e.delegateTarget.nodeName);\n  },\n  mouseupDelegated: function(e) {\n    this.setEventMessage('mouseup | delegate: ' + e.delegateTarget.nodeName);\n  },\n  mouseenterEvent: function(e) {\n    this.setEventMessage('mouseenter | delegate: ' + e.delegateTarget.nodeName);\n  },\n  mouseleaveDelegated: function(e) {\n    this.setEventMessage('mouseleave | delegate: ' + e.delegateTarget.nodeName);\n  },\n  setEventMessage: function(msg) {\n    var self = this;\n    this.model.set('message', msg);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('message', '---');\n    }, 1200);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    },
    {
      name: 'Window Bindings',
      index: 5,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      description_html: '
Adds an event binding to the window

Bindable by prepending win- to any event that the window fires (primarily scroll or resize, and height/width/scroll values are cached to prevent repeated reads)

',
      template: '
Resize the window and see the event data below:
\n
captured: {{message}}
',
      js: "var View = tungsten.backbone.View;\nvar Model = tungsten.backbone.Model;\n\nvar AppModel = Model.extend({\n  defaults: {\n    message: '---'\n  }\n});\n\nvar AppView = View.extend({\n  events: {\n    'win-resize': 'winResize'\n  },\n  winResize: function(e) {\n    var w = e.current.width;\n    var h = e.current.height;\n    this.setEventMessage('resize (' + w + ',' + h + ')');\n  },\n  setEventMessage: function(msg) {\n    var self = this;\n    this.model.set('message', msg);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('message', '---');\n    }, 1200);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    },
    {
      name: 'Outside Events',
      index: 6,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      description_html: '
Adds an event binding to events firing outside of the element

Bindable by appending -outside to any event type

In this example swipeup-outside, swipedown-outside, mousedown-outside, and mouseup-outside are shown. The -outside is in relation to the colored .js-event-area. If the listed events occur within the event area, the callbacks will not fire. They will only fire when triggered from outside the event area.
',
      template: '
Test events in the colored area below.
\n

\n
captured: {{message}}
',
      js: "var View = tungsten.backbone.View;\nvar Model = tungsten.backbone.Model;\n\nvar AppModel = Model.extend({\n  defaults: {\n    message: '---'\n  }\n});\n\nvar AppView = View.extend({\n  events: {\n    'swipeup-outside .js-event-area': 'swipeupOutside',\n    'swipeup-outside .js-event-area': 'swipedownOutside',\n    'mousedown-outside .js-event-area': 'mousedownOutside',\n    'mouseup-outside .js-event-area': 'mouseupOutside'\n  },\n  swipeupOutside: function() {\n    this.setEventMessage('swipeup OUTSIDE');\n  },\n  swipedownOutside: function() {\n    this.setEventMessage('swipedown OUTSIDE');\n  },\n  mousedownOutside: function() {\n    this.setEventMessage('mousedown OUTSIDE');\n  },\n  mouseupOutside: function() {\n    this.setEventMessage('mouseup OUTSIDE');\n  },\n  setEventMessage: function(msg) {\n    var self = this;\n    this.model.set('message', msg);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('message', '---');\n    }, 1200);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    },
    {
      name: 'Submit Data',
      index: 7,
      // template_highlights: [{start: {line: 0, ch: 6}, end: {line: 0, ch: 12}}],
      description_html: '
Adds an event binding to form submit events with the form\'s serialized data passed as the second parameter of the callback (uses form-serialize)

Bindable by using the submit-data event type

This example has a short form bound with the submit-data event. This will fire a callback when the form is submitted with the serialized data. Here we are just printing out the data but we could instead do form validation and/or status messaging.
',
      template: '<form class="js-event-form pure-form">\n    <input type="text" name="name" placeholder="Name" value=""/>\n    <input type="text" name="email" placeholder="Email" value=""/>\n    <input class="pure-button pure-button-primary" type="submit" value="Submit"/>\n</form>\n
\n
submitted: {{data}}
',
      js: "var View = tungsten.backbone.View;\nvar Model = tungsten.backbone.Model;\n\nvar AppModel = Model.extend({\n  defaults: {\n    data: '---'\n  }\n});\n\nvar AppView = View.extend({\n  events: {\n    'submit-data .js-event-form': 'formSubmit'\n  },\n  formSubmit: function(e, data) {\n    e.preventDefault();\n    this.setEventMessage(JSON.stringify(data));\n  },\n  setEventMessage: function(data) {\n    var self = this;\n    this.model.set('data', 'submitted: ' + data);\n    clearTimeout(this.messageTimeout);\n    this.messageTimeout = setTimeout(function() {\n      self.model.set('data', '---');\n    }, 4000);\n  }\n});\n\nnew AppView({\n  el: '#app',\n  template: compiledTemplates.app_view,\n  model: new AppModel(),\n  dynamicInitialize: true\n});"
    }
  ]
});
<|endoftext|>


<commit_before>

var OcrJs = OcrJs || {};

OcrJs.HocrEditor = OcrJs.HocrEditor || {};

var HE = OcrJs.HocrEditor;

HE.EditCommand = OcrJs.UndoCommand.extend({
    init: function(editor, elem, origtext, newtext) {
        this._super("Edit text");
        this.redo = function() {
            $(elem).html(newtext);
            editor.setCurrentLine(elem);
        };
        this.undo = function() {
            $(elem).html(origtext);
            editor.setCurrentLine(elem);
        };
    }
});

HE.DeleteLineCommand = OcrJs.UndoCommand.extend({
    init: function(editor, line) {
        this._super("Delete paragraph");
        var prev = line.prev(),
            next = line.next(),
            parent = line.parent();
        this.redo = function() {
            line.detach();
        };
        this.undo = function() {
            if (prev.length > 0)
                prev.after(line);
            else if (next.length > 0)
                next.after(line);
            else
                parent.append(line);
        };
    }
});

HE.Editor = OcrJs.Base.extend({
    init: function(parent, cmdstack, options) {
        this._super(parent);

        this.parent = parent;

        this._listeners = {
            clickPosition: [],
            hoverPosition: [],
            lineSelected: [],
            lineDeselected: [],
            startEditing: [],
            stopEditing: [],
       };
        this._undostack = cmdstack;

        this._hocr = new HE.HocrDoc(parent);

        this._currentline = null;

        this.setupEvents();
    },

    currentLine: function() {
        return this._currentline;
    },

    getData: function() {
        var hover = $(".ocr_line.hover"),
        editing = $(".ocr_line.editing");
        hover.removeClass("hover");
        editing.removeClass("editing");

        var data = $("#transcript").html();
        hover.addClass("hover");
        editing.addClass("editing");
        return data;
    },

    resetSize: function() {

    },

    setWaiting: function(waiting) {
        $(parent).toggleClass("waiting", waiting);
    },

    setupEvents: function() {
        var self = this;

        $(".ocr_line").live("dblclick.editline", function(event) {
            self._hocr.setCurrent(this);
            self.setCurrentLine(this);
            self.editLine(this);
        });

        $(".ocr_line").live("click.selectline", function(event) {
            self._hocr.setCurrent(this);
            self.setCurrentLine(this);
        });

        $(".ocr_line").live("mouseover.selectline", function(event) {
            self.trigger("hoverPosition", self._hocr.parseBbox($(this)));
        });
    },

    cmdReplaceLineText: function(element, origtext, newtext) {
        if (origtext != newtext) {
            this._undostack.push(
                    new HE.EditCommand(this, element, origtext, newtext));
        }
    },

    cmdDeleteLine: function() {
        this._undostack.push(
            new HE.DeleteLineCommand(this, this.currentLine()));
    },

    forward: function() {
        var line = this._hocr.nextLine();
        if (!line)
            line = this._hocr.firstLine();
        this.setCurrentLine(line);
    },

    backward: function() {
        var line = this._hocr.prevLine();
        if (!line)
            line = this._hocr.lastLine();
        this.setCurrentLine(line);
    },

    // check is an element is visible - returns -1 if the elem
    // is above the viewport, 0 if visible, 1 if below
    isScrolledIntoView: function(elem) {
        if (!elem)
            return 0;
        var docviewtop = $(this.parent).scrollTop();
        var docviewbottom = docviewtop + $(this.parent).height();
        var elemtop = $(elem).offset().top;
        var elembottom = elemtop + $(elem).height();
        if (elembottom > docviewbottom)
            return 1;
        if (elemtop < docviewtop)
            return -1;
        return 0;
    },

    setCurrentLine: function(line) {
        line = $(line);
        this._currentline = line;
        line.focus();
        this._hocr.setCurrent(line.get(0));
        $(".ocr_line", this._pagediv).removeClass("hover");
        line.addClass("hover").focus();
        var pos = this.isScrolledIntoView(line.get(0));
        if (pos != 0) {
            line.get(0).scrollIntoView(pos == -1);
        }
        this.trigger("clickPosition", this._hocr.parseBbox(line));
        this.trigger("lineSelected");
    },

    editLine: function(element) {
        element = element || this._currentline;
        this.trigger("startEditing", element);
        $(element)
            .find("*").addClass("selectable").end()
            .addClass("selectable editing")
            .focus().prop("contentEditable",true).click();

    },

    finishEditing: function(element, initialcontent, save, blur) {
        $(element)
            .find("*").removeClass("selectable").end()
            .removeClass("selectable editing")
            .unbind(".finishedit")
            .removeAttr("contentEditable");
        // if the editing wasn't terminated by a blur of the element,
        // trigger a blur and way since this seems to be the only
        // was to force Firefix and Chrome to remove the contentEditable
        // styling from the line
        if (!blur)
            $(element).blur();
        if (save)
            this.cmdReplaceLineText(element, initialcontent, $(element).html());
        else
            $(element).html(initialcontent);
        this.trigger("stopEditing", element);
    },

    refresh: function() {
        this._hocr.reset();
    },

    _setCaretAfter: function(el) {
        var sel, range;
        if (window.getSelection && document.createRange) {
            range = document.createRange();
            //range.setStartAfter(el);
            range.collapse(true);
            sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        } else if (document.body.createTextRange) {
            range = document.body.createTextRange();
            range.moveToElementText(el);
            range.collapse(false);
            range.select();
        }
    },
});
<commit_msg>Fix waiting indicator<commit_after>

var OcrJs = OcrJs || {};

OcrJs.HocrEditor = OcrJs.HocrEditor || {};

var HE = OcrJs.HocrEditor;

HE.EditCommand = OcrJs.UndoCommand.extend({
    init: function(editor, elem, origtext, newtext) {
        this._super("Edit text");
        this.redo = function() {
            $(elem).html(newtext);
            editor.setCurrentLine(elem);
        };
        this.undo = function() {
            $(elem).html(origtext);
            editor.setCurrentLine(elem);
        };
    }
});

HE.DeleteLineCommand = OcrJs.UndoCommand.extend({
    init: function(editor, line) {
        this._super("Delete paragraph");
        var prev = line.prev(),
            next = line.next(),
            parent = line.parent();
        this.redo = function() {
            line.detach();
        };
        this.undo = function() {
            if (prev.length > 0)
                prev.after(line);
            else if (next.length > 0)
                next.after(line);
            else
                parent.append(line);
        };
    }
});

HE.Editor = OcrJs.Base.extend({
    init: function(parent, cmdstack, options) {
        this._super(parent);

        this.parent = parent;

        this._listeners = {
            clickPosition: [],
            hoverPosition: [],
            lineSelected: [],
            lineDeselected: [],
            startEditing: [],
            stopEditing: [],
       };
        this._undostack = cmdstack;

        this._hocr = new HE.HocrDoc(parent);

        this._currentline = null;

        this.setupEvents();
    },

    currentLine: function() {
        return this._currentline;
    },

    getData: function() {
        var hover = $(".ocr_line.hover"),
        editing = $(".ocr_line.editing");
        hover.removeClass("hover");
        editing.removeClass("editing");

        var data = $("#transcript").html();
        hover.addClass("hover");
        editing.addClass("editing");
        return data;
    },

    resetSize: function() {

    },

    setWaiting: function(waiting) {
        $(this.parent).toggleClass("waiting", waiting);
    },

    setupEvents: function() {
        var self = this;

        $(".ocr_line").live("dblclick.editline", function(event) {
            self._hocr.setCurrent(this);
            self.setCurrentLine(this);
            self.editLine(this);
        });

        $(".ocr_line").live("click.selectline", function(event) {
            self._hocr.setCurrent(this);
            self.setCurrentLine(this);
        });

        $(".ocr_line").live("mouseover.selectline", function(event) {
            self.trigger("hoverPosition", self._hocr.parseBbox($(this)));
        });
    },

    cmdReplaceLineText: function(element, origtext, newtext) {
        if (origtext != newtext) {
            this._undostack.push(
                    new HE.EditCommand(this, element, origtext, newtext));
        }
    },

    cmdDeleteLine: function() {
        this._undostack.push(
            new HE.DeleteLineCommand(this, this.currentLine()));
    },

    forward: function() {
        var line = this._hocr.nextLine();
        if (!line)
            line = this._hocr.firstLine();
        this.setCurrentLine(line);
    },

    backward: function() {
        var line = this._hocr.prevLine();
        if (!line)
            line = this._hocr.lastLine();
        this.setCurrentLine(line);
    },

    // check is an element is visible - returns -1 if the elem
    // is above the viewport, 0 if visible, 1 if below
    isScrolledIntoView: function(elem) {
        if (!elem)
            return 0;
        var docviewtop = $(this.parent).scrollTop();
        var docviewbottom = docviewtop + $(this.parent).height();
        var elemtop = $(elem).offset().top;
        var elembottom = elemtop + $(elem).height();
        if (elembottom > docviewbottom)
            return 1;
        if (elemtop < docviewtop)
            return -1;
        return 0;
    },

    setCurrentLine: function(line) {
        line = $(line);
        this._currentline = line;
        line.focus();
        this._hocr.setCurrent(line.get(0));
        $(".ocr_line", this._pagediv).removeClass("hover");
        line.addClass("hover").focus();
        var pos = this.isScrolledIntoView(line.get(0));
        if (pos != 0) {
            line.get(0).scrollIntoView(pos == -1);
        }
        this.trigger("clickPosition", this._hocr.parseBbox(line));
        this.trigger("lineSelected");
    },

    editLine: function(element) {
        element = element || this._currentline;
        this.trigger("startEditing", element);
        $(element)
            .find("*").addClass("selectable").end()
            .addClass("selectable editing")
            .focus().prop("contentEditable",true).click();

    },

    finishEditing: function(element, initialcontent, save, blur) {
        $(element)
            .find("*").removeClass("selectable").end()
            .removeClass("selectable editing")
            .unbind(".finishedit")
            .removeAttr("contentEditable");
        // if the editing wasn't terminated by a blur of the element,
        // trigger a blur and way since this seems to be the only
        // was to force Firefix and Chrome to remove the contentEditable
        // styling from the line
        if (!blur)
            $(element).blur();
        if (save)
            this.cmdReplaceLineText(element, initialcontent, $(element).html());
        else
            $(element).html(initialcontent);
        this.trigger("stopEditing", element);
    },

    refresh: function() {
        this._hocr.reset();
    },

    _setCaretAfter: function(el) {
        var sel, range;
        if (window.getSelection && document.createRange) {
            range = document.createRange();
            //range.setStartAfter(el);
            range.collapse(true);
            sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        } else if (document.body.createTextRange) {
            range = document.body.createTextRange();
            range.moveToElementText(el);
            range.collapse(false);
            range.select();
        }
    },
});
<|endoftext|>


<commit_before>enyo.depends(
	"sample.css",
	"ButtonGroupSample.js",
	"ButtonSample.js",
	"CheckboxSample.js",
	"GroupboxSample.js",
	"IconButtonSample.js",
	"InputSample.js",
	"PopupSample.js",
	"ProgressSample.js",
	"SliderSample.js",
	"ToggleButtonSample.js",
	"ToolbarSample.js",
	"MenuSample.js",
	"TooltipSample.js",
	"SpinnerSample.js",
	"PickerSample.js",
	"DatePickerSample.js",
	"TimePickerSample.js",
	"ContextualPopupSample.js"
);<commit_msg>Add SubmoduleSample.js to samples/package.js<commit_after>enyo.depends(
	"sample.css",
	"ButtonGroupSample.js",
	"ButtonSample.js",
	"CheckboxSample.js",
	"GroupboxSample.js",
	"IconButtonSample.js",
	"InputSample.js",
	"PopupSample.js",
	"ProgressSample.js",
	"SliderSample.js",
	"ToggleButtonSample.js",
	"ToolbarSample.js",
	"MenuSample.js",
	"TooltipSample.js",
	"SpinnerSample.js",
	"PickerSample.js",
	"DatePickerSample.js",
	"TimePickerSample.js",
	"ContextualPopupSample.js",
	"SubmenuSample.js"
);<|endoftext|>


<commit_before>
(function () {
    angular
        .module('andyBeeApp')
        .factory('GeocacheService', GeocacheService);

    GeocacheService.$inject = ['$rootScope', '$resource', 'PreferenceService', 'LoggingService'];
    function GeocacheService ($rootScope, $resource, PreferenceService, LoggingService) {
        var rest_dblist = $resource('/andyBee/api/v1.0/db/', null, {
            post: {
                method: 'POST'
            }
        });
        var rest_geocache = $resource('/andyBee/api/v1.0/db/:db/geocaches/:geocache_id');
        var serv = {
            // which of the tabs (list, map, detail, console) is active
            detail: {},
            selected_tab: 0,
            show_details: show_details,

            // stuff related to the list of DBs
            db_list: [],
            get_dblist: get_dblist,
            db_name: '',

            // stuff related to geocaches within a db
            geocache_list: [],
            get_geocache_list: get_geocache_list,

        };

        return serv;

        function get_dblist (success_cb) {
            rest_dblist.get(dblist_response, LoggingService.log_RESTful_error);

            function dblist_response (result) {
                serv.db_list = result.dbs;
                success_cb();
            }
        }

        function get_geocache_list (db_name) {
            if (inArray(db_name, serv.db_list)) {
                rest_geocache.get({db: db_name}, geocache_list_response, LoggingService.log_RESTful_error);
            }
            else {
                // new DB to allocate.
                rest_dblist.post({db: db_name}, db_allocated, LoggingService.log_RESTful_error);
            }

            function db_allocated (result) {
                serv.db_name = result.db_name;
                PreferenceService.set_used_db(result.db_name);
                serv.nbr_caches = 0;
                serv.geocache_list = [];
                $rootScope.$broadcast('geocaches_updated');
            }

            function geocache_list_response (result) {
                serv.db_name = result.db_name;
                PreferenceService.set_used_db(result.db_name);
                serv.nbr_caches = result.nbr_caches;
                serv.geocache_list = result.geocaches;
                $rootScope.$broadcast('geocaches_updated');
            }
        }

        function show_details (id) {
            serv.selected_tab = 2; // switch to the details tab
            rest_geocache.get({db: serv.db_name, geocache_id: id}, geocache_response, LoggingService.log_RESTful_error);

            function geocache_response (result) {
                serv.detail = result.geocache;
                $rootScope.$broadcast('geocache_details_updated');
            }
        }

        function inArray(needle, haystack) {
            var length = haystack.length;
            for (var i = 0; i < length; i++) {
                if (haystack[i] == needle)
                    return true;
            }
            return false;
        }
    }

})();
<commit_msg>Forgotten to update geocache.service as well<commit_after>
(function () {
    angular
        .module('andyBeeApp')
        .factory('GeocacheService', GeocacheService);

    GeocacheService.$inject = ['$rootScope', '$resource', 'PreferenceService', 'LoggingService'];
    function GeocacheService ($rootScope, $resource, PreferenceService, LoggingService) {
        var rest_dblist = $resource('/andyBee/api/v1.0/db/', null, {
            post: {
                method: 'POST'
            }
        });
        var rest_geocache = $resource('/andyBee/api/v1.0/db/:db/geocaches/:geocache_id');
        var serv = {
            // which of the tabs (list, map, detail, console) is active
            detail: {},
            selected_tab: 0,
            show_details: show_details,

            // stuff related to the list of DBs
            db_list: [],
            get_dblist: get_dblist,
            db_name: '',

            // stuff related to geocaches within a db
            geocache_list: [],
            get_geocache_list: get_geocache_list,

        };

        return serv;

        function get_dblist (success_cb) {
            rest_dblist.get(dblist_response, LoggingService.log_RESTful_error);

            function dblist_response (result) {
                serv.db_list = result.dbs;
                success_cb();
            }
        }

        function get_geocache_list (db_name) {
            if (inArray(db_name, serv.db_list)) {
                rest_geocache.get({db: db_name}, geocache_list_response, LoggingService.log_RESTful_error);
            }
            else {
                // new DB to allocate.
                rest_dblist.post({db: db_name}, db_allocated, LoggingService.log_RESTful_error);
            }

            function db_allocated (result) {
                serv.db_name = result.db_name;
                PreferenceService.update_used_db(result.db_name);
                serv.nbr_caches = 0;
                serv.geocache_list = [];
                $rootScope.$broadcast('geocaches_updated');
            }

            function geocache_list_response (result) {
                serv.db_name = result.db_name;
                PreferenceService.update_used_db(result.db_name);
                serv.nbr_caches = result.nbr_caches;
                serv.geocache_list = result.geocaches;
                $rootScope.$broadcast('geocaches_updated');
            }
        }

        function show_details (id) {
            serv.selected_tab = 2; // switch to the details tab
            rest_geocache.get({db: serv.db_name, geocache_id: id}, geocache_response, LoggingService.log_RESTful_error);

            function geocache_response (result) {
                serv.detail = result.geocache;
                $rootScope.$broadcast('geocache_details_updated');
            }
        }

        function inArray(needle, haystack) {
            var length = haystack.length;
            for (var i = 0; i < length; i++) {
                if (haystack[i] == needle)
                    return true;
            }
            return false;
        }
    }

})();
<|endoftext|>


<commit_before>import {msg} from 'translate'
import {recipeActionBuilder} from '../recipe'
import _ from 'lodash'
import api from 'api'
import moment from 'moment'

const DATE_FORMAT = 'YYYY-MM-DD'

export const breakDetectionOptions = {
    conservative: {
        minObservations: 6,
        chiSquareProbability: 0.99,
        minNumOfYearsScaler: 1.33,
        lambda: 20,
        maxIterations: 25000
    },

    moderate: {
        minObservations: 4,
        chiSquareProbability: 0.9,
        minNumOfYearsScaler: 1.33,
        lambda: 20,
        maxIterations: 25000
    },

    aggressive: {
        minObservations: 3,
        chiSquareProbability: 0.75,
        minNumOfYearsScaler: 1.33,
        lambda: 20,
        maxIterations: 25000
    }
}

export const defaultModel = {
    dates: {
        startDate: '2000-01-01',
        endDate: moment().format(DATE_FORMAT)
    },
    sources: {
        dataSets: {
            LANDSAT: ['LANDSAT_8', 'LANDSAT_7', 'LANDSAT_TM']
        },
        breakpointBands: ['ndfi']
    },
    options: {
        corrections: [],
        mask: ['SNOW'],
        orbits: ['ASCENDING'],
        geometricCorrection: 'ELLIPSOID',
        speckleFilter: 'NONE',
        outlierRemoval: 'NONE'
    },
    ccdcOptions: breakDetectionOptions.moderate
}

export const RecipeActions = id => {
    const actionBuilder = recipeActionBuilder(id)

    return {
        setChartPixel(latLng) {
            return actionBuilder('SET_CHART_PIXEL', latLng)
                .set('ui.chartPixel', latLng)
                .build()
                .dispatch()
        },

        retrieve(retrieveOptions) {
            return actionBuilder('REQUEST_MOSAIC_RETRIEVAL', {retrieveOptions})
                .setAll({
                    'ui.retrieveState': 'SUBMITTED',
                    'ui.retrieveOptions': retrieveOptions
                })
                .sideEffect(recipe => submitRetrieveRecipeTask(recipe))
                .build()
        }
    }
}

function toBackendRecipe({recipe, bands}) {
    const name = recipe.title || recipe.placeholder
    const preprocess = recipe.model.options
    const ccdcOptions = recipe.model.ccdcOptions
    const breakpointBands = recipe.model.sources.breakpointBands
    return {
        title: msg(['process.ccdc.panel.retrieve.task'], {name}),
        description: name,
        bands,
        breakpointBands,
        dataSets: _.flatten(Object.values(recipe.model.sources.dataSets)),
        aoi: recipe.model.aoi,
        fromDate: recipe.model.dates.startDate,
        toDate: recipe.model.dates.endDate,
        maskSnow: preprocess.mask.includes('SNOW'),
        brdfCorrect: preprocess.corrections.includes('BRDF'),
        surfaceReflectance: preprocess.corrections.includes('SR'),
        calibrate: true,
        ...preprocess,
        ...ccdcOptions
    }
}

export const loadCCDCTimeSeries$ = ({recipe, latLng, bands}) =>
    api.gee.loadCCDCTimeSeries$({recipe: toBackendRecipe({
            recipe,
            bands
    }), latLng})

const submitRetrieveRecipeTask = recipe => {
    const task = {
        'operation': 'ccdc.asset_export',
        'params': {
            ...toBackendRecipe({
                recipe,
                bands: recipe.ui.retrieveOptions.bands
            }),
            scale: recipe.ui.retrieveOptions.scale
        }
    }
    return api.tasks.submit$(task).subscribe()
}

export const dateRange = dates => ([moment(dates.startDate, DATE_FORMAT), moment(dates.endDate, DATE_FORMAT)])
<commit_msg>Increased minObservation for CCDC presets.<commit_after>import {msg} from 'translate'
import {recipeActionBuilder} from '../recipe'
import _ from 'lodash'
import api from 'api'
import moment from 'moment'

const DATE_FORMAT = 'YYYY-MM-DD'

export const breakDetectionOptions = {
    conservative: {
        minObservations: 6,
        chiSquareProbability: 0.99,
        minNumOfYearsScaler: 1.33,
        lambda: 20,
        maxIterations: 25000
    },

    moderate: {
        minObservations: 6,
        chiSquareProbability: 0.9,
        minNumOfYearsScaler: 1.33,
        lambda: 20,
        maxIterations: 25000
    },

    aggressive: {
        minObservations: 4,
        chiSquareProbability: 0.75,
        minNumOfYearsScaler: 1.33,
        lambda: 20,
        maxIterations: 25000
    }
}

export const defaultModel = {
    dates: {
        startDate: '2000-01-01',
        endDate: moment().format(DATE_FORMAT)
    },
    sources: {
        dataSets: {
            LANDSAT: ['LANDSAT_8', 'LANDSAT_7', 'LANDSAT_TM']
        },
        breakpointBands: ['ndfi']
    },
    options: {
        corrections: [],
        mask: ['SNOW'],
        orbits: ['ASCENDING'],
        geometricCorrection: 'ELLIPSOID',
        speckleFilter: 'NONE',
        outlierRemoval: 'NONE'
    },
    ccdcOptions: breakDetectionOptions.moderate
}

export const RecipeActions = id => {
    const actionBuilder = recipeActionBuilder(id)

    return {
        setChartPixel(latLng) {
            return actionBuilder('SET_CHART_PIXEL', latLng)
                .set('ui.chartPixel', latLng)
                .build()
                .dispatch()
        },

        retrieve(retrieveOptions) {
            return actionBuilder('REQUEST_MOSAIC_RETRIEVAL', {retrieveOptions})
                .setAll({
                    'ui.retrieveState': 'SUBMITTED',
                    'ui.retrieveOptions': retrieveOptions
                })
                .sideEffect(recipe => submitRetrieveRecipeTask(recipe))
                .build()
        }
    }
}

function toBackendRecipe({recipe, bands}) {
    const name = recipe.title || recipe.placeholder
    const preprocess = recipe.model.options
    const ccdcOptions = recipe.model.ccdcOptions
    const breakpointBands = recipe.model.sources.breakpointBands
    return {
        title: msg(['process.ccdc.panel.retrieve.task'], {name}),
        description: name,
        bands,
        breakpointBands,
        dataSets: _.flatten(Object.values(recipe.model.sources.dataSets)),
        aoi: recipe.model.aoi,
        fromDate: recipe.model.dates.startDate,
        toDate: recipe.model.dates.endDate,
        maskSnow: preprocess.mask.includes('SNOW'),
        brdfCorrect: preprocess.corrections.includes('BRDF'),
        surfaceReflectance: preprocess.corrections.includes('SR'),
        calibrate: true,
        ...preprocess,
        ...ccdcOptions
    }
}

export const loadCCDCTimeSeries$ = ({recipe, latLng, bands}) =>
    api.gee.loadCCDCTimeSeries$({recipe: toBackendRecipe({
            recipe,
            bands
    }), latLng})

const submitRetrieveRecipeTask = recipe => {
    const task = {
        'operation': 'ccdc.asset_export',
        'params': {
            ...toBackendRecipe({
                recipe,
                bands: recipe.ui.retrieveOptions.bands
            }),
            scale: recipe.ui.retrieveOptions.scale
        }
    }
    return api.tasks.submit$(task).subscribe()
}

export const dateRange = dates => ([moment(dates.startDate, DATE_FORMAT), moment(dates.endDate, DATE_FORMAT)])
<|endoftext|>


<commit_before>exports.browsers = [
    {
        platform: 'Windows 7',
        browserName: 'internet explorer',
        version: '10.0'
    },
    {
        platform: 'Windows 7',
        browserName: 'internet explorer',
        version: '11.0'
    },
    {
        platform: 'Windows 7',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 7',
        browserName: 'firefox'
    },

    {
        platform: 'Windows 8',
        browserName: 'internet explorer',
        version: '10.0'
    },
    {
        platform: 'Windows 8',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 8',
        browserName: 'firefox'
    },

    {
        platform: 'Windows 8.1',
        browserName: 'internet explorer',
        version: '11.0'
    },
    {
        platform: 'Windows 8.1',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 8.1',
        browserName: 'firefox'
    },
    {
        browserName: "safari",
        platform: "OS X 10.11",
        version: "9"
    }
    // ,
    // {
    //     platform: 'iOS',
    //     version: '8.4',
    //     device: 'iPad Simulator',
    //     browserName: 'Safari',
    //     deviceOrientation: 'landscape'
    // }
];
<commit_msg>Add more browsers for saucelabs test<commit_after>exports.browsers = [
    {
        platform: 'Windows 7',
        browserName: 'internet explorer',
        version: '10.0'
    },
    {
        platform: 'Windows 7',
        browserName: 'internet explorer',
        version: '11.0'
    },
    {
        platform: 'Windows 7',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 7',
        browserName: 'firefox'
    },

    {
        platform: 'Windows 8',
        browserName: 'internet explorer',
        version: '10.0'
    },
    {
        platform: 'Windows 8',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 8',
        browserName: 'firefox'
    },

    {
        platform: 'Windows 8.1',
        browserName: 'internet explorer',
        version: '11.0'
    },
    {
        platform: 'Windows 8.1',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 8.1',
        browserName: 'firefox'
    },
    {
        browserName: "safari",
        platform: "OS X 10.11",
        version: "9"
    },
    {
        platformName: 'iOS',
        platformVersion: '8.4',
        deviceName: 'iPad Simulator',
        browserName: 'Safari',
        deviceOrientation: 'landscape'
    },
    {
        platformName: 'iOS',
        platformVersion: '9.2',
        deviceName: 'iPad Simulator',
        browserName: 'Safari',
        deviceOrientation: 'landscape'
    },
    {
        platformName: 'iOS',
        platformVersion: '9.3',
        deviceName: 'iPad Simulator',
        browserName: 'Safari',
        deviceOrientation: 'landscape'
    },

    {
        platform: 'Linux',
        browserName: 'chrome'
    },
    {
        platform: 'Linux',
        browserName: 'firefox'
    },

    {
        platform: 'OS X 10.10',
        browserName: 'chrome'
    },
    {
        platform: 'OS X 10.10',
        browserName: 'firefox'
    },
    {
        platform: 'OS X 10.10',
        browserName: 'safari'
    },

    {
        platform: 'OS X 10.11',
        browserName: 'chrome'
    },
    {
        platform: 'OS X 10.11',
        browserName: 'firefox'
    },
    {
        platform: 'OS X 10.11',
        browserName: 'safari'
    },

    {
        platform: 'Windows 7',
        browserName: 'internet explorer',
        version: '10.0'
    },
    {
        platform: 'Windows 8',
        browserName: 'internet explorer',
        version: '10.0'
    }
];
<|endoftext|>


<commit_before>var terrain;
var cameraLoc;

function setup(){
  createCanvas(windowWidth, windowHeight);

  terrain = new Terrain();
  cameraLoc = createVector(0, 0, 0);
}

function draw(){
  background(45);

  let baseColor = color(30, 90, 30);
  let maxColor = color(230, 240, 230);

  let cellSize = 10;
  noStroke();
  for (var x = 0; x < width; x += cellSize){
    for (var z = 0; z < height; z += cellSize){
      let heightAtLoc = terrain.elevationAt(cameraLoc.x + x, cameraLoc.z + z);
      fill( lerpColor(baseColor, maxColor, heightAtLoc) );
      rect(x, z, cellSize, cellSize);
    }
  }
  cameraLoc.z -= 10;
}

class Terrain {
  constructor(){
    this.noiseScale = 0.005;
  }

  elevationAt(x, z){
    return noise(x * this.noiseScale, z * this.noiseScale);
  }
}
<commit_msg>p5js/terrain-3d - Switch to 3D, not quite working<commit_after>var cameraLoc;

// From: https://en.wikipedia.org/wiki/Aircraft_principal_axes
var cameraPitch = 0;
var cameraRoll = 0;
var cameraYaw = 0;

function setup(){
  createCanvas(windowWidth, windowHeight, WEBGL);

  terrain = new Terrain();
  cameraLoc = createVector(0, 100, 0);
  cameraPitch = - PI / 6;

  stroke(255);
}

function draw(){
  background(45);

  let baseColor = color(30, 90, 30);
  let maxColor = color(230, 240, 230);

  let cellSize = 10;
  let renderDepth = 20;
  let renderWidth = 2 * (width / cellSize);

  let generatedTerrain = [];

  // Build Terrain
  for (var x = 0; x < renderWidth; x++){
    generatedTerrain[x] = [];
    for (var z = 0; z < renderDepth; z++){
      let xScaled = (x * cellSize) + cameraLoc.x;
      let zScaled = (z * cellSize) + cameraLoc.z;
      let heightAtLoc = terrain.elevationAt(xScaled, zScaled);
      generatedTerrain[x][z] = heightAtLoc;
    }
  }

  // translate(width/2, height/2);
  translate(cameraLoc.x, cameraLoc.y, cameraLoc.z);
  rotateX(cameraPitch);

  // Render mesh for Terrain
  noFill();
  beginShape(TRIANGLE_STRIP);
  for (var x = 0; x < renderWidth; x++){
    generatedTerrain[x] = [];
    for (var z = 0; z < renderDepth-1; z++){
      let xScaled = (x * cellSize) + cameraLoc.x;
      let zScaled = (z * cellSize) + cameraLoc.z;

      let nextScaledZ = zScaled + cellSize;
      vertex(xScaled, generatedTerrain[x][z], zScaled);
      vertex(xScaled, generatedTerrain[x][z+1], nextScaledZ);
    }
  }
  endShape();
  cameraLoc.z += 10;
}

class Terrain {
  constructor(){
    this.noiseScale = 0.005;
  }

  elevationAt(x, z){
    return 100 * noise(x * this.noiseScale, z * this.noiseScale);
  }
}
<|endoftext|>


<commit_before>(function () {
  'use strict';
  // Poll report controller
  angular.module('polls')
    .controller('PollReportController', PollReportController);

  PollReportController.$inject = [
    '$location',
    '$scope',
    '$stateParams',
    '$state',
    'Action',
  ];

  function PollReportController(
    $location,
    $scope,
    $stateParams,
    $state,
    Action
  ) {
    var ctrl = this;
    ctrl.endDate = {};
    ctrl.startDate = {};
    ctrl.years = [];
    ctrl.months = [];
    ctrl.dates = [];
    ctrl.yearCnt = 0;
    ctrl.monthCnt = 0;
    ctrl.weekCnt = 0;

    // Chart data
    ctrl.mode = 1; // 1: month - 2: year - 3: weeks
    ctrl.year = '';
    ctrl.month = '';
    ctrl.date = '';
    ctrl.series = ['Thành viên', 'Khách'];

    onPrepare();

    // Lấy id của poll trong đường dẫn để request API
    function onPrepare() {
      if (!$stateParams.pollId) {
        $state.go('home');
      } else {
        Action.get_poll($stateParams.pollId)
          .then(_poll => {
            ctrl.poll = _poll;
            onCreate();
          });
      }
    }

    // Init data
    function onCreate() {
      // Nếu poll không tồn tại thì về trang chủ
      if (!ctrl.poll || !ctrl.poll._id) {
        $state.go('home');
        $scope.handleShowMessage('LB_POLL_EXIST_ERROR', true);
        return;
      }
      // Kiểm tra nếu poll là private và không có code share thì không show thông tin poll
      if (!ctrl.poll.isCurrentUserOwner && !ctrl.poll.isPublic) {
        $state.go('home');
        $scope.handleShowMessage('LB_POLLS_PRIVATE_ERROR', true);
        return;
      }
      // Collect dữ liệu hiển thị màn hình
      prepareShowingData();
      prepareDays();
      prepareYears();
    }
    // Lấy các thông tin vote
    function prepareShowingData() {
      // Thiết lập các thông tin cho poll
      ctrl.opts = _.where(ctrl.poll.opts, { status: 1 });
      ctrl.votes = ctrl.poll.votes || [];
      ctrl.voteopts = ctrl.poll.voteopts || [];
      ctrl.votedTotal = ctrl.voteopts.length;
      ctrl.opts.forEach(opt => {
        opt.voteCnt = _.where(ctrl.voteopts, { opt: opt._id }).length || 0;
        opt.progressVal = Action.calPercen(ctrl.votedTotal, opt.voteCnt);
      });
    }
    function prepareDays() {
      ctrl.startDate = moment(ctrl.poll.created).utc();
      var now = moment().utc();
      if (!ctrl.poll.close) {
        ctrl.endDate = moment().utc();
      } else {
        var closeDate = moment(ctrl.poll.close).utc();
        if (closeDate.isBefore(now)) {
          ctrl.endDate = closeDate;
        } else {
          ctrl.endDate = now;
        }
      }
    }
    function prepareYears() {
      ctrl.yearCnt = ctrl.endDate.diff(ctrl.startDate, 'years');
      for (var index = 0; index < ctrl.yearCnt; index++) {
        var item = ctrl.startDate.clone().add(index, 'years').year();
        ctrl.years.push(item);
      }
      ctrl.year = ctrl.years[ctrl.years.length - 1];
      handleChangeYear();
    }
    function prepareMonths() {
      ctrl.monthCnt = ctrl.endDate.diff(ctrl.startDate, 'months');
      for (var index = 0; index <= ctrl.monthCnt; index++) {
        var item = ctrl.startDate.clone().add(index, 'months').month();
        ctrl.months.push(item);
      }
      ctrl.month = ctrl.months[ctrl.months.length - 1];
    }
    function prepareReportTraffic() {
      ctrl.traffic = {};
      ctrl.traffic.labels = 1;

    }

    /**
     * HANDLES
     */
    ctrl.handleChangeYear = handleChangeYear;
    function handleChangeYear() {
      ctrl.months = handleGetMonthsOfYear(ctrl.year);
      ctrl.month = ctrl.months[ctrl.months.length - 1];
      handleChangeMonth();
    }
    ctrl.handleChangeMonth = handleChangeMonth;
    function handleChangeMonth() {
      ctrl.dates = handleGetDatesOfMonth(ctrl.year, ctrl.month);
      ctrl.date = ctrl.dates[ctrl.dates.length - 1];
    }
    ctrl.handleChangeDate = handleChangeDate;
    function handleChangeDate() {
    }

    function handleGetMonthsOfYear(year) {
      var mmYear = moment().utc().year(year);
      var startMonth = mmYear.clone().startOf('year');
      var endMonth = mmYear.clone().endOf('year').subtract(1, 'days');
      var durration = endMonth.diff(startMonth, 'months');
      var months = [];
      for (var index = 0; index <= durration; index++) {
        var item = startMonth.clone().add(index, 'months').month() + 1;
        months.push(item);
      }
      mmYear = undefined;
      startMonth = undefined;
      endMonth = undefined;
      return months;
    }

    function handleGetDatesOfMonth(year, month) {
      var mmMonth = moment().utc().year(year).month(month);
      console.log(mmMonth);
      var startDate = mmMonth.clone().startOf('month');
      console.log(startDate);
      var endDate = mmMonth.clone().endOf('month').subtract(1, 'hour');
      console.log(endDate);
      var duration = endDate.diff(startDate, 'days');
      var dates = [];
      for (var index = 0; index <= duration; index++) {
        var item = startDate.clone().add(index, 'day').date();
        dates.push(item);
      }
      mmMonth = undefined;
      startDate = undefined;
      endDate = undefined;
      return dates;
    }
  }
})();
<commit_msg>Commited from HaNhatLenh at Fri, Sep 22, 2017 10:58:42 PM<commit_after>(function () {
  'use strict';
  // Poll report controller
  angular.module('polls')
    .controller('PollReportController', PollReportController);

  PollReportController.$inject = [
    '$location',
    '$scope',
    '$stateParams',
    '$state',
    'Action',
  ];

  function PollReportController(
    $location,
    $scope,
    $stateParams,
    $state,
    Action
  ) {
    var ctrl = this;
    ctrl.endDate = {};
    ctrl.startDate = {};
    ctrl.years = [];
    ctrl.months = [];
    ctrl.dates = [];
    ctrl.yearCnt = 0;
    ctrl.monthCnt = 0;
    ctrl.weekCnt = 0;

    // Chart data
    ctrl.mode = 1; // 1: month - 2: year - 3: weeks
    ctrl.year = '';
    ctrl.month = '';
    ctrl.date = '';
    ctrl.series = ['Thành viên', 'Khách'];

    onPrepare();

    // Lấy id của poll trong đường dẫn để request API
    function onPrepare() {
      if (!$stateParams.pollId) {
        $state.go('home');
      } else {
        Action.get_poll($stateParams.pollId)
          .then(_poll => {
            ctrl.poll = _poll;
            onCreate();
          });
      }
    }

    // Init data
    function onCreate() {
      // Nếu poll không tồn tại thì về trang chủ
      if (!ctrl.poll || !ctrl.poll._id) {
        $state.go('home');
        $scope.handleShowMessage('LB_POLL_EXIST_ERROR', true);
        return;
      }
      // Kiểm tra nếu poll là private và không có code share thì không show thông tin poll
      if (!ctrl.poll.isCurrentUserOwner && !ctrl.poll.isPublic) {
        $state.go('home');
        $scope.handleShowMessage('LB_POLLS_PRIVATE_ERROR', true);
        return;
      }
      // Collect dữ liệu hiển thị màn hình
      prepareShowingData();
      prepareDays();
      prepareYears();
    }
    // Lấy các thông tin vote
    function prepareShowingData() {
      // Thiết lập các thông tin cho poll
      ctrl.opts = _.where(ctrl.poll.opts, { status: 1 });
      ctrl.votes = ctrl.poll.votes || [];
      ctrl.voteopts = ctrl.poll.voteopts || [];
      ctrl.votedTotal = ctrl.voteopts.length;
      ctrl.opts.forEach(opt => {
        opt.voteCnt = _.where(ctrl.voteopts, { opt: opt._id }).length || 0;
        opt.progressVal = Action.calPercen(ctrl.votedTotal, opt.voteCnt);
      });
    }
    function prepareDays() {
      ctrl.startDate = moment(ctrl.poll.created).utc();
      var now = moment().utc();
      if (!ctrl.poll.close) {
        ctrl.endDate = moment().utc();
      } else {
        var closeDate = moment(ctrl.poll.close).utc();
        if (closeDate.isBefore(now)) {
          ctrl.endDate = closeDate;
        } else {
          ctrl.endDate = now;
        }
      }
    }
    function prepareYears() {
      ctrl.yearCnt = ctrl.endDate.diff(ctrl.startDate, 'years');
      for (var index = 0; index <= ctrl.yearCnt; index++) {
        var item = ctrl.startDate.clone().add(index, 'years').year();
        ctrl.years.push(item);
      }
      ctrl.year = ctrl.years[ctrl.years.length - 1];
      handleChangeYear();
    }
    function prepareMonths() {
      ctrl.monthCnt = ctrl.endDate.diff(ctrl.startDate, 'months');
      for (var index = 0; index <= ctrl.monthCnt; index++) {
        var item = ctrl.startDate.clone().add(index, 'months').month();
        ctrl.months.push(item);
      }
      ctrl.month = ctrl.months[ctrl.months.length - 1];
    }
    function prepareReportTraffic() {
      ctrl.traffic = {};
      ctrl.traffic.labels = 1;

    }

    /**
     * HANDLES
     */
    ctrl.handleChangeYear = handleChangeYear;
    function handleChangeYear() {
      ctrl.months = handleGetMonthsOfYear(ctrl.year);
      ctrl.month = ctrl.months[ctrl.months.length - 1];
      handleChangeMonth();
    }
    ctrl.handleChangeMonth = handleChangeMonth;
    function handleChangeMonth() {
      ctrl.dates = handleGetDatesOfMonth(ctrl.year, ctrl.month);
      ctrl.date = ctrl.dates[ctrl.dates.length - 1];
    }
    ctrl.handleChangeDate = handleChangeDate;
    function handleChangeDate() {
    }

    function handleGetMonthsOfYear(year) {
      var mmYear = moment().utc().year(year);
      var startMonth = mmYear.clone().startOf('year');
      var endMonth = mmYear.clone().endOf('year').subtract(1, 'days');
      var durration = endMonth.diff(startMonth, 'months');
      var months = [];
      for (var index = 0; index <= durration; index++) {
        var item = startMonth.clone().add(index, 'months').month() + 1;
        months.push(item);
      }
      mmYear = undefined;
      startMonth = undefined;
      endMonth = undefined;
      return months;
    }

    function handleGetDatesOfMonth(year, month) {
      var mmMonth = moment().utc().year(year).month(month);
      var startDate = mmMonth.clone().startOf('month');
      var endDate = mmMonth.clone().endOf('month').subtract(1, 'hour');
      var duration = endDate.diff(startDate, 'days');
      var dates = [];
      for (var index = 0; index <= duration; index++) {
        var item = startDate.clone().add(index, 'day').date();
        dates.push(item);
      }
      mmMonth = undefined;
      startDate = undefined;
      endDate = undefined;
      return dates;
    }
  }
})();
<|endoftext|>


<commit_before>(function () {
  'use strict';

  angular
    .module('search')
    .controller('SearchMapController', SearchMapController);

  /* @ngInject */
  function SearchMapController($scope, $stateParams, $timeout, $analytics, $window, OffersService, Authentication, leafletData, messageCenterService, MapLayersFactory, MapMarkersFactory, SearchMapService, FiltersService) {

    // `search-map-canvas` is id of <leaflet> element
    var mapId = 'search-map-canvas';

    // Prefix for Leaflet events
    // @link https://github.com/angular-ui/ui-leaflet/commit/d22b3f0
    var listenerPrefix = 'leafletDirectiveMap.' + mapId;

    // ViewModel
    var vm = this;

    // Exposed to the view
    vm.pruneCluster = new PruneClusterForLeaflet(60, 60);
    vm.mapLayerstyle = 'street';
    vm.notFound = false;
    vm.mapCenter = false;
    vm.currentSelection = MapMarkersFactory.getOfferCircle({
      layer: 'selectedOffers'
    });

    vm.mapMinimumZoom = 4;
    vm.mapBounds = {};
    vm.mapLayers = {
      baselayers: MapLayersFactory.getLayers({ streets: true, satellite: true, outdoors: true }),
      overlays: {
        selectedOffers: {
          name: 'Selected hosts',
          type: 'group',
          visible: false
        }
      }
    };
    vm.mapDefaults = {
      attributionControl: false, // Adding this manually below
      keyboard: true,
      worldCopyJump: true,
      zoomControlPosition: 'bottomright',
      controls: {
        scale: true,
        layers: {
          visible: true,
          position: 'bottomleft',
          collapsed: true
        }
      }
    };
    vm.mapPaths = {
      selected: vm.currentSelection
    };
    /**
     * Catch map events:
     * click, dblclick, mousedown, mouseup, mouseover, mouseout, mousemove, contextmenu, focus, blur,
     * preclick, load, unload, viewreset, movestart, move, moveend, dragstart, drag, dragend, zoomstart,
     * zoomend, zoomlevelschange, resize, autopanstart, layeradd, layerremove, baselayerchange, overlayadd,
     * overlayremove, locationfound, locationerror, popupopen, popupclose
     */
    vm.mapEvents = {
      map: {
        enable: ['click', 'moveend', 'load', 'baselayerchange'],
        logic: 'emit'
      }
    };
    vm.mapLastBounds = {
      northEastLng: 0,
      northEastLat: 0,
      southWestLng: 0,
      southWestLat: 0
    };

    // Init
    activate();

    /**
     * Initialize controller
     */
    function activate() {

      // Set map's initial location
      SearchMapService.getMapCenter().then(function(mapCenter) {
        vm.mapCenter = mapCenter;
      });

      // Wait for Leaflet object
      leafletData.getMap(mapId).then(function(map) {

        // Add attribution controller
        map.addControl(L.control.attribution({
          position: 'bottomright',
          prefix: ''
        }));

        // Set active area to accommodate sidebar
        // @link https://github.com/Mappy/Leaflet-active-area
        map.setActiveArea('search-map-active-area');
      });

      // Set Leaflet listeners
      $scope.$on(listenerPrefix + '.baselayerchange', onBaseLayerChange);
      $scope.$on(listenerPrefix + '.load', onLeafletLoad);
      $scope.$on(listenerPrefix + '.moveend', onLeafletMoveEnd);
      $scope.$on(listenerPrefix + '.click', closeOffer);

      // If offer gets closed elsewhere
      $scope.$on('search.closeOffer', function() {
        vm.mapLayers.overlays.selectedOffers.visible = false;
      });

      // Listen to new map location values from other controllers
      $scope.$on('search.mapCenter', function(event, mapCenter) {
        vm.mapCenter = mapCenter;
      });
      $scope.$on('search.mapBounds', function(event, mapBounds) {
        vm.mapBounds = mapBounds;
      });

      // Listen to other controllers
      $scope.$on('search.resetMarkers', resetMarkers);

      // Setting up the marker and click event
      vm.pruneCluster.PrepareLeafletMarker = function(leafletMarker, data) {

        // Set offer icon if not set yet
        if (!leafletMarker.options.iconSet) {
          leafletMarker.setIcon(data.icon);
          leafletMarker.options.iconSet = true;
        }

        // Handle click events
        if (!leafletMarker.listens('click')) {
          leafletMarker.on('click', function($event) {

            $scope.$emit('search.loadingOffer');

            // Load offer details
            OffersService
              .get({ offerId: data.offerId })
              .$promise
              .then(function(offer) {
                previewOffer(offer, false, $event);
              })
              .catch(function() {
                closeOffer();
                messageCenterService.add('danger', 'Something went wrong. Make sure you are connected to internet and try again. ');
              });
          });
        }
      };

      // Initializing either location search or offer
      // Center map to the offer, if there is one
      if ($stateParams.offer && $scope.$parent.search.offer) {
        $scope.$parent.search.offer
          .$promise
          .then(function(offer) {
            previewOffer(offer, true);
          });
      }

    }

    /**
     * Open hosting offer
     */
    function previewOffer(offer, reCenterMap, $event) {
      if (offer.location) {
        // Let parent controller handle setting this to scope
        $scope.$emit('search.previewOffer', offer);

        // Position circle
        // If (click) event was passed to us, use coordinates from that.
        // This is because on map the dot is always at `fuzzyLocation`, but for
        // `offer` we have real location when user is owner of that `offer`.
        // Otherwise circle would be off for user's own markers.
        vm.currentSelection.latlngs = $event && $event.latlng ? $event.latlng : offer.location;

        // Make circle visible
        vm.mapLayers.overlays.selectedOffers.visible = true;

        // Re-position map
        if (reCenterMap) {
          vm.mapCenter = {
            // See above explanation for using `$event` coordinates
            lat: $event && $event.latlng ? $event.latlng.lat : offer.location[0],
            lng: $event && $event.latlng ? $event.latlng.lng : offer.location[1],
            zoom: 13
          };
        }
        $analytics.eventTrack('offer.preview', {
          category: 'search.map',
          label: 'Preview offer'
        });
      }
    }

    /**
     * Close hosting offer
     */
    function closeOffer() {
      vm.mapLayers.overlays.selectedOffers.visible = false;
      $scope.$emit('search.closeOffer');
    }

    /**
     * Force refresh markers on map
     */
    function resetMarkers() {
      getMarkers(true);
    }

    /**
     * Load markers to the current bounding box
     */
    function getMarkers(forcedRefresh) {

      // Don't proceed if:
      // - Map does not have bounds set (typically at map init these might be missing for some milliseconds)
      // - If user isn't public(confirmed) yet - no need to hit API just to get 401
      if (!vm.mapBounds.northEast || !Authentication.user.public) return;

      // Don't do anything on too big zoom levels
      if (vm.mapCenter.zoom <= vm.mapMinimumZoom) {
        return;
      }

      // If we get out of the boundig box of the last api query we have to call the API for the new markers
      // Note also `forcedRefresh`, in which case previous bounding box is ignored
      if (forcedRefresh ||
          vm.mapBounds.northEast.lng > vm.mapLastBounds.northEastLng ||
          vm.mapBounds.northEast.lat > vm.mapLastBounds.northEastLat ||
          vm.mapBounds.southWest.lng < vm.mapLastBounds.southWestLng ||
          vm.mapBounds.southWest.lat < vm.mapLastBounds.southWestLat) {

        // We add a margin to the boundings depending on the zoom level
        var boundingDelta = 10 / vm.mapCenter.zoom;

        // Saving the current bounding box amd zoom
        vm.mapLastBounds = {
          northEastLng: vm.mapBounds.northEast.lng + boundingDelta,
          northEastLat: vm.mapBounds.northEast.lat + boundingDelta,
          southWestLng: vm.mapBounds.southWest.lng - boundingDelta,
          southWestLat: vm.mapBounds.southWest.lat - boundingDelta
        };

        vm.lastZoom = vm.mapCenter.zoom;

        // API Call
        // @TODO: cancel any pending queries:
        // @link https://code.angularjs.org/1.5.11/docs/api/ngResource/service/$resource#cancelling-requests
        OffersService.query({
          northEastLng: vm.mapLastBounds.northEastLng,
          northEastLat: vm.mapLastBounds.northEastLat,
          southWestLng: vm.mapLastBounds.southWestLng,
          southWestLat: vm.mapLastBounds.southWestLat,
          filters: FiltersService.get()
        }, function(offers) {

          // Remove last markers
          // eslint-disable-next-line new-cap
          vm.pruneCluster.RemoveMarkers();

          // Let's go through those markers
          // This loop might look weird but it's actually speed optimized :P
          for (var i = -1, len = offers.length; ++i < len;) {
            var marker = new PruneCluster.Marker(
              offers[i].location[0],
              offers[i].location[1]
            );

            marker.data.icon = MapMarkersFactory.getIcon(offers[i]);
            marker.data.offerId = offers[i]._id;

            // Register markers
            // eslint-disable-next-line new-cap
            vm.pruneCluster.RegisterMarker(marker);
          }

          // Update markers
          // eslint-disable-next-line new-cap
          vm.pruneCluster.ProcessView();
        }, function() {
          messageCenterService.add('danger', 'Sorry, something went wrong. Please try again.');
        });
      }
    }

    /**
     * When Leaflet map has loaded
     */
    function onLeafletLoad() {
      leafletData.getMap(mapId).then(function(map) {
        map.addLayer(vm.pruneCluster);
      });

      // If the zoom is big enough we wait for the map to be loaded with timeout and we get the markers
      if (vm.mapCenter.zoom > vm.mapMinimumZoom) {
        var loadMarkers = function() {
          if (angular.isDefined(vm.mapBounds.northEast)) {
            getMarkers();
          } else {
            // $timeout does $apply for us
            $timeout(loadMarkers, 10);
          }
        };
        // $timeout does $apply for us
        $timeout(loadMarkers, 10);
      }
    }

    /**
     * When moving the map has ended. Fires also on zoom changes.
     */
    function onLeafletMoveEnd() {
      if (vm.mapCenter.zoom > vm.mapMinimumZoom) {
        getMarkers();
      } else {
        // Otherwise hide the markers...

        /* eslint-disable new-cap */
        vm.pruneCluster.RemoveMarkers();
        vm.pruneCluster.ProcessView();
        /* eslint-enable new-cap */

        vm.mapLastBounds = {
          northEastLng: 0,
          northEastLat: 0,
          southWestLng: 0,
          southWestLat: 0
        };
      }

      SearchMapService.cacheMapCenter(vm.mapCenter);
    }

    /**
     * When Leaflet base layer changes
     */
    function onBaseLayerChange(event, layer) {
      $analytics.eventTrack('baselayerchange', {
        category: 'search.map',
        label: layer.leafletEvent.name
      });
      // Determine currently selected baselayer style 'TRStyle' has to be
      // set when defining layers. Possible values are: street, satellite
      // Defaults to street
      $timeout(function() {
        vm.mapLayerstyle = (layer.leafletEvent.layer.options.TRStyle) ? layer.leafletEvent.layer.options.TRStyle : 'streets';
      });
    }

  }

}());
<commit_msg>Set URL history state when opening offers on map<commit_after>(function () {
  'use strict';

  angular
    .module('search')
    .controller('SearchMapController', SearchMapController);

  /* @ngInject */
  function SearchMapController($scope, $state, $stateParams, $timeout, $analytics, $window, OffersService, Authentication, leafletData, messageCenterService, MapLayersFactory, MapMarkersFactory, SearchMapService, FiltersService) {

    // `search-map-canvas` is id of <leaflet> element
    var mapId = 'search-map-canvas';

    // Prefix for Leaflet events
    // @link https://github.com/angular-ui/ui-leaflet/commit/d22b3f0
    var listenerPrefix = 'leafletDirectiveMap.' + mapId;

    // ViewModel
    var vm = this;

    // Exposed to the view
    vm.pruneCluster = new PruneClusterForLeaflet(60, 60);
    vm.mapLayerstyle = 'street';
    vm.notFound = false;
    vm.mapCenter = false;
    vm.currentSelection = MapMarkersFactory.getOfferCircle({
      layer: 'selectedOffers'
    });

    vm.mapMinimumZoom = 4;
    vm.mapBounds = {};
    vm.mapLayers = {
      baselayers: MapLayersFactory.getLayers({ streets: true, satellite: true, outdoors: true }),
      overlays: {
        selectedOffers: {
          name: 'Selected hosts',
          type: 'group',
          visible: false
        }
      }
    };
    vm.mapDefaults = {
      attributionControl: false, // Adding this manually below
      keyboard: true,
      worldCopyJump: true,
      zoomControlPosition: 'bottomright',
      controls: {
        scale: true,
        layers: {
          visible: true,
          position: 'bottomleft',
          collapsed: true
        }
      }
    };
    vm.mapPaths = {
      selected: vm.currentSelection
    };
    /**
     * Catch map events:
     * click, dblclick, mousedown, mouseup, mouseover, mouseout, mousemove, contextmenu, focus, blur,
     * preclick, load, unload, viewreset, movestart, move, moveend, dragstart, drag, dragend, zoomstart,
     * zoomend, zoomlevelschange, resize, autopanstart, layeradd, layerremove, baselayerchange, overlayadd,
     * overlayremove, locationfound, locationerror, popupopen, popupclose
     */
    vm.mapEvents = {
      map: {
        enable: ['click', 'moveend', 'load', 'baselayerchange'],
        logic: 'emit'
      }
    };
    vm.mapLastBounds = {
      northEastLng: 0,
      northEastLat: 0,
      southWestLng: 0,
      southWestLat: 0
    };

    // Init
    activate();

    /**
     * Initialize controller
     */
    function activate() {

      // Set map's initial location
      SearchMapService.getMapCenter().then(function(mapCenter) {
        vm.mapCenter = mapCenter;
      });

      // Wait for Leaflet object
      leafletData.getMap(mapId).then(function(map) {

        // Add attribution controller
        map.addControl(L.control.attribution({
          position: 'bottomright',
          prefix: ''
        }));

        // Set active area to accommodate sidebar
        // @link https://github.com/Mappy/Leaflet-active-area
        map.setActiveArea('search-map-active-area');
      });

      // Set Leaflet listeners
      $scope.$on(listenerPrefix + '.baselayerchange', onBaseLayerChange);
      $scope.$on(listenerPrefix + '.load', onLeafletLoad);
      $scope.$on(listenerPrefix + '.moveend', onLeafletMoveEnd);
      $scope.$on(listenerPrefix + '.click', closeOffer);

      // If offer gets closed elsewhere
      $scope.$on('search.closeOffer', function() {
        vm.mapLayers.overlays.selectedOffers.visible = false;

        // Set history state + URL without reloading the view
        setOfferUrl('');
      });

      // Listen to new map location values from other controllers
      $scope.$on('search.mapCenter', function(event, mapCenter) {
        vm.mapCenter = mapCenter;
      });
      $scope.$on('search.mapBounds', function(event, mapBounds) {
        vm.mapBounds = mapBounds;
      });

      // Listen to other controllers
      $scope.$on('search.resetMarkers', resetMarkers);

      // Setting up the marker and click event
      vm.pruneCluster.PrepareLeafletMarker = function(leafletMarker, data) {

        // Set offer icon if not set yet
        if (!leafletMarker.options.iconSet) {
          leafletMarker.setIcon(data.icon);
          leafletMarker.options.iconSet = true;
        }

        // Handle click events
        if (!leafletMarker.listens('click')) {
          leafletMarker.on('click', function($event) {

            $scope.$emit('search.loadingOffer');

            // Load offer details
            OffersService
              .get({ offerId: data.offerId })
              .$promise
              .then(function(offer) {
                previewOffer(offer, false, $event);
              })
              .catch(function() {
                closeOffer();
                messageCenterService.add('danger', 'Something went wrong. Make sure you are connected to internet and try again. ');
              });
          });
        }
      };

      // Initializing either location search or offer
      // Center map to the offer, if there is one
      if ($stateParams.offer && $scope.$parent.search.offer) {
        $scope.$parent.search.offer
          .$promise
          .then(function(offer) {
            previewOffer(offer, true);
          });
      }

    }

    /**
     * Open hosting offer
     */
    function previewOffer(offer, reCenterMap, $event) {
      if (offer.location) {
        // Let parent controller handle setting this to scope
        $scope.$emit('search.previewOffer', offer);

        // Set history state + URL without reloading the view
        setOfferUrl(offer._id);

        // Position circle
        // If (click) event was passed to us, use coordinates from that.
        // This is because on map the dot is always at `fuzzyLocation`, but for
        // `offer` we have real location when user is owner of that `offer`.
        // Otherwise circle would be off for user's own markers.
        vm.currentSelection.latlngs = $event && $event.latlng ? $event.latlng : offer.location;

        // Make circle visible
        vm.mapLayers.overlays.selectedOffers.visible = true;

        // Re-position map
        if (reCenterMap) {
          vm.mapCenter = {
            // See above explanation for using `$event` coordinates
            lat: $event && $event.latlng ? $event.latlng.lat : offer.location[0],
            lng: $event && $event.latlng ? $event.latlng.lng : offer.location[1],
            zoom: 13
          };
        }
        $analytics.eventTrack('offer.preview', {
          category: 'search.map',
          label: 'Preview offer'
        });
      }
    }

    /**
     * Close hosting offer
     */
    function closeOffer() {
      vm.mapLayers.overlays.selectedOffers.visible = false;
      $scope.$emit('search.closeOffer');
    }

    /**
     * Set URL history state without reloading the page
     */
    function setOfferUrl(offerId) {
      $state.go(
        'search.map',
        { offer: offerId || '' },
        {
          location: true, // will update the url in the location bar,
          inherit: true, // will inherit url parameters from current url.
          notify: false, // will not broadcast $stateChangeStart and $stateChangeSuccess events.
          reload: false // will not force transition even if no state or params have changed
        }
      );
    }

    /**
     * Force refresh markers on map
     */
    function resetMarkers() {
      getMarkers(true);
    }

    /**
     * Load markers to the current bounding box
     */
    function getMarkers(forcedRefresh) {

      // Don't proceed if:
      // - Map does not have bounds set (typically at map init these might be missing for some milliseconds)
      // - If user isn't public(confirmed) yet - no need to hit API just to get 401
      if (!vm.mapBounds.northEast || !Authentication.user.public) return;

      // Don't do anything on too big zoom levels
      if (vm.mapCenter.zoom <= vm.mapMinimumZoom) {
        return;
      }

      // If we get out of the boundig box of the last api query we have to call the API for the new markers
      // Note also `forcedRefresh`, in which case previous bounding box is ignored
      if (forcedRefresh ||
          vm.mapBounds.northEast.lng > vm.mapLastBounds.northEastLng ||
          vm.mapBounds.northEast.lat > vm.mapLastBounds.northEastLat ||
          vm.mapBounds.southWest.lng < vm.mapLastBounds.southWestLng ||
          vm.mapBounds.southWest.lat < vm.mapLastBounds.southWestLat) {

        // We add a margin to the boundings depending on the zoom level
        var boundingDelta = 10 / vm.mapCenter.zoom;

        // Saving the current bounding box amd zoom
        vm.mapLastBounds = {
          northEastLng: vm.mapBounds.northEast.lng + boundingDelta,
          northEastLat: vm.mapBounds.northEast.lat + boundingDelta,
          southWestLng: vm.mapBounds.southWest.lng - boundingDelta,
          southWestLat: vm.mapBounds.southWest.lat - boundingDelta
        };

        vm.lastZoom = vm.mapCenter.zoom;

        // API Call
        // @TODO: cancel any pending queries:
        // @link https://code.angularjs.org/1.5.11/docs/api/ngResource/service/$resource#cancelling-requests
        OffersService.query({
          northEastLng: vm.mapLastBounds.northEastLng,
          northEastLat: vm.mapLastBounds.northEastLat,
          southWestLng: vm.mapLastBounds.southWestLng,
          southWestLat: vm.mapLastBounds.southWestLat,
          filters: FiltersService.get()
        }, function(offers) {

          // Remove last markers
          // eslint-disable-next-line new-cap
          vm.pruneCluster.RemoveMarkers();

          // Let's go through those markers
          // This loop might look weird but it's actually speed optimized :P
          for (var i = -1, len = offers.length; ++i < len;) {
            var marker = new PruneCluster.Marker(
              offers[i].location[0],
              offers[i].location[1]
            );

            marker.data.icon = MapMarkersFactory.getIcon(offers[i]);
            marker.data.offerId = offers[i]._id;

            // Register markers
            // eslint-disable-next-line new-cap
            vm.pruneCluster.RegisterMarker(marker);
          }

          // Update markers
          // eslint-disable-next-line new-cap
          vm.pruneCluster.ProcessView();
        }, function() {
          messageCenterService.add('danger', 'Sorry, something went wrong. Please try again.');
        });
      }
    }

    /**
     * When Leaflet map has loaded
     */
    function onLeafletLoad() {
      leafletData.getMap(mapId).then(function(map) {
        map.addLayer(vm.pruneCluster);
      });

      // If the zoom is big enough we wait for the map to be loaded with timeout and we get the markers
      if (vm.mapCenter.zoom > vm.mapMinimumZoom) {
        var loadMarkers = function() {
          if (angular.isDefined(vm.mapBounds.northEast)) {
            getMarkers();
          } else {
            // $timeout does $apply for us
            $timeout(loadMarkers, 10);
          }
        };
        // $timeout does $apply for us
        $timeout(loadMarkers, 10);
      }
    }

    /**
     * When moving the map has ended. Fires also on zoom changes.
     */
    function onLeafletMoveEnd() {
      if (vm.mapCenter.zoom > vm.mapMinimumZoom) {
        getMarkers();
      } else {
        // Otherwise hide the markers...

        /* eslint-disable new-cap */
        vm.pruneCluster.RemoveMarkers();
        vm.pruneCluster.ProcessView();
        /* eslint-enable new-cap */

        vm.mapLastBounds = {
          northEastLng: 0,
          northEastLat: 0,
          southWestLng: 0,
          southWestLat: 0
        };
      }

      SearchMapService.cacheMapCenter(vm.mapCenter);
    }

    /**
     * When Leaflet base layer changes
     */
    function onBaseLayerChange(event, layer) {
      $analytics.eventTrack('baselayerchange', {
        category: 'search.map',
        label: layer.leafletEvent.name
      });
      // Determine currently selected baselayer style 'TRStyle' has to be
      // set when defining layers. Possible values are: street, satellite
      // Defaults to street
      $timeout(function() {
        vm.mapLayerstyle = (layer.leafletEvent.layer.options.TRStyle) ? layer.leafletEvent.layer.options.TRStyle : 'streets';
      });
    }

  }

}());
<|endoftext|>