<commit_before>"use strict";

/**
 * @constructor
 * @extends ViewBase
 */
var ProfilerView = function(id, name, container_class, html, default_handler)
{
  var SUCCESS = 0;

  var event_type = ProfilerService.EventType;
  var TYPE_GENERIC = event_type.GENERIC;
  var TYPE_PROCESS = event_type.PROCESS;
  var TYPE_DOCUMENT_PARSING = event_type.DOCUMENT_PARSING;
  var TYPE_CSS_PARSING = event_type.CSS_PARSING;
  var TYPE_SCRIPT_COMPILATION = event_type.SCRIPT_COMPILATION;
  var TYPE_THREAD_EVALUATION = event_type.THREAD_EVALUATION;
  var TYPE_REFLOW = event_type.REFLOW;
  var TYPE_STYLE_RECALCULATION = event_type.STYLE_RECALCULATION;
  var TYPE_CSS_SELECTOR_MATCHING = event_type.CSS_SELECTOR_MATCHING;
  var TYPE_LAYOUT = event_type.LAYOUT;
  var TYPE_PAINT = event_type.PAINT;

  var mode = ProfilerService.Mode;
  var MODE_ALL = mode.ALL;
  var MODE_REDUCE_UNIQUE_TYPES = mode.REDUCE_UNIQUE_TYPES;
  var MODE_REDUCE_UNIQUE_EVENTS = mode.REDUCE_UNIQUE_EVENTS;
  var MODE_REDUCE_ALL = mode.REDUCE_ALL;

  var SESSION_ID = 0;
  var TIMELINE_LIST = 2;
  var TIMELINE_ID = 0;

  var AGGREGATED_EVENTS_WIDTH = parseInt(document.styleSheets.getDeclaration(".profiler-timeline").left) || 0;

  // Parent-children relationships
  this._children = {};
  this._children[TYPE_STYLE_RECALCULATION] = TYPE_CSS_SELECTOR_MATCHING;

  this._default_types = [
    //TYPE_GENERIC,
    //TYPE_PROCESS,
    TYPE_DOCUMENT_PARSING,
    TYPE_CSS_PARSING,
    TYPE_SCRIPT_COMPILATION,
    TYPE_THREAD_EVALUATION,
    TYPE_REFLOW,
    TYPE_STYLE_RECALCULATION,
    //TYPE_STYLE_SELECTOR_MATCHING,
    TYPE_LAYOUT,
    TYPE_PAINT
  ];

  this._timeline_modes = [
    {id: "_timeline_list", mode: MODE_ALL},
    {id: "_aggregated_list", mode: MODE_REDUCE_UNIQUE_TYPES},
    {id: "_reduced_list", mode: MODE_REDUCE_ALL}
  ];

  this._reset = function()
  {
    this._table = null;
    this._current_session_id = null;
    this._current_timeline_id = null;
    this._event_id = null;
    this._event_type = null;
    this._timeline_list = null;
    this._aggregated_list = null;
    this._reduced_list = null;
    this._details_time = 0;
  };

  this._reset_details = function()
  {
    this._table = null;
    this._details_time = 0;
  };

  this._start_profiler = function()
  {
    this._reset();
    this._container.clearAndRender(this._templates.empty(ui_strings.S_PROFILER_PROFILING));
    this._profiler.start_profiler(this._handle_start_profiler.bind(this));
    this._overlay.set_window_id(this._profiler.get_window_id());
  };

  this._handle_start_profiler = function(status, msg)
  {
    if (status === SUCCESS)
    {
      this._old_session_id = this._current_session_id;
      this._current_session_id = msg[SESSION_ID];
    }
    else
    {
      this._show_error_message(msg);
    }
    this._update_record_button_title();
  };

  this._stop_profiler = function()
  {
    this._container.clearAndRender(this._templates.empty(ui_strings.S_PROFILER_CALCULATING));
    var config = {session_id: this._current_session_id};
    this._profiler.stop_profiler(this._handle_stop_profiler.bind(this), config);
  };

  this._handle_stop_profiler = function(status, msg)
  {
    if (status === SUCCESS)
    {
      this._current_timeline_id = msg[TIMELINE_LIST][0][TIMELINE_ID];
      this._timeline_modes.forEach(function(timeline_mode) {
        var config = {
          session_id: this._current_session_id,
          timeline_id: this._current_timeline_id,
          mode: timeline_mode.mode,
          event_type_list: this._default_types,
        };
        this._profiler.get_events(this._handle_timeline_list.bind(this, timeline_mode.id), config);
      }, this);
    }
    else
    {
      this._show_error_message(msg);
    }
    this._update_record_button_title();
  };

  this._handle_timeline_list = function(id, status, msg)
  {
    if (status === SUCCESS)
    {
      this[id] = new cls.Profiler["1.0"].EventList(msg);
      if (this._timeline_list && this._aggregated_list && this._reduced_list)
        this._update_view();
    }
    else
    {
      this._show_error_message(msg);
    }
  };

  this._show_error_message = function(msg)
  {
    this._container.clearAndRender(this._templates.empty(ui_strings.S_PROFILER_PROFILING_FAILED));
    opera.postError(ui_strings.S_DRAGONFLY_INFO_MESSAGE + ": \n" + msg)
  };

  this._update_view = function()
  {
    if (!this._container)
      return;
    var zero_point = this._get_zero_point();
    var has_details_events = this._table && this._table.get_data().length > 0;
    var template = null;
    if (this._timeline_list && this._timeline_list.eventList)
    {
      var width = this._container.clientWidth - AGGREGATED_EVENTS_WIDTH;
      template = this._timeline_list.eventList[0]
               ? this._templates.main(has_details_events,
                                      this._templates.legend(this._aggregated_list),
                                      this._templates.event_list_all(this._timeline_list,
                                                                     this._event_id,
                                                                     width,
                                                                     zero_point),
                                      this._templates.details(this._table),
                                      this._templates.status(this._details_time))
               : this._templates.empty(ui_strings.S_PROFILER_NO_DATA);
    }
    else
    {
      template = this._templates.empty(ui_strings.S_PROFILER_START_MESSAGE);
    }
    this._container.clearAndRender(template);
    this._details_list = this._container.querySelector(".profiler-details-list");
    this._status = this._container.querySelector(".profiler-status");
  };

  this.createView = function(container)
  {
    this._container = container;
    this._update_view();
  };

  this.create_disabled_view = function(container)
  {
    container.clearAndRender(this._templates.disabled_view());
  };

  this.ondestroy = function() {};

  this.focus = function() {};

  this.blur = function() {};

  this.onclick = function() {};

  this.onresize = function()
  {
    if (this._container)
      this._update_view();
  };

  /**
   * Starts the profiler if it's active, or stops it otherwise.
   */
  this._start_stop_profiler = function()
  {
    if (this._profiler.is_active)
      this._stop_profiler();
    else
      this._start_profiler();
  };

  this._get_event_details = function(event, target)
  {
    this._event_id = Number(target.getAttribute("data-event-id")) || null;
    this._event_type = Number(target.getAttribute("data-event-type"));
    this._show_details_list();
  };

  this._show_details_list = function()
  {
    var child_type = this._children[this._event_type];
    if (child_type)
    {
      this._details_list.clearAndRender(this._templates.empty(ui_strings.S_PROFILER_CALCULATING));
      var config = {
        session_id: this._current_session_id,
        timeline_id: this._current_timeline_id,
        mode: MODE_REDUCE_UNIQUE_EVENTS,
        event_id: this._event_id,
        event_type_list: [child_type]
      };
      this._profiler.get_events(this._handle_details_list.bind(this, child_type), config);
    }
    else
    {
      this._reset_details();
      this._details_list.clearAndRender(this._templates.no_events());
      this._details_list.addClass("profiler-no-status");
      this._status.addClass("profiler-no-status");
    }
  };

  this._handle_details_list = function(child_type, status, msg)
  {
    var sortby = this._table ? this._table.sortby : "time";
    var reversed = this._table && this._table.reversed;
    var table_def = this._templates._tabledefs[child_type];
    this._table = new SortableTable(table_def,
                                    null,
                                    table_def.column_order,
                                    sortby,
                                    null,
                                    reversed);
    var data = this._get_top_list_data(new cls.Profiler["1.0"].EventList(msg));
    if (data.length)
    {
      this._table.set_data(data);
      this._details_time = data.reduce(function(prev, curr) {
        return prev + curr.time;
      }, 0);
      this._details_list.clearAndRender(this._templates.details(this._table));
      this._status.clearAndRender(this._templates.status(this._details_time));
      this._details_list.removeClass("profiler-no-status");
      this._status.removeClass("profiler-no-status");
    }
    else
    {
      this._details_list.clearAndRender(this._templates.no_events());
    }
  };

  this._get_top_list_data = function(events)
  {
    var type = events.eventList && events.eventList[0] && events.eventList[0].type;
    switch (type)
    {
    case TYPE_CSS_SELECTOR_MATCHING:
      return events.eventList.map(function(event) {
        return {
          selector: event.cssSelectorMatching.selector,
          time: event.time,
          hits: event.hits
        };
      });
    }
    return [];
  };

  // TODO: this should not be in the view
  /**
   * Get an event based on its ID. If no event has the ID, returns null.
   *
   * @param {Number} id The id of the event to return
   * @return {Event|null} An event in case one was found with ID `id`,
   *         otherwise `null`.
   */
  this._get_event_by_id = function(id)
  {
    var timeline_list = this._timeline_list;
    var events = timeline_list && timeline_list.eventList;
    if (events)
    {
      for (var i = 0, event; event = events[i]; i++)
      {
        if (event.eventID === id)
          return event;
      }
    }
    return null;
  };

  this._reload_window = function(event, target)
  {
    if (this._current_session_id)
      this._profiler.release_session(null, {session_id: this._old_session_id});
    this._reset();
    window.services.scope.enable_profile(window.app.profiles.PROFILER);
  };

  this._update_record_button_title = function()
  {
    window.toolbars[this.id].set_button_title("profiler-start-stop", this._profiler.is_active ?
                                                                     ui_strings.S_BUTTON_STOP_PROFILER :
                                                                     ui_strings.S_BUTTON_START_PROFILER);
  };

  this._get_zero_point = function()
  {
    return this._has_zero_at_first_event
         ? (this._timeline_list &&
            this._timeline_list.eventList &&
            this._timeline_list.eventList[0] &&
            this._timeline_list.eventList[0].interval.start)
         : 0;
  };

  this._on_profile_enabled = function(msg)
  {
    if (msg.profile === window.app.profiles.PROFILER)
    {
      this._has_overlay_service = window.services["overlay"] && window.services["overlay"].is_enabled;
      window.runtimes.reloadWindow();
    }
  };

  this._on_settings_changed = function(msg)
  {
    if (msg.id === this.id && msg.key === "zero-at-first-event")
      this._has_zero_at_first_event = msg.value;
  };

  this._on_settings_initialized = function(msg)
  {
    if (msg.view_id === this.id)
      this._has_zero_at_first_event = msg.settings["zero-at-first-event"];
  };

  this._ontooltip = function(event, target)
  {
    var id = Number(target.get_attr("parent-node-chain", "data-event-id"));
    var timeline_event = this._get_event_by_id(id);
    if (timeline_event)
    {
      this._tooltip.show(this._templates.get_title_all(timeline_event));
    }
  };

  this._show_event_details = function(event, target)
  {
    if (!this._has_overlay_service)
      return;
    var id = Number(target.get_attr("parent-node-chain", "data-event-id"));
    var timeline_event = this._get_event_by_id(id);
    if (timeline_event)
    {
      if (timeline_event.type === TYPE_PAINT)
      {
        var area = timeline_event.paint.area;
        var config = {x: area.x, y: area.y, w: area.w, h:area.h};
        this._overlay.create_overlay(null, config);
      }
    }
  };

  this._hide_event_details = function(event, target)
  {
    if (!this._has_overlay_service)
      return;
    this._overlay.remove_overlay();
  };

  this._init = function(id, name, container_class, html, default_handler)
  {
    View.prototype.init.call(this, id, name, container_class, html, default_handler);
    this.required_services = ["profiler"];
    this._profiler = new ProfilerService();
    this._overlay = new OverlayService();
    this._templates = new ProfilerTemplates();
    this._has_overlay_service = false;
    this._has_zero_at_first_event = false;
    this._old_session_id = null;
    this._reset();

    this._tooltip = Tooltips.register("profiler-event", true, false);
    this._tooltip.ontooltip = this._ontooltip.bind(this);

    this._on_profile_enabled_bound = this._on_profile_enabled.bind(this);
    this._on_settings_changed_bound = this._on_settings_changed.bind(this);
    this._on_settings_initialized_bound = this._on_settings_initialized.bind(this);
    window.messages.addListener("profile-enabled", this._on_profile_enabled_bound);
    window.messages.addListener("setting-changed", this._on_settings_changed_bound);
    window.messages.addListener("settings-initialized", this._on_settings_initialized_bound);

    window.event_handlers.click["profiler-start-stop"] = this._start_stop_profiler.bind(this);
    window.event_handlers.click["profiler-reload-window"] = this._reload_window.bind(this);
    window.event_handlers.mousedown["profiler-event"] = this._get_event_details.bind(this);
    window.event_handlers.mouseover["profiler-event"] = this._show_event_details.bind(this);
    window.event_handlers.mouseout["profiler-event"] = this._hide_event_details.bind(this);
  };

  this._init(id, name, container_class, html, default_handler);
};

ProfilerView.prototype = ViewBase;

ProfilerView.create_ui_widgets = function()
{
  new ToolbarConfig(
    "profiler_all",
    [
      {
        handler: "profiler-start-stop",
        title: ui_strings.S_BUTTON_START_PROFILER
      }
    ]
  );

  new Settings(
    "profiler_all",
    {
      "zero-at-first-event": true
    },
    {
      "zero-at-first-event": ui_strings.S_SWITCH_CHANGE_START_TO_FIRST_EVENT
    }
  );

  new Switches(
    "profiler_all",
    [
      "zero-at-first-event"
    ]
  );
};

<commit_msg>Fix sorting.<commit_after>"use strict";

/**
 * @constructor
 * @extends ViewBase
 */
var ProfilerView = function(id, name, container_class, html, default_handler)
{
  var SUCCESS = 0;

  var event_type = ProfilerService.EventType;
  var TYPE_GENERIC = event_type.GENERIC;
  var TYPE_PROCESS = event_type.PROCESS;
  var TYPE_DOCUMENT_PARSING = event_type.DOCUMENT_PARSING;
  var TYPE_CSS_PARSING = event_type.CSS_PARSING;
  var TYPE_SCRIPT_COMPILATION = event_type.SCRIPT_COMPILATION;
  var TYPE_THREAD_EVALUATION = event_type.THREAD_EVALUATION;
  var TYPE_REFLOW = event_type.REFLOW;
  var TYPE_STYLE_RECALCULATION = event_type.STYLE_RECALCULATION;
  var TYPE_CSS_SELECTOR_MATCHING = event_type.CSS_SELECTOR_MATCHING;
  var TYPE_LAYOUT = event_type.LAYOUT;
  var TYPE_PAINT = event_type.PAINT;

  var mode = ProfilerService.Mode;
  var MODE_ALL = mode.ALL;
  var MODE_REDUCE_UNIQUE_TYPES = mode.REDUCE_UNIQUE_TYPES;
  var MODE_REDUCE_UNIQUE_EVENTS = mode.REDUCE_UNIQUE_EVENTS;
  var MODE_REDUCE_ALL = mode.REDUCE_ALL;

  var SESSION_ID = 0;
  var TIMELINE_LIST = 2;
  var TIMELINE_ID = 0;

  var AGGREGATED_EVENTS_WIDTH = parseInt(document.styleSheets.getDeclaration(".profiler-timeline").left) || 0;

  // Parent-children relationships
  this._children = {};
  this._children[TYPE_STYLE_RECALCULATION] = TYPE_CSS_SELECTOR_MATCHING;

  this._default_types = [
    //TYPE_GENERIC,
    //TYPE_PROCESS,
    TYPE_DOCUMENT_PARSING,
    TYPE_CSS_PARSING,
    TYPE_SCRIPT_COMPILATION,
    TYPE_THREAD_EVALUATION,
    TYPE_REFLOW,
    TYPE_STYLE_RECALCULATION,
    //TYPE_STYLE_SELECTOR_MATCHING,
    TYPE_LAYOUT,
    TYPE_PAINT
  ];

  this._timeline_modes = [
    {id: "_timeline_list", mode: MODE_ALL},
    {id: "_aggregated_list", mode: MODE_REDUCE_UNIQUE_TYPES},
    {id: "_reduced_list", mode: MODE_REDUCE_ALL}
  ];

  this._reset = function()
  {
    this._table = null;
    this._current_session_id = null;
    this._current_timeline_id = null;
    this._event_id = null;
    this._event_type = null;
    this._timeline_list = null;
    this._aggregated_list = null;
    this._reduced_list = null;
    this._details_time = 0;
  };

  this._reset_details = function()
  {
    this._table = null;
    this._details_time = 0;
  };

  this._start_profiler = function()
  {
    this._reset();
    this._container.clearAndRender(this._templates.empty(ui_strings.S_PROFILER_PROFILING));
    this._profiler.start_profiler(this._handle_start_profiler.bind(this));
    this._overlay.set_window_id(this._profiler.get_window_id());
  };

  this._handle_start_profiler = function(status, msg)
  {
    if (status === SUCCESS)
    {
      this._old_session_id = this._current_session_id;
      this._current_session_id = msg[SESSION_ID];
    }
    else
    {
      this._show_error_message(msg);
    }
    this._update_record_button_title();
  };

  this._stop_profiler = function()
  {
    this._container.clearAndRender(this._templates.empty(ui_strings.S_PROFILER_CALCULATING));
    var config = {session_id: this._current_session_id};
    this._profiler.stop_profiler(this._handle_stop_profiler.bind(this), config);
  };

  this._handle_stop_profiler = function(status, msg)
  {
    if (status === SUCCESS)
    {
      this._current_timeline_id = msg[TIMELINE_LIST][0][TIMELINE_ID];
      this._timeline_modes.forEach(function(timeline_mode) {
        var config = {
          session_id: this._current_session_id,
          timeline_id: this._current_timeline_id,
          mode: timeline_mode.mode,
          event_type_list: this._default_types,
        };
        this._profiler.get_events(this._handle_timeline_list.bind(this, timeline_mode.id), config);
      }, this);
    }
    else
    {
      this._show_error_message(msg);
    }
    this._update_record_button_title();
  };

  this._handle_timeline_list = function(id, status, msg)
  {
    if (status === SUCCESS)
    {
      this[id] = new cls.Profiler["1.0"].EventList(msg);
      if (this._timeline_list && this._aggregated_list && this._reduced_list)
        this._update_view();
    }
    else
    {
      this._show_error_message(msg);
    }
  };

  this._show_error_message = function(msg)
  {
    this._container.clearAndRender(this._templates.empty(ui_strings.S_PROFILER_PROFILING_FAILED));
    opera.postError(ui_strings.S_DRAGONFLY_INFO_MESSAGE + ": \n" + msg)
  };

  this._update_view = function()
  {
    if (!this._container)
      return;
    var zero_point = this._get_zero_point();
    var has_details_events = this._table && this._table.get_data().length > 0;
    var template = null;
    if (this._timeline_list && this._timeline_list.eventList)
    {
      var width = this._container.clientWidth - AGGREGATED_EVENTS_WIDTH;
      template = this._timeline_list.eventList[0]
               ? this._templates.main(has_details_events,
                                      this._templates.legend(this._aggregated_list),
                                      this._templates.event_list_all(this._timeline_list,
                                                                     this._event_id,
                                                                     width,
                                                                     zero_point),
                                      this._templates.details(this._table),
                                      this._templates.status(this._details_time))
               : this._templates.empty(ui_strings.S_PROFILER_NO_DATA);
    }
    else
    {
      template = this._templates.empty(ui_strings.S_PROFILER_START_MESSAGE);
    }
    this._container.clearAndRender(template);
    this._details_list = this._container.querySelector(".profiler-details-list");
    this._status = this._container.querySelector(".profiler-status");
  };

  this.createView = function(container)
  {
    this._container = container;
    this._update_view();
  };

  this.create_disabled_view = function(container)
  {
    container.clearAndRender(this._templates.disabled_view());
  };

  this.ondestroy = function() {};

  this.focus = function() {};

  this.blur = function() {};

  this.onclick = function() {};

  this.onresize = function()
  {
    if (this._container)
      this._update_view();
  };

  /**
   * Starts the profiler if it's active, or stops it otherwise.
   */
  this._start_stop_profiler = function()
  {
    if (this._profiler.is_active)
      this._stop_profiler();
    else
      this._start_profiler();
  };

  this._get_event_details = function(event, target)
  {
    this._event_id = Number(target.getAttribute("data-event-id")) || null;
    this._event_type = Number(target.getAttribute("data-event-type"));
    this._show_details_list();
  };

  this._show_details_list = function()
  {
    var child_type = this._children[this._event_type];
    if (child_type)
    {
      this._details_list.clearAndRender(this._templates.empty(ui_strings.S_PROFILER_CALCULATING));
      var config = {
        session_id: this._current_session_id,
        timeline_id: this._current_timeline_id,
        mode: MODE_REDUCE_UNIQUE_EVENTS,
        event_id: this._event_id,
        event_type_list: [child_type]
      };
      this._profiler.get_events(this._handle_details_list.bind(this, child_type), config);
    }
    else
    {
      this._reset_details();
      this._details_list.clearAndRender(this._templates.no_events());
      this._details_list.addClass("profiler-no-status");
      this._status.addClass("profiler-no-status");
    }
  };

  this._handle_details_list = function(child_type, status, msg)
  {
    var sortby = this._table ? this._table.sortby : "time";
    var reversed = this._table ? this._table.reversed : true;
    var table_def = this._templates._tabledefs[child_type];
    this._table = new SortableTable(table_def,
                                    null,
                                    table_def.column_order,
                                    sortby,
                                    null,
                                    reversed);
    var data = this._get_top_list_data(new cls.Profiler["1.0"].EventList(msg));
    if (data.length)
    {
      this._table.set_data(data);
      this._details_time = data.reduce(function(prev, curr) {
        return prev + curr.time;
      }, 0);
      this._details_list.clearAndRender(this._templates.details(this._table));
      this._status.clearAndRender(this._templates.status(this._details_time));
      this._details_list.removeClass("profiler-no-status");
      this._status.removeClass("profiler-no-status");
    }
    else
    {
      this._details_list.clearAndRender(this._templates.no_events());
    }
  };

  this._get_top_list_data = function(events)
  {
    var type = events.eventList && events.eventList[0] && events.eventList[0].type;
    switch (type)
    {
    case TYPE_CSS_SELECTOR_MATCHING:
      return events.eventList.map(function(event) {
        return {
          selector: event.cssSelectorMatching.selector,
          time: event.time,
          hits: event.hits
        };
      });
    }
    return [];
  };

  // TODO: this should not be in the view
  /**
   * Get an event based on its ID. If no event has the ID, returns null.
   *
   * @param {Number} id The id of the event to return
   * @return {Event|null} An event in case one was found with ID `id`,
   *         otherwise `null`.
   */
  this._get_event_by_id = function(id)
  {
    var timeline_list = this._timeline_list;
    var events = timeline_list && timeline_list.eventList;
    if (events)
    {
      for (var i = 0, event; event = events[i]; i++)
      {
        if (event.eventID === id)
          return event;
      }
    }
    return null;
  };

  this._reload_window = function(event, target)
  {
    if (this._current_session_id)
      this._profiler.release_session(null, {session_id: this._old_session_id});
    this._reset();
    window.services.scope.enable_profile(window.app.profiles.PROFILER);
  };

  this._update_record_button_title = function()
  {
    window.toolbars[this.id].set_button_title("profiler-start-stop", this._profiler.is_active ?
                                                                     ui_strings.S_BUTTON_STOP_PROFILER :
                                                                     ui_strings.S_BUTTON_START_PROFILER);
  };

  this._get_zero_point = function()
  {
    return this._has_zero_at_first_event
         ? (this._timeline_list &&
            this._timeline_list.eventList &&
            this._timeline_list.eventList[0] &&
            this._timeline_list.eventList[0].interval.start)
         : 0;
  };

  this._on_profile_enabled = function(msg)
  {
    if (msg.profile === window.app.profiles.PROFILER)
    {
      this._has_overlay_service = window.services["overlay"] && window.services["overlay"].is_enabled;
      window.runtimes.reloadWindow();
    }
  };

  this._on_settings_changed = function(msg)
  {
    if (msg.id === this.id && msg.key === "zero-at-first-event")
      this._has_zero_at_first_event = msg.value;
  };

  this._on_settings_initialized = function(msg)
  {
    if (msg.view_id === this.id)
      this._has_zero_at_first_event = msg.settings["zero-at-first-event"];
  };

  this._ontooltip = function(event, target)
  {
    var id = Number(target.get_attr("parent-node-chain", "data-event-id"));
    var timeline_event = this._get_event_by_id(id);
    if (timeline_event)
    {
      this._tooltip.show(this._templates.get_title_all(timeline_event));
    }
  };

  this._show_event_details = function(event, target)
  {
    if (!this._has_overlay_service)
      return;
    var id = Number(target.get_attr("parent-node-chain", "data-event-id"));
    var timeline_event = this._get_event_by_id(id);
    if (timeline_event)
    {
      if (timeline_event.type === TYPE_PAINT)
      {
        var area = timeline_event.paint.area;
        var config = {x: area.x, y: area.y, w: area.w, h:area.h};
        this._overlay.create_overlay(null, config);
      }
    }
  };

  this._hide_event_details = function(event, target)
  {
    if (!this._has_overlay_service)
      return;
    this._overlay.remove_overlay();
  };

  this._init = function(id, name, container_class, html, default_handler)
  {
    View.prototype.init.call(this, id, name, container_class, html, default_handler);
    this.required_services = ["profiler"];
    this._profiler = new ProfilerService();
    this._overlay = new OverlayService();
    this._templates = new ProfilerTemplates();
    this._has_overlay_service = false;
    this._has_zero_at_first_event = false;
    this._old_session_id = null;
    this._reset();

    this._tooltip = Tooltips.register("profiler-event", true, false);
    this._tooltip.ontooltip = this._ontooltip.bind(this);

    this._on_profile_enabled_bound = this._on_profile_enabled.bind(this);
    this._on_settings_changed_bound = this._on_settings_changed.bind(this);
    this._on_settings_initialized_bound = this._on_settings_initialized.bind(this);
    window.messages.addListener("profile-enabled", this._on_profile_enabled_bound);
    window.messages.addListener("setting-changed", this._on_settings_changed_bound);
    window.messages.addListener("settings-initialized", this._on_settings_initialized_bound);

    window.event_handlers.click["profiler-start-stop"] = this._start_stop_profiler.bind(this);
    window.event_handlers.click["profiler-reload-window"] = this._reload_window.bind(this);
    window.event_handlers.mousedown["profiler-event"] = this._get_event_details.bind(this);
    window.event_handlers.mouseover["profiler-event"] = this._show_event_details.bind(this);
    window.event_handlers.mouseout["profiler-event"] = this._hide_event_details.bind(this);
  };

  this._init(id, name, container_class, html, default_handler);
};

ProfilerView.prototype = ViewBase;

ProfilerView.create_ui_widgets = function()
{
  new ToolbarConfig(
    "profiler_all",
    [
      {
        handler: "profiler-start-stop",
        title: ui_strings.S_BUTTON_START_PROFILER
      }
    ]
  );

  new Settings(
    "profiler_all",
    {
      "zero-at-first-event": true
    },
    {
      "zero-at-first-event": ui_strings.S_SWITCH_CHANGE_START_TO_FIRST_EVENT
    }
  );

  new Switches(
    "profiler_all",
    [
      "zero-at-first-event"
    ]
  );
};

<|endoftext|>


<commit_before>'use strict'

/**
 * Scans all currently visible rooms and records useful information about them.
 */

const RESERVE_AMOUNT = 5000

class EmpireMarket extends kernel.process {
  constructor (...args) {
    super(...args)
    this.priority = PRIORITIES_EMPIRE_MARKET
  }

  main () {
    if (!this.data.sent) {
      this.data.sent = {}
    }

    if (!this.data.lma || Game.time - this.data.lma > TERMINAL_COOLDOWN) {
      this.terminals = _.shuffle(Empire.terminals)
      for (let terminal of this.terminals) {
        this.manageTerminal(terminal)
      }
    }
    if (!this.data.lra || Game.time - this.data.lra >= MARKET_STATS_INTERVAL) {
      this.data.lra = Game.time
      this.recordAverages()
    }
  }

  manageTerminal (terminal) {
    if (terminal.cooldown || terminal.store[RESOURCE_ENERGY] < 1000) {
      return false
    }

    // Check to see if terminal should send energy to other cities.
    if (this.shouldSendEnergy(terminal)) {
      const energyTarget = this.getEnergyTarget()
      if (energyTarget) {
        // Max cost of sending 1 energy is 1, for a total of two energy.
        const amount = Math.min(5000, terminal.store[RESOURCE_ENERGY] / 2)
        terminal.send(RESOURCE_ENERGY, amount, energyTarget, `interempire energy transfer`)
        return
      }
    }

    // Sell any resources in terminal.
    const carrying = Object.keys(terminal.store)
    for (let resource of carrying) {
      if (resource === RESOURCE_ENERGY) {
        continue
      }

      if (terminal.store[resource] < TERMINAL_MIN_SEND) {
        continue
      }

      // Don't send resources that were recently received.
      if (this.data.sent[terminal.room.name] && this.data.sent[terminal.room.name][resource]) {
        if (Game.time - this.data.sent[terminal.room.name][resource] < 5000) {
          continue
        }
      }

      const resourceType = Mineral.getResourceType(resource)
      if (resourceType === 'tier1' && resourceType === 'tier2') {
        continue
      }

      let useableAmount = terminal.store[resource]

      // Is it needed by storage?
      const storageNeed = terminal.room.storage.getResourceNeed(resource)
      if (storageNeed > 0) {
        continue
      }

      // Is it needed elsehwere in the empire?
      const target = this.getResourceTarget(resource)
      if (target) {
        if (!this.data.sent[target]) {
          this.data.sent[target] = {}
        }
        this.data.sent[target][resource] = Game.time
        terminal.send(resource, Math.min(1000, useableAmount), target, `interempire resource transfer`)
        return
      }

      // Is there more than `buffer` amount?
      if (useableAmount - RESERVE_AMOUNT > 100) {
        qlib.market.sellImmediately(resource, terminal.room, useableAmount - RESERVE_AMOUNT)
        return
      }
    }
  }

  getResourceTarget (resource) {
    const terminals = _.shuffle(this.terminals)
    for (const terminal of terminals) {
      if (terminal.store.getFreeCapacity() <= 0)
        continue;
      const room = terminal.room
      if (!terminal.store[resource] || terminal.store[resource] < RESERVE_AMOUNT) {
        if (room.storage.getResourceNeed(resource)) {
          return room.name
        }
      }
    }
    return false
  }

  getEnergyTarget () {
    // Set lowest level to 8 so RCL8 rooms only get economy support, not GCL support.
    let lowestLevel = 8
    let lowestCity = false
    let lowestLevelProgress = 0
    // Lazy way to ensure specific rooms aren't favored by order added to empire.
    const terminals = _.shuffle(this.terminals)
    for (const terminal of terminals) {
      if (!terminal.canReceive(RESOURCE_ENERGY)) {
        continue
      }
      if (terminal.room.isEconomyCapable('DUMP_ENERGY')) {
        continue
      }

      // If room *needs* energy supply it.
      if (terminal.room.isEconomyCapable('REQUEST_ENERGY')) {
        return terminal.room.name
      }

      // Built up lowest level room.
      // If two rooms are equal level, pick the one closer to the next level.
      const roomLevel = terminal.room.controller.level
      const roomProgress = terminal.room.controller.progress
      if (roomLevel < lowestLevel || (roomLevel === lowestLevel && roomProgress > lowestLevelProgress)) {
        lowestLevel = terminal.room.controller.level
        lowestLevelProgress = terminal.room.controller.progress
        lowestCity = terminal.room.name
      }
    }
    return lowestCity
  }

  shouldSendEnergy (terminal) {
    if (terminal.store[RESOURCE_ENERGY] < 10000) {
      return false
    }
    if (terminal.room.getRoomSetting('SHARE_ENERGY') && terminal.room.isEconomyCapable('SHARE_ENERGY')) {
      return true
    }
    return terminal.room.isEconomyCapable('DUMP_ENERGY')
  }

  recordAverages () {
    const resources = [
      RESOURCE_POWER,
      RESOURCE_HYDROGEN,
      RESOURCE_OXYGEN,
      RESOURCE_UTRIUM,
      RESOURCE_LEMERGIUM,
      RESOURCE_KEANIUM,
      RESOURCE_ZYNTHIUM,
      RESOURCE_CATALYST,
      RESOURCE_GHODIUM,
      RESOURCE_HYDROXIDE,
      RESOURCE_CATALYZED_UTRIUM_ACID,
      RESOURCE_CATALYZED_UTRIUM_ALKALIDE,
      RESOURCE_CATALYZED_KEANIUM_ACID,
      RESOURCE_CATALYZED_KEANIUM_ALKALIDE,
      RESOURCE_CATALYZED_LEMERGIUM_ACID,
      RESOURCE_CATALYZED_LEMERGIUM_ALKALIDE,
      RESOURCE_CATALYZED_ZYNTHIUM_ACID,
      RESOURCE_CATALYZED_ZYNTHIUM_ALKALIDE,
      RESOURCE_CATALYZED_GHODIUM_ACID,
      RESOURCE_CATALYZED_GHODIUM_ALKALIDE
    ]
    for (let resource of resources) {
      qlib.market.getAveragePrice(resource, ORDER_BUY)
      qlib.market.getAveragePrice(resource, ORDER_SELL)
    }
  }
}

module.exports = EmpireMarket
<commit_msg>Standard<commit_after>'use strict'

/**
 * Scans all currently visible rooms and records useful information about them.
 */

const RESERVE_AMOUNT = 5000

class EmpireMarket extends kernel.process {
  constructor (...args) {
    super(...args)
    this.priority = PRIORITIES_EMPIRE_MARKET
  }

  main () {
    if (!this.data.sent) {
      this.data.sent = {}
    }

    if (!this.data.lma || Game.time - this.data.lma > TERMINAL_COOLDOWN) {
      this.terminals = _.shuffle(Empire.terminals)
      for (let terminal of this.terminals) {
        this.manageTerminal(terminal)
      }
    }
    if (!this.data.lra || Game.time - this.data.lra >= MARKET_STATS_INTERVAL) {
      this.data.lra = Game.time
      this.recordAverages()
    }
  }

  manageTerminal (terminal) {
    if (terminal.cooldown || terminal.store[RESOURCE_ENERGY] < 1000) {
      return false
    }

    // Check to see if terminal should send energy to other cities.
    if (this.shouldSendEnergy(terminal)) {
      const energyTarget = this.getEnergyTarget()
      if (energyTarget) {
        // Max cost of sending 1 energy is 1, for a total of two energy.
        const amount = Math.min(5000, terminal.store[RESOURCE_ENERGY] / 2)
        terminal.send(RESOURCE_ENERGY, amount, energyTarget, `interempire energy transfer`)
        return
      }
    }

    // Sell any resources in terminal.
    const carrying = Object.keys(terminal.store)
    for (let resource of carrying) {
      if (resource === RESOURCE_ENERGY) {
        continue
      }

      if (terminal.store[resource] < TERMINAL_MIN_SEND) {
        continue
      }

      // Don't send resources that were recently received.
      if (this.data.sent[terminal.room.name] && this.data.sent[terminal.room.name][resource]) {
        if (Game.time - this.data.sent[terminal.room.name][resource] < 5000) {
          continue
        }
      }

      const resourceType = Mineral.getResourceType(resource)
      if (resourceType === 'tier1' && resourceType === 'tier2') {
        continue
      }

      let useableAmount = terminal.store[resource]

      // Is it needed by storage?
      const storageNeed = terminal.room.storage.getResourceNeed(resource)
      if (storageNeed > 0) {
        continue
      }

      // Is it needed elsehwere in the empire?
      const target = this.getResourceTarget(resource)
      if (target) {
        if (!this.data.sent[target]) {
          this.data.sent[target] = {}
        }
        this.data.sent[target][resource] = Game.time
        terminal.send(resource, Math.min(1000, useableAmount), target, `interempire resource transfer`)
        return
      }

      // Is there more than `buffer` amount?
      if (useableAmount - RESERVE_AMOUNT > 100) {
        qlib.market.sellImmediately(resource, terminal.room, useableAmount - RESERVE_AMOUNT)
        return
      }
    }
  }

  getResourceTarget (resource) {
    const terminals = _.shuffle(this.terminals)
    for (const terminal of terminals) {
      if (terminal.store.getFreeCapacity() <= 0) {
        continue
      }
      const room = terminal.room
      if (!terminal.store[resource] || terminal.store[resource] < RESERVE_AMOUNT) {
        if (room.storage.getResourceNeed(resource)) {
          return room.name
        }
      }
    }
    return false
  }

  getEnergyTarget () {
    // Set lowest level to 8 so RCL8 rooms only get economy support, not GCL support.
    let lowestLevel = 8
    let lowestCity = false
    let lowestLevelProgress = 0
    // Lazy way to ensure specific rooms aren't favored by order added to empire.
    const terminals = _.shuffle(this.terminals)
    for (const terminal of terminals) {
      if (!terminal.canReceive(RESOURCE_ENERGY)) {
        continue
      }
      if (terminal.room.isEconomyCapable('DUMP_ENERGY')) {
        continue
      }

      // If room *needs* energy supply it.
      if (terminal.room.isEconomyCapable('REQUEST_ENERGY')) {
        return terminal.room.name
      }

      // Built up lowest level room.
      // If two rooms are equal level, pick the one closer to the next level.
      const roomLevel = terminal.room.controller.level
      const roomProgress = terminal.room.controller.progress
      if (roomLevel < lowestLevel || (roomLevel === lowestLevel && roomProgress > lowestLevelProgress)) {
        lowestLevel = terminal.room.controller.level
        lowestLevelProgress = terminal.room.controller.progress
        lowestCity = terminal.room.name
      }
    }
    return lowestCity
  }

  shouldSendEnergy (terminal) {
    if (terminal.store[RESOURCE_ENERGY] < 10000) {
      return false
    }
    if (terminal.room.getRoomSetting('SHARE_ENERGY') && terminal.room.isEconomyCapable('SHARE_ENERGY')) {
      return true
    }
    return terminal.room.isEconomyCapable('DUMP_ENERGY')
  }

  recordAverages () {
    const resources = [
      RESOURCE_POWER,
      RESOURCE_HYDROGEN,
      RESOURCE_OXYGEN,
      RESOURCE_UTRIUM,
      RESOURCE_LEMERGIUM,
      RESOURCE_KEANIUM,
      RESOURCE_ZYNTHIUM,
      RESOURCE_CATALYST,
      RESOURCE_GHODIUM,
      RESOURCE_HYDROXIDE,
      RESOURCE_CATALYZED_UTRIUM_ACID,
      RESOURCE_CATALYZED_UTRIUM_ALKALIDE,
      RESOURCE_CATALYZED_KEANIUM_ACID,
      RESOURCE_CATALYZED_KEANIUM_ALKALIDE,
      RESOURCE_CATALYZED_LEMERGIUM_ACID,
      RESOURCE_CATALYZED_LEMERGIUM_ALKALIDE,
      RESOURCE_CATALYZED_ZYNTHIUM_ACID,
      RESOURCE_CATALYZED_ZYNTHIUM_ALKALIDE,
      RESOURCE_CATALYZED_GHODIUM_ACID,
      RESOURCE_CATALYZED_GHODIUM_ALKALIDE
    ]
    for (let resource of resources) {
      qlib.market.getAveragePrice(resource, ORDER_BUY)
      qlib.market.getAveragePrice(resource, ORDER_SELL)
    }
  }
}

module.exports = EmpireMarket
<|endoftext|>


<commit_before>var map;
var hasInit = false;
var markers;

/**
 * The load article control will load article markers onto the map
 */

function ArticleControl(controlDiv, map) {

  // Set CSS styles for the DIV containing the control
  // Setting padding to 5 px will offset the control
  // from the edge of the map.
  controlDiv.style.padding = '5px';

  // Set CSS for the control border.
  var controlUI = document.createElement('div');
  controlUI.style.backgroundColor = 'white';
  controlUI.style.borderStyle = 'solid';
  controlUI.style.borderWidth = '2px';
  controlUI.style.cursor = 'pointer';
  controlUI.style.textAlign = 'center';
  controlUI.title = 'Load articles when you click the button';
  controlDiv.appendChild(controlUI);

  // Set CSS for the control interior.
  var controlText = document.createElement('div');
  controlText.style.fontFamily = 'Arial,sans-serif';
  controlText.style.fontSize = '12px';
  controlText.style.paddingLeft = '4px';
  controlText.style.paddingRight = '4px';
  controlText.innerHTML = 'Load Articles';
  controlUI.appendChild(controlText);

  // Setup the click event listeners: simply set the map to Chicago.
  google.maps.event.addDomListener(controlUI, 'click', addMarker);
}

function initialize() {
	if (!hasInit) {
		var mapOptions = {
		  center: new google.maps.LatLng(40.714623,-74.006605 ),
		  zoom: 12,
		  mapTypeId: google.maps.MapTypeId.ROADMAP
		};

		map = new google.maps.Map(document.getElementById("map-canvas"),
	    mapOptions);

		hasInit = true;
	}
    //addMarker();
    // Create the DIV to hold the control and call the ArticleControl() constructor
	// passing in this DIV.
	var articleControlDiv = document.createElement('div');
	var articleControl = new ArticleControl(articleControlDiv, map);

	articleControlDiv.index = 1;
	map.controls[google.maps.ControlPosition.TOP_RIGHT].push(articleControlDiv);
}

// Adds marker to specfic lat lng
function addMarker(JSONObj) {
    var infowindow = new google.maps.InfoWindow({
        content: JSONObj.Blurb;
 	});
	var myLatlng = new google.maps.LatLng(JSONObj.lat, JSONObj.lng);
	var marker = new google.maps.Marker({
	  position: myLatlng,
	  map: map,
	  title: JSONObj.headline
	});

  google.maps.event.addListener(marker, 'mouseover', function() {
    infowindow.open(map,marker);
  });

}

// Passes in lat lng from center of map and returns relevant articles. We then build markers on the map.
function loadArticles() {
  //Get lat lng for center
  var currentLatLng = map.getCenter();
  var lat = currentLatLng.lat;
  var lng = currentLatLng.lng;

  //Get region radius
  var bounds = map.getBounds();
  var swPoint = bounds.getSouthWest();
  var proximitymeter = google.maps.geometry.spherical.computeDistanceBetween(swPoint, currentLatLng);
  var proximitymiles = proximitymeter * 0.000621371192;

  //call Gabe's interface to get a bunch of articles

  //loop through relevant articles and get address
  //var JSONObj = gabeFunction(lat, lng, proximitymiles);
  for(var i = 0; i < JSONObj.length; i++) {
    console.log("Plotting headline " + JSONObj[i].headline + "at " + JSONObj[i].lat + " " + JSONObj[i].lng);
    addMarker(JSONObj[i]);
  }
}

google.maps.event.addDomListener(window, 'load', initialize);
<commit_msg>added close on mouseout<commit_after>var map;
var hasInit = false;
var markers;

/**
 * The load article control will load article markers onto the map
 */

function ArticleControl(controlDiv, map) {

  // Set CSS styles for the DIV containing the control
  // Setting padding to 5 px will offset the control
  // from the edge of the map.
  controlDiv.style.padding = '5px';

  // Set CSS for the control border.
  var controlUI = document.createElement('div');
  controlUI.style.backgroundColor = 'white';
  controlUI.style.borderStyle = 'solid';
  controlUI.style.borderWidth = '2px';
  controlUI.style.cursor = 'pointer';
  controlUI.style.textAlign = 'center';
  controlUI.title = 'Load articles when you click the button';
  controlDiv.appendChild(controlUI);

  // Set CSS for the control interior.
  var controlText = document.createElement('div');
  controlText.style.fontFamily = 'Arial,sans-serif';
  controlText.style.fontSize = '12px';
  controlText.style.paddingLeft = '4px';
  controlText.style.paddingRight = '4px';
  controlText.innerHTML = 'Load Articles';
  controlUI.appendChild(controlText);

  // Setup the click event listeners: simply set the map to Chicago.
  google.maps.event.addDomListener(controlUI, 'click', addMarker);
}

function initialize() {
	if (!hasInit) {
		var mapOptions = {
		  center: new google.maps.LatLng(40.714623,-74.006605 ),
		  zoom: 12,
		  mapTypeId: google.maps.MapTypeId.ROADMAP
		};

		map = new google.maps.Map(document.getElementById("map-canvas"),
	    mapOptions);

		hasInit = true;
	}
    //addMarker();
    // Create the DIV to hold the control and call the ArticleControl() constructor
	// passing in this DIV.
	var articleControlDiv = document.createElement('div');
	var articleControl = new ArticleControl(articleControlDiv, map);

	articleControlDiv.index = 1;
	map.controls[google.maps.ControlPosition.TOP_RIGHT].push(articleControlDiv);
}

// Adds marker to specfic lat lng
function addMarker(JSONObj) {
    var infowindow = new google.maps.InfoWindow({
        content: JSONObj.Blurb;
 	});
	var myLatlng = new google.maps.LatLng(JSONObj.lat, JSONObj.lng);
	var marker = new google.maps.Marker({
	  position: myLatlng,
	  map: map,
	  title: JSONObj.headline
	});

  google.maps.event.addListener(marker, 'mouseover', function() {
    infowindow.open(map,marker);
  });
  google.maps.event.addListener(marker, 'mouseout', function() {
    infowindow.close();
  });

}

// Passes in lat lng from center of map and returns relevant articles. We then build markers on the map.
function loadArticles() {
  //Get lat lng for center
  var currentLatLng = map.getCenter();
  var lat = currentLatLng.lat;
  var lng = currentLatLng.lng;

  //Get region radius
  var bounds = map.getBounds();
  var swPoint = bounds.getSouthWest();
  var proximitymeter = google.maps.geometry.spherical.computeDistanceBetween(swPoint, currentLatLng);
  var proximitymiles = proximitymeter * 0.000621371192;

  //call Gabe's interface to get a bunch of articles

  //loop through relevant articles and get address
  //var JSONObj = gabeFunction(lat, lng, proximitymiles);
  for(var i = 0; i < JSONObj.length; i++) {
    console.log("Plotting headline " + JSONObj[i].headline + "at " + JSONObj[i].lat + " " + JSONObj[i].lng);
    addMarker(JSONObj[i]);
  }
}

google.maps.event.addDomListener(window, 'load', initialize);
<|endoftext|>


<commit_before>var map;
var hasInit = false;
var markers;

/**
 * The load article control will load article markers onto the map
 */

function ArticleControl(controlDiv, map) {

  // Set CSS styles for the DIV containing the control
  // Setting padding to 5 px will offset the control
  // from the edge of the map.
  controlDiv.style.padding = '5px';

  // Set CSS for the control border.
  var controlUI = document.createElement('div');
  controlUI.style.backgroundColor = 'white';
  controlUI.style.borderStyle = 'solid';
  controlUI.style.borderWidth = '2px';
  controlUI.style.cursor = 'pointer';
  controlUI.style.textAlign = 'center';
  controlUI.title = 'Load articles when you click the button';
  controlDiv.appendChild(controlUI);

  // Set CSS for the control interior.
  var controlText = document.createElement('div');
  controlText.style.fontFamily = 'Arial,sans-serif';
  controlText.style.fontSize = '12px';
  controlText.style.paddingLeft = '4px';
  controlText.style.paddingRight = '4px';
  controlText.innerHTML = 'Load Articles';
  controlUI.appendChild(controlText);

  // Setup the click event listeners: simply set the map to Chicago.
  google.maps.event.addDomListener(controlUI, 'click', addMarker);
}

function initialize() {
	if (!hasInit) {
		var mapOptions = {
		  center: new google.maps.LatLng(40.714623,-74.006605 ),
		  zoom: 12,
		  mapTypeId: google.maps.MapTypeId.ROADMAP
		};

		map = new google.maps.Map(document.getElementById("map-canvas"),
	    mapOptions);

		hasInit = true;
	}
    //addMarker();
    // Create the DIV to hold the control and call the ArticleControl() constructor
	// passing in this DIV.
	var articleControlDiv = document.createElement('div');
	var articleControl = new ArticleControl(articleControlDiv, map);

	articleControlDiv.index = 1;
	map.controls[google.maps.ControlPosition.TOP_RIGHT].push(articleControlDiv);
}

// Adds marker to specfic lat lng
function addMarker(lat, lng) {
    var infowindow = new google.maps.InfoWindow({
        content: "Hello World"
 	});
	var myLatlng = new google.maps.LatLng(lat, lng);
	var marker = new google.maps.Marker({
	  position: myLatlng,
	  map: map,
	  title: 'Hello World!'
	});
        google.maps.event.addListener(marker, 'mouseover', function() {
                  infowindow.open(map,marker);
                });
    google.maps.event.addListener(marker, 'click', function() {
        infowindow.open(map,marker);
    });
}

// Passes in lat lng from center of map and returns relevant articles. We then build markers on the map.
function loadArticles() {
  var currentLatLng = getCurrentLatLng();
  var currentRadius = getCurrentRadius();
  //call Gabe's interface to get a bunch of articles

  //loop through relevant articles and get address
    //markerLatLng = geoLocate(address);
    //addmarker(markerLatLng[0], markerLatLng[1]);
}

google.maps.event.addDomListener(window, 'load', initialize);
<commit_msg>more stuff<commit_after>var map;
var hasInit = false;
var markers;

/**
 * The load article control will load article markers onto the map
 */

function ArticleControl(controlDiv, map) {

  // Set CSS styles for the DIV containing the control
  // Setting padding to 5 px will offset the control
  // from the edge of the map.
  controlDiv.style.padding = '5px';

  // Set CSS for the control border.
  var controlUI = document.createElement('div');
  controlUI.style.backgroundColor = 'white';
  controlUI.style.borderStyle = 'solid';
  controlUI.style.borderWidth = '2px';
  controlUI.style.cursor = 'pointer';
  controlUI.style.textAlign = 'center';
  controlUI.title = 'Load articles when you click the button';
  controlDiv.appendChild(controlUI);

  // Set CSS for the control interior.
  var controlText = document.createElement('div');
  controlText.style.fontFamily = 'Arial,sans-serif';
  controlText.style.fontSize = '12px';
  controlText.style.paddingLeft = '4px';
  controlText.style.paddingRight = '4px';
  controlText.innerHTML = 'Load Articles';
  controlUI.appendChild(controlText);

  // Setup the click event listeners: simply set the map to Chicago.
  google.maps.event.addDomListener(controlUI, 'click', addMarker);
}

function initialize() {
	if (!hasInit) {
		var mapOptions = {
		  center: new google.maps.LatLng(40.714623,-74.006605 ),
		  zoom: 12,
		  mapTypeId: google.maps.MapTypeId.ROADMAP
		};

		map = new google.maps.Map(document.getElementById("map-canvas"),
	    mapOptions);

		hasInit = true;
	}
    //addMarker();
    // Create the DIV to hold the control and call the ArticleControl() constructor
	// passing in this DIV.
	var articleControlDiv = document.createElement('div');
	var articleControl = new ArticleControl(articleControlDiv, map);

	articleControlDiv.index = 1;
	map.controls[google.maps.ControlPosition.TOP_RIGHT].push(articleControlDiv);
}

// Adds marker to specfic lat lng
function addMarker(JSONObj) {
    var infowindow = new google.maps.InfoWindow({
        content: JSONObj.Blurb;
 	});
	var myLatlng = new google.maps.LatLng(JSONObj.lat, JSONObj.lng);
	var marker = new google.maps.Marker({
	  position: myLatlng,
	  map: map,
	  title: JSONObj.headline
	});

  google.maps.event.addListener(marker, 'mouseover', function() {
    infowindow.open(map,marker);
  });

  google.maps.event.addListener(marker, 'click', function() {
    infowindow.open(map,marker);
  });
}

// Passes in lat lng from center of map and returns relevant articles. We then build markers on the map.
function loadArticles() {
  //Get lat lng for center
  var currentLatLng = map.getCenter();
  var lat = currentLatLng.lat;
  var lng = currentLatLng.lng;

  //Get region radius
  var bounds = map.getBounds();
  var swPoint = bounds.getSouthWest();
  var proximitymeter = google.maps.geometry.spherical.computeDistanceBetween(swPoint, currentLatLng);
  var proximitymiles = proximitymeter * 0.000621371192;

  //call Gabe's interface to get a bunch of articles

  //loop through relevant articles and get address
  //var JSONObj = gabeFunction(lat, lng, proximitymiles);
  for(var i = 0; i < JSONObj.length; i++) {
    console.log("Plotting headline " + JSONObj[i].headline + "at " + JSONObj[i].lat + " " + JSONObj[i].lng);
    addMarker(JSONObj[i]);
  }
}

google.maps.event.addDomListener(window, 'load', initialize);
<|endoftext|>


<commit_before>const _ = require('lodash');
const semver = require('semver');
const debug = require('@tryghost/debug')('error-handler');
const errors = require('@tryghost/errors');
const {prepareStackForUser} = require('@tryghost/errors').utils;
const tpl = require('@tryghost/tpl');

const messages = {
    pageNotFound: 'Page not found',
    resourceNotFound: 'Resource not found',
    methodNotAcceptableVersionAhead: {
        message: 'Request not acceptable for provided Accept-Version header.',
        context: 'Provided client version {acceptVersion} is ahead of current Ghost instance version {ghostVersion}.',
        help: 'Upgrade your Ghost instance.'
    },
    methodNotAcceptableVersionBehind: {
        message: 'Request not acceptable for provided Accept-Version header.',
        context: 'Provided client version {acceptVersion} is outdated and is behind current Ghost version {ghostVersion}.',
        help: 'Upgrade your Ghost API client.'
    },
    actions: {
        images: {
            upload: 'upload image'
        }
    },
    userMessages: {
        BookshelfRelationsError: 'Database error, cannot {action}.',
        InternalServerError: 'Internal server error, cannot {action}.',
        IncorrectUsageError: 'Incorrect usage error, cannot {action}.',
        NotFoundError: 'Resource not found error, cannot {action}.',
        BadRequestError: 'Request not understood error, cannot {action}.',
        UnauthorizedError: 'Authorisation error, cannot {action}.',
        NoPermissionError: 'Permission error, cannot {action}.',
        ValidationError: 'Validation error, cannot {action}.',
        UnsupportedMediaTypeError: 'Unsupported media error, cannot {action}.',
        TooManyRequestsError: 'Too many requests error, cannot {action}.',
        MaintenanceError: 'Server down for maintenance, cannot {action}.',
        MethodNotAllowedError: 'Method not allowed, cannot {action}.',
        RequestEntityTooLargeError: 'Request too large, cannot {action}.',
        TokenRevocationError: 'Token is not available, cannot {action}.',
        VersionMismatchError: 'Version mismatch error, cannot {action}.',
        DataExportError: 'Error exporting content.',
        DataImportError: 'Duplicated entry, cannot save {action}.',
        DatabaseVersionError: 'Database version compatibility error, cannot {action}.',
        EmailError: 'Error sending email!',
        ThemeValidationError: 'Theme validation error, cannot {action}.',
        HostLimitError: 'Host Limit error, cannot {action}.',
        DisabledFeatureError: 'Theme validation error, the {{{helperName}}} helper is not available. Cannot {action}.',
        UpdateCollisionError: 'Saving failed! Someone else is editing this post.'
    },
    UnknownError: 'Unknown error - {name}, cannot {action}.'
};

/**
 * Get an error ready to be shown the the user
 */
module.exports.prepareError = (err, req, res, next) => {
    debug(err);

    if (Array.isArray(err)) {
        err = err[0];
    }

    if (!errors.utils.isGhostError(err)) {
        // We need a special case for 404 errors
        if (err.statusCode && err.statusCode === 404) {
            err = new errors.NotFoundError({
                err: err
            });
        } else if (err.stack.match(/node_modules\/handlebars\//)) {
            // Temporary handling of theme errors from handlebars
            // @TODO remove this when #10496 is solved properly
            err = new errors.IncorrectUsageError({
                err: err,
                message: err.message,
                statusCode: err.statusCode
            });
        } else {
            err = new errors.InternalServerError({
                err: err,
                message: err.message,
                statusCode: err.statusCode
            });
        }
    }

    // used for express logging middleware see core/server/app.js
    req.err = err;

    // alternative for res.status();
    res.statusCode = err.statusCode;

    // never cache errors
    res.set({
        'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'
    });

    next(err);
};

module.exports.prepareStack = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    const clonedError = prepareStackForUser(err);

    next(clonedError);
};

const jsonErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    res.json({
        errors: [{
            message: err.message,
            context: err.context,
            help: err.help,
            errorType: err.errorType,
            errorDetails: err.errorDetails,
            ghostErrorCode: err.ghostErrorCode
        }]
    });
};

const jsonErrorRendererV2 = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    const userError = prepareUserMessage(err, req);

    res.json({
        errors: [{
            message: userError.message || null,
            context: userError.context || null,
            type: err.errorType || null,
            details: err.errorDetails || null,
            property: err.property || null,
            help: err.help || null,
            code: err.code || null,
            id: err.id || null,
            ghostErrorCode: err.ghostErrorCode || null
        }]
    });
};

const prepareUserMessage = (err, req) => {
    const userError = {
        message: err.message,
        context: err.context
    };

    const docName = _.get(req, 'frameOptions.docName');
    const method = _.get(req, 'frameOptions.method');

    if (docName && method) {
        let action;

        const actionMap = {
            browse: 'list',
            read: 'read',
            add: 'save',
            edit: 'edit',
            destroy: 'delete'
        };

        if (_.get(messages.actions, [docName, method])) {
            action = tpl(messages.actions[docName][method]);
        } else if (Object.keys(actionMap).includes(method)) {
            let resource = docName;

            if (method !== 'browse') {
                resource = resource.replace(/s$/, '');
            }

            action = `${actionMap[method]} ${resource}`;
        }

        if (action) {
            if (err.context) {
                userError.context = `${err.message} ${err.context}`;
            } else {
                userError.context = err.message;
            }

            if (_.get(messages.userMessages, err.name)) {
                userError.message = tpl(messages.userMessages[err.name], {action: action});
            } else {
                userError.message = tpl(messages.UnknownError, {action, name: err.name});
            }
        }
    }

    return userError;
};

module.exports.resourceNotFound = (req, res, next) => {
    if (req && req.headers && req.headers['accept-version']
        && res.locals && res.locals.safeVersion
        && semver.compare(semver.coerce(req.headers['accept-version']), semver.coerce(res.locals.safeVersion)) !== 0) {
        const versionComparison = semver.compare(
            semver.coerce(req.headers['accept-version']),
            semver.coerce(res.locals.safeVersion)
        );

        let notAcceptableError;
        if (versionComparison === 1) {
            notAcceptableError = new errors.RequestNotAcceptableError({
                message: tpl(
                    messages.methodNotAcceptableVersionAhead.message
                ),
                context: tpl(messages.methodNotAcceptableVersionAhead.context, {
                    acceptVersion: req.headers['accept-version'],
                    ghostVersion: `v${res.locals.safeVersion}`
                }),
                help: tpl(messages.methodNotAcceptableVersionAhead.help),
                code: 'UPDATE_GHOST'
            });
        } else {
            notAcceptableError = new errors.RequestNotAcceptableError({
                message: tpl(
                    messages.methodNotAcceptableVersionBehind.message
                ),
                context: tpl(messages.methodNotAcceptableVersionBehind.context, {
                    acceptVersion: req.headers['accept-version'],
                    ghostVersion: `v${res.locals.safeVersion}`
                }),
                help: tpl(messages.methodNotAcceptableVersionBehind.help),
                code: 'UPDATE_CLIENT'
            });
        }

        next(notAcceptableError);
    } else {
        next(new errors.NotFoundError({message: tpl(messages.resourceNotFound)}));
    }
};

module.exports.pageNotFound = (req, res, next) => {
    next(new errors.NotFoundError({message: tpl(messages.pageNotFound)}));
};

/**
 * @deprecated: not used as of Ghost v5 and should be removed in a major bump
 */
module.exports.handleJSONResponse = sentry => [
    // Make sure the error can be served
    module.exports.prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Format the stack for the user
    module.exports.prepareStack,
    // Render the error using JSON format
    jsonErrorRenderer
];

/**
 * @deprecated with above
 * @TODO: rename this without the v2 in a major bump
 */
module.exports.handleJSONResponseV2 = sentry => [
    // Make sure the error can be served
    module.exports.prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Format the stack for the user
    module.exports.prepareStack,
    // Render the error using JSON format
    jsonErrorRendererV2
];

module.exports.handleHTMLResponse = sentry => [
    // Make sure the error can be served
    module.exports.prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Format the stack for the user
    module.exports.prepareStack
];
<commit_msg>Added additional ghostErrorCode note for the major<commit_after>const _ = require('lodash');
const semver = require('semver');
const debug = require('@tryghost/debug')('error-handler');
const errors = require('@tryghost/errors');
const {prepareStackForUser} = require('@tryghost/errors').utils;
const tpl = require('@tryghost/tpl');

const messages = {
    pageNotFound: 'Page not found',
    resourceNotFound: 'Resource not found',
    methodNotAcceptableVersionAhead: {
        message: 'Request not acceptable for provided Accept-Version header.',
        context: 'Provided client version {acceptVersion} is ahead of current Ghost instance version {ghostVersion}.',
        help: 'Upgrade your Ghost instance.'
    },
    methodNotAcceptableVersionBehind: {
        message: 'Request not acceptable for provided Accept-Version header.',
        context: 'Provided client version {acceptVersion} is outdated and is behind current Ghost version {ghostVersion}.',
        help: 'Upgrade your Ghost API client.'
    },
    actions: {
        images: {
            upload: 'upload image'
        }
    },
    userMessages: {
        BookshelfRelationsError: 'Database error, cannot {action}.',
        InternalServerError: 'Internal server error, cannot {action}.',
        IncorrectUsageError: 'Incorrect usage error, cannot {action}.',
        NotFoundError: 'Resource not found error, cannot {action}.',
        BadRequestError: 'Request not understood error, cannot {action}.',
        UnauthorizedError: 'Authorisation error, cannot {action}.',
        NoPermissionError: 'Permission error, cannot {action}.',
        ValidationError: 'Validation error, cannot {action}.',
        UnsupportedMediaTypeError: 'Unsupported media error, cannot {action}.',
        TooManyRequestsError: 'Too many requests error, cannot {action}.',
        MaintenanceError: 'Server down for maintenance, cannot {action}.',
        MethodNotAllowedError: 'Method not allowed, cannot {action}.',
        RequestEntityTooLargeError: 'Request too large, cannot {action}.',
        TokenRevocationError: 'Token is not available, cannot {action}.',
        VersionMismatchError: 'Version mismatch error, cannot {action}.',
        DataExportError: 'Error exporting content.',
        DataImportError: 'Duplicated entry, cannot save {action}.',
        DatabaseVersionError: 'Database version compatibility error, cannot {action}.',
        EmailError: 'Error sending email!',
        ThemeValidationError: 'Theme validation error, cannot {action}.',
        HostLimitError: 'Host Limit error, cannot {action}.',
        DisabledFeatureError: 'Theme validation error, the {{{helperName}}} helper is not available. Cannot {action}.',
        UpdateCollisionError: 'Saving failed! Someone else is editing this post.'
    },
    UnknownError: 'Unknown error - {name}, cannot {action}.'
};

/**
 * Get an error ready to be shown the the user
 */
module.exports.prepareError = (err, req, res, next) => {
    debug(err);

    if (Array.isArray(err)) {
        err = err[0];
    }

    if (!errors.utils.isGhostError(err)) {
        // We need a special case for 404 errors
        if (err.statusCode && err.statusCode === 404) {
            err = new errors.NotFoundError({
                err: err
            });
        } else if (err.stack.match(/node_modules\/handlebars\//)) {
            // Temporary handling of theme errors from handlebars
            // @TODO remove this when #10496 is solved properly
            err = new errors.IncorrectUsageError({
                err: err,
                message: err.message,
                statusCode: err.statusCode
            });
        } else {
            err = new errors.InternalServerError({
                err: err,
                message: err.message,
                statusCode: err.statusCode
            });
        }
    }

    // used for express logging middleware see core/server/app.js
    req.err = err;

    // alternative for res.status();
    res.statusCode = err.statusCode;

    // never cache errors
    res.set({
        'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'
    });

    next(err);
};

module.exports.prepareStack = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    const clonedError = prepareStackForUser(err);

    next(clonedError);
};

const jsonErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    res.json({
        errors: [{
            message: err.message,
            context: err.context,
            help: err.help,
            errorType: err.errorType,
            errorDetails: err.errorDetails,
            ghostErrorCode: err.ghostErrorCode
        }]
    });
};

const jsonErrorRendererV2 = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    const userError = prepareUserMessage(err, req);

    res.json({
        errors: [{
            message: userError.message || null,
            context: userError.context || null,
            type: err.errorType || null,
            details: err.errorDetails || null,
            property: err.property || null,
            help: err.help || null,
            code: err.code || null,
            id: err.id || null
            // @TODO: add ghostErrorCode here in a major (I thought V2 was for the V2 api, but it's actually the newer/correct handler)
        }]
    });
};

const prepareUserMessage = (err, req) => {
    const userError = {
        message: err.message,
        context: err.context
    };

    const docName = _.get(req, 'frameOptions.docName');
    const method = _.get(req, 'frameOptions.method');

    if (docName && method) {
        let action;

        const actionMap = {
            browse: 'list',
            read: 'read',
            add: 'save',
            edit: 'edit',
            destroy: 'delete'
        };

        if (_.get(messages.actions, [docName, method])) {
            action = tpl(messages.actions[docName][method]);
        } else if (Object.keys(actionMap).includes(method)) {
            let resource = docName;

            if (method !== 'browse') {
                resource = resource.replace(/s$/, '');
            }

            action = `${actionMap[method]} ${resource}`;
        }

        if (action) {
            if (err.context) {
                userError.context = `${err.message} ${err.context}`;
            } else {
                userError.context = err.message;
            }

            if (_.get(messages.userMessages, err.name)) {
                userError.message = tpl(messages.userMessages[err.name], {action: action});
            } else {
                userError.message = tpl(messages.UnknownError, {action, name: err.name});
            }
        }
    }

    return userError;
};

module.exports.resourceNotFound = (req, res, next) => {
    if (req && req.headers && req.headers['accept-version']
        && res.locals && res.locals.safeVersion
        && semver.compare(semver.coerce(req.headers['accept-version']), semver.coerce(res.locals.safeVersion)) !== 0) {
        const versionComparison = semver.compare(
            semver.coerce(req.headers['accept-version']),
            semver.coerce(res.locals.safeVersion)
        );

        let notAcceptableError;
        if (versionComparison === 1) {
            notAcceptableError = new errors.RequestNotAcceptableError({
                message: tpl(
                    messages.methodNotAcceptableVersionAhead.message
                ),
                context: tpl(messages.methodNotAcceptableVersionAhead.context, {
                    acceptVersion: req.headers['accept-version'],
                    ghostVersion: `v${res.locals.safeVersion}`
                }),
                help: tpl(messages.methodNotAcceptableVersionAhead.help),
                code: 'UPDATE_GHOST'
            });
        } else {
            notAcceptableError = new errors.RequestNotAcceptableError({
                message: tpl(
                    messages.methodNotAcceptableVersionBehind.message
                ),
                context: tpl(messages.methodNotAcceptableVersionBehind.context, {
                    acceptVersion: req.headers['accept-version'],
                    ghostVersion: `v${res.locals.safeVersion}`
                }),
                help: tpl(messages.methodNotAcceptableVersionBehind.help),
                code: 'UPDATE_CLIENT'
            });
        }

        next(notAcceptableError);
    } else {
        next(new errors.NotFoundError({message: tpl(messages.resourceNotFound)}));
    }
};

module.exports.pageNotFound = (req, res, next) => {
    next(new errors.NotFoundError({message: tpl(messages.pageNotFound)}));
};

/**
 * @deprecated: not used as of Ghost v5 and should be removed in a major bump
 */
module.exports.handleJSONResponse = sentry => [
    // Make sure the error can be served
    module.exports.prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Format the stack for the user
    module.exports.prepareStack,
    // Render the error using JSON format
    jsonErrorRenderer
];

/**
 * @deprecated with above
 * @TODO: rename this without the v2 in a major bump
 */
module.exports.handleJSONResponseV2 = sentry => [
    // Make sure the error can be served
    module.exports.prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Format the stack for the user
    module.exports.prepareStack,
    // Render the error using JSON format
    jsonErrorRendererV2
];

module.exports.handleHTMLResponse = sentry => [
    // Make sure the error can be served
    module.exports.prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Format the stack for the user
    module.exports.prepareStack
];
<|endoftext|>


<commit_before>import { Component } from 'react'
import transferStaticProps from './transferStaticProps'
import {
  GENERATION,
  PROXY_KEY,
  UNWRAP_PROXY,
  CACHED_RESULT,
  PROXY_IS_MOUNTED,
} from './constants'
import {
  getDisplayName,
  isReactClass,
  isReactComponentInstance,
  identity,
  safeDefineProperty,
  proxyClassCreator,
} from './utils'
import { inject, checkLifeCycleMethods, mergeComponents } from './inject'
import config from '../configuration'

const has = Object.prototype.hasOwnProperty

let proxies = new WeakMap()

export const resetClassProxies = () => {
  proxies = new WeakMap()
}

const blackListedClassMembers = [
  'constructor',
  'render',
  'componentWillMount',
  'componentDidMount',
  'componentWillReceiveProps',
  'componentWillUnmount',
  'hotComponentRender',

  'getInitialState',
  'getDefaultProps',
]

const defaultRenderOptions = {
  componentWillRender: identity,
  componentDidUpdate: result => result,
  componentDidRender: result => result,
}

const defineClassMember = (Class, methodName, methodBody) =>
  safeDefineProperty(Class.prototype, methodName, {
    configurable: true,
    writable: true,
    enumerable: false,
    value: methodBody,
  })

const defineClassMembers = (Class, methods) =>
  Object.keys(methods).forEach(methodName =>
    defineClassMember(Class, methodName, methods[methodName]),
  )

const setSFPFlag = (component, flag) =>
  safeDefineProperty(component, 'isStatelessFunctionalProxy', {
    configurable: false,
    writable: false,
    enumerable: false,
    value: flag,
  })

const copyMethodDescriptors = (target, source) => {
  if (source) {
    // it is possible to use `function-double` to construct an ideal clone, but does not make a sence
    const keys = Object.getOwnPropertyNames(source)

    keys.forEach(key =>
      safeDefineProperty(
        target,
        key,
        Object.getOwnPropertyDescriptor(source, key),
      ),
    )

    safeDefineProperty(target, 'toString', {
      configurable: true,
      writable: false,
      enumerable: false,
      value: function toString() {
        return String(source)
      },
    })
  }

  return target
}

function createClassProxy(InitialComponent, proxyKey, options) {
  const renderOptions = {
    ...defaultRenderOptions,
    ...options,
  }
  // Prevent double wrapping.
  // Given a proxy class, return the existing proxy managing it.
  const existingProxy = proxies.get(InitialComponent)

  if (existingProxy) {
    return existingProxy
  }

  let CurrentComponent
  let savedDescriptors = {}
  let injectedMembers = {}
  let proxyGeneration = 0
  let classUpdatePostponed = null
  let instancesCount = 0
  let isFunctionalComponent = !isReactClass(InitialComponent)

  let lastInstance = null

  function postConstructionAction() {
    this[GENERATION] = 0

    lastInstance = this
    // is there is an update pending
    if (classUpdatePostponed) {
      const callUpdate = classUpdatePostponed
      classUpdatePostponed = null
      callUpdate()
    }
    // As long we can't override constructor
    // every class shall evolve from a base class
    inject(this, proxyGeneration, injectedMembers)
  }

  function proxiedUpdate() {
    if (this) {
      inject(this, proxyGeneration, injectedMembers)
    }
  }

  function lifeCycleWrapperFactory(wrapperName, sideEffect = identity) {
    return copyMethodDescriptors(function wrappedMethod(...rest) {
      proxiedUpdate.call(this)
      sideEffect(this)
      return (
        !isFunctionalComponent &&
        CurrentComponent.prototype[wrapperName] &&
        CurrentComponent.prototype[wrapperName].apply(this, rest)
      )
    }, InitialComponent.prototype && InitialComponent.prototype[wrapperName])
  }

  function methodWrapperFactory(wrapperName, realMethod) {
    return copyMethodDescriptors(function wrappedMethod(...rest) {
      return realMethod.apply(this, rest)
    }, realMethod)
  }

  const fakeBasePrototype = Base =>
    Object.getOwnPropertyNames(Base)
      .filter(key => blackListedClassMembers.indexOf(key) === -1)
      .filter(key => {
        const descriptor = Object.getOwnPropertyDescriptor(Base, key)
        return typeof descriptor.value === 'function'
      })
      .reduce((acc, key) => {
        acc[key] = methodWrapperFactory(key, Base[key])
        return acc
      }, {})

  const componentDidMount = lifeCycleWrapperFactory(
    'componentDidMount',
    target => {
      target[PROXY_IS_MOUNTED] = true
      instancesCount++
    },
  )
  const componentDidUpdate = lifeCycleWrapperFactory(
    'componentDidUpdate',
    renderOptions.componentDidUpdate,
  )
  const componentWillUnmount = lifeCycleWrapperFactory(
    'componentWillUnmount',
    target => {
      target[PROXY_IS_MOUNTED] = false
      instancesCount--
    },
  )

  function hotComponentRender() {
    // repeating subrender call to keep RENDERED_GENERATION up to date
    renderOptions.componentWillRender(this)
    proxiedUpdate.call(this)
    let result

    // We need to use hasOwnProperty here, as the cached result is a React node
    // and can be null or some other falsy value.
    if (has.call(this, CACHED_RESULT)) {
      result = this[CACHED_RESULT]
      delete this[CACHED_RESULT]
    } else if (isFunctionalComponent) {
      result = CurrentComponent(this.props, this.context)
    } else {
      result = (CurrentComponent.prototype.render || this.render).call(this)
    }

    return renderOptions.componentDidRender.call(this, result)
  }

  function proxiedRender() {
    renderOptions.componentWillRender(this)
    return hotComponentRender.call(this)
  }

  const defineProxyMethods = (Proxy, Base = {}) => {
    defineClassMembers(Proxy, {
      ...fakeBasePrototype(Base),
      render: proxiedRender,
      hotComponentRender,
      componentDidMount,
      componentDidUpdate,
      componentWillUnmount,
    })
  }

  let ProxyFacade
  let ProxyComponent = null
  let proxy

  if (!isFunctionalComponent) {
    // Component
    ProxyComponent = proxyClassCreator(InitialComponent, postConstructionAction)

    defineProxyMethods(ProxyComponent, InitialComponent.prototype)

    ProxyFacade = ProxyComponent
  } else if (!config.allowSFC) {
    // SFC Converted to component. Does not support returning precreated instances from render.
    ProxyComponent = proxyClassCreator(Component, postConstructionAction)

    defineProxyMethods(ProxyComponent)
    ProxyFacade = ProxyComponent
  } else {
    // SFC

    // This function only gets called for the initial mount. The actual
    // rendered component instance will be the return value.

    // eslint-disable-next-line func-names
    ProxyFacade = function(props, context) {
      const result = CurrentComponent(props, context)

      // simple SFC, could continue to be SFC
      if (config.pureSFC) {
        if (!CurrentComponent.contextTypes) {
          if (!ProxyFacade.isStatelessFunctionalProxy) {
            setSFPFlag(ProxyFacade, true)
          }

          return renderOptions.componentDidRender(result)
        }
      }
      setSFPFlag(ProxyFacade, false)

      // This is a Relay-style container constructor. We can't do the prototype-
      // style wrapping for this as we do elsewhere, so just we just pass it
      // through as-is.
      if (isReactComponentInstance(result)) {
        ProxyComponent = null
        return result
      }

      // Otherwise, it's a normal functional component. Build the real proxy
      // and use it going forward.
      ProxyComponent = proxyClassCreator(Component, postConstructionAction)

      defineProxyMethods(ProxyComponent)

      const determinateResult = new ProxyComponent(props, context)

      // Cache the initial render result so we don't call the component function
      // a second time for the initial render.
      determinateResult[CACHED_RESULT] = result
      return determinateResult
    }
  }

  function get() {
    return ProxyFacade
  }

  function getCurrent() {
    return CurrentComponent
  }

  safeDefineProperty(ProxyFacade, UNWRAP_PROXY, {
    configurable: false,
    writable: false,
    enumerable: false,
    value: getCurrent,
  })

  safeDefineProperty(ProxyFacade, PROXY_KEY, {
    configurable: false,
    writable: false,
    enumerable: false,
    value: proxyKey,
  })

  safeDefineProperty(ProxyFacade, 'toString', {
    configurable: true,
    writable: false,
    enumerable: false,
    value: function toString() {
      return String(CurrentComponent)
    },
  })

  function update(NextComponent) {
    if (typeof NextComponent !== 'function') {
      throw new Error('Expected a constructor.')
    }

    if (NextComponent === CurrentComponent) {
      return
    }

    // Prevent proxy cycles
    const existingProxy = proxies.get(NextComponent)
    if (existingProxy) {
      return
    }

    isFunctionalComponent = !isReactClass(NextComponent)

    proxies.set(NextComponent, proxy)

    proxyGeneration++

    // Save the next constructor so we call it
    const PreviousComponent = CurrentComponent
    CurrentComponent = NextComponent

    // Try to infer displayName
    const displayName = getDisplayName(CurrentComponent)

    safeDefineProperty(ProxyFacade, 'displayName', {
      configurable: true,
      writable: false,
      enumerable: true,
      value: displayName,
    })

    if (ProxyComponent) {
      safeDefineProperty(ProxyComponent, 'name', {
        value: displayName,
      })
    }

    savedDescriptors = transferStaticProps(
      ProxyFacade,
      savedDescriptors,
      PreviousComponent,
      NextComponent,
    )

    if (isFunctionalComponent || !ProxyComponent) {
      // nothing
    } else {
      const classHotReplacement = () => {
        checkLifeCycleMethods(ProxyComponent, NextComponent)
        Object.setPrototypeOf(ProxyComponent.prototype, NextComponent.prototype)
        defineProxyMethods(ProxyComponent, NextComponent.prototype)
        if (proxyGeneration > 1) {
          injectedMembers = mergeComponents(
            ProxyComponent,
            NextComponent,
            InitialComponent,
            lastInstance,
            injectedMembers,
          )
        }
      }

      // Was constructed once
      if (instancesCount > 0) {
        classHotReplacement()
      } else {
        classUpdatePostponed = classHotReplacement
      }
    }
  }

  update(InitialComponent)

  const dereference = () => {
    proxies.delete(InitialComponent)
    proxies.delete(ProxyFacade)
    proxies.delete(CurrentComponent)
  }

  proxy = { get, update, dereference, getCurrent: () => CurrentComponent }

  proxies.set(InitialComponent, proxy)
  proxies.set(ProxyFacade, proxy)

  safeDefineProperty(proxy, UNWRAP_PROXY, {
    configurable: false,
    writable: false,
    enumerable: false,
    value: getCurrent,
  })

  return proxy
}

export default createClassProxy
<commit_msg>Handle lazy indeterminate static properties<commit_after>import { Component } from 'react'
import transferStaticProps from './transferStaticProps'
import {
  GENERATION,
  PROXY_KEY,
  UNWRAP_PROXY,
  CACHED_RESULT,
  PROXY_IS_MOUNTED,
} from './constants'
import {
  getDisplayName,
  isReactClass,
  isReactComponentInstance,
  identity,
  safeDefineProperty,
  proxyClassCreator,
} from './utils'
import { inject, checkLifeCycleMethods, mergeComponents } from './inject'
import config from '../configuration'

const has = Object.prototype.hasOwnProperty

let proxies = new WeakMap()

export const resetClassProxies = () => {
  proxies = new WeakMap()
}

const blackListedClassMembers = [
  'constructor',
  'render',
  'componentWillMount',
  'componentDidMount',
  'componentWillReceiveProps',
  'componentWillUnmount',
  'hotComponentRender',

  'getInitialState',
  'getDefaultProps',
]

const defaultRenderOptions = {
  componentWillRender: identity,
  componentDidUpdate: result => result,
  componentDidRender: result => result,
}

const defineClassMember = (Class, methodName, methodBody) =>
  safeDefineProperty(Class.prototype, methodName, {
    configurable: true,
    writable: true,
    enumerable: false,
    value: methodBody,
  })

const defineClassMembers = (Class, methods) =>
  Object.keys(methods).forEach(methodName =>
    defineClassMember(Class, methodName, methods[methodName]),
  )

const setSFPFlag = (component, flag) =>
  safeDefineProperty(component, 'isStatelessFunctionalProxy', {
    configurable: false,
    writable: false,
    enumerable: false,
    value: flag,
  })

const copyMethodDescriptors = (target, source) => {
  if (source) {
    // it is possible to use `function-double` to construct an ideal clone, but does not make a sence
    const keys = Object.getOwnPropertyNames(source)

    keys.forEach(key =>
      safeDefineProperty(
        target,
        key,
        Object.getOwnPropertyDescriptor(source, key),
      ),
    )

    safeDefineProperty(target, 'toString', {
      configurable: true,
      writable: false,
      enumerable: false,
      value: function toString() {
        return String(source)
      },
    })
  }

  return target
}

function createClassProxy(InitialComponent, proxyKey, options) {
  const renderOptions = {
    ...defaultRenderOptions,
    ...options,
  }
  // Prevent double wrapping.
  // Given a proxy class, return the existing proxy managing it.
  const existingProxy = proxies.get(InitialComponent)

  if (existingProxy) {
    return existingProxy
  }

  let CurrentComponent
  let savedDescriptors = {}
  let injectedMembers = {}
  let proxyGeneration = 0
  let classUpdatePostponed = null
  let instancesCount = 0
  let isFunctionalComponent = !isReactClass(InitialComponent)

  let lastInstance = null

  function postConstructionAction() {
    this[GENERATION] = 0

    lastInstance = this
    // is there is an update pending
    if (classUpdatePostponed) {
      const callUpdate = classUpdatePostponed
      classUpdatePostponed = null
      callUpdate()
    }
    // As long we can't override constructor
    // every class shall evolve from a base class
    inject(this, proxyGeneration, injectedMembers)
  }

  function proxiedUpdate() {
    if (this) {
      inject(this, proxyGeneration, injectedMembers)
    }
  }

  function lifeCycleWrapperFactory(wrapperName, sideEffect = identity) {
    return copyMethodDescriptors(function wrappedMethod(...rest) {
      proxiedUpdate.call(this)
      sideEffect(this)
      return (
        !isFunctionalComponent &&
        CurrentComponent.prototype[wrapperName] &&
        CurrentComponent.prototype[wrapperName].apply(this, rest)
      )
    }, InitialComponent.prototype && InitialComponent.prototype[wrapperName])
  }

  function methodWrapperFactory(wrapperName, realMethod) {
    return copyMethodDescriptors(function wrappedMethod(...rest) {
      return realMethod.apply(this, rest)
    }, realMethod)
  }

  const fakeBasePrototype = Base =>
    Object.getOwnPropertyNames(Base)
      .filter(key => blackListedClassMembers.indexOf(key) === -1)
      .filter(key => {
        const descriptor = Object.getOwnPropertyDescriptor(Base, key)
        return typeof descriptor.value === 'function'
      })
      .reduce((acc, key) => {
        acc[key] = methodWrapperFactory(key, Base[key])
        return acc
      }, {})

  const componentDidMount = lifeCycleWrapperFactory(
    'componentDidMount',
    target => {
      target[PROXY_IS_MOUNTED] = true
      instancesCount++
    },
  )
  const componentDidUpdate = lifeCycleWrapperFactory(
    'componentDidUpdate',
    renderOptions.componentDidUpdate,
  )
  const componentWillUnmount = lifeCycleWrapperFactory(
    'componentWillUnmount',
    target => {
      target[PROXY_IS_MOUNTED] = false
      instancesCount--
    },
  )

  function hotComponentRender() {
    // repeating subrender call to keep RENDERED_GENERATION up to date
    renderOptions.componentWillRender(this)
    proxiedUpdate.call(this)
    let result

    // We need to use hasOwnProperty here, as the cached result is a React node
    // and can be null or some other falsy value.
    if (has.call(this, CACHED_RESULT)) {
      result = this[CACHED_RESULT]
      delete this[CACHED_RESULT]
    } else if (isFunctionalComponent) {
      result = CurrentComponent(this.props, this.context)
    } else {
      result = (CurrentComponent.prototype.render || this.render).call(this)
    }

    return renderOptions.componentDidRender.call(this, result)
  }

  function proxiedRender() {
    renderOptions.componentWillRender(this)
    return hotComponentRender.call(this)
  }

  const defineProxyMethods = (Proxy, Base = {}) => {
    defineClassMembers(Proxy, {
      ...fakeBasePrototype(Base),
      render: proxiedRender,
      hotComponentRender,
      componentDidMount,
      componentDidUpdate,
      componentWillUnmount,
    })
  }

  let ProxyFacade
  let ProxyComponent = null
  let proxy

  if (!isFunctionalComponent) {
    // Component
    ProxyComponent = proxyClassCreator(InitialComponent, postConstructionAction)

    defineProxyMethods(ProxyComponent, InitialComponent.prototype)

    ProxyFacade = ProxyComponent
  } else if (!config.allowSFC) {
    // SFC Converted to component. Does not support returning precreated instances from render.
    ProxyComponent = proxyClassCreator(Component, postConstructionAction)

    defineProxyMethods(ProxyComponent)
    ProxyFacade = ProxyComponent
  } else {
    // SFC

    // This function only gets called for the initial mount. The actual
    // rendered component instance will be the return value.

    // eslint-disable-next-line func-names
    ProxyFacade = function(props, context) {
      const result = CurrentComponent(props, context)

      // simple SFC, could continue to be SFC
      if (config.pureSFC) {
        if (!CurrentComponent.contextTypes) {
          if (!ProxyFacade.isStatelessFunctionalProxy) {
            setSFPFlag(ProxyFacade, true)
          }

          return renderOptions.componentDidRender(result)
        }
      }
      setSFPFlag(ProxyFacade, false)

      // This is a Relay-style container constructor. We can't do the prototype-
      // style wrapping for this as we do elsewhere, so just we just pass it
      // through as-is.
      if (isReactComponentInstance(result)) {
        ProxyComponent = null

        // Relay lazily sets statics like getDerivedStateFromProps on initial
        // render in lazy construction, so we need to do the same here.
        transferStaticProps(
          ProxyFacade,
          savedDescriptors,
          null,
          CurrentComponent,
        )

        return result
      }

      // Otherwise, it's a normal functional component. Build the real proxy
      // and use it going forward.
      ProxyComponent = proxyClassCreator(Component, postConstructionAction)

      defineProxyMethods(ProxyComponent)

      const determinateResult = new ProxyComponent(props, context)

      // Cache the initial render result so we don't call the component function
      // a second time for the initial render.
      determinateResult[CACHED_RESULT] = result
      return determinateResult
    }
  }

  function get() {
    return ProxyFacade
  }

  function getCurrent() {
    return CurrentComponent
  }

  safeDefineProperty(ProxyFacade, UNWRAP_PROXY, {
    configurable: false,
    writable: false,
    enumerable: false,
    value: getCurrent,
  })

  safeDefineProperty(ProxyFacade, PROXY_KEY, {
    configurable: false,
    writable: false,
    enumerable: false,
    value: proxyKey,
  })

  safeDefineProperty(ProxyFacade, 'toString', {
    configurable: true,
    writable: false,
    enumerable: false,
    value: function toString() {
      return String(CurrentComponent)
    },
  })

  function update(NextComponent) {
    if (typeof NextComponent !== 'function') {
      throw new Error('Expected a constructor.')
    }

    if (NextComponent === CurrentComponent) {
      return
    }

    // Prevent proxy cycles
    const existingProxy = proxies.get(NextComponent)
    if (existingProxy) {
      return
    }

    isFunctionalComponent = !isReactClass(NextComponent)

    proxies.set(NextComponent, proxy)

    proxyGeneration++

    // Save the next constructor so we call it
    const PreviousComponent = CurrentComponent
    CurrentComponent = NextComponent

    // Try to infer displayName
    const displayName = getDisplayName(CurrentComponent)

    safeDefineProperty(ProxyFacade, 'displayName', {
      configurable: true,
      writable: false,
      enumerable: true,
      value: displayName,
    })

    if (ProxyComponent) {
      safeDefineProperty(ProxyComponent, 'name', {
        value: displayName,
      })
    }

    savedDescriptors = transferStaticProps(
      ProxyFacade,
      savedDescriptors,
      PreviousComponent,
      NextComponent,
    )

    if (isFunctionalComponent || !ProxyComponent) {
      // nothing
    } else {
      const classHotReplacement = () => {
        checkLifeCycleMethods(ProxyComponent, NextComponent)
        Object.setPrototypeOf(ProxyComponent.prototype, NextComponent.prototype)
        defineProxyMethods(ProxyComponent, NextComponent.prototype)
        if (proxyGeneration > 1) {
          injectedMembers = mergeComponents(
            ProxyComponent,
            NextComponent,
            InitialComponent,
            lastInstance,
            injectedMembers,
          )
        }
      }

      // Was constructed once
      if (instancesCount > 0) {
        classHotReplacement()
      } else {
        classUpdatePostponed = classHotReplacement
      }
    }
  }

  update(InitialComponent)

  const dereference = () => {
    proxies.delete(InitialComponent)
    proxies.delete(ProxyFacade)
    proxies.delete(CurrentComponent)
  }

  proxy = { get, update, dereference, getCurrent: () => CurrentComponent }

  proxies.set(InitialComponent, proxy)
  proxies.set(ProxyFacade, proxy)

  safeDefineProperty(proxy, UNWRAP_PROXY, {
    configurable: false,
    writable: false,
    enumerable: false,
    value: getCurrent,
  })

  return proxy
}

export default createClassProxy
<|endoftext|>