<commit_before>//var _this = this;

osm.poi = {
  i18n: {},
  // layers:{},
  opt:{

  },

  initialize: function() {
    osm.poi.layer = new L.LayerGroup();
    //osm.map.addLayer(osm.poi.layer);
    osm.poi.tree=$('#leftpoi div.leftcontent')
      .jstree({
        "plugins" : ["json_data", "checkbox","ui"],
        "json_data" : {
          "ajax" : {
            "type": 'GET',
            "url": "data/poidatatree.json"
          }
        },
        "checkbox": {
          "override_ui": true
        }
      })
      .bind("change_state.check_box.jstree", function (event, data) {
        osm.poi.updateMarkerTree(event, data);
      });
    $.getJSON('data/poimarker.json',
      function(results){
        osm.poi._markers=results;
      }
    )
  },

  enable: function() {
    osm.map.addLayer(osm.poi.layer);
    this.opt.on=true;
    osm.map.on('moveend', osm.poi.updateMarkerTree);
    // osm.map.on('popupopen', osm.poi.bindOpenPopup);
    // osm.map.on('popupclose', osm.poi.bindClosePopup);
  },

  disable: function() {
    this.opt.on=false;
    osm.map.removeLayer(osm.poi.layer);
    osm.map.off('moveend',this.updateMarkerTree);
    // osm.map.off('popupopen', osm.poi.bindOpenPopup);
    // osm.map.off('popupclose', osm.poi.bindClosePopup);
  },

  choiceMarker: function(nclass){
    if (osm.poi._markers.indexOf(nclass) == -1)
      icon_url = 'img/poi_marker/default.png';
    else
      icon_url = 'img/poi_marker/'+nclass+'.png';
    return icon_url;
  },

  updateMarkerTree: function(){
    if (osm.poi.ajax && osm.poi.ajax.state() == "pending")
      osm.poi.ajax.abort();
    $("#leftpoi .loader").addClass('on');
    var checked=$('.jstree-checked.jstree-leaf', osm.poi.tree );
    if (checked.length) {
      var nclass=[];
      for (i=0;i<checked.length;i++) {
        nclass.push(checked[i].attributes['nclass'].nodeValue);
      }
      nclass=nclass.join(',');
      var bounds = osm.map.getBounds();
      var sw = bounds.getSouthWest(), ne = bounds.getNorthEast();
      osm.poi.ajax=$.getJSON('/api/poi', {action:"getpoibbox", nclass:nclass, t:ne.lat, r:ne.lng, b:sw.lat, l:sw.lng},
        function(results){
          markers={};
          for (item1 in osm.poi.layer._layers){
            markers[$(osm.poi.layer._layers[item1]._popup._content,'div.poi_popup').attr('id')]=osm.poi.layer._layers[item1]
          }
          for (item2 in results.data){
            if (markers[results.data[item2].id]){
              delete markers[results.data[item2].id];
            }
            else {
              icon_url = osm.poi.choiceMarker(results.data[item2].nclass);
              _marker = new L.Marker(new L.LatLng(results.data[item2].lat, results.data[item2].lon), {icon:new osm.poi.poiIcon({iconUrl: icon_url})});
              _marker.bindPopup(osm.poi.createPopupText(results.data[item2]));
              osm.poi.layer.addLayer(_marker);
            }
          }
          for (item3 in markers){
            osm.map.removeLayer(markers[item3]);
            delete osm.poi.layer._layers[markers[item3]._leaflet_id];
          }
          $("#leftpoi .loader").removeClass('on');
        })
      .error(
        function(jqXHR, textStatus, errorThrown){
          $("#leftpoi .loader").removeClass('on');
          if (textStatus!="abort") {
            console.log('Ошибка: ' + textStatus + '
' + errorThrown.message);
          }
        });
    }
    else {
      $("#leftpoi .loader").removeClass('on');
      osm.poi.layer.clearLayers();
    }
  },
  poiIcon :  L.Icon.extend({
		options: {
			//iconUrl: 'img/marker-icon.png',
			iconSize: new L.Point(31, 31),
			shadowSize: new L.Point(0, 0),
			iconAnchor: new L.Point(16, 30),
			popupAnchor: new L.Point(0, -11)
		}
	}),

  createPopupText: function(getdata) {
    if (!(getdata == null)) {
      var operator;
      if (!(getdata.operator==null)) {
        operator=$('
').addClass('poi_operator')
          .append($('').text('Владелец: '))
          .append($('').text(getdata.operator).addClass('poi_value'))
      }
      var brand;
      if (!(getdata.brand==null)) {
        brand=$('
').addClass('poi_brand')
          .append($('').text('Бренд: '))
          .append($('').text(getdata.brand).addClass('poi_value'))
      }
      var phone;
      if (!(getdata.phone==null)) {
        phone=$('
').addClass('poi_phone')
          .append($('').text('Телефон: '))
          .append($('').text(getdata.phone).addClass('poi_value'))
      }
      var fax;
      if (!(getdata.fax==null)) {
        fax=$('
').addClass('poi_fax')
          .append($('').text('Факс: '))
          .append($('').text(getdata.fax).addClass('poi_value'))
      }

	var website;
	if (!(getdata.website==null)) {
		// assume http:// for protocol-less links
		var properLink = getdata.website;
		if (properLink.indexOf(':') < 0) {
			properLink = 'http://' + properLink;
		}

		website=$('
').addClass('poi_website')
			.append($('').text('Web-сайт: '))
			.append($('').addClass('poi_value')
				.append($('').attr('href', properLink).attr('target', '_blank').text(getdata.website))
			);
	}

      var moretags=$('');
      for (xName in getdata.tags_ru) {
        if (getdata.tags_ru[xName]!="неизвестно") {
          moretags.after($('
').addClass('poi_moretags')
            .append($('').text(xName+': '))
            .append($('').text(getdata.tags_ru[xName])))
        }
      }

      if (getdata.opening_hours) {
        getdata.opening_hours = getdata.opening_hours.toLowerCase()
		  .replace(/([a-z].) /g, '$1: ')
          .replace(/mo-fr/g, 'по будням')
          .replace(/sa-su/g, 'по выходным')
          .replace(/mo-su/g, 'ежедневно')
          .replace(/mo/g, 'Пн')
          .replace(/tu/g, 'Вт')
          .replace(/we/g, 'Ср')
          .replace(/th/g, 'Чт')
          .replace(/fr/g, 'Пт')
          .replace(/sa/g, 'Сб')
          .replace(/su/g, 'Вс')
          .replace('24/7', 'круглосуточно')
          .replace(/off/g, 'не работает')
          .replace(/,(?!\s)/g, ', ')
          .replace(/-(?=\d)/g, '—')
          .replace(/\s*;\s*/g, ' | ');
        getdata.opening_hours = getdata.opening_hours.charAt(0).toUpperCase() + getdata.opening_hours.substr(1);
      }
      ret = $('
').addClass('poi_popup').attr('id',getdata.id)
        .append($('
').addClass('poi_header')
          .append($('').text(getdata.class_ru).addClass('poi_name'))
          .append($('').text(getdata.name_ru||'').addClass('poi_value'))
        )
        .append($('
')
          .append($('
').addClass('poi_opening_hours')
            .append($('').text('Время работы: '))
            .append($('').text(getdata.opening_hours||osm.poi.opt.nulldisplay).addClass('poi_value'))
          )
          .append($('
').addClass('poi_addr')
            .append($('').text('Адрес: '))
            .append($('').text(getdata.addr_full_name||"").addClass('poi_value'))
          )
          .append(operator)
          .append(brand)
          .append(phone)
          .append(fax)
          .append(website)
          .append(moretags)
        )

      return $('
').append(ret.clone()).remove().html();
    }
  },

  createPopup: function(id, marker) {
    var textPopup = $('')
    textPopup = $('
').append(textPopup.clone()).remove().html();
    marker.bindPopup(textPopup);
    $.getJSON("/api/poi", {action: 'getpoiinfo', id: id}, function(json){
      if (!(json.data == null)) {

        textP = osm.poi.createPopupText(json.data);

        isopen=osm.map.hasLayer(marker._popup);
        if (isopen) {marker.closePopup();}
        marker.bindPopup(textP,{maxWidth:400});
        icon_url = osm.poi.choiceMarker(json.data.nclass);
        marker.setIcon(new osm.poi.poiIcon({iconUrl: icon_url}));
        if (isopen) {marker.openPopup();}
      }
    })
  },

  bindOpenPopup: function(){
    osm.poi.openpopup=arguments[0].popup;
  },
  bindClosePopup: function(){
    delete osm.poi.openpopup;
  }

}

osm.poi.opt={
  on: false,
  nulldisplay: "Неизвестно"
}
<commit_msg>Fixes #125 - added tel: link to POI phones<commit_after>//var _this = this;

osm.poi = {
  i18n: {},
  // layers:{},
  opt:{

  },

  initialize: function() {
    osm.poi.layer = new L.LayerGroup();
    //osm.map.addLayer(osm.poi.layer);
    osm.poi.tree=$('#leftpoi div.leftcontent')
      .jstree({
        "plugins" : ["json_data", "checkbox","ui"],
        "json_data" : {
          "ajax" : {
            "type": 'GET',
            "url": "data/poidatatree.json"
          }
        },
        "checkbox": {
          "override_ui": true
        }
      })
      .bind("change_state.check_box.jstree", function (event, data) {
        osm.poi.updateMarkerTree(event, data);
      });
    $.getJSON('data/poimarker.json',
      function(results){
        osm.poi._markers=results;
      }
    )
  },

  enable: function() {
    osm.map.addLayer(osm.poi.layer);
    this.opt.on=true;
    osm.map.on('moveend', osm.poi.updateMarkerTree);
    // osm.map.on('popupopen', osm.poi.bindOpenPopup);
    // osm.map.on('popupclose', osm.poi.bindClosePopup);
  },

  disable: function() {
    this.opt.on=false;
    osm.map.removeLayer(osm.poi.layer);
    osm.map.off('moveend',this.updateMarkerTree);
    // osm.map.off('popupopen', osm.poi.bindOpenPopup);
    // osm.map.off('popupclose', osm.poi.bindClosePopup);
  },

  choiceMarker: function(nclass){
    if (osm.poi._markers.indexOf(nclass) == -1)
      icon_url = 'img/poi_marker/default.png';
    else
      icon_url = 'img/poi_marker/'+nclass+'.png';
    return icon_url;
  },

  updateMarkerTree: function(){
    if (osm.poi.ajax && osm.poi.ajax.state() == "pending")
      osm.poi.ajax.abort();
    $("#leftpoi .loader").addClass('on');
    var checked=$('.jstree-checked.jstree-leaf', osm.poi.tree );
    if (checked.length) {
      var nclass=[];
      for (i=0;i<checked.length;i++) {
        nclass.push(checked[i].attributes['nclass'].nodeValue);
      }
      nclass=nclass.join(',');
      var bounds = osm.map.getBounds();
      var sw = bounds.getSouthWest(), ne = bounds.getNorthEast();
      osm.poi.ajax=$.getJSON('/api/poi', {action:"getpoibbox", nclass:nclass, t:ne.lat, r:ne.lng, b:sw.lat, l:sw.lng},
        function(results){
          markers={};
          for (item1 in osm.poi.layer._layers){
            markers[$(osm.poi.layer._layers[item1]._popup._content,'div.poi_popup').attr('id')]=osm.poi.layer._layers[item1]
          }
          for (item2 in results.data){
            if (markers[results.data[item2].id]){
              delete markers[results.data[item2].id];
            }
            else {
              icon_url = osm.poi.choiceMarker(results.data[item2].nclass);
              _marker = new L.Marker(new L.LatLng(results.data[item2].lat, results.data[item2].lon), {icon:new osm.poi.poiIcon({iconUrl: icon_url})});
              _marker.bindPopup(osm.poi.createPopupText(results.data[item2]));
              osm.poi.layer.addLayer(_marker);
            }
          }
          for (item3 in markers){
            osm.map.removeLayer(markers[item3]);
            delete osm.poi.layer._layers[markers[item3]._leaflet_id];
          }
          $("#leftpoi .loader").removeClass('on');
        })
      .error(
        function(jqXHR, textStatus, errorThrown){
          $("#leftpoi .loader").removeClass('on');
          if (textStatus!="abort") {
            console.log('Ошибка: ' + textStatus + '
' + errorThrown.message);
          }
        });
    }
    else {
      $("#leftpoi .loader").removeClass('on');
      osm.poi.layer.clearLayers();
    }
  },
  poiIcon :  L.Icon.extend({
		options: {
			//iconUrl: 'img/marker-icon.png',
			iconSize: new L.Point(31, 31),
			shadowSize: new L.Point(0, 0),
			iconAnchor: new L.Point(16, 30),
			popupAnchor: new L.Point(0, -11)
		}
	}),

  createPopupText: function(getdata) {
    if (!(getdata == null)) {
      var operator;
      if (!(getdata.operator==null)) {
        operator=$('
').addClass('poi_operator')
          .append($('').text('Владелец: '))
          .append($('').text(getdata.operator).addClass('poi_value'))
      }
      var brand;
      if (!(getdata.brand==null)) {
        brand=$('
').addClass('poi_brand')
          .append($('').text('Бренд: '))
          .append($('').text(getdata.brand).addClass('poi_value'))
      }
      var phone;
      if (!(getdata.phone==null)) {
        phone=$('
').addClass('poi_phone')
          .append($('').text('Телефон: '))
          .append($('').addClass('poi_value')
            .append($('').attr('href', 'tel:'+getdata.phone).text(getdata.phone))
          )
      }
      var fax;
      if (!(getdata.fax==null)) {
        fax=$('
').addClass('poi_fax')
          .append($('').text('Факс: '))
          .append($('').text(getdata.fax).addClass('poi_value'))
      }

	var website;
	if (!(getdata.website==null)) {
		// assume http:// for protocol-less links
		var properLink = getdata.website;
		if (properLink.indexOf(':') < 0) {
			properLink = 'http://' + properLink;
		}

		website=$('
').addClass('poi_website')
			.append($('').text('Web-сайт: '))
			.append($('').addClass('poi_value')
				.append($('').attr('href', properLink).attr('target', '_blank').text(getdata.website))
			);
	}

      var moretags=$('');
      for (xName in getdata.tags_ru) {
        if (getdata.tags_ru[xName]!="неизвестно") {
          moretags.after($('
').addClass('poi_moretags')
            .append($('').text(xName+': '))
            .append($('').text(getdata.tags_ru[xName])))
        }
      }

      if (getdata.opening_hours) {
        getdata.opening_hours = getdata.opening_hours.toLowerCase()
		  .replace(/([a-z].) /g, '$1: ')
          .replace(/mo-fr/g, 'по будням')
          .replace(/sa-su/g, 'по выходным')
          .replace(/mo-su/g, 'ежедневно')
          .replace(/mo/g, 'Пн')
          .replace(/tu/g, 'Вт')
          .replace(/we/g, 'Ср')
          .replace(/th/g, 'Чт')
          .replace(/fr/g, 'Пт')
          .replace(/sa/g, 'Сб')
          .replace(/su/g, 'Вс')
          .replace('24/7', 'круглосуточно')
          .replace(/off/g, 'не работает')
          .replace(/,(?!\s)/g, ', ')
          .replace(/-(?=\d)/g, '—')
          .replace(/\s*;\s*/g, ' | ');
        getdata.opening_hours = getdata.opening_hours.charAt(0).toUpperCase() + getdata.opening_hours.substr(1);
      }
      ret = $('
').addClass('poi_popup').attr('id',getdata.id)
        .append($('
').addClass('poi_header')
          .append($('').text(getdata.class_ru).addClass('poi_name'))
          .append($('').text(getdata.name_ru||'').addClass('poi_value'))
        )
        .append($('
')
          .append($('
').addClass('poi_opening_hours')
            .append($('').text('Время работы: '))
            .append($('').text(getdata.opening_hours||osm.poi.opt.nulldisplay).addClass('poi_value'))
          )
          .append($('
').addClass('poi_addr')
            .append($('').text('Адрес: '))
            .append($('').text(getdata.addr_full_name||"").addClass('poi_value'))
          )
          .append(operator)
          .append(brand)
          .append(phone)
          .append(fax)
          .append(website)
          .append(moretags)
        )

      return $('
').append(ret.clone()).remove().html();
    }
  },

  createPopup: function(id, marker) {
    var textPopup = $('')
    textPopup = $('
').append(textPopup.clone()).remove().html();
    marker.bindPopup(textPopup);
    $.getJSON("/api/poi", {action: 'getpoiinfo', id: id}, function(json){
      if (!(json.data == null)) {

        textP = osm.poi.createPopupText(json.data);

        isopen=osm.map.hasLayer(marker._popup);
        if (isopen) {marker.closePopup();}
        marker.bindPopup(textP,{maxWidth:400});
        icon_url = osm.poi.choiceMarker(json.data.nclass);
        marker.setIcon(new osm.poi.poiIcon({iconUrl: icon_url}));
        if (isopen) {marker.openPopup();}
      }
    })
  },

  bindOpenPopup: function(){
    osm.poi.openpopup=arguments[0].popup;
  },
  bindClosePopup: function(){
    delete osm.poi.openpopup;
  }

}

osm.poi.opt={
  on: false,
  nulldisplay: "Неизвестно"
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, brackets, window, Mustache */

/**
 * Utilities for creating and managing standard modal dialogs.
 */
define(function (require, exports, module) {
    "use strict";

    require("utils/Global");

    var KeyBindingManager = require("command/KeyBindingManager"),
        KeyEvent          = require("utils/KeyEvent"),
        NativeApp         = require("utils/NativeApp"),
        Strings           = require("strings"),
        DialogTemplate    = require("text!htmlContent/dialog-template.html");

    /**
     * Dialog Buttons IDs
     * @const {string}
     */
    var DIALOG_BTN_CANCEL           = "cancel",
        DIALOG_BTN_OK               = "ok",
        DIALOG_BTN_DONTSAVE         = "dontsave",
        DIALOG_CANCELED             = "_canceled",
        DIALOG_BTN_DOWNLOAD         = "download";

    /**
     * Dialog Buttons Class Names
     * @const {string}
     */
    var DIALOG_BTN_CLASS_PRIMARY    = "primary",
        DIALOG_BTN_CLASS_NORMAL     = "",
        DIALOG_BTN_CLASS_LEFT       = "left";

    /** @type {number} The z-index used for the dialogs. Each new dialog increase this number by 2 */
    var zIndex = 1050;

    /**
     * @private
     * Dismises a modal dialog
     * @param {$.Element} $dlg
     * @param {string} buttonId
     */
    function _dismissDialog($dlg, buttonId) {
        $dlg.data("buttonId", buttonId);
        $dlg.modal("hide");
        $(".modal-wrapper:last").remove();
    }

    /**
     * @private
     * Returns true if the modal dialog has a button with the given ID
     * @param {$.Element} $dlg
     * @param {string} buttonId
     * @return {boolean}
     */
    function _hasButton($dlg, buttonId) {
        return ($dlg.find("[data-button-id='" + buttonId + "']").length > 0);
    }

    /**
     * @private
     * Handles the use of Tab so that it stays inside the Dialog
     * @param {$.Event} event
     * @param {$.Element} $dlg
     */
    function _handleTab(event, $dlg) {
        var $inputs = $(":input:enabled, a", $dlg).filter(":visible");

        if ($(event.target).closest($dlg).length) {
            // If it's the first or last tabbable element, focus the last/first element
            if ((!event.shiftKey && event.target === $inputs[$inputs.length - 1]) ||
                    (event.shiftKey && event.target === $inputs[0])) {
                $inputs.filter(event.shiftKey ? ":last" : ":first").focus();
                event.preventDefault();

            // If there is no element to focus, don't let it focus outside of the dialog
            } else if (!$inputs.length) {
                event.preventDefault();
            }

        // If the focus left the dialog, focus the first element in the dialog
        } else {
            $inputs.first().focus();
            event.preventDefault();
        }
    }

    /**
     * Handles the keyDown event for the dialogs
     * @param {$.Event} e
     * @param {boolean} autoDismiss
     * @return {boolean}
     */
    var _keydownHook = function (e, autoDismiss) {
        var $primaryBtn = this.find(".primary"),
            buttonId    = null,
            which       = String.fromCharCode(e.which);

        // There might be a textfield in the dialog's UI; don't want to mistake normal typing for dialog dismissal
        var inTextArea    = (e.target.tagName === "TEXTAREA"),
            inTypingField = inTextArea || ($(e.target).filter(":text, :password").length > 0);

        if (e.which === KeyEvent.DOM_VK_TAB) {
            _handleTab(e, this);
        } else if (e.which === KeyEvent.DOM_VK_ESCAPE) {
            buttonId = DIALOG_BTN_CANCEL;
        } else if (e.which === KeyEvent.DOM_VK_RETURN && !inTextArea) {  // enter key in single-line text input still dismisses
            // Click primary button
            $primaryBtn.click();
        } else if (e.which === KeyEvent.DOM_VK_SPACE) {
            // Space bar on focused button or link
            this.find(".dialog-button:focus, a:focus").click();
        } else if (brackets.platform === "mac") {
            // CMD+D Don't Save
            if (e.metaKey && (which === "D")) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            // FIXME (issue #418) CMD+. Cancel swallowed by native shell
            } else if (e.metaKey && (e.which === KeyEvent.DOM_VK_PERIOD)) {
                buttonId = DIALOG_BTN_CANCEL;
            }
        } else { // if (brackets.platform === "win") {
            // 'N' Don't Save
            if (which === "N" && !inTypingField) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            }
        }

        if (autoDismiss && buttonId) {
            _dismissDialog(this, buttonId);
        }

        // Stop any other global hooks from processing the event (but
        // allow it to continue bubbling if we haven't otherwise stopped it).
        return true;
    };

    /**
     * @constructor
     * @private
     *
     * @param {$.Element} $dlg The dialog jQuery element
     * @param {$.Promise} promise A promise that will be resolved with the ID of the clicked button when the dialog
     *     is dismissed. Never rejected.
     */
    function Dialog($dlg, promise) {
        this._$dlg    = $dlg;
        this._promise = promise;
    }

    /** @type {$.Element} The dialog jQuery element */
    Dialog.prototype.getElement = function () {
        return this._$dlg;
    };

    /** @type {$.Promise} The dialog promise */
    Dialog.prototype.getPromise = function () {
        return this._promise;
    };

    /**
     * Closes the dialog if is visible
     */
    Dialog.prototype.close = function () {
        if (this._$dlg.is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
            _dismissDialog(this._$dlg, DIALOG_CANCELED);
        }
    };

    /**
     * Adds a done callback to the dialog promise
     */
    Dialog.prototype.done = function (callback) {
        this._promise.done(callback);
    };

    /**
     * Creates a new modal dialog from a given template.
     * The template can either be a string or a jQuery object representing a DOM node that is *not* in the current DOM.
     *
     * @param {string} template A string template or jQuery object to use as the dialog HTML.
     * @param {boolean=} autoDismiss Whether to automatically dismiss the dialog when one of the buttons
     *      is clicked. Default true. If false, you'll need to manually handle button clicks and the Esc
     *      key, and dismiss the dialog yourself when ready with `cancelModalDialogIfOpen()`.
     * @return {Dialog}
     */
    function showModalDialogUsingTemplate(template, autoDismiss) {
        if (autoDismiss === undefined) {
            autoDismiss = true;
        }

        $("body").append("

");

        var result  = $.Deferred(),
            promise = result.promise(),
            $dlg    = $(template)
                .addClass("instance")
                .appendTo(".modal-inner-wrapper:last");

        // Save the dialog promise for unit tests
        $dlg.data("promise", promise);

        var keydownHook = function (e) {
            return _keydownHook.call($dlg, e, autoDismiss);
        };

        // Pipe dialog-closing notification back to client code
        $dlg.one("hidden", function () {
            var buttonId = $dlg.data("buttonId");
            if (!buttonId) {    // buttonId will be undefined if closed via Bootstrap's "x" button
                buttonId = DIALOG_BTN_CANCEL;
            }

            // Let call stack return before notifying that dialog has closed; this avoids issue #191
            // if the handler we're triggering might show another dialog (as long as there's no
            // fade-out animation)
            window.setTimeout(function () {
                result.resolve(buttonId);
            }, 0);

            // Remove the dialog instance from the DOM.
            $dlg.remove();
            $(".modal-backdrop:last").addClass("last-backdrop");

            // Remove our global keydown handler.
            KeyBindingManager.removeGlobalKeydownHook(keydownHook);
        }).one("shown", function () {
            // Set focus to the default button
            var primaryBtn = $dlg.find(".primary");

            if (primaryBtn) {
                primaryBtn.focus();
            }

            // Push our global keydown handler onto the global stack of handlers.
            KeyBindingManager.addGlobalKeydownHook(keydownHook);
        });

        // Click handler for buttons
        if (autoDismiss) {
            $dlg.one("click", ".dialog-button", function (e) {
                _dismissDialog($dlg, $(this).attr("data-button-id"));
            });
        }

        $(".last-backdrop").removeClass("last-backdrop");

        // Run the dialog
        $dlg
            .modal({
                backdrop: "static",
                show:     true,
                selector: ".modal-inner-wrapper:last",
                keyboard: false // handle the ESC key ourselves so we can deal with nested dialogs
            })
            // Updates the z-index of the modal dialog and the backdrop
            .css("z-index", zIndex + 1)
            .next()
            .css("z-index", zIndex)
            .addClass("last-backdrop");

        zIndex += 2;

        return (new Dialog($dlg, promise));
    }

    /**
     * Creates a new general purpose modal dialog using the default template and the template variables given
     * as parameters as described.
     *
     * @param {string} dlgClass A class name identifier for the dialog. Typically one of DefaultDialogs.*
     * @param {string=} title The title of the dialog. Can contain HTML markup. Defaults to "".
     * @param {string=} message The message to display in the dialog. Can contain HTML markup. Defaults to "".
     * @param {Array.<{className: string, id: string, text: string}>=} buttons An array of buttons where each button
     *      has a class, id and text property. The id is used in "data-button-id". Defaults to a single Ok button.
     *      Typically className is one of DIALOG_BTN_CLASS_*, id is one of DIALOG_BTN_*
     * @return {Dialog}
     */
    function showModalDialog(dlgClass, title, message, buttons) {
        var templateVars = {
            dlgClass: dlgClass,
            title:    title   || "",
            message:  message || "",
            buttons:  buttons || [{ className: DIALOG_BTN_CLASS_PRIMARY, id: DIALOG_BTN_OK, text: Strings.OK }]
        };
        var template = Mustache.render(DialogTemplate, templateVars);

        return showModalDialogUsingTemplate(template);
    }

    /**
     * Immediately closes any dialog instances with the given class. The dialog callback for each instance will
     * be called with the special buttonId DIALOG_CANCELED (note: callback is run asynchronously).
     * @param {string} dlgClass The class name identifier for the dialog.
     * @param {string=} buttonId The button id to use when closing the dialog. Defaults to DIALOG_CANCELED
     */
    function cancelModalDialogIfOpen(dlgClass, buttonId) {
        $("." + dlgClass + ".instance").each(function () {
            if ($(this).is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
                _dismissDialog($(this), buttonId || DIALOG_CANCELED);
            }
        });
    }

    exports.DIALOG_BTN_CANCEL            = DIALOG_BTN_CANCEL;
    exports.DIALOG_BTN_OK                = DIALOG_BTN_OK;
    exports.DIALOG_BTN_DONTSAVE          = DIALOG_BTN_DONTSAVE;
    exports.DIALOG_CANCELED              = DIALOG_CANCELED;
    exports.DIALOG_BTN_DOWNLOAD          = DIALOG_BTN_DOWNLOAD;

    exports.DIALOG_BTN_CLASS_PRIMARY     = DIALOG_BTN_CLASS_PRIMARY;
    exports.DIALOG_BTN_CLASS_NORMAL      = DIALOG_BTN_CLASS_NORMAL;
    exports.DIALOG_BTN_CLASS_LEFT        = DIALOG_BTN_CLASS_LEFT;

    exports.showModalDialog              = showModalDialog;
    exports.showModalDialogUsingTemplate = showModalDialogUsingTemplate;
    exports.cancelModalDialogIfOpen      = cancelModalDialogIfOpen;
});
<commit_msg>Fix dialogs not hiding properly<commit_after>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, brackets, window, Mustache */

/**
 * Utilities for creating and managing standard modal dialogs.
 */
define(function (require, exports, module) {
    "use strict";

    require("utils/Global");

    var KeyBindingManager = require("command/KeyBindingManager"),
        KeyEvent          = require("utils/KeyEvent"),
        NativeApp         = require("utils/NativeApp"),
        Strings           = require("strings"),
        DialogTemplate    = require("text!htmlContent/dialog-template.html");

    /**
     * Dialog Buttons IDs
     * @const {string}
     */
    var DIALOG_BTN_CANCEL           = "cancel",
        DIALOG_BTN_OK               = "ok",
        DIALOG_BTN_DONTSAVE         = "dontsave",
        DIALOG_CANCELED             = "_canceled",
        DIALOG_BTN_DOWNLOAD         = "download";

    /**
     * Dialog Buttons Class Names
     * @const {string}
     */
    var DIALOG_BTN_CLASS_PRIMARY    = "primary",
        DIALOG_BTN_CLASS_NORMAL     = "",
        DIALOG_BTN_CLASS_LEFT       = "left";

    /** @type {number} The z-index used for the dialogs. Each new dialog increase this number by 2 */
    var zIndex = 1050;

    /**
     * @private
     * Dismises a modal dialog
     * @param {$.Element} $dlg
     * @param {string} buttonId
     */
    function _dismissDialog($dlg, buttonId) {
        $dlg.data("buttonId", buttonId);
        $dlg.modal("hide");
    }

    /**
     * @private
     * Returns true if the modal dialog has a button with the given ID
     * @param {$.Element} $dlg
     * @param {string} buttonId
     * @return {boolean}
     */
    function _hasButton($dlg, buttonId) {
        return ($dlg.find("[data-button-id='" + buttonId + "']").length > 0);
    }

    /**
     * @private
     * Handles the use of Tab so that it stays inside the Dialog
     * @param {$.Event} event
     * @param {$.Element} $dlg
     */
    function _handleTab(event, $dlg) {
        var $inputs = $(":input:enabled, a", $dlg).filter(":visible");

        if ($(event.target).closest($dlg).length) {
            // If it's the first or last tabbable element, focus the last/first element
            if ((!event.shiftKey && event.target === $inputs[$inputs.length - 1]) ||
                    (event.shiftKey && event.target === $inputs[0])) {
                $inputs.filter(event.shiftKey ? ":last" : ":first").focus();
                event.preventDefault();

            // If there is no element to focus, don't let it focus outside of the dialog
            } else if (!$inputs.length) {
                event.preventDefault();
            }

        // If the focus left the dialog, focus the first element in the dialog
        } else {
            $inputs.first().focus();
            event.preventDefault();
        }
    }

    /**
     * Handles the keyDown event for the dialogs
     * @param {$.Event} e
     * @param {boolean} autoDismiss
     * @return {boolean}
     */
    var _keydownHook = function (e, autoDismiss) {
        var $primaryBtn = this.find(".primary"),
            buttonId    = null,
            which       = String.fromCharCode(e.which);

        // There might be a textfield in the dialog's UI; don't want to mistake normal typing for dialog dismissal
        var inTextArea    = (e.target.tagName === "TEXTAREA"),
            inTypingField = inTextArea || ($(e.target).filter(":text, :password").length > 0);

        if (e.which === KeyEvent.DOM_VK_TAB) {
            _handleTab(e, this);
        } else if (e.which === KeyEvent.DOM_VK_ESCAPE) {
            buttonId = DIALOG_BTN_CANCEL;
        } else if (e.which === KeyEvent.DOM_VK_RETURN && !inTextArea) {  // enter key in single-line text input still dismisses
            // Click primary button
            $primaryBtn.click();
        } else if (e.which === KeyEvent.DOM_VK_SPACE) {
            // Space bar on focused button or link
            this.find(".dialog-button:focus, a:focus").click();
        } else if (brackets.platform === "mac") {
            // CMD+D Don't Save
            if (e.metaKey && (which === "D")) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            // FIXME (issue #418) CMD+. Cancel swallowed by native shell
            } else if (e.metaKey && (e.which === KeyEvent.DOM_VK_PERIOD)) {
                buttonId = DIALOG_BTN_CANCEL;
            }
        } else { // if (brackets.platform === "win") {
            // 'N' Don't Save
            if (which === "N" && !inTypingField) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            }
        }

        if (autoDismiss && buttonId) {
            _dismissDialog(this, buttonId);
        }

        // Stop any other global hooks from processing the event (but
        // allow it to continue bubbling if we haven't otherwise stopped it).
        return true;
    };

    /**
     * @constructor
     * @private
     *
     * @param {$.Element} $dlg The dialog jQuery element
     * @param {$.Promise} promise A promise that will be resolved with the ID of the clicked button when the dialog
     *     is dismissed. Never rejected.
     */
    function Dialog($dlg, promise) {
        this._$dlg    = $dlg;
        this._promise = promise;

        this._$dlg.one("hidden", function () {
            $(".modal-wrapper:last").remove();
        });
    }

    /** @type {$.Element} The dialog jQuery element */
    Dialog.prototype.getElement = function () {
        return this._$dlg;
    };

    /** @type {$.Promise} The dialog promise */
    Dialog.prototype.getPromise = function () {
        return this._promise;
    };

    /**
     * Closes the dialog if is visible
     */
    Dialog.prototype.close = function () {
        if (this._$dlg.is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
            _dismissDialog(this._$dlg, DIALOG_CANCELED);
        }
    };

    /**
     * Adds a done callback to the dialog promise
     */
    Dialog.prototype.done = function (callback) {
        this._promise.done(callback);
    };

    /**
     * Creates a new modal dialog from a given template.
     * The template can either be a string or a jQuery object representing a DOM node that is *not* in the current DOM.
     *
     * @param {string} template A string template or jQuery object to use as the dialog HTML.
     * @param {boolean=} autoDismiss Whether to automatically dismiss the dialog when one of the buttons
     *      is clicked. Default true. If false, you'll need to manually handle button clicks and the Esc
     *      key, and dismiss the dialog yourself when ready with `cancelModalDialogIfOpen()`.
     * @return {Dialog}
     */
    function showModalDialogUsingTemplate(template, autoDismiss) {
        if (autoDismiss === undefined) {
            autoDismiss = true;
        }

        $("body").append("

");

        var result  = $.Deferred(),
            promise = result.promise(),
            $dlg    = $(template)
                .addClass("instance")
                .appendTo(".modal-inner-wrapper:last");

        // Save the dialog promise for unit tests
        $dlg.data("promise", promise);

        var keydownHook = function (e) {
            return _keydownHook.call($dlg, e, autoDismiss);
        };

        // Pipe dialog-closing notification back to client code
        $dlg.one("hidden", function () {
            var buttonId = $dlg.data("buttonId");
            if (!buttonId) {    // buttonId will be undefined if closed via Bootstrap's "x" button
                buttonId = DIALOG_BTN_CANCEL;
            }

            // Let call stack return before notifying that dialog has closed; this avoids issue #191
            // if the handler we're triggering might show another dialog (as long as there's no
            // fade-out animation)
            window.setTimeout(function () {
                result.resolve(buttonId);
            }, 0);

            // Remove the dialog instance from the DOM.
            $dlg.remove();
            $(".modal-backdrop:last").addClass("last-backdrop");

            // Remove our global keydown handler.
            KeyBindingManager.removeGlobalKeydownHook(keydownHook);
        }).one("shown", function () {
            // Set focus to the default button
            var primaryBtn = $dlg.find(".primary");

            if (primaryBtn) {
                primaryBtn.focus();
            }

            // Push our global keydown handler onto the global stack of handlers.
            KeyBindingManager.addGlobalKeydownHook(keydownHook);
        });

        // Click handler for buttons
        if (autoDismiss) {
            $dlg.one("click", ".dialog-button", function (e) {
                _dismissDialog($dlg, $(this).attr("data-button-id"));
            });
        }

        $(".last-backdrop").removeClass("last-backdrop");

        // Run the dialog
        $dlg
            .modal({
                backdrop: "static",
                show:     true,
                selector: ".modal-inner-wrapper:last",
                keyboard: false // handle the ESC key ourselves so we can deal with nested dialogs
            })
            // Updates the z-index of the modal dialog and the backdrop
            .css("z-index", zIndex + 1)
            .next()
            .css("z-index", zIndex)
            .addClass("last-backdrop");

        zIndex += 2;

        return (new Dialog($dlg, promise));
    }

    /**
     * Creates a new general purpose modal dialog using the default template and the template variables given
     * as parameters as described.
     *
     * @param {string} dlgClass A class name identifier for the dialog. Typically one of DefaultDialogs.*
     * @param {string=} title The title of the dialog. Can contain HTML markup. Defaults to "".
     * @param {string=} message The message to display in the dialog. Can contain HTML markup. Defaults to "".
     * @param {Array.<{className: string, id: string, text: string}>=} buttons An array of buttons where each button
     *      has a class, id and text property. The id is used in "data-button-id". Defaults to a single Ok button.
     *      Typically className is one of DIALOG_BTN_CLASS_*, id is one of DIALOG_BTN_*
     * @return {Dialog}
     */
    function showModalDialog(dlgClass, title, message, buttons) {
        var templateVars = {
            dlgClass: dlgClass,
            title:    title   || "",
            message:  message || "",
            buttons:  buttons || [{ className: DIALOG_BTN_CLASS_PRIMARY, id: DIALOG_BTN_OK, text: Strings.OK }]
        };
        var template = Mustache.render(DialogTemplate, templateVars);

        return showModalDialogUsingTemplate(template);
    }

    /**
     * Immediately closes any dialog instances with the given class. The dialog callback for each instance will
     * be called with the special buttonId DIALOG_CANCELED (note: callback is run asynchronously).
     * @param {string} dlgClass The class name identifier for the dialog.
     * @param {string=} buttonId The button id to use when closing the dialog. Defaults to DIALOG_CANCELED
     */
    function cancelModalDialogIfOpen(dlgClass, buttonId) {
        $("." + dlgClass + ".instance").each(function () {
            if ($(this).is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
                _dismissDialog($(this), buttonId || DIALOG_CANCELED);
            }
        });
    }

    exports.DIALOG_BTN_CANCEL            = DIALOG_BTN_CANCEL;
    exports.DIALOG_BTN_OK                = DIALOG_BTN_OK;
    exports.DIALOG_BTN_DONTSAVE          = DIALOG_BTN_DONTSAVE;
    exports.DIALOG_CANCELED              = DIALOG_CANCELED;
    exports.DIALOG_BTN_DOWNLOAD          = DIALOG_BTN_DOWNLOAD;

    exports.DIALOG_BTN_CLASS_PRIMARY     = DIALOG_BTN_CLASS_PRIMARY;
    exports.DIALOG_BTN_CLASS_NORMAL      = DIALOG_BTN_CLASS_NORMAL;
    exports.DIALOG_BTN_CLASS_LEFT        = DIALOG_BTN_CLASS_LEFT;

    exports.showModalDialog              = showModalDialog;
    exports.showModalDialogUsingTemplate = showModalDialogUsingTemplate;
    exports.cancelModalDialogIfOpen      = cancelModalDialogIfOpen;
});
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Helper.extend({
  compute([value = this.default], options) {
    this.options = options;
    if (!this.value || !this._update) {
      this.value = value;
    }
    let current = this.value;
    let actions = Object.keys(this.actions).reduce((actions, key)=> {
      actions[key] = {
        value: (...args)=> {
          let action = this.actions[key];
          var nextState = this.setState((curr) => action.call(null, curr, ...args));
          sendActionNotification(this, key, nextState);
          return nextState;
        }
      };
      return actions;
    }, {});

    return Object.create(this.wrap(current), actions);
  },

  wrap(value) {
    return value;
  },

  setState(updateFn = (current)=> current) {
    this._update = true;
    var nextState = updateFn.call(this, this.value);
    if (nextState !== this.value) {
      this.value = nextState;
      this.recompute();
      sendActionNotification(this, 'state', nextState);
    }
    return this.value;
  }
});

function sendActionNotification(helper, actionName, state) {
  var actionCallback = helper.options[Ember.String.dasherize(`on-${actionName}`)];
  Ember.sendEvent(helper, actionName, state);
  if (actionCallback && actionCallback.call) {
    actionCallback.call(null, state);
  }
}
<commit_msg>make sure to pass state as event arguments<commit_after>import Ember from 'ember';

export default Ember.Helper.extend({
  compute([value = this.default], options) {
    this.options = options;
    if (!this.value || !this._update) {
      this.value = value;
    }
    let current = this.value;
    let actions = Object.keys(this.actions).reduce((actions, key)=> {
      actions[key] = {
        value: (...args)=> {
          let action = this.actions[key];
          var nextState = this.setState((curr) => action.call(null, curr, ...args));
          sendActionNotification(this, key, nextState);
          return nextState;
        }
      };
      return actions;
    }, {});

    return Object.create(this.wrap(current), actions);
  },

  wrap(value) {
    return value;
  },

  setState(updateFn = (current)=> current) {
    this._update = true;
    var nextState = updateFn.call(this, this.value);
    if (nextState !== this.value) {
      this.value = nextState;
      this.recompute();
      sendActionNotification(this, 'state', nextState);
    }
    return this.value;
  }
});

function sendActionNotification(helper, actionName, state) {
  var actionCallback = helper.options[Ember.String.dasherize(`on-${actionName}`)];
  Ember.sendEvent(helper, actionName, [state]);
  if (actionCallback && actionCallback.call) {
    actionCallback.call(null, state);
  }
}
<|endoftext|>


<commit_before><commit_msg>#40 Populate NewTabUtils links cache before populating most visited links dropdown<commit_after><|endoftext|>


<commit_before>import Ember from 'ember';

Ember.Application.reopen({
  create: function() {
    Ember.assert('You must implement create() for Ember.Application');
  },
  open: function(id) {
    Ember.assert('You must implement open(id) for Ember.Application');
  }
});
<commit_msg>Removed library script. Modifying the Application class doesn't seem to be necessary.<commit_after><|endoftext|>


<commit_before>/*
 * This Source Code is subject to the terms of the Mozilla Public License
 * version 2.0 (the 'License'). You can obtain a copy of the License at
 * http://mozilla.org/MPL/2.0/.
 */

/* global TelemetryController */

const {Cu} = require('chrome');

Cu.import('resource://gre/modules/TelemetryController.jsm');

const { setTimeout, clearTimeout } = require('sdk/timers');
const Events = require('sdk/system/events');
const PrefsService = require('sdk/preferences/service');
const self = require('sdk/self');
const store = require('sdk/simple-storage').storage;

const seedrandom = require('seedrandom');

// Event type for receiving pings from experiments
const EVENT_SEND_METRIC = 'testpilot::send-metric';
const EVENT_RECEIVE_VARIANT_DEFS = 'testpilot::register-variants';
const EVENT_SEND_VARIANTS = 'testpilot::receive-variants';

// Minimum interval of time between main Telemetry pings in ms
const PING_INTERVAL = 24 * 60 * 60 * 1000;

// Interval between Telemetry ping time checks, more frequent than the actual
// ping so we can play catch-up if the computer was asleep at the ping time.
const PING_CHECK_INTERVAL = 10 * 60 * 1000;

// List of preferences we'll override on install & restore on uninstall
const PREFERENCE_OVERRIDES = {
  'toolkit.telemetry.enabled': true,
  'datareporting.healthreport.uploadEnabled': true
};

let pingTimer = null;

const variantMaker = {
  makeTest: function(test) {
    let summedWeight = 0;
    const variants = [];
    test.variants.forEach(variant => {
      summedWeight += variant.weight;
      for (let i = 0; i < variant.weight; i++) {
        variants.push(variant.value);
      }
    });
    const seed = `${test.name}_${store.clientUUID}`;
    return variants[Math.floor(seedrandom(seed)() * summedWeight)];
  },

  parseTests: function(tests) {
    const results = {};
    Object.keys(tests).forEach(key => {
      results[key] = this.makeTest(tests[key]);
    });
    return results;
  }
};

module.exports = {

  init: function() {
    if (!store.telemetryPingPayload) {
      store.lastTelemetryPingTimestamp = false;
      store.telemetryPingPayload = {
        version: 1,
        tests: {}
      };
    }

    this.maybePingTelemetry();

    Events.on(EVENT_SEND_METRIC, this.onExperimentPing);
    Events.on(EVENT_RECEIVE_VARIANT_DEFS, this.onReceiveVariantDefs);
  },

  onEnable: function() {
    // Backup existing preference settings and then override.
    store.metricsPrefsBackup = {};
    Object.keys(PREFERENCE_OVERRIDES).forEach(name => {
      store.metricsPrefsBackup[name] = PrefsService.get(name);
      PrefsService.set(name, PREFERENCE_OVERRIDES[name]);
    });
  },

  onDisable: function() {
    // Restore previous preference settings before override.
    if (store.metricsPrefsBackup) {
      Object.keys(PREFERENCE_OVERRIDES).forEach(name => {
        PrefsService.set(name, store.metricsPrefsBackup[name]);
      });
    }
  },

  destroy: function() {
    // Stop the ping timer, if any
    if (pingTimer) {
      clearTimeout(pingTimer);
    }
    Events.off(EVENT_SEND_METRIC, this.onExperimentPing);
  },

  maybePingTelemetry: function() {
    const now = Date.now();
    const shouldPing =
      // Fresh install, no timestamp. Ping immediately.
      (!store.lastTelemetryPingTimestamp) ||
      // Subsequent pings should go out after PING_INTERVAL
      (now - store.lastTelemetryPingTimestamp > PING_INTERVAL);

    if (shouldPing) {
      store.lastTelemetryPingTimestamp = now;
      this.pingTelemetry();
    }

    pingTimer = setTimeout(
      () => this.maybePingTelemetry(),
      PING_CHECK_INTERVAL
    );
  },

  pingTelemetry: function() {
    TelemetryController.submitExternalPing(
      'testpilot',
      store.telemetryPingPayload,
      { addClientId: true, addEnvironment: true }
    );
  },

  updateExperiment: function(addonId, data) {
    store.telemetryPingPayload.tests[addonId] = Object.assign(
      store.telemetryPingPayload.tests[addonId] || {},
      data
    );
  },

  experimentEnabled: function(addonId) {
    this.updateExperiment(addonId, {last_enabled: Date.now()});
  },

  experimentDisabled: function(addonId) {
    this.updateExperiment(addonId, {last_disabled: Date.now()});
  },

  experimentFeaturesChanged: function(addonId, features) {
    this.updateExperiment(addonId, {features: features});
  },

  onReceiveVariantDefs: function(ev) {
    if (!store.experimentVariants) {
      store.experimentVariants = {};
    }

    const { subject, data } = ev;
    const dataParsed = variantMaker.parseTests(JSON.parse(data));

    store.experimentVariants[subject] = dataParsed;
    this.experimentFeaturesChanged(subject, dataParsed);
    Events.emit(EVENT_SEND_VARIANTS, {
      data: JSON.stringify(dataParsed),
      subject: self.id
    });
  },

  onExperimentPing: function(ev) {
    const { subject, data } = ev;
    const dataParsed = JSON.parse(data);

    // TODO: Map add-on ID (subject) to other pingTypes as necessary
    const pingType = 'testpilottest';

    if (store.experimentVariants && subject in store.experimentVariants) {
      dataParsed.variants = store.experimentVariants[subject];
    }

    const payload = {
      version: 1,
      test: subject,
      payload: dataParsed
    };

    TelemetryController.submitExternalPing(
      pingType, payload,
      { addClientId: true, addEnvironment: true }
    );
  }

};
<commit_msg>Use module-global `Metrics` instead of `this` in metrics.js<commit_after>/*
 * This Source Code is subject to the terms of the Mozilla Public License
 * version 2.0 (the 'License'). You can obtain a copy of the License at
 * http://mozilla.org/MPL/2.0/.
 */

/* global TelemetryController */

const {Cu} = require('chrome');

Cu.import('resource://gre/modules/TelemetryController.jsm');

const { setTimeout, clearTimeout } = require('sdk/timers');
const Events = require('sdk/system/events');
const PrefsService = require('sdk/preferences/service');
const self = require('sdk/self');
const store = require('sdk/simple-storage').storage;

const seedrandom = require('seedrandom');

// Event type for receiving pings from experiments
const EVENT_SEND_METRIC = 'testpilot::send-metric';
const EVENT_RECEIVE_VARIANT_DEFS = 'testpilot::register-variants';
const EVENT_SEND_VARIANTS = 'testpilot::receive-variants';

// Minimum interval of time between main Telemetry pings in ms
const PING_INTERVAL = 24 * 60 * 60 * 1000;

// Interval between Telemetry ping time checks, more frequent than the actual
// ping so we can play catch-up if the computer was asleep at the ping time.
const PING_CHECK_INTERVAL = 10 * 60 * 1000;

// List of preferences we'll override on install & restore on uninstall
const PREFERENCE_OVERRIDES = {
  'toolkit.telemetry.enabled': true,
  'datareporting.healthreport.uploadEnabled': true
};

let pingTimer = null;

const variantMaker = {
  makeTest: function(test) {
    let summedWeight = 0;
    const variants = [];
    test.variants.forEach(variant => {
      summedWeight += variant.weight;
      for (let i = 0; i < variant.weight; i++) {
        variants.push(variant.value);
      }
    });
    const seed = `${test.name}_${store.clientUUID}`;
    return variants[Math.floor(seedrandom(seed)() * summedWeight)];
  },

  parseTests: function(tests) {
    const results = {};
    Object.keys(tests).forEach(key => {
      results[key] = this.makeTest(tests[key]);
    });
    return results;
  }
};

const Metrics = module.exports = {

  init: function() {
    if (!store.telemetryPingPayload) {
      store.lastTelemetryPingTimestamp = false;
      store.telemetryPingPayload = {
        version: 1,
        tests: {}
      };
    }

    Metrics.maybePingTelemetry();

    Events.on(EVENT_SEND_METRIC, Metrics.onExperimentPing);
    Events.on(EVENT_RECEIVE_VARIANT_DEFS, Metrics.onReceiveVariantDefs);
  },

  onEnable: function() {
    // Backup existing preference settings and then override.
    store.metricsPrefsBackup = {};
    Object.keys(PREFERENCE_OVERRIDES).forEach(name => {
      store.metricsPrefsBackup[name] = PrefsService.get(name);
      PrefsService.set(name, PREFERENCE_OVERRIDES[name]);
    });
  },

  onDisable: function() {
    // Restore previous preference settings before override.
    if (store.metricsPrefsBackup) {
      Object.keys(PREFERENCE_OVERRIDES).forEach(name => {
        PrefsService.set(name, store.metricsPrefsBackup[name]);
      });
    }
  },

  destroy: function() {
    // Stop the ping timer, if any
    if (pingTimer) {
      clearTimeout(pingTimer);
    }
    Events.off(EVENT_SEND_METRIC, Metrics.onExperimentPing);
  },

  maybePingTelemetry: function() {
    const now = Date.now();
    const shouldPing =
      // Fresh install, no timestamp. Ping immediately.
      (!store.lastTelemetryPingTimestamp) ||
      // Subsequent pings should go out after PING_INTERVAL
      (now - store.lastTelemetryPingTimestamp > PING_INTERVAL);

    if (shouldPing) {
      store.lastTelemetryPingTimestamp = now;
      Metrics.pingTelemetry();
    }

    pingTimer = setTimeout(
      Metrics.maybePingTelemetry,
      PING_CHECK_INTERVAL
    );
  },

  pingTelemetry: function() {
    TelemetryController.submitExternalPing(
      'testpilot',
      store.telemetryPingPayload,
      { addClientId: true, addEnvironment: true }
    );
  },

  updateExperiment: function(addonId, data) {
    store.telemetryPingPayload.tests[addonId] = Object.assign(
      store.telemetryPingPayload.tests[addonId] || {},
      data
    );
  },

  experimentEnabled: function(addonId) {
    Metrics.updateExperiment(addonId, {last_enabled: Date.now()});
  },

  experimentDisabled: function(addonId) {
    Metrics.updateExperiment(addonId, {last_disabled: Date.now()});
  },

  experimentFeaturesChanged: function(addonId, features) {
    Metrics.updateExperiment(addonId, {features: features});
  },

  onReceiveVariantDefs: function(ev) {
    if (!store.experimentVariants) {
      store.experimentVariants = {};
    }

    const { subject, data } = ev;
    const dataParsed = variantMaker.parseTests(JSON.parse(data));

    store.experimentVariants[subject] = dataParsed;
    Metrics.experimentFeaturesChanged(subject, dataParsed);
    Events.emit(EVENT_SEND_VARIANTS, {
      data: JSON.stringify(dataParsed),
      subject: self.id
    });
  },

  onExperimentPing: function(ev) {
    const { subject, data } = ev;
    const dataParsed = JSON.parse(data);

    // TODO: Map add-on ID (subject) to other pingTypes as necessary
    const pingType = 'testpilottest';

    if (store.experimentVariants && subject in store.experimentVariants) {
      dataParsed.variants = store.experimentVariants[subject];
    }

    const payload = {
      version: 1,
      test: subject,
      payload: dataParsed
    };

    TelemetryController.submitExternalPing(
      pingType, payload,
      { addClientId: true, addEnvironment: true }
    );
  }

};
<|endoftext|>


<commit_before>import Ember from 'ember';
import Semantic from '../semantic';

const EMBER = Ember.A(['class', 'classNames', 'classNameBindings', 'tagName']);

Semantic.BaseMixin = Ember.Mixin.create({
  /// Internal Variables
  _initialized: false,
  _bindableAttrs: null,
  _settableAttrs: null,

  /// EMBER HOOKS
  init() {
    this._super(...arguments);

    if (Ember.isBlank(this.get('module'))) {
      return Ember.Logger.error('A module was not declared on semantic extended type');
    }
    this.set('_bindableAttrs', Ember.A());
    this.set('_settableAttrs', Ember.A());
  },

  didInsertElement() {
    this._super(...arguments);
    this.initSemanticModule();

    // Get the modules settable and gettable properties.
    let settableProperties = Ember.A(Object.keys(this.execute('internal', 'set')));
    let gettableProperties = Ember.A(Object.keys(this.execute('internal', 'get')));

    for (let key in this.attrs) {
      // If it has a settable and gettable attribute, then its bindable
      if (settableProperties.contains(key) && gettableProperties.contains(key)) {
        this.get('_bindableAttrs').addObject(key);
      } else if (settableProperties.contains(key)) {
        // otherwise, its settable only
        this.get('_settableAttrs').addObject(key);
      }
    }
    this.didInitSemantic();
    this.set('_initialized', true);
  },

  willDestroyElement() {
    this._super(...arguments);
    let selector = this.$();
    if (selector != null) {
      let module = selector[this.get('module')];
      if (typeof module === 'function') {
        return module('destroy');
      }
    }
  },

  didUpdateAttrs() {
    this._super(...arguments);
    for (let bindableAttr of this.get('_bindableAttrs')) {
      let attrValue = this._getAttrValue(bindableAttr);
      let moduleValue = this.getSemanticAttr(bindableAttr);
      if (!this.areAttrValuesEqual(bindableAttr, attrValue, moduleValue)) {
        this.updateSemanticAttr(bindableAttr, attrValue);
      }
    }
    for (let settableAttr of this.get('_settableAttrs')) {
      let attrValue = this._getAttrValue(settableAttr);
      this.updateSemanticAttr(settableAttr, attrValue);
    }
  },

  /// Semantic Hooks
  willInitSemantic(settings) {
    // Use this method to modify the settings object on inherited components, before module initialization
  },

  initSemanticModule() {
    let settings = this._settings(this.get("module"));
    this.$()[this.get("module")](settings);
  },

  didInitSemantic() {
    // Use this method after the module is initialized to do post initialized changes
  },

  getSemanticAttr(attrName) {
    this.execute(`get ${attrName}`);
  },

  updateSemanticAttr(attrName, attrValue) {
    this.execute(`set ${attrName}`, attrValue);
  },

  areAttrValuesEqual(attrName, attrValue, moduleValue) {
    // Enhance this
    return attrValue === moduleValue;
  },

  // Semantic Helper Methods
  execute() {
    let selector = this.$();
    if (selector != null) {
      let module = selector[this.get('module')];
      if (typeof module === 'function') {
        return module.apply(this.$(), arguments);
      }
    }
  },

  actions: {
    execute() {
      this.execute(...arguments);
    }
  },

  // Internal Methods
  _getAttrValue(name) {
    let value = this.attrs[name];

    if (Ember.isBlank(value)) {
      return value;
    }

    // if its a mutable object, get the actual value
    if (typeof value === 'object') {
      let objectKeys = Ember.A(Object.keys(value));
      if (objectKeys.any((objectkey) => objectkey.startsWith('MUTABLE_CELL') )) {
        value = value.value;
      }
    }

    return value;
  },

  _settings(module) {
    var component, custom, key, prop, value;

    component = window.$.fn[module];
    if (!component) {
      throw 'Unable to find semantic module: ' + module;
    }

    custom = {
      debug: Semantic.UI_DEBUG,
      performance: Semantic.UI_PERFORMANCE,
      verbose: Semantic.UI_VERBOSE
    };

    for (let key in this.attrs) {
      let value = this._getAttrValue(key);

      if (Ember.isBlank(component.settings[key])) {
        if (!EMBER.contains(key)) {
          Ember.Logger.debug(`You passed in the property '${key}', but a setting doesn't exist on the Semantic UI module: ${module}`);
        }
        continue;
      }

      if (value != null) {
        custom[key] = value;
      }
    }

    // Init, and allow any overrides
    this.willInitSemantic(custom);

    // Late bind any functions over to use the right scope
    for (let key in custom) {
      let value = custom[key];
      if (typeof value === 'function') {
        custom[key] = Ember.run.bind(this, this._updateFunctionWithParameters(key, value));
      }
    }

    return custom;
  },

  _updateFunctionWithParameters(key, fn) {
    return function() {
      var args = [].splice.call(arguments, 0);
      // var internal = this.get(`_${key}`);

      // if (internal) {
      //   internal.apply(this, args);
      // }

      // if (internal !== fn) {
      // return fn.apply(this, [this].concat(args));
      if (this.get('_initialized')) {
        return fn.apply(this, args);
      }
      // }

      // return true;
    };
  }
});

export default Semantic.BaseMixin;
<commit_msg>Adding additional abstractions to mixin base<commit_after>import Ember from 'ember';
import Semantic from '../semantic';

const EMBER = Ember.A(['class', 'classNames', 'classNameBindings', 'tagName']);

Semantic.BaseMixin = Ember.Mixin.create({
  /// Internal Variables
  _initialized: false,
  _bindableAttrs: null,
  _settableAttrs: null,

  /// EMBER HOOKS
  init() {
    this._super(...arguments);

    if (Ember.isBlank(this.getSemanticModuleName())) {
      return Ember.Logger.error('A module was not declared on semantic extended type');
    }
    this.set('_initialized', false);
    this.set('_bindableAttrs', Ember.A());
    this.set('_settableAttrs', Ember.A());
  },

  didInsertElement() {
    this._super(...arguments);
    this.initSemanticModule();

    // Get the modules settable and gettable properties.
    let settableProperties = Ember.A(Object.keys(this.execute('internal', 'set')));
    let gettableProperties = Ember.A(Object.keys(this.execute('internal', 'get')));

    for (let key in this.attrs) {
      // If it has a settable and gettable attribute, then its bindable
      if (settableProperties.contains(key) && gettableProperties.contains(key)) {
        this.get('_bindableAttrs').addObject(key);
      } else if (settableProperties.contains(key)) {
        // otherwise, its settable only
        this.get('_settableAttrs').addObject(key);
      }
    }
    this.didInitSemantic();
    this.set('_initialized', true);
  },

  willDestroyElement() {
    this._super(...arguments);
    this.execute('destroy');
  },

  didUpdateAttrs() {
    this._super(...arguments);
    for (let bindableAttr of this.get('_bindableAttrs')) {
      let attrValue = this._getAttrValue(bindableAttr);
      let moduleValue = this.getSemanticAttr(bindableAttr);
      if (!this.areAttrValuesEqual(bindableAttr, attrValue, moduleValue)) {
        this.setSemanticAttr(bindableAttr, attrValue);
      }
    }
    for (let settableAttr of this.get('_settableAttrs')) {
      let attrValue = this._getAttrValue(settableAttr);
      this.setSemanticAttr(settableAttr, attrValue);
    }
  },

  /// Semantic Hooks
  getSemanticModuleName() {
    return this.get('module');
  },

  getSemanticModule() {
    let selector = this.$();
    if (selector != null) {
      let module = selector[this.getSemanticModuleName()];
      if (typeof module === 'function') {
        return module;
      }
    }
  },

  willInitSemantic(settings) {
    // Use this method to modify the settings object on inherited components, before module initialization
  },

  initSemanticModule() {
    let module = this.getSemanticModule();
    if (module) {
      module(this._settings());
    } else {
      Ember.Logger.error(`The Semantic UI module ${this.getSemanticModuleName()} was not found and did not initialize`);
    }
  },

  didInitSemantic() {
    // Use this method after the module is initialized to do post initialized changes
  },

  getSemanticAttr(attrName) {
    this.execute(`get ${attrName}`);
  },

  setSemanticAttr(attrName, attrValue) {
    this.execute(`set ${attrName}`, attrValue);
  },

  areAttrValuesEqual(attrName, attrValue, moduleValue) {
    // Enhance this
    return attrValue === moduleValue;
  },

  // Semantic Helper Methods
  execute() {
    let module = this.getSemanticModule();
    if (module) {
      return module.apply(this.$(), arguments);
    }
  },

  actions: {
    execute() {
      return this.execute(...arguments);
    }
  },

  // Private Methods
  _getAttrValue(name) {
    let value = this.attrs[name];

    if (Ember.isBlank(value)) {
      return value;
    }

    // if its a mutable object, get the actual value
    if (typeof value === 'object') {
      let objectKeys = Ember.A(Object.keys(value));
      if (objectKeys.any((objectkey) => objectkey.startsWith('MUTABLE_CELL') )) {
        value = value.value;
      }
    }

    return value;
  },

  _settings() {
    let moduleName = this.getSemanticModuleName();

    let component = window.$.fn[moduleName];
    if (!component) {
      Ember.Logger.error(`Unable to find jQuery Semantic UI module: ${moduleName}`);
      return;
    }

    let custom = {
      debug: Semantic.UI_DEBUG,
      performance: Semantic.UI_PERFORMANCE,
      verbose: Semantic.UI_VERBOSE
    };

    for (let key in this.attrs) {
      let value = this._getAttrValue(key);

      if (Ember.isBlank(component.settings[key])) {
        if (!EMBER.contains(key)) {
          // TODO: Add better ember keys here
          Ember.Logger.debug(`You passed in the property '${key}', but a setting doesn't exist on the Semantic UI module: ${moduleName}`);
        }
        continue;
      }

      if (value != null) {
        custom[key] = value;
      }
    }

    // Init, and allow any overrides
    this.willInitSemantic(custom);

    // Late bind any functions over to use the right scope
    for (let key in custom) {
      let value = custom[key];
      if (typeof value === 'function') {
        custom[key] = Ember.run.bind(this, this._updateFunctionWithParameters(key, value));
      }
    }

    return custom;
  },

  _updateFunctionWithParameters(key, fn) {
    return function() {
      // TODO: Allow internal wrapper
      var args = [].splice.call(arguments, 0);
      // var internal = this.get(`_${key}`);

      // if (internal) {
      //   internal.apply(this, args);
      // }

      // if (internal !== fn) {
      // return fn.apply(this, [this].concat(args));
      if (this.get('_initialized')) {
        return fn.apply(this, args);
      }
      // }

      // return true;
    };
  }
});

export default Semantic.BaseMixin;
<|endoftext|>


<commit_before>import Ember from 'ember';
import getOwner from 'ember-getowner-polyfill';

export default Ember.Service.extend({
  fbInitPromise: null,

  FBInit() {
    if (this.fbInitPromise) { return this.fbInitPromise; }

    const ENV = getOwner(this)._lookupFactory('config:environment');

    if (ENV.FB && ENV.FB.skipInit) {
      this.fbInitPromise = Ember.RSVP.Promise.resolve('skip init');
      return this.fbInitPromise;
    }

    var original = window.fbAsyncInit;
    var initSettings = ENV.FB;
    if (!initSettings || !initSettings.appId || !initSettings.version) {
      return Ember.RSVP.reject('No settings for init');
    }

    this.fbInitPromise = new Ember.RSVP.Promise(function(resolve){
      window.fbAsyncInit = function() {
        window.FB.init(initSettings);
        Ember.run(null, resolve);
      };
      Ember.$.getScript('//connect.facebook.net/en_US/sdk.js', function() {
        Ember.run(null, resolve);
      });
    }).then(function() {
      if (original) {
        window.fbAsyncInit = original;
        window.fbAsyncInit();
        window.fbAsyncInit.hasRun = true;
      }
    });

    return this.fbInitPromise;
  },

  setAccessToken(token) {
    this.accessToken = token;
    return token;
  },

  loginWith: function(token) {
    console.warn('DEPRECATED: please, use setAccessToken instead');
    this.setAccessToken(token);
  },

  api(path) {
    var method = 'GET';
    var parameters = {};
    var arg;

    if (!path) { return Ember.RSVP.reject('Please, provide a path for your request'); }

    switch (arguments.length) {
      case 2:
        arg = arguments[1];
        if (typeof arg === 'string') {
          method = arg;
        } else {
          parameters = arg;
        }
        break;
      case 3:
        method = arguments[1];
        parameters = arguments[2];
    }

    parameters = Ember.$.extend(parameters, {access_token: this.accessToken});

    return this.FBInit().then(function() {
      return new Ember.RSVP.Promise(function(resolve, reject) {
        window.FB.api(path, method, parameters, function(response) {
          if (response.error) {
            Ember.run(null, reject, response.error);
            return;
          }

          Ember.run(null, resolve, response);
        });
      });
    });
  },

  ui: function(params) {
    return this.FBInit().then(function() {
      return new Ember.RSVP.Promise(function(resolve, reject) {
        window.FB.ui(params, function(response) {
          if (response && !response.error_code) {
            Ember.run(null, resolve, response);
            return;
          }

          Ember.run(null, reject, response);
        });
      });
    });
  },

  // Facebook Login Methods

  getLoginStatus: function(forceRequest) {
    return this.FBInit().then(function() {
      return new Ember.RSVP.Promise(function(resolve) {
        window.FB.getLoginStatus(function(response) {
          Ember.run(null, resolve, response);
        }, forceRequest);
      });
    });
  },

  login: function(scope) {
    var service = this;
    return this.FBInit().then(function() {
      return new Ember.RSVP.Promise(function(resolve, reject) {
        window.FB.login(function(response) {
          if (response.authResponse) {
            service.accessToken = response.authResponse.accessToken;
            Ember.run(null, resolve, response);
          } else {
            Ember.run(null, reject, response);
          }
        }, {scope: scope});
      });
    });
  },

  logout: function() {
    return this.FBInit().then(function() {
      return new Ember.RSVP.Promise(function(resolve) {
        window.FB.logout(function(response) {
          Ember.run(null, resolve, response);
        });
      });
    });
  },

  getAuthResponse: function() {
    return window.FB.getAuthResponse();
  }
});
<commit_msg>FIX: properly resolve the FBInit promise<commit_after>import Ember from 'ember';
import getOwner from 'ember-getowner-polyfill';

export default Ember.Service.extend({
  fbInitPromise: null,

  FBInit() {
    if (this.fbInitPromise) { return this.fbInitPromise; }

    const ENV = getOwner(this)._lookupFactory('config:environment');

    if (ENV.FB && ENV.FB.skipInit) {
      this.fbInitPromise = Ember.RSVP.Promise.resolve('skip init');
      return this.fbInitPromise;
    }

    var original = window.fbAsyncInit;
    var initSettings = ENV.FB;
    if (!initSettings || !initSettings.appId || !initSettings.version) {
      return Ember.RSVP.reject('No settings for init');
    }

    this.fbInitPromise = new Ember.RSVP.Promise(function(resolve){
      window.fbAsyncInit = function() {
        window.FB.init(initSettings);
        Ember.run(null, resolve);
      };
      Ember.$.getScript('//connect.facebook.net/en_US/sdk.js', function() {
        // Do nothing here, wait for window.fbAsyncInit to be called.
      });
    }).then(function() {
      if (original) {
        window.fbAsyncInit = original;
        window.fbAsyncInit();
        window.fbAsyncInit.hasRun = true;
      }
    });

    return this.fbInitPromise;
  },

  setAccessToken(token) {
    this.accessToken = token;
    return token;
  },

  loginWith: function(token) {
    console.warn('DEPRECATED: please, use setAccessToken instead');
    this.setAccessToken(token);
  },

  api(path) {
    var method = 'GET';
    var parameters = {};
    var arg;

    if (!path) { return Ember.RSVP.reject('Please, provide a path for your request'); }

    switch (arguments.length) {
      case 2:
        arg = arguments[1];
        if (typeof arg === 'string') {
          method = arg;
        } else {
          parameters = arg;
        }
        break;
      case 3:
        method = arguments[1];
        parameters = arguments[2];
    }

    parameters = Ember.$.extend(parameters, {access_token: this.accessToken});

    return this.FBInit().then(function() {
      return new Ember.RSVP.Promise(function(resolve, reject) {
        window.FB.api(path, method, parameters, function(response) {
          if (response.error) {
            Ember.run(null, reject, response.error);
            return;
          }

          Ember.run(null, resolve, response);
        });
      });
    });
  },

  ui: function(params) {
    return this.FBInit().then(function() {
      return new Ember.RSVP.Promise(function(resolve, reject) {
        window.FB.ui(params, function(response) {
          if (response && !response.error_code) {
            Ember.run(null, resolve, response);
            return;
          }

          Ember.run(null, reject, response);
        });
      });
    });
  },

  // Facebook Login Methods

  getLoginStatus: function(forceRequest) {
    return this.FBInit().then(function() {
      return new Ember.RSVP.Promise(function(resolve) {
        window.FB.getLoginStatus(function(response) {
          Ember.run(null, resolve, response);
        }, forceRequest);
      });
    });
  },

  login: function(scope) {
    var service = this;
    return this.FBInit().then(function() {
      return new Ember.RSVP.Promise(function(resolve, reject) {
        window.FB.login(function(response) {
          if (response.authResponse) {
            service.accessToken = response.authResponse.accessToken;
            Ember.run(null, resolve, response);
          } else {
            Ember.run(null, reject, response);
          }
        }, {scope: scope});
      });
    });
  },

  logout: function() {
    return this.FBInit().then(function() {
      return new Ember.RSVP.Promise(function(resolve) {
        window.FB.logout(function(response) {
          Ember.run(null, resolve, response);
        });
      });
    });
  },

  getAuthResponse: function() {
    return window.FB.getAuthResponse();
  }
});
<|endoftext|>


<commit_before>const PROJECT_URL = 'https://github.com/jasonmit/ember-intl';

export default {
  unsetLocale: `${PROJECT_URL}#setting-runtime-locale`,
  asyncTranslations: `${PROJECT_URL}/blob/master/docs/asynchronously-loading-translations.md#asynchronous-loading-of-translations`,
  polyfill: `${PROJECT_URL}/blob/master/docs/intljs-polyfill.md`
};
<commit_msg>Update links.js with new guide URL (#925)<commit_after>const GUIDE_URL = 'https://ember-intl.github.io/ember-intl/docs/guide/';

export default {
  unsetLocale: `${GUIDE_URL}ember-service-api#locale`,
  asyncTranslations: `${GUIDE_URL}asynchronously-loading-translations`,
  polyfill: `${GUIDE_URL}intljs-polyfill`
};
<|endoftext|>


<commit_before>(function(ctx){

  // Substance.RemoteStore Interface
  // -------

  var RemoteStore = function(options) {
    var that = this;
    this.client = options.client;

    // Create a new document
    // -------

    this.create = function(id, cb) {
      this.client.createDocument(id, cb);
    };

    // Get document by id
    // -------

    this.getInfo = function(id, cb) {
      this.client.getDocumentInfo(id, cb);
    };

    this.get = function(id, cb) {
      this.client.getDocument(id, cb);
    };

    // List all allowed documents complete with metadata
    // -------

    // TODO: Currently the hub returns a hash for documents, should be a list!
    this.list = function (cb) {
      this.client.listDocuments(cb);
    };

    // Permanently deletes a document
    // -------

    this.delete = function (id, cb) {
      this.client.deleteDocument(id, cb);
    };

    // Checks if a document exists
    // -------

    this.exists = function (id, cb) {
      this.client.listDocuments(function(err, docs) {
        if (err) cb(err);
        cb(null, (id in docs));
      });
    };

    // Retrieves a range of the document's commits
    // -------

    this.commits = function(id, last, since, cb) {
      this.client.documentCommits(id, last, since, cb);
    };

    // Stores a sequence of commits for a given document id.
    // -------

    // TODO: update original API so they also take meta and refs
    this.update = function(id, options, cb) {
      this.client.updateDocument(id, options, cb);
    };

    this.getRefs = function(id, branch, cb) {
      that.client.getDocumentInfo(id, function(err, doc) {
        if (err) return cb(err);
        cb(null, doc.refs[branch]);
      });
    }

    this.createBlob = function(docId, blobId, data, cb) {
      return this.client.createBlob(docId, blobId, data, cb);
    }

    this.getBlob = function(docId, blobId, data, cb) {
      return this.client.getBlob(docId, blobId, data, cb);
    }

    this.deleteBlob = function(docId, blobId, cb) {
      return this.client.deleteBlob(docId, blobId, cb);
    }

    this.listBlobs = function(docId, cb) {
      return this.client.listBlobs(docId, cb);
    }

  };

  // Exports
  if (typeof exports !== 'undefined') {
    exports.RemoteStore = RemoteStore;
  } else {
    if (!ctx.Substance) ctx.Substance = {};
    ctx.Substance.RemoteStore = RemoteStore;
  }
})(this);
<commit_msg>Create with options.<commit_after>(function(ctx){

  // Substance.RemoteStore Interface
  // -------

  var RemoteStore = function(options) {
    var that = this;
    this.client = options.client;

    // Create a new document
    // -------

    this.create = function(id, options, cb) {
      this.client.createDocument(id, options, cb);
    };

    // Get document by id
    // -------

    this.getInfo = function(id, cb) {
      this.client.getDocumentInfo(id, cb);
    };

    this.get = function(id, cb) {
      this.client.getDocument(id, cb);
    };

    // List all allowed documents complete with metadata
    // -------

    // TODO: Currently the hub returns a hash for documents, should be a list!
    this.list = function (cb) {
      this.client.listDocuments(cb);
    };

    // Permanently deletes a document
    // -------

    this.delete = function (id, cb) {
      this.client.deleteDocument(id, cb);
    };

    // Checks if a document exists
    // -------

    this.exists = function (id, cb) {
      this.client.listDocuments(function(err, docs) {
        if (err) cb(err);
        cb(null, (id in docs));
      });
    };

    // Retrieves a range of the document's commits
    // -------

    this.commits = function(id, last, since, cb) {
      this.client.documentCommits(id, last, since, cb);
    };

    // Stores a sequence of commits for a given document id.
    // -------

    // TODO: update original API so they also take meta and refs
    this.update = function(id, options, cb) {
      this.client.updateDocument(id, options, cb);
    };

    this.getRefs = function(id, branch, cb) {
      that.client.getDocumentInfo(id, function(err, doc) {
        if (err) return cb(err);
        cb(null, doc.refs[branch]);
      });
    }

    this.createBlob = function(docId, blobId, data, cb) {
      return this.client.createBlob(docId, blobId, data, cb);
    }

    this.getBlob = function(docId, blobId, data, cb) {
      return this.client.getBlob(docId, blobId, data, cb);
    }

    this.deleteBlob = function(docId, blobId, cb) {
      return this.client.deleteBlob(docId, blobId, cb);
    }

    this.listBlobs = function(docId, cb) {
      return this.client.listBlobs(docId, cb);
    }

  };

  // Exports
  if (typeof exports !== 'undefined') {
    exports.RemoteStore = RemoteStore;
  } else {
    if (!ctx.Substance) ctx.Substance = {};
    ctx.Substance.RemoteStore = RemoteStore;
  }
})(this);
<|endoftext|>


<commit_before>import * as renderUtils from './utils';

/**
 * Render an individual page: replace its `contents` property
 * with compiled contents
 */
function renderPage (page, drizzleData) {
  page.contents = renderUtils.applyTemplate(page.contents,
    renderUtils.localContext(page, drizzleData),
    drizzleData.options);
}

/**
 * Traverse pages data object. render individual pages
 */
function walkPages (pages, drizzleData) {
  if (pages.contents) {
    return renderPage(pages, drizzleData);
  }
  for (var pageKey in pages) {
    walkPages(pages[pageKey], drizzleData);
  }
  return drizzleData;
}

function renderPages (drizzleData) {
  return walkPages(drizzleData.pages, drizzleData);
}

export default renderPages;
<commit_msg>Extend layouts and wrap pages<commit_after>import * as renderUtils from './utils';

function wrapWithLayout (page, drizzleData) {
  const layout = page.layout || 'default';
  return `  {{#extend '${layout}' }}
    {{#content 'body'}}${page.contents}{{/content}}
  {{/extend}}`;
}

/**
 * Render an individual page: replace its `contents` property
 * with compiled contents
 */
function renderPage (page, drizzleData) {
  page.contents = renderUtils.applyTemplate(
    wrapWithLayout(page, drizzleData),
    renderUtils.localContext(page, drizzleData),
    drizzleData.options);
}

/**
 * Traverse pages data object. render individual pages
 */
function walkPages (pages, drizzleData) {
  if (pages.contents) {
    return renderPage(pages, drizzleData);
  }
  for (var pageKey in pages) {
    walkPages(pages[pageKey], drizzleData);
  }
  return drizzleData;
}

function renderPages (drizzleData) {
  return walkPages(drizzleData.pages, drizzleData);
}

export default renderPages;
<|endoftext|>


<commit_before>/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule React
 */

'use strict';

var ReactChildren = require('ReactChildren');
var ReactComponent = require('ReactComponent');
var ReactPureComponent = require('ReactPureComponent');
var ReactClass = require('ReactClass');
var ReactDOMFactories = require('ReactDOMFactories');
var ReactElement = require('ReactElement');
var ReactPropTypes = require('ReactPropTypes');
var ReactVersion = require('ReactVersion');

var onlyChild = require('onlyChild');
var warning = require('warning');

var createElement = ReactElement.createElement;
var createFactory = ReactElement.createFactory;
var cloneElement = ReactElement.cloneElement;

if (__DEV__) {
  var ReactElementValidator = require('ReactElementValidator');
  createElement = ReactElementValidator.createElement;
  createFactory = ReactElementValidator.createFactory;
  cloneElement = ReactElementValidator.cloneElement;
}

var __spread = Object.assign;

if (__DEV__) {
  var warned = false;
  __spread = function() {
    warning(
      warned,
      'React.__spread is deprecated and should not be used. Use ' +
      'Object.assign directly or another helper function with similar ' +
      'semantics. You may be seeing this warning due to your compiler. ' +
      'See https://fb.me/react-spread-deprecation for more details.'
    );
    warned = true;
    return Object.assign.apply(null, arguments);
  };
}

var React = {

  // Modern

  Children: {
    map: ReactChildren.map,
    forEach: ReactChildren.forEach,
    count: ReactChildren.count,
    toArray: ReactChildren.toArray,
    only: onlyChild,
  },

  Component: ReactComponent,
  PureComponent: ReactPureComponent,

  createElement: createElement,
  cloneElement: cloneElement,
  isValidElement: ReactElement.isValidElement,

  // Classic

  PropTypes: ReactPropTypes,
  createClass: ReactClass.createClass,
  createFactory: createFactory,
  createMixin: function(mixin) {
    // Currently a noop. Will be used to validate and trace mixins.
    return mixin;
  },

  // This looks DOM specific but these are actually isomorphic helpers
  // since they are just generating DOM strings.
  DOM: ReactDOMFactories,

  version: ReactVersion,

  // Deprecated hook for JSX spread, don't use this for anything.
  __spread: __spread,
};

module.exports = React;
<commit_msg>Deprecate React.createMixin<commit_after>/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule React
 */

'use strict';

var ReactChildren = require('ReactChildren');
var ReactComponent = require('ReactComponent');
var ReactPureComponent = require('ReactPureComponent');
var ReactClass = require('ReactClass');
var ReactDOMFactories = require('ReactDOMFactories');
var ReactElement = require('ReactElement');
var ReactPropTypes = require('ReactPropTypes');
var ReactVersion = require('ReactVersion');

var onlyChild = require('onlyChild');
var warning = require('warning');

var createElement = ReactElement.createElement;
var createFactory = ReactElement.createFactory;
var cloneElement = ReactElement.cloneElement;

if (__DEV__) {
  var ReactElementValidator = require('ReactElementValidator');
  createElement = ReactElementValidator.createElement;
  createFactory = ReactElementValidator.createFactory;
  cloneElement = ReactElementValidator.cloneElement;
}

var __spread = Object.assign;
var createMixin = function(mixin) {
  return mixin;
};

if (__DEV__) {
  var warnedForSpread = false;
  var warnedForCreateMixin = false;
  __spread = function() {
    warning(
      warnedForSpread,
      'React.__spread is deprecated and should not be used. Use ' +
      'Object.assign directly or another helper function with similar ' +
      'semantics. You may be seeing this warning due to your compiler. ' +
      'See https://fb.me/react-spread-deprecation for more details.'
    );
    warnedForSpread = true;
    return Object.assign.apply(null, arguments);
  };

  createMixin = function(mixin) {
    warning(
      warnedForCreateMixin,
      'React.createMixin is deprecated and should not be used. You ' +
      'can use your mixin directly instead.'
    );
    warnedForCreateMixin = true;
    return mixin;
  };

}

var React = {

  // Modern

  Children: {
    map: ReactChildren.map,
    forEach: ReactChildren.forEach,
    count: ReactChildren.count,
    toArray: ReactChildren.toArray,
    only: onlyChild,
  },

  Component: ReactComponent,
  PureComponent: ReactPureComponent,

  createElement: createElement,
  cloneElement: cloneElement,
  isValidElement: ReactElement.isValidElement,

  // Classic

  PropTypes: ReactPropTypes,
  createClass: ReactClass.createClass,
  createFactory: createFactory,
  createMixin: createMixin,

  // This looks DOM specific but these are actually isomorphic helpers
  // since they are just generating DOM strings.
  DOM: ReactDOMFactories,

  version: ReactVersion,

  // Deprecated hook for JSX spread, don't use this for anything.
  __spread: __spread,
};

module.exports = React;
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {List} from 'immutable';
import * as UserState from '../../modules/user/UserState';
import * as NavigationState from '../../modules/navigation/NavigationState';
import SpeechBubble from '../../components/SpeechBubble';
import Hemmo from '../../components/Hemmo';
import {getSizeByHeight, getSizeByWidth, getImage} from '../../services/graphics';

import {
  TouchableOpacity,
  Image,
  View
} from 'react-native';

let moods = require('../../data/moods.js');
let styles = require('./styles.js');

const MoodViewContainer = React.createClass({

  propTypes: {
    activityIndex: PropTypes.number.isRequired,
    dispatch: PropTypes.func
  },

  getInitialState() {
    return {
      selectedMoods: List(),
      showBubble: true
    };
  },

  hideBubble() {
    this.setState({showBubble: false});
  },

  restartAudioAndText() {
    this.setState({showBubble: true});
  },

  renderBubble(text) {
    return (
      <SpeechBubble
        text={text}
        bubbleType={'puhekupla_oikea'}
        hideBubble={this.hideBubble}
        style={{top: 110, left: 100, margin: 30, fontSize: 14, size: 0.5}}
      />
    );
  },

  save() {
    this.props.dispatch(UserState.saveAnswer(null, 'moods', this.state.selectedMoods));
    this.props.dispatch(NavigationState.pushRoute({key: 'Record', allowReturn: true}));
  },

  selectMood(mood) {
    let selectedMoods = this.state.selectedMoods;

    // If the mood is already selected, remove it from the list. Otherwise, add it
    this.setState({
      selectedMoods: selectedMoods.includes(mood) ? selectedMoods.filter(m => m !== mood) : selectedMoods.concat(mood)
    });
  },

  renderMood(mood, key) {
    return (
      <TouchableOpacity
        key={key}
        style={styles.mood}
        onPress={this.selectMood.bind(this, mood)}>
        <Image
          source={getImage(mood)}
          style={[
            getSizeByWidth(mood, 0.17),
            key === moods.length - 1 ? {marginBottom: getSizeByWidth('ympyra_keski', 0.17).height + 5} : null
          ]}
        />
        {this.state.selectedMoods.includes(mood) ? this.renderCheckmark() : null}
      </TouchableOpacity>
    );
  },

  renderCheckmark() {
    return (
      <Image
        source={getImage('valittu')}
        style={[styles.check, getSizeByHeight('valittu', 0.1)]}
      />
    );
  },

  renderMoods() {
    return (
      <View style={styles.moodColumn}>
        {moods.map((mood, key) => this.renderMood(mood, key))}
      </View>
    );
  },

  renderHemmo() {
    return (
      <View style={styles.hemmo}>
        <Hemmo
          key={'hemmo'}
          image={'hemmo_keski'}
          size={0.45} restartAudioAndText={this.restartAudioAndText}
        />
      </View>
    );
  },

  renderNextButton() {
    return (
      <TouchableOpacity onPress={this.save} style={styles.saveButton}>
        <Image source={getImage('nappula_seuraava')} style={getSizeByHeight('nappula_seuraava', 0.1)}/>
      </TouchableOpacity>
    );
  },

  render() {
    return (
      <Image source={getImage('tausta_perus2')} style={styles.container}>
        <Image source={getImage('tausta_levea')} style={styles.moodContainer}>
          {this.renderMoods()}
          {this.renderHemmo()}
          {this.renderNextButton()}
          {this.state.showBubble ? this.renderBubble('moods') : null}
        </Image>
      </Image>
    );
  }
});

export default connect(
  state => ({
    activityIndex: state.getIn(['user', 'currentUser', 'activityIndex'])
  })
)(MoodViewContainer);
<commit_msg>Refactor MoodViewContainer<commit_after>import React, {PropTypes, Component} from 'react';
import {connect} from 'react-redux';
import {List} from 'immutable';
import {pushRoute} from '../navigation/NavigationState';
import {saveAnswer} from '../user/UserState';
import SpeechBubble from '../../components/SpeechBubble';
import Hemmo from '../../components/Hemmo';
import {getSizeByHeight, getSizeByWidth, getImage} from '../../services/graphics';
import {
  TouchableOpacity,
  Image,
  View
} from 'react-native';

let moods = require('../../data/moods.js');
let styles = require('./styles.js');

const mapStateToProps = state => ({
  activityIndex: state.getIn(['user', 'currentUser', 'activityIndex'])
});

const mapDispatchToProps = dispatch => ({
  saveAnswer: (index, destination, answers) => dispatch(saveAnswer(index, destination, answers)),
  pushRoute: (key) => dispatch(pushRoute(key))
});

@connect(mapStateToProps, mapDispatchToProps)
export default class MoodViewContainer extends Component {

  static propTypes = {
    activityIndex: PropTypes.number.isRequired,
    saveAnswer: PropTypes.func.isRequired,
    pushRoute: PropTypes.func.isRequired
  };

  state = {
    selectedMoods: List(),
    showBubble: true
  };

  hideBubble = () => {
    this.setState({showBubble: false});
  };

  restartAudioAndText = () => {
    this.setState({showBubble: true});
  };

  renderBubble = (text) => {
    return (
      <SpeechBubble
        text={text}
        bubbleType={'puhekupla_oikea'}
        hideBubble={this.hideBubble}
        style={{top: 110, left: 100, margin: 30, fontSize: 14, size: 0.5}}
      />
    );
  };

  save = () => {
    this.props.saveAnswer(null, 'moods', this.state.selectedMoods);
    this.props.pushRoute({key: 'Record', allowReturn: true});
  };

  selectMood = (mood) => {
    let selectedMoods = this.state.selectedMoods;

    // If the mood is already selected, remove it from the list. Otherwise, add it
    this.setState({
      selectedMoods: selectedMoods.includes(mood) ? selectedMoods.filter(m => m !== mood) : selectedMoods.concat(mood)
    });
  };

  renderMood = (mood, key) => {
    return (
      <TouchableOpacity
        key={key}
        style={styles.mood}
        onPress={() => this.selectMood(mood)}>
        <Image
          source={getImage(mood)}
          style={[
            getSizeByWidth(mood, 0.17),
            key === moods.length - 1 ? {marginBottom: getSizeByWidth('ympyra_keski', 0.17).height + 5} : null
          ]}
        />
        {this.state.selectedMoods.includes(mood) ? this.renderCheckmark() : null}
      </TouchableOpacity>
    );
  };

  renderCheckmark = () => {
    return (
      <Image
        source={getImage('valittu')}
        style={[styles.check, getSizeByHeight('valittu', 0.1)]}
      />
    );
  };

  renderMoods = () => {
    return (
      <View style={styles.moodColumn}>
        {moods.map((mood, key) => this.renderMood(mood, key))}
      </View>
    );
  };

  renderHemmo = () => {
    return (
      <View style={styles.hemmo}>
        <Hemmo
          key={'hemmo'}
          image={'hemmo_keski'}
          size={0.45} restartAudioAndText={this.restartAudioAndText}
        />
      </View>
    );
  };

  renderNextButton = () => {
    return (
      <TouchableOpacity onPress={this.save} style={styles.saveButton}>
        <Image source={getImage('nappula_seuraava')} style={getSizeByHeight('nappula_seuraava', 0.1)}/>
      </TouchableOpacity>
    );
  };

  render() {
    return (
      <Image source={getImage('tausta_perus2')} style={styles.container}>
        <Image source={getImage('tausta_levea')} style={styles.moodContainer}>
          {this.renderMoods()}
          {this.renderHemmo()}
          {this.renderNextButton()}
          {this.state.showBubble ? this.renderBubble('moods') : null}
        </Image>
      </Image>
    );
  }
}
<|endoftext|>