<commit_before>const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");

var self = this, icon, prefHandlers = [];

function include(path) {
  Services.scriptloader.loadSubScript(addon.getResourceURI(path).spec, self);
}

var addon = {
  getResourceURI: function(filePath) {
    return {spec: __SCRIPT_URI_SPEC__ + "/../" + filePath};
  }
};

function $(node, childId) {
  if (node.getElementById) {
    return node.getElementById(childId);
  } else {
    return node.querySelector("#" + childId);
  }
}

var prefsObserver = {
  observe: function(subject, topic, data) {
    if (topic == "nsPref:changed") {
      prefHandlers.forEach(function(func) {func(data);});
    }
  }
};

function modify(window) {
  if (!window) return;

  let doc = window.document,
      win = doc.querySelector("window");

  // Add toolbar button
  let button = doc.createElement("toolbarbutton");
  button.setAttribute("id", BUTTON_ID);
  button.setAttribute("label", _("label"));
  button.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional");
  button.setAttribute("tooltiptext", _("tooltip"));
  button.style.listStyleImage = "url(" + icon + ")";
  button.addEventListener("command", main.action, false);
  restorePosition(doc, button);

  // Add shortcut
  let keyset = appendKeyset(doc, win);

  let handlerIdx = prefHandlers.push(function(name) {
    switch (name) {
    case PREF_SHORTCUT_KEY:
    case PREF_SHORTCUT_MODIFIERS:
      win.removeChild(keyset);
      keyset = appendKeyset(doc, win);
    }
  }) - 1;

  if (getPref(PREF_MENU_ITEM)) {
    // Bookmarks toolbar menu
    let menuItem = doc.createElement("menuitem"),
        bookmarksItem = $(doc, "BMB_bookmarksToolbar");

    if (!bookmarksItem) { // not on any of the toolbars
      let palette = $(doc, "navigator-toolbox").palette;
      bookmarksItem = $(palette, "BMB_bookmarksToolbar");
    }

    menuItem.setAttribute("class", "menuitem-iconic");
    menuItem.setAttribute("label", _("label"));
    menuItem.setAttribute("key", KEY_ID);
    menuItem.style.listStyleImage = "url(" + icon + ")";
    menuItem.addEventListener("command", main.action, false);
    bookmarksItem.parentNode.insertBefore(menuItem,
                                          bookmarksItem.previousSibling);

    // Main bookmarks menu
    let menuItem2 = menuItem.cloneNode(),
        allTabsItem = $(doc, "menu_bookmarkAllTabs");
    menuItem2.addEventListener("command", main.action, false);
    allTabsItem.parentNode.insertBefore(menuItem2, allTabsItem);

    // Appmenu bookmarks menu
    let menuItem3 = menuItem.cloneNode(),
        appmenuItem = $(doc, "appmenu_bookmarksToolbar");
    menuItem3.addEventListener("command", main.action, false);
    appmenuItem.parentNode.insertBefore(menuItem3, appmenuItem.previousSibling);

    unload(function() {
      menuItem.parentNode.removeChild(menuItem);
      allTabsItem.parentNode.removeChild(menuItem2);
      appmenuItem.parentNode.removeChild(menuItem3);
    }, window);
  }

  unload(function() {
    button.parentNode.removeChild(button);
    keyset.parentNode.removeChild(keyset);
    prefHandlers.splice(handlerIdx, 1);
  }, window);
}

function appendKeyset(doc, win) {
  let keyset = doc.createElement("keyset");
  keyset.setAttribute("id", KEYSET_ID);
  let shortcut = getPref(PREF_SHORTCUT_KEY);

  if (shortcut) {
    let replaceKey = doc.createElement("key");
    replaceKey.setAttribute("id", KEY_ID);
    replaceKey.setAttribute("key", shortcut);
    replaceKey.setAttribute("modifiers", getPref(PREF_SHORTCUT_MODIFIERS));
    replaceKey.setAttribute("oncommand", "void(0);");
    replaceKey.addEventListener("command", main.action, true);
    keyset.appendChild(replaceKey);
  }

  win.appendChild(keyset);
  return keyset;
}

function makeOptionsObserver (id) {
  return {
    observe: function(subject, topic, data) {
      if (data == id) {
        var doc = subject;
        var menulist = doc.getElementById("bmreplace-keys-menulist");
        if (menulist.childNodes.length == 0) {
          let popup = doc.createElement("menupopup"),
              a = "a".charCodeAt(0),
              z = "z".charCodeAt(0),
              none = doc.createElement("menuitem");

          none.setAttribute("value", "");
          none.setAttribute("label", "None");
          popup.appendChild(none);

          for (let c = a; c <= z; c++) {
            let item = doc.createElement("menuitem"),
                s = String.fromCharCode(c);
            item.setAttribute("value", s);
            item.setAttribute("label", s);
            popup.appendChild(item);
          };
          menulist.appendChild(popup);
        }
        menulist.value = getPref(PREF_SHORTCUT_KEY);
      }
    }
  };
};

function startup(data, reason) {
  include("content/main.js");
  include("content/bookmarks.js");
  include("includes/utils.js");
  include("includes/l10n.js");
  include("includes/buttons.js");
  include("includes/prefs.js");
  icon = addon.getResourceURI("content/icon.png").spec;

  l10n(addon, "bmr.properties");
  unload(l10n.unload);

  let branch = Services.prefs.getBranch(PREF_BRANCH);

  if (ADDON_INSTALL == reason) {
    setDefaultPosition(BUTTON_ID, "nav-bar", "downloads-button");
    branch.setCharPref(PREF_VERSION, data.version);
  };

  if (ADDON_UPGRADE == reason) {
    upgrade(data.version);
  }

  setDefaultPrefs();

  watchWindows(modify, "navigator:browser");

  let optionsObserver = makeOptionsObserver(data.id);

  branch.addObserver("", prefsObserver, false);
  Services.obs.addObserver(optionsObserver, "addon-options-displayed", false);

  unload(function() {
    branch.removeObserver("", prefsObserver);
    Services.obs.removeObserver(optionsObserver, "addon-options-displayed");
  });
};

function shutdown(data, reason) { unload(); };

function install() {}

function uninstall() {}

function upgrade(version) {
  let lastVersion = getPref(PREF_VERSION),
      prefs = Services.prefs;

  if (lastVersion < "1.2") {
    prefs.deleteBranch("extensions.bmreplace.button-position.");
  }

  if (lastVersion < "1.3") {
    let ktTag = "keep-title";
    try {
      let name = "extensions.bmreplace.keep-title-tag";
      ktTag = prefs.getCharPref(name);
      prefs.deleteBranch(name);
    } catch(e) {}

    let bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
          .getService(Ci.nsINavBookmarksService),
        ts = Cc["@mozilla.org/browser/tagging-service;1"]
          .getService(Ci.nsITaggingService);
    for each (let uri in ts.getURIsForTag(ktTag)) {
      for each (let id in bms.getBookmarkIdsForURI(uri)) {
        bm.setKeepTitle(id, true);
      }
    }
  }

  getPref.branch.setCharPref(PREF_VERSION, version);
}
<commit_msg>Declare some globals<commit_after>/*global Components, Services, restorePosition, main, bm,
  getPref, unload, watchWindows, __SCRIPT_URI_SPEC__, l10n,
  setDefaultPosition, setDefaultPrefs */
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");

var self = this, icon, prefHandlers = [];

function include(path) {
  Services.scriptloader.loadSubScript(addon.getResourceURI(path).spec, self);
}

var addon = {
  getResourceURI: function(filePath) {
    return {spec: __SCRIPT_URI_SPEC__ + "/../" + filePath};
  }
};

function $(node, childId) {
  if (node.getElementById) {
    return node.getElementById(childId);
  } else {
    return node.querySelector("#" + childId);
  }
}

var prefsObserver = {
  observe: function(subject, topic, data) {
    if (topic == "nsPref:changed") {
      prefHandlers.forEach(function(func) {func(data);});
    }
  }
};

function modify(window) {
  if (!window) return;

  let doc = window.document,
      win = doc.querySelector("window");

  // Add toolbar button
  let button = doc.createElement("toolbarbutton");
  button.setAttribute("id", BUTTON_ID);
  button.setAttribute("label", _("label"));
  button.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional");
  button.setAttribute("tooltiptext", _("tooltip"));
  button.style.listStyleImage = "url(" + icon + ")";
  button.addEventListener("command", main.action, false);
  restorePosition(doc, button);

  // Add shortcut
  let keyset = appendKeyset(doc, win);

  let handlerIdx = prefHandlers.push(function(name) {
    switch (name) {
    case PREF_SHORTCUT_KEY:
    case PREF_SHORTCUT_MODIFIERS:
      win.removeChild(keyset);
      keyset = appendKeyset(doc, win);
    }
  }) - 1;

  if (getPref(PREF_MENU_ITEM)) {
    // Bookmarks toolbar menu
    let menuItem = doc.createElement("menuitem"),
        bookmarksItem = $(doc, "BMB_bookmarksToolbar");

    if (!bookmarksItem) { // not on any of the toolbars
      let palette = $(doc, "navigator-toolbox").palette;
      bookmarksItem = $(palette, "BMB_bookmarksToolbar");
    }

    menuItem.setAttribute("class", "menuitem-iconic");
    menuItem.setAttribute("label", _("label"));
    menuItem.setAttribute("key", KEY_ID);
    menuItem.style.listStyleImage = "url(" + icon + ")";
    menuItem.addEventListener("command", main.action, false);
    bookmarksItem.parentNode.insertBefore(menuItem,
                                          bookmarksItem.previousSibling);

    // Main bookmarks menu
    let menuItem2 = menuItem.cloneNode(),
        allTabsItem = $(doc, "menu_bookmarkAllTabs");
    menuItem2.addEventListener("command", main.action, false);
    allTabsItem.parentNode.insertBefore(menuItem2, allTabsItem);

    // Appmenu bookmarks menu
    let menuItem3 = menuItem.cloneNode(),
        appmenuItem = $(doc, "appmenu_bookmarksToolbar");
    menuItem3.addEventListener("command", main.action, false);
    appmenuItem.parentNode.insertBefore(menuItem3, appmenuItem.previousSibling);

    unload(function() {
      menuItem.parentNode.removeChild(menuItem);
      allTabsItem.parentNode.removeChild(menuItem2);
      appmenuItem.parentNode.removeChild(menuItem3);
    }, window);
  }

  unload(function() {
    button.parentNode.removeChild(button);
    keyset.parentNode.removeChild(keyset);
    prefHandlers.splice(handlerIdx, 1);
  }, window);
}

function appendKeyset(doc, win) {
  let keyset = doc.createElement("keyset");
  keyset.setAttribute("id", KEYSET_ID);
  let shortcut = getPref(PREF_SHORTCUT_KEY);

  if (shortcut) {
    let replaceKey = doc.createElement("key");
    replaceKey.setAttribute("id", KEY_ID);
    replaceKey.setAttribute("key", shortcut);
    replaceKey.setAttribute("modifiers", getPref(PREF_SHORTCUT_MODIFIERS));
    replaceKey.setAttribute("oncommand", "void(0);");
    replaceKey.addEventListener("command", main.action, true);
    keyset.appendChild(replaceKey);
  }

  win.appendChild(keyset);
  return keyset;
}

function makeOptionsObserver (id) {
  return {
    observe: function(subject, topic, data) {
      if (data == id) {
        var doc = subject;
        var menulist = doc.getElementById("bmreplace-keys-menulist");
        if (menulist.childNodes.length == 0) {
          let popup = doc.createElement("menupopup"),
              a = "a".charCodeAt(0),
              z = "z".charCodeAt(0),
              none = doc.createElement("menuitem");

          none.setAttribute("value", "");
          none.setAttribute("label", "None");
          popup.appendChild(none);

          for (let c = a; c <= z; c++) {
            let item = doc.createElement("menuitem"),
                s = String.fromCharCode(c);
            item.setAttribute("value", s);
            item.setAttribute("label", s);
            popup.appendChild(item);
          };
          menulist.appendChild(popup);
        }
        menulist.value = getPref(PREF_SHORTCUT_KEY);
      }
    }
  };
};

function startup(data, reason) {
  include("content/main.js");
  include("content/bookmarks.js");
  include("includes/utils.js");
  include("includes/l10n.js");
  include("includes/buttons.js");
  include("includes/prefs.js");
  icon = addon.getResourceURI("content/icon.png").spec;

  l10n(addon, "bmr.properties");
  unload(l10n.unload);

  let branch = Services.prefs.getBranch(PREF_BRANCH);

  if (ADDON_INSTALL == reason) {
    setDefaultPosition(BUTTON_ID, "nav-bar", "downloads-button");
    branch.setCharPref(PREF_VERSION, data.version);
  };

  if (ADDON_UPGRADE == reason) {
    upgrade(data.version);
  }

  setDefaultPrefs();

  watchWindows(modify, "navigator:browser");

  let optionsObserver = makeOptionsObserver(data.id);

  branch.addObserver("", prefsObserver, false);
  Services.obs.addObserver(optionsObserver, "addon-options-displayed", false);

  unload(function() {
    branch.removeObserver("", prefsObserver);
    Services.obs.removeObserver(optionsObserver, "addon-options-displayed");
  });
};

function shutdown(data, reason) { unload(); };

function install() {}

function uninstall() {}

function upgrade(version) {
  let lastVersion = getPref(PREF_VERSION),
      prefs = Services.prefs;

  if (lastVersion < "1.2") {
    prefs.deleteBranch("extensions.bmreplace.button-position.");
  }

  if (lastVersion < "1.3") {
    let ktTag = "keep-title";
    try {
      let name = "extensions.bmreplace.keep-title-tag";
      ktTag = prefs.getCharPref(name);
      prefs.deleteBranch(name);
    } catch(e) {}

    let bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
          .getService(Ci.nsINavBookmarksService),
        ts = Cc["@mozilla.org/browser/tagging-service;1"]
          .getService(Ci.nsITaggingService);
    for each (let uri in ts.getURIsForTag(ktTag)) {
      for each (let id in bms.getBookmarkIdsForURI(uri)) {
        bm.setKeepTitle(id, true);
      }
    }
  }

  getPref.branch.setCharPref(PREF_VERSION, version);
}
<|endoftext|>


<commit_before>(function($, window, document, undefined) {
    'use strict';

    var defaults = {
            pixelSize: 10,
            canvasWidth: 50,
            canvasHeight: 50,
            canvasBackground: '#fff',
            canvasBorderColor: '#000',
            canvasBorderWidth: 1,
            canvasBorderStyle: 'solid'
        };

    function PaintCSS(element, options) {
        options = $.extend({}, defaults, options);
        this.$canvas = $(element);
        this.$painting = this.$canvas.children(':first');
        this.init(options);
    }

    $.extend(PaintCSS.prototype, {

        settings: {},
        shadowStack: [],
        color: '#f00',

        init: function(options) {
            this.$canvas.css({
                position: 'relative',
                cursor: 'crosshair',
                overflow: 'hidden',
                boxSizing: 'content-box'
            });
            this.$painting.css({
                position: 'absolute',
                lineHeight: '1',
                top: '-1em',
                left: '-1em',
                width: '1em',
                height: '1em',
            });
            this.setPixelSize(options.pixelSize);
            this.setCanvasSize(options.canvasWidth, options.canvasHeight);
            this.setCanvasBackground(options.canvasBackground);
            this.setCanvasBorderColor(options.canvasBorderColor);
            this.setCanvasBorderWidth(options.canvasBorderWidth);
            this.setCanvasBorderStyle(options.canvasBorderStyle);
            this.setMouseEvent();
        },

        setPixelSize: function(pixelSize) {
            this.settings.pixelSize = pixelSize;
            this.$canvas.css('font-size', pixelSize);
        },

        setCanvasSize: function(width, height) {
            this.settings.canvasWidth = width;
            this.settings.canvasHeight = height;
            this.$canvas.css('width', width + 'em');
            this.$canvas.css('height', height + 'em');
            this.initShadowStack();
        },

        setCanvasBackground: function(background) {
            this.settings.canvasBackground = background;
            this.$canvas.css('background', background);
        },

        setCanvasBorderColor: function(color) {
            this.settings.canvasBorderColor = color;
            this.$canvas.css('border-color', color);
        },

        setCanvasBorderWidth: function(width) {
            this.settings.canvasBorderWidth = width;
            this.$canvas.css('border-width', width + 'px');
        },

        setCanvasBorderStyle: function(style) {
            this.settings.canvasBorderStyle = style;
            this.$canvas.css('border-style', style);
        },

        getCanvasOffset: function() {
            var offset = this.$canvas.offset();
            offset.top = offset.top + this.settings.canvasBorderWidth;
            offset.left = offset.left + this.settings.canvasBorderWidth;
            this.canvasOffset = offset;
        },

        setMouseEvent: function() {
            this.getCanvasOffset();
            this.mouseDown = false;
            $(window).off('mouseup.paint').on({
                'mouseup.paint': function(ev) {
                    this.mouseDown = false;
                }.bind(this)
            });
            this.$canvas.off('mousedown.paint').on({
                'mousedown.paint': function(ev) {
                    this.mouseDown = true;
                }.bind(this)
            });
            this.$canvas.off('mousemove.paint').on({
                'mousemove.paint': function(ev) {
                    if (this.mouseDown) {
                        var x = Math.floor((ev.pageX - this.canvasOffset.left) / this.settings.pixelSize),
                            y = Math.floor((ev.pageY - this.canvasOffset.top) / this.settings.pixelSize);
                        if (x >= 0 && x < this.settings.canvasWidth && y >= 0 && y <= this.settings.canvasHeight) {
                            this.shadowStack[x][y] = this.color;
                            this.paint();
                        }
                    }
                }.bind(this)
            });
        },

        initShadowStack: function() {
            this.shadowStack = [];
            for (var x = 0; x < this.settings.canvasWidth; x++) {
                this.shadowStack[x] = [];
                for (var y = 0; y < this.settings.canvasHeight; y++) {
                    this.shadowStack[x][y] = null;
                }
            }
        },

        paint: function() {
            var shadows = [];
            this.shadowStack.forEach(function(row, x) {
                row.forEach(function(color, y) {
                    if (color!==null) {
                        shadows.push((x + 1) + 'em ' + (y + 1) + 'em ' + color);
                    }
                }.bind(this));
            }.bind(this));
            shadows = shadows.length > 0 ? shadows.join(',') : '';
            this.$painting.css('box-shadow', shadows);
        }

    });

    $.fn.paintCSS = function(options) {
        return this.each(function() {
            if (!$.data(this, 'paintCSS')) {
                $.data(this, 'paintCSS', new PaintCSS(this, options));
            }
        });
    };

})(jQuery, window, document);
<commit_msg>add mousemove trigger to mousedown event, so you can paint by clicking without moving<commit_after>(function($, window, document, undefined) {
    'use strict';

    var defaults = {
            pixelSize: 10,
            canvasWidth: 50,
            canvasHeight: 50,
            canvasBackground: '#fff',
            canvasBorderColor: '#000',
            canvasBorderWidth: 1,
            canvasBorderStyle: 'solid'
        };

    function PaintCSS(element, options) {
        options = $.extend({}, defaults, options);
        this.$canvas = $(element);
        this.$painting = this.$canvas.children(':first');
        this.init(options);
    }

    $.extend(PaintCSS.prototype, {

        settings: {},
        shadowStack: [],
        color: '#f00',

        init: function(options) {
            this.$canvas.css({
                position: 'relative',
                cursor: 'crosshair',
                overflow: 'hidden',
                boxSizing: 'content-box'
            });
            this.$painting.css({
                position: 'absolute',
                lineHeight: '1',
                top: '-1em',
                left: '-1em',
                width: '1em',
                height: '1em',
            });
            this.setPixelSize(options.pixelSize);
            this.setCanvasSize(options.canvasWidth, options.canvasHeight);
            this.setCanvasBackground(options.canvasBackground);
            this.setCanvasBorderColor(options.canvasBorderColor);
            this.setCanvasBorderWidth(options.canvasBorderWidth);
            this.setCanvasBorderStyle(options.canvasBorderStyle);
            this.setMouseEvents();
        },

        setPixelSize: function(pixelSize) {
            this.settings.pixelSize = pixelSize;
            this.$canvas.css('font-size', pixelSize);
        },

        setCanvasSize: function(width, height) {
            this.settings.canvasWidth = width;
            this.settings.canvasHeight = height;
            this.$canvas.css('width', width + 'em');
            this.$canvas.css('height', height + 'em');
            this.initShadowStack();
        },

        setCanvasBackground: function(background) {
            this.settings.canvasBackground = background;
            this.$canvas.css('background', background);
        },

        setCanvasBorderColor: function(color) {
            this.settings.canvasBorderColor = color;
            this.$canvas.css('border-color', color);
        },

        setCanvasBorderWidth: function(width) {
            this.settings.canvasBorderWidth = width;
            this.$canvas.css('border-width', width + 'px');
        },

        setCanvasBorderStyle: function(style) {
            this.settings.canvasBorderStyle = style;
            this.$canvas.css('border-style', style);
        },

        getCanvasOffset: function() {
            var offset = this.$canvas.offset();
            offset.top = offset.top + this.settings.canvasBorderWidth;
            offset.left = offset.left + this.settings.canvasBorderWidth;
            this.canvasOffset = offset;
        },

        setMouseEvents: function() {
            this.getCanvasOffset();
            this.mouseDown = false;
            $(window).off('mouseup.paint').on({
                'mouseup.paint': function(ev) {
                    this.mouseDown = false;
                }.bind(this)
            });
            this.$canvas.off('mousedown.paint').on({
                'mousedown.paint': function(ev) {
                    this.mouseDown = true;
                    this.$canvas.trigger('mousemove.paint',[ev.pageX, ev.pageY]);
                }.bind(this)
            });
            this.$canvas.off('mousemove.paint').on({
                'mousemove.paint': function(ev, mouseDownX, mouseDownY) {
                    if (this.mouseDown) {
                        if (mouseDownX && mouseDownY) {
                            ev.pageX = mouseDownX;
                            ev.pageY = mouseDownY;
                        }
                        var x = Math.floor((ev.pageX - this.canvasOffset.left) / this.settings.pixelSize),
                            y = Math.floor((ev.pageY - this.canvasOffset.top) / this.settings.pixelSize);
                        if (x >= 0 && x < this.settings.canvasWidth && y >= 0 && y <= this.settings.canvasHeight) {
                            this.shadowStack[x][y] = this.color;
                            this.paint();
                        }
                    }
                }.bind(this)
            });
        },

        initShadowStack: function() {
            this.shadowStack = [];
            for (var x = 0; x < this.settings.canvasWidth; x++) {
                this.shadowStack[x] = [];
                for (var y = 0; y < this.settings.canvasHeight; y++) {
                    this.shadowStack[x][y] = null;
                }
            }
        },

        paint: function() {
            var shadows = [];
            this.shadowStack.forEach(function(row, x) {
                row.forEach(function(color, y) {
                    if (color!==null) {
                        shadows.push((x + 1) + 'em ' + (y + 1) + 'em ' + color);
                    }
                }.bind(this));
            }.bind(this));
            shadows = shadows.length > 0 ? shadows.join(',') : '';
            this.$painting.css('box-shadow', shadows);
        }

    });

    $.fn.paintCSS = function(options) {
        return this.each(function() {
            if (!$.data(this, 'paintCSS')) {
                $.data(this, 'paintCSS', new PaintCSS(this, options));
            }
        });
    };

})(jQuery, window, document);
<|endoftext|>


<commit_before>import isAndroid from './isAndroid'
import { debounce, concatTranscripts, browserSupportsPolyfills } from './utils'
import { isNative } from './NativeSpeechRecognition'

export default class RecognitionManager {
  constructor(SpeechRecognition) {
    this.recognition = null
    this.pauseAfterDisconnect = false
    this.interimTranscript = ''
    this.finalTranscript = ''
    this.listening = false
    this.subscribers = {}
    this.onStopListening = () => {}
    this.previousResultWasFinalOnly = false

    this.resetTranscript = this.resetTranscript.bind(this)
    this.startListening = this.startListening.bind(this)
    this.stopListening = this.stopListening.bind(this)
    this.abortListening = this.abortListening.bind(this)
    this.setSpeechRecognition = this.setSpeechRecognition.bind(this)

    this.setSpeechRecognition(SpeechRecognition)

    if (isAndroid()) {
      this.updateFinalTranscript = debounce(this.updateFinalTranscript, 250, true)
    }
  }

  setSpeechRecognition(SpeechRecognition) {
    const browserSupportsRecogniser = !!SpeechRecognition && (
      isNative(SpeechRecognition) || browserSupportsPolyfills()
    )
    if (browserSupportsRecogniser) {
      this.recognition = new SpeechRecognition()
      this.recognition.continuous = false
      this.recognition.interimResults = true
      this.recognition.onresult = this.updateTranscript.bind(this)
      this.recognition.onend = this.onRecognitionDisconnect.bind(this)
    }
    this.emitBrowserSupportsSpeechRecognitionChange(browserSupportsRecogniser)
  }

  subscribe(id, callbacks) {
    this.subscribers[id] = callbacks
  }

  unsubscribe(id) {
    delete this.subscribers[id]
  }

  emitListeningChange(listening) {
    this.listening = listening
    Object.keys(this.subscribers).forEach((id) => {
      const { onListeningChange } = this.subscribers[id]
      onListeningChange(listening)
    })
  }

  emitTranscriptChange(interimTranscript, finalTranscript) {
    Object.keys(this.subscribers).forEach((id) => {
      const { onTranscriptChange } = this.subscribers[id]
      onTranscriptChange(interimTranscript, finalTranscript)
    })
  }

  emitClearTranscript() {
    Object.keys(this.subscribers).forEach((id) => {
      const { onClearTranscript } = this.subscribers[id]
      onClearTranscript()
    })
  }

  emitBrowserSupportsSpeechRecognitionChange(browserSupportsSpeechRecognitionChange) {
    Object.keys(this.subscribers).forEach((id) => {
      const { onBrowserSupportsSpeechRecognitionChange, onBrowserSupportsContinuousListeningChange } = this.subscribers[id]
      onBrowserSupportsSpeechRecognitionChange(browserSupportsSpeechRecognitionChange)
      onBrowserSupportsContinuousListeningChange(browserSupportsSpeechRecognitionChange)
    })
  }

  disconnect(disconnectType) {
    if (this.recognition && this.listening) {
      switch (disconnectType) {
        case 'ABORT':
          this.pauseAfterDisconnect = true
          this.abort()
          break
        case 'RESET':
          this.pauseAfterDisconnect = false
          this.abort()
          break
        case 'STOP':
        default:
          this.pauseAfterDisconnect = true
          this.stop()
      }
    }
  }

  onRecognitionDisconnect() {
    this.onStopListening()
    this.listening = false
    if (this.pauseAfterDisconnect) {
      this.emitListeningChange(false)
    } else if (this.recognition) {
      if (this.recognition.continuous) {
        this.startListening({ continuous: this.recognition.continuous })
      } else {
        this.emitListeningChange(false)
      }
    }
    this.pauseAfterDisconnect = false
  }

  updateTranscript({ results, resultIndex }) {
    const currentIndex = resultIndex === undefined ? results.length - 1 : resultIndex
    this.interimTranscript = ''
    this.finalTranscript = ''
    for (let i = currentIndex; i < results.length; ++i) {
      if (results[i].isFinal && (!isAndroid() || results[i][0].confidence > 0)) {
        this.updateFinalTranscript(results[i][0].transcript)
      } else {
        this.interimTranscript = concatTranscripts(
          this.interimTranscript,
          results[i][0].transcript
        )
      }
    }
    let isDuplicateResult = false
    if (this.interimTranscript === '' && this.finalTranscript !== '') {
      if (this.previousResultWasFinalOnly) {
        isDuplicateResult = true
      }
      this.previousResultWasFinalOnly = true
    } else {
      this.previousResultWasFinalOnly = false
    }
    if (!isDuplicateResult) {
      this.emitTranscriptChange(this.interimTranscript, this.finalTranscript)
    }
  }

  updateFinalTranscript(newFinalTranscript) {
    this.finalTranscript = concatTranscripts(
      this.finalTranscript,
      newFinalTranscript
    )
  }

  resetTranscript() {
    this.disconnect('RESET')
  }

  async startListening({ continuous = false, language } = {}) {
    if (!this.recognition) {
      return
    }

    const isContinuousChanged = continuous !== this.recognition.continuous
    const isLanguageChanged = language && language !== this.recognition.lang
    if (isContinuousChanged || isLanguageChanged) {
      if (this.listening) {
        await this.stopListening()
      }
      this.recognition.continuous = isContinuousChanged ? continuous : this.recognition.continuous
      this.recognition.lang = isLanguageChanged ? language : this.recognition.lang
    }
    if (!this.listening) {
      if (!this.recognition.continuous) {
        this.resetTranscript()
        this.emitClearTranscript()
      }
      try {
        this.start()
      } catch (DOMException) {
        // Tried to start recognition after it has already started - safe to swallow this error
      }
      this.emitListeningChange(true)
    }
  }

  async abortListening() {
    this.disconnect('ABORT')
    this.emitListeningChange(false)
    await new Promise(resolve => {
      this.onStopListening = resolve
    })
  }

  async stopListening() {
    this.disconnect('STOP')
    this.emitListeningChange(false)
    await new Promise(resolve => {
      this.onStopListening = resolve
    })
  }

  getRecognition() {
    return this.recognition
  }

  start() {
    if (this.recognition && !this.listening) {
      this.recognition.start()
      this.listening = true
    }
  }

  stop() {
    if (this.recognition && this.listening) {
      this.recognition.stop()
      this.listening = false
    }
  }

  abort() {
    if (this.recognition && this.listening) {
      this.recognition.abort()
      this.listening = false
    }
  }
}
<commit_msg>[Green] Clean up previous speech recognition when applying polyfill<commit_after>import isAndroid from './isAndroid'
import { debounce, concatTranscripts, browserSupportsPolyfills } from './utils'
import { isNative } from './NativeSpeechRecognition'

export default class RecognitionManager {
  constructor(SpeechRecognition) {
    this.recognition = null
    this.pauseAfterDisconnect = false
    this.interimTranscript = ''
    this.finalTranscript = ''
    this.listening = false
    this.subscribers = {}
    this.onStopListening = () => {}
    this.previousResultWasFinalOnly = false

    this.resetTranscript = this.resetTranscript.bind(this)
    this.startListening = this.startListening.bind(this)
    this.stopListening = this.stopListening.bind(this)
    this.abortListening = this.abortListening.bind(this)
    this.setSpeechRecognition = this.setSpeechRecognition.bind(this)

    this.setSpeechRecognition(SpeechRecognition)

    if (isAndroid()) {
      this.updateFinalTranscript = debounce(this.updateFinalTranscript, 250, true)
    }
  }

  setSpeechRecognition(SpeechRecognition) {
    const browserSupportsRecogniser = !!SpeechRecognition && (
      isNative(SpeechRecognition) || browserSupportsPolyfills()
    )
    if (browserSupportsRecogniser) {
      if (this.recognition) {
        this.recognition.onresult = () => {}
        this.recognition.onend = () => {}
        if (this.listening) {
          this.stopListening()
        }
      }
      this.recognition = new SpeechRecognition()
      this.recognition.continuous = false
      this.recognition.interimResults = true
      this.recognition.onresult = this.updateTranscript.bind(this)
      this.recognition.onend = this.onRecognitionDisconnect.bind(this)
    }
    this.emitBrowserSupportsSpeechRecognitionChange(browserSupportsRecogniser)
  }

  subscribe(id, callbacks) {
    this.subscribers[id] = callbacks
  }

  unsubscribe(id) {
    delete this.subscribers[id]
  }

  emitListeningChange(listening) {
    this.listening = listening
    Object.keys(this.subscribers).forEach((id) => {
      const { onListeningChange } = this.subscribers[id]
      onListeningChange(listening)
    })
  }

  emitTranscriptChange(interimTranscript, finalTranscript) {
    Object.keys(this.subscribers).forEach((id) => {
      const { onTranscriptChange } = this.subscribers[id]
      onTranscriptChange(interimTranscript, finalTranscript)
    })
  }

  emitClearTranscript() {
    Object.keys(this.subscribers).forEach((id) => {
      const { onClearTranscript } = this.subscribers[id]
      onClearTranscript()
    })
  }

  emitBrowserSupportsSpeechRecognitionChange(browserSupportsSpeechRecognitionChange) {
    Object.keys(this.subscribers).forEach((id) => {
      const { onBrowserSupportsSpeechRecognitionChange, onBrowserSupportsContinuousListeningChange } = this.subscribers[id]
      onBrowserSupportsSpeechRecognitionChange(browserSupportsSpeechRecognitionChange)
      onBrowserSupportsContinuousListeningChange(browserSupportsSpeechRecognitionChange)
    })
  }

  disconnect(disconnectType) {
    if (this.recognition && this.listening) {
      switch (disconnectType) {
        case 'ABORT':
          this.pauseAfterDisconnect = true
          this.abort()
          break
        case 'RESET':
          this.pauseAfterDisconnect = false
          this.abort()
          break
        case 'STOP':
        default:
          this.pauseAfterDisconnect = true
          this.stop()
      }
    }
  }

  onRecognitionDisconnect() {
    this.onStopListening()
    this.listening = false
    if (this.pauseAfterDisconnect) {
      this.emitListeningChange(false)
    } else if (this.recognition) {
      if (this.recognition.continuous) {
        this.startListening({ continuous: this.recognition.continuous })
      } else {
        this.emitListeningChange(false)
      }
    }
    this.pauseAfterDisconnect = false
  }

  updateTranscript({ results, resultIndex }) {
    const currentIndex = resultIndex === undefined ? results.length - 1 : resultIndex
    this.interimTranscript = ''
    this.finalTranscript = ''
    for (let i = currentIndex; i < results.length; ++i) {
      if (results[i].isFinal && (!isAndroid() || results[i][0].confidence > 0)) {
        this.updateFinalTranscript(results[i][0].transcript)
      } else {
        this.interimTranscript = concatTranscripts(
          this.interimTranscript,
          results[i][0].transcript
        )
      }
    }
    let isDuplicateResult = false
    if (this.interimTranscript === '' && this.finalTranscript !== '') {
      if (this.previousResultWasFinalOnly) {
        isDuplicateResult = true
      }
      this.previousResultWasFinalOnly = true
    } else {
      this.previousResultWasFinalOnly = false
    }
    if (!isDuplicateResult) {
      this.emitTranscriptChange(this.interimTranscript, this.finalTranscript)
    }
  }

  updateFinalTranscript(newFinalTranscript) {
    this.finalTranscript = concatTranscripts(
      this.finalTranscript,
      newFinalTranscript
    )
  }

  resetTranscript() {
    this.disconnect('RESET')
  }

  async startListening({ continuous = false, language } = {}) {
    if (!this.recognition) {
      return
    }

    const isContinuousChanged = continuous !== this.recognition.continuous
    const isLanguageChanged = language && language !== this.recognition.lang
    if (isContinuousChanged || isLanguageChanged) {
      if (this.listening) {
        await this.stopListening()
      }
      this.recognition.continuous = isContinuousChanged ? continuous : this.recognition.continuous
      this.recognition.lang = isLanguageChanged ? language : this.recognition.lang
    }
    if (!this.listening) {
      if (!this.recognition.continuous) {
        this.resetTranscript()
        this.emitClearTranscript()
      }
      try {
        this.start()
      } catch (DOMException) {
        // Tried to start recognition after it has already started - safe to swallow this error
      }
      this.emitListeningChange(true)
    }
  }

  async abortListening() {
    this.disconnect('ABORT')
    this.emitListeningChange(false)
    await new Promise(resolve => {
      this.onStopListening = resolve
    })
  }

  async stopListening() {
    this.disconnect('STOP')
    this.emitListeningChange(false)
    await new Promise(resolve => {
      this.onStopListening = resolve
    })
  }

  getRecognition() {
    return this.recognition
  }

  start() {
    if (this.recognition && !this.listening) {
      this.recognition.start()
      this.listening = true
    }
  }

  stop() {
    if (this.recognition && this.listening) {
      this.recognition.stop()
      this.listening = false
    }
  }

  abort() {
    if (this.recognition && this.listening) {
      this.recognition.abort()
      this.listening = false
    }
  }
}
<|endoftext|>


<commit_before>// Copyright 2016-2019, University of Colorado Boulder

/**
 * Spinner for numbers.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Andrea Lin (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var AccessibleNumberSpinner = require( 'SUN/accessibility/AccessibleNumberSpinner' );
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var sun = require( 'SUN/sun' );
  var SunConstants = require( 'SUN/SunConstants' );
  var Tandem = require( 'TANDEM/Tandem' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );

  // possible values for options.arrowsPosition
  var ARROWS_POSITION_VALUES = [
    'leftRight', // arrow buttons on left and right of value
    'topBottom', // arrow buttons on top and bottom of value
    'bothRight', // both arrow buttons to the right of the value
    'bothBottom' // both arrow buttons below the value
  ];

  // possible values for options.valueAlign
  var VALUE_ALIGN_VALUES = [ 'center', 'left', 'right' ];

  /**
   * @param {Property.<number>} numberProperty value, must be an integer
   * @param {Property.<Range>} rangeProperty - Dynamic range of values, min and max must be integers
   * @param {Object} [options]
   * @mixes AccessibleNumberSpinner
   * @constructor
   */
  function NumberSpinner( numberProperty, rangeProperty, options ) {

    assert && assert( rangeProperty.value.contains( numberProperty.get() ),
      'value ' + numberProperty.get() + ' is out of range ' + rangeProperty.value.toString() );

    options = _.extend( {
      enabledProperty: new Property( true ),

      // {string} where to place the arrow buttons, see ARROWS_POSITION_VALUES
      arrowsPosition: 'bothRight',

      // {number|null} By default, arrows are scaled to fit dimensions of value background. This is an additional scale factor.
      arrowsScale: null,
      arrowButtonFill: 'white',
      arrowButtonStroke: 'black',
      arrowButtonLineWidth: 1,

      valuePattern: SunConstants.VALUE_NAMED_PLACEHOLDER, // {string} must contain SunConstants.VALUE_NAMED_PLACEHOLDER
      decimalPlaces: 0,
      deltaValue: 1,
      font: new PhetFont( 28 ),

      // {string} alignment for value, see VALUE_ALIGN_VALUES
      valueAlign: 'center',
      xSpacing: 5,
      ySpacing: 3,

      // background node
      xMargin: 5,
      yMargin: 3,
      cornerRadius: 5,
      backgroundMinWidth: 0,
      backgroundFill: 'white',
      backgroundStroke: 'black',
      backgroundLineWidth: 1,

      // arrow button pointer areas
      touchAreaXDilation: 0,
      touchAreaYDilation: 0,
      mouseAreaXDilation: 0,
      mouseAreaYDilation: 0
    }, options );

    // validate options
    assert && assert( _.includes( ARROWS_POSITION_VALUES, options.arrowsPosition ), 'invalid arrowsPosition: ' + options.arrowsPosition );
    assert && assert( _.includes( VALUE_ALIGN_VALUES, options.valueAlign ), 'invalid valueAlign: ' + options.valueAlign );
    assert && assert( options.valuePattern.indexOf( SunConstants.VALUE_NAMED_PLACEHOLDER ) !== -1,
      'missing value placeholder in options.valuePattern: ' + options.valuePattern );

    Tandem.indicateUninstrumentedCode();

    var self = this;

    var valueOptions = {
      font: options.font,
      fill: 'black'
    };

    // compute max width of the value that's going to be displayed
    var minString = StringUtils.fillIn( options.valuePattern, {
      value: Util.toFixed( rangeProperty.value.min, options.decimalPlaces )
    } );
    var maxString = StringUtils.format( options.valuePattern, {
      value: Util.toFixed( rangeProperty.value.max, options.decimalPlaces )
    } );
    var maxWidth = Math.max(
      new Text( minString, valueOptions ).width,
      new Text( maxString, valueOptions ).width
    );

    // number
    var numberNode = new Text( numberProperty.get(), valueOptions );

    // compute the size of the background
    var backgroundWidth = Math.max( maxWidth + 2 * options.xMargin, options.backgroundMinWidth );
    var backgroundHeight = numberNode.height + ( 2 * options.yMargin );

    // background for displaying the value
    var backgroundNode = new Rectangle( 0, 0, backgroundWidth, backgroundHeight,
      options.cornerRadius, options.cornerRadius, {
        fill: options.backgroundFill,
        stroke: options.backgroundStroke,
        lineWidth: options.backgroundLineWidth
      } );
    numberNode.center = backgroundNode.center;
    var valueParent = new Node( { children: [ backgroundNode, numberNode ] } );

    // buttons
    var arrowButtonOptions = {
      baseColor: options.arrowButtonFill,
      stroke: options.arrowButtonStroke,
      lineWidth: options.arrowButtonLineWidth,
      focusable: false
    };

    // increment button
    var incrementFunction = function() {
      numberProperty.set( numberProperty.get() + options.deltaValue );
    };
    var incrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'up' : 'right';
    var incrementButton = new ArrowButton( incrementDirection, incrementFunction, arrowButtonOptions );

    // decrement button
    var decrementFunction = function() {
      numberProperty.set( numberProperty.get() - options.deltaValue );
    };
    var decrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'down' : 'left';
    var decrementButton = new ArrowButton( decrementDirection, decrementFunction, arrowButtonOptions );

    // arrow button scaling
    var arrowsScale;
    if ( !arrowsScale ) {
      if ( options.arrowsPosition === 'leftRight' ) {
        arrowsScale = valueParent.height / incrementButton.height;
      }
      else if ( options.arrowsPosition === 'topBottom' ) {
        arrowsScale = valueParent.width / incrementButton.width;
      }
      else if ( options.arrowsPosition === 'bothRight' ) {
        arrowsScale = ( ( valueParent.height / 2 ) - ( options.ySpacing / 2 ) ) / incrementButton.height;
      }
      else { // 'bothBottom'
        arrowsScale = ( ( valueParent.width / 2 ) - ( options.xSpacing / 2 ) ) / incrementButton.width;
      }
    }
    if ( options.arrowsScale ) {
      arrowsScale = arrowsScale * options.arrowsScale;
    }
    incrementButton.setScaleMagnitude( arrowsScale );
    decrementButton.setScaleMagnitude( arrowsScale );

    // layout
    if ( options.arrowsPosition === 'leftRight' ) {
      incrementButton.left = valueParent.right + options.xSpacing;
      decrementButton.right = valueParent.left - options.xSpacing;
      incrementButton.centerY = decrementButton.centerY = valueParent.centerY;
    }
    else if ( options.arrowsPosition === 'topBottom' ) {
      incrementButton.centerX = decrementButton.centerX = valueParent.centerX;
      incrementButton.bottom = valueParent.top - options.ySpacing;
      decrementButton.top = valueParent.bottom + options.ySpacing;
    }
    else if ( options.arrowsPosition === 'bothRight' ) {
      incrementButton.left = decrementButton.left = valueParent.right + options.xSpacing;
      incrementButton.bottom = valueParent.centerY - ( options.ySpacing / 2 );
      decrementButton.top = valueParent.centerY + ( options.ySpacing / 2 );
    }
    else { // 'bothBottom'
      incrementButton.left = valueParent.centerX + ( options.xSpacing / 2 );
      decrementButton.right = valueParent.centerX - ( options.xSpacing / 2 );
      incrementButton.top = decrementButton.top = valueParent.bottom + options.ySpacing;
    }

    // touch areas
    if ( options.touchAreaXDilation || options.touchAreaYDilation ) {

      incrementButton.touchArea = incrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );
      decrementButton.touchArea = decrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedY( -options.touchAreaYDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedY( options.touchAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedX( options.touchAreaXDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedX( -options.touchAreaXDilation );
      }
    }

    // mouse areas
    if ( options.mouseAreaXDilation || options.mouseAreaYDilation ) {

      incrementButton.mouseArea = incrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );
      decrementButton.mouseArea = decrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedY( -options.mouseAreaYDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedY( options.mouseAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedX( options.mouseAreaXDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedX( -options.mouseAreaXDilation );
      }
    }

    assert && assert( !options.children, 'decoration not supported' );
    options.children = [ valueParent, incrementButton, decrementButton ];

    Node.call( this, options );

    var updateEnabled = function() {
      // enable/disable arrow buttons
      incrementButton.enabled = ( ( numberProperty.value + options.deltaValue ) <= rangeProperty.value.max );
      decrementButton.enabled = ( ( numberProperty.value - options.deltaValue ) >= rangeProperty.value.min );
    };

    // synchronize with number value
    var numberPropertyObserver = function( value ) {
      assert && assert( rangeProperty.value.contains( value ), 'value out of range: ' + value );

      // update the number
      numberNode.text = StringUtils.fillIn( options.valuePattern, {
        value: Util.toFixed( value, options.decimalPlaces )
      } );

      // update the alignment
      switch( options.valueAlign ) {
        case 'center':
          numberNode.center = backgroundNode.center;
          break;
        case 'left':
          numberNode.left = backgroundNode.left + options.xMargin;
          break;
        case 'right':
          numberNode.right = backgroundNode.right - options.xMargin;
          break;
        default:
          throw new Error( 'invalid valueAlign: ' + options.valueAlign );
      }

      updateEnabled();
    };
    numberProperty.link( numberPropertyObserver ); // must be unlinked in dispose

    // Dynamic range changes, see https://github.com/phetsims/scenery-phet/issues/305
    var rangeObserver = function( range ) {
      // If our value is outside our new range, adjust it to be within the range.
      numberProperty.value = range.constrainValue( numberProperty.value );

      // Range changes may change whether the buttons are enabled
      updateEnabled();
    };
    rangeProperty.link( rangeObserver );

    // enable or disable this component
    this.enabledProperty = options.enabledProperty; // @public
    var enabledPropertyObserver = function( enabled ) {
      self.pickable = enabled;
      self.opacity = enabled ? 1 : 0.5;
      //TODO if !enabled, cancel any interaction that is in progress, see scenery#218
    };
    this.enabledProperty.link( enabledPropertyObserver );

    // a11y - initialize accessibility features
    assert && assert( !options.a11yValueDelta, 'a11yValueDelta supported by arrow buttons, do not pass value to NumberSpinner' );
    assert && assert( !options.a11yUseTimer, 'interval handled by arrow buttons, do not use callback timer' );
    options.a11yValueDelta = 0;
    options.a11yUseTimer = false;
    this.initializeAccessibleNumberSpinner( numberProperty, rangeProperty, this.enabledProperty, options );

    // a11y - click arrow buttons on keyboard increment/decrement; must be disposed
    var increasedListener = function() { incrementButton.a11yClick(); };
    var decreasedListener = function() { decrementButton.a11yClick(); };
    this.valueIncrementEmitter.addListener( increasedListener );
    this.valueDecrementEmitter.addListener( decreasedListener );

    // @private
    this.disposeNumberSpinner = function() {

      // dispose a11y features
      self.valueIncrementEmitter.removeListener( increasedListener );
      self.valueDecrementEmitter.removeListener( decreasedListener );
      self.disposeAccessibleNumberSpinner();

      numberProperty.unlink( numberPropertyObserver );
      rangeProperty.unlink( rangeObserver );
      self.enabledProperty.unlink( enabledPropertyObserver );
    };
  }

  sun.register( 'NumberSpinner', NumberSpinner );

  inherit( Node, NumberSpinner, {

    // @public Ensures that this node is eligible for GC.
    dispose: function() {
      this.disposeNumberSpinner();
      Node.prototype.dispose.call( this );
    },

    // @public
    setEnabled: function( enabled ) { this.enabledProperty.set( enabled ); },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled: function() { return this.enabledProperty.get(); },
    get enabled() { return this.getEnabled(); }
  } );

  AccessibleNumberSpinner.mixInto( NumberSpinner );

  return NumberSpinner;
} );
<commit_msg>replace format with fillIn, #472<commit_after>// Copyright 2016-2019, University of Colorado Boulder

/**
 * Spinner for numbers.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Andrea Lin (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var AccessibleNumberSpinner = require( 'SUN/accessibility/AccessibleNumberSpinner' );
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var sun = require( 'SUN/sun' );
  var SunConstants = require( 'SUN/SunConstants' );
  var Tandem = require( 'TANDEM/Tandem' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );

  // possible values for options.arrowsPosition
  var ARROWS_POSITION_VALUES = [
    'leftRight', // arrow buttons on left and right of value
    'topBottom', // arrow buttons on top and bottom of value
    'bothRight', // both arrow buttons to the right of the value
    'bothBottom' // both arrow buttons below the value
  ];

  // possible values for options.valueAlign
  var VALUE_ALIGN_VALUES = [ 'center', 'left', 'right' ];

  /**
   * @param {Property.<number>} numberProperty value, must be an integer
   * @param {Property.<Range>} rangeProperty - Dynamic range of values, min and max must be integers
   * @param {Object} [options]
   * @mixes AccessibleNumberSpinner
   * @constructor
   */
  function NumberSpinner( numberProperty, rangeProperty, options ) {

    assert && assert( rangeProperty.value.contains( numberProperty.get() ),
      'value ' + numberProperty.get() + ' is out of range ' + rangeProperty.value.toString() );

    options = _.extend( {
      enabledProperty: new Property( true ),

      // {string} where to place the arrow buttons, see ARROWS_POSITION_VALUES
      arrowsPosition: 'bothRight',

      // {number|null} By default, arrows are scaled to fit dimensions of value background. This is an additional scale factor.
      arrowsScale: null,
      arrowButtonFill: 'white',
      arrowButtonStroke: 'black',
      arrowButtonLineWidth: 1,

      valuePattern: SunConstants.VALUE_NAMED_PLACEHOLDER, // {string} must contain SunConstants.VALUE_NAMED_PLACEHOLDER
      decimalPlaces: 0,
      deltaValue: 1,
      font: new PhetFont( 28 ),

      // {string} alignment for value, see VALUE_ALIGN_VALUES
      valueAlign: 'center',
      xSpacing: 5,
      ySpacing: 3,

      // background node
      xMargin: 5,
      yMargin: 3,
      cornerRadius: 5,
      backgroundMinWidth: 0,
      backgroundFill: 'white',
      backgroundStroke: 'black',
      backgroundLineWidth: 1,

      // arrow button pointer areas
      touchAreaXDilation: 0,
      touchAreaYDilation: 0,
      mouseAreaXDilation: 0,
      mouseAreaYDilation: 0
    }, options );

    // validate options
    assert && assert( _.includes( ARROWS_POSITION_VALUES, options.arrowsPosition ), 'invalid arrowsPosition: ' + options.arrowsPosition );
    assert && assert( _.includes( VALUE_ALIGN_VALUES, options.valueAlign ), 'invalid valueAlign: ' + options.valueAlign );
    assert && assert( options.valuePattern.indexOf( SunConstants.VALUE_NAMED_PLACEHOLDER ) !== -1,
      'missing value placeholder in options.valuePattern: ' + options.valuePattern );

    Tandem.indicateUninstrumentedCode();

    var self = this;

    var valueOptions = {
      font: options.font,
      fill: 'black'
    };

    // compute max width of the value that's going to be displayed
    var minString = StringUtils.fillIn( options.valuePattern, {
      value: Util.toFixed( rangeProperty.value.min, options.decimalPlaces )
    } );
    var maxString = StringUtils.fillIn( options.valuePattern, {
      value: Util.toFixed( rangeProperty.value.max, options.decimalPlaces )
    } );
    var maxWidth = Math.max(
      new Text( minString, valueOptions ).width,
      new Text( maxString, valueOptions ).width
    );

    // number
    var numberNode = new Text( numberProperty.get(), valueOptions );

    // compute the size of the background
    var backgroundWidth = Math.max( maxWidth + 2 * options.xMargin, options.backgroundMinWidth );
    var backgroundHeight = numberNode.height + ( 2 * options.yMargin );

    // background for displaying the value
    var backgroundNode = new Rectangle( 0, 0, backgroundWidth, backgroundHeight,
      options.cornerRadius, options.cornerRadius, {
        fill: options.backgroundFill,
        stroke: options.backgroundStroke,
        lineWidth: options.backgroundLineWidth
      } );
    numberNode.center = backgroundNode.center;
    var valueParent = new Node( { children: [ backgroundNode, numberNode ] } );

    // buttons
    var arrowButtonOptions = {
      baseColor: options.arrowButtonFill,
      stroke: options.arrowButtonStroke,
      lineWidth: options.arrowButtonLineWidth,
      focusable: false
    };

    // increment button
    var incrementFunction = function() {
      numberProperty.set( numberProperty.get() + options.deltaValue );
    };
    var incrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'up' : 'right';
    var incrementButton = new ArrowButton( incrementDirection, incrementFunction, arrowButtonOptions );

    // decrement button
    var decrementFunction = function() {
      numberProperty.set( numberProperty.get() - options.deltaValue );
    };
    var decrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'down' : 'left';
    var decrementButton = new ArrowButton( decrementDirection, decrementFunction, arrowButtonOptions );

    // arrow button scaling
    var arrowsScale;
    if ( !arrowsScale ) {
      if ( options.arrowsPosition === 'leftRight' ) {
        arrowsScale = valueParent.height / incrementButton.height;
      }
      else if ( options.arrowsPosition === 'topBottom' ) {
        arrowsScale = valueParent.width / incrementButton.width;
      }
      else if ( options.arrowsPosition === 'bothRight' ) {
        arrowsScale = ( ( valueParent.height / 2 ) - ( options.ySpacing / 2 ) ) / incrementButton.height;
      }
      else { // 'bothBottom'
        arrowsScale = ( ( valueParent.width / 2 ) - ( options.xSpacing / 2 ) ) / incrementButton.width;
      }
    }
    if ( options.arrowsScale ) {
      arrowsScale = arrowsScale * options.arrowsScale;
    }
    incrementButton.setScaleMagnitude( arrowsScale );
    decrementButton.setScaleMagnitude( arrowsScale );

    // layout
    if ( options.arrowsPosition === 'leftRight' ) {
      incrementButton.left = valueParent.right + options.xSpacing;
      decrementButton.right = valueParent.left - options.xSpacing;
      incrementButton.centerY = decrementButton.centerY = valueParent.centerY;
    }
    else if ( options.arrowsPosition === 'topBottom' ) {
      incrementButton.centerX = decrementButton.centerX = valueParent.centerX;
      incrementButton.bottom = valueParent.top - options.ySpacing;
      decrementButton.top = valueParent.bottom + options.ySpacing;
    }
    else if ( options.arrowsPosition === 'bothRight' ) {
      incrementButton.left = decrementButton.left = valueParent.right + options.xSpacing;
      incrementButton.bottom = valueParent.centerY - ( options.ySpacing / 2 );
      decrementButton.top = valueParent.centerY + ( options.ySpacing / 2 );
    }
    else { // 'bothBottom'
      incrementButton.left = valueParent.centerX + ( options.xSpacing / 2 );
      decrementButton.right = valueParent.centerX - ( options.xSpacing / 2 );
      incrementButton.top = decrementButton.top = valueParent.bottom + options.ySpacing;
    }

    // touch areas
    if ( options.touchAreaXDilation || options.touchAreaYDilation ) {

      incrementButton.touchArea = incrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );
      decrementButton.touchArea = decrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedY( -options.touchAreaYDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedY( options.touchAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedX( options.touchAreaXDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedX( -options.touchAreaXDilation );
      }
    }

    // mouse areas
    if ( options.mouseAreaXDilation || options.mouseAreaYDilation ) {

      incrementButton.mouseArea = incrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );
      decrementButton.mouseArea = decrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedY( -options.mouseAreaYDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedY( options.mouseAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedX( options.mouseAreaXDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedX( -options.mouseAreaXDilation );
      }
    }

    assert && assert( !options.children, 'decoration not supported' );
    options.children = [ valueParent, incrementButton, decrementButton ];

    Node.call( this, options );

    var updateEnabled = function() {
      // enable/disable arrow buttons
      incrementButton.enabled = ( ( numberProperty.value + options.deltaValue ) <= rangeProperty.value.max );
      decrementButton.enabled = ( ( numberProperty.value - options.deltaValue ) >= rangeProperty.value.min );
    };

    // synchronize with number value
    var numberPropertyObserver = function( value ) {
      assert && assert( rangeProperty.value.contains( value ), 'value out of range: ' + value );

      // update the number
      numberNode.text = StringUtils.fillIn( options.valuePattern, {
        value: Util.toFixed( value, options.decimalPlaces )
      } );

      // update the alignment
      switch( options.valueAlign ) {
        case 'center':
          numberNode.center = backgroundNode.center;
          break;
        case 'left':
          numberNode.left = backgroundNode.left + options.xMargin;
          break;
        case 'right':
          numberNode.right = backgroundNode.right - options.xMargin;
          break;
        default:
          throw new Error( 'invalid valueAlign: ' + options.valueAlign );
      }

      updateEnabled();
    };
    numberProperty.link( numberPropertyObserver ); // must be unlinked in dispose

    // Dynamic range changes, see https://github.com/phetsims/scenery-phet/issues/305
    var rangeObserver = function( range ) {
      // If our value is outside our new range, adjust it to be within the range.
      numberProperty.value = range.constrainValue( numberProperty.value );

      // Range changes may change whether the buttons are enabled
      updateEnabled();
    };
    rangeProperty.link( rangeObserver );

    // enable or disable this component
    this.enabledProperty = options.enabledProperty; // @public
    var enabledPropertyObserver = function( enabled ) {
      self.pickable = enabled;
      self.opacity = enabled ? 1 : 0.5;
      //TODO if !enabled, cancel any interaction that is in progress, see scenery#218
    };
    this.enabledProperty.link( enabledPropertyObserver );

    // a11y - initialize accessibility features
    assert && assert( !options.a11yValueDelta, 'a11yValueDelta supported by arrow buttons, do not pass value to NumberSpinner' );
    assert && assert( !options.a11yUseTimer, 'interval handled by arrow buttons, do not use callback timer' );
    options.a11yValueDelta = 0;
    options.a11yUseTimer = false;
    this.initializeAccessibleNumberSpinner( numberProperty, rangeProperty, this.enabledProperty, options );

    // a11y - click arrow buttons on keyboard increment/decrement; must be disposed
    var increasedListener = function() { incrementButton.a11yClick(); };
    var decreasedListener = function() { decrementButton.a11yClick(); };
    this.valueIncrementEmitter.addListener( increasedListener );
    this.valueDecrementEmitter.addListener( decreasedListener );

    // @private
    this.disposeNumberSpinner = function() {

      // dispose a11y features
      self.valueIncrementEmitter.removeListener( increasedListener );
      self.valueDecrementEmitter.removeListener( decreasedListener );
      self.disposeAccessibleNumberSpinner();

      numberProperty.unlink( numberPropertyObserver );
      rangeProperty.unlink( rangeObserver );
      self.enabledProperty.unlink( enabledPropertyObserver );
    };
  }

  sun.register( 'NumberSpinner', NumberSpinner );

  inherit( Node, NumberSpinner, {

    // @public Ensures that this node is eligible for GC.
    dispose: function() {
      this.disposeNumberSpinner();
      Node.prototype.dispose.call( this );
    },

    // @public
    setEnabled: function( enabled ) { this.enabledProperty.set( enabled ); },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled: function() { return this.enabledProperty.get(); },
    get enabled() { return this.getEnabled(); }
  } );

  AccessibleNumberSpinner.mixInto( NumberSpinner );

  return NumberSpinner;
} );
<|endoftext|>


<commit_before>/*
 *
 * Copyright 2013 Anis Kadri
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

var fs = require('fs')
  , path = require('path')
  , osenv = require('osenv')
  , shell = require('shelljs')
  , util = require('util')
  , plugman_exe = path.join(__dirname, '..', 'plugman.js')
  , test_dir = path.join(osenv.tmpdir(), 'test_plugman');

exports.setUp = function(callback) {
    shell.mkdir('-p', test_dir);
    callback();
}

exports.tearDown = function(callback) {
    // remove the temp files (projects and plugins)
    shell.rm('-rf', test_dir);
    callback();
}

exports['should list all plugins'] = function (test) {

    var command = util.format('%s %s --list', shell.which('node'), plugman_exe),
        options = {silent: true},
        ret = shell.exec(command, options);
    test.equal(0, ret.code);
    test.ok(ret.output);
    test.done();

}

exports['should install and uninstall ios plugin'] = function (test) {
  var test_project_dir = path.join(test_dir, 'ios-config-xml'),
      test_plugin_dir = path.join(test_dir, 'WebNotifications'),
      srcDir = path.resolve(test_project_dir, 'SampleApp/Plugins'),
      options = {silent: true},
      command;

    // copy the ios test project to a temp directory
    shell.cp('-r', path.join(__dirname, 'projects', 'ios-config-xml'), test_dir);

    // copy the ios test plugin to a temp directory
    shell.cp('-r', path.join(__dirname, 'plugins', 'WebNotifications'), test_dir);

    command = util.format('%s %s --platform ios --project %s --plugin %s', shell.which('node'),
                                                                           plugman_exe,
                                                                           test_project_dir,
                                                                           test_plugin_dir
                         );
    var ret = shell.exec(command, options);
    test.equal(0, ret.code);
    test.equal("plugin installed\n", ret.output);

    test.ok(fs.existsSync(srcDir + '/WebNotifications.m'))
    test.ok(fs.existsSync(srcDir + '/WebNotifications.h'));

    command = util.format('%s %s --remove --platform ios --project %s --plugin %s', shell.which('node'),
                                                                                plugman_exe,
                                                                                test_project_dir,
                                                                                test_plugin_dir
                         );
    var ret = shell.exec(command, options);
    test.equal(0, ret.code);
    test.equal("plugin uninstalled\n", ret.output);

    test.ok(!fs.existsSync(srcDir + '/WebNotifications.m'))
    test.ok(!fs.existsSync(srcDir + '/WebNotifications.h'));

    test.done();
}

exports['should revert ios install on failures'] = function (test) {
  var test_project_dir = path.join(test_dir, 'ios-config-xml'),
      test_plugin_dir = path.join(test_dir, 'FaultyPlugin'),
      srcDir = path.resolve(test_project_dir, 'SampleApp/Plugins'),
      options = {silent: false},
      command;

    // copy the ios test project to a temp directory
    shell.cp('-r', path.join(__dirname, 'projects', 'ios-config-xml'), test_dir);

    // copy the ios test plugin to a temp directory
    shell.cp('-r', path.join(__dirname, 'plugins', 'FaultyPlugin'), test_dir);

    command = util.format('%s %s --platform ios --project %s --plugin %s', shell.which('node'),
                                                                           plugman_exe,
                                                                           test_project_dir,
                                                                           test_plugin_dir
                         );
    var ret = shell.exec(command, options);
    test.equal(0, ret.code);
    //console.log(ret.output);
    test.notEqual("plugin installed\n", ret.output);

    test.ok(!fs.existsSync(srcDir + '/FaultyPlugin.m'))
    test.ok(!fs.existsSync(srcDir + '/FaultyPlugin.h'));

    test.done();
}

exports['should install and uninstall android plugin'] = function (test) {
  var test_project_dir = path.join(test_dir, 'android_two'),
      test_plugin_dir = path.join(test_dir, 'ChildBrowser'),
      options = {silent: true},
      command;

    // copy the ios test project to a temp directory
    shell.cp('-r', path.join(__dirname, 'projects', 'android_two'), test_dir);

    // copy the ios test plugin to a temp directory
    shell.cp('-r', path.join(__dirname, 'plugins', 'ChildBrowser'), test_dir);

    command = util.format('%s %s --platform android --project %s --plugin %s', shell.which('node'),
                                                                           plugman_exe,
                                                                           test_project_dir,
                                                                           test_plugin_dir
                         );
    var ret = shell.exec(command, options);
    test.equal(0, ret.code);
    test.equal("plugin installed\n", ret.output);

    command = util.format('%s %s --remove --platform android --project %s --plugin %s', shell.which('node'),
                                                                                plugman_exe,
                                                                                test_project_dir,
                                                                                test_plugin_dir
                         );
    var ret = shell.exec(command, options);
    test.equal(0, ret.code);
    test.equal("plugin uninstalled\n", ret.output);

    test.done();
}
<commit_msg>updating options<commit_after>/*
 *
 * Copyright 2013 Anis Kadri
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

var fs = require('fs')
  , path = require('path')
  , osenv = require('osenv')
  , shell = require('shelljs')
  , util = require('util')
  , plugman_exe = path.join(__dirname, '..', 'plugman.js')
  , test_dir = path.join(osenv.tmpdir(), 'test_plugman');

exports.setUp = function(callback) {
    shell.mkdir('-p', test_dir);
    callback();
}

exports.tearDown = function(callback) {
    // remove the temp files (projects and plugins)
    shell.rm('-rf', test_dir);
    callback();
}

exports['should list all plugins'] = function (test) {

    var command = util.format('%s %s --list', shell.which('node'), plugman_exe),
        options = {silent: true},
        ret = shell.exec(command, options);
    test.equal(0, ret.code);
    test.ok(ret.output);
    test.done();

}

exports['should install and uninstall ios plugin'] = function (test) {
  var test_project_dir = path.join(test_dir, 'ios-config-xml'),
      test_plugin_dir = path.join(test_dir, 'WebNotifications'),
      srcDir = path.resolve(test_project_dir, 'SampleApp/Plugins'),
      options = {silent: true},
      command;

    // copy the ios test project to a temp directory
    shell.cp('-r', path.join(__dirname, 'projects', 'ios-config-xml'), test_dir);

    // copy the ios test plugin to a temp directory
    shell.cp('-r', path.join(__dirname, 'plugins', 'WebNotifications'), test_dir);

    command = util.format('%s %s --platform ios --project %s --plugin %s', shell.which('node'),
                                                                           plugman_exe,
                                                                           test_project_dir,
                                                                           test_plugin_dir
                         );
    var ret = shell.exec(command, options);
    test.equal(0, ret.code);
    test.equal("plugin installed\n", ret.output);

    test.ok(fs.existsSync(srcDir + '/WebNotifications.m'))
    test.ok(fs.existsSync(srcDir + '/WebNotifications.h'));

    command = util.format('%s %s --remove --platform ios --project %s --plugin %s', shell.which('node'),
                                                                                plugman_exe,
                                                                                test_project_dir,
                                                                                test_plugin_dir
                         );
    var ret = shell.exec(command, options);
    test.equal(0, ret.code);
    test.equal("plugin uninstalled\n", ret.output);

    test.ok(!fs.existsSync(srcDir + '/WebNotifications.m'))
    test.ok(!fs.existsSync(srcDir + '/WebNotifications.h'));

    test.done();
}

exports['should revert ios install on failures'] = function (test) {
  var test_project_dir = path.join(test_dir, 'ios-config-xml'),
      test_plugin_dir = path.join(test_dir, 'FaultyPlugin'),
      srcDir = path.resolve(test_project_dir, 'SampleApp/Plugins'),
      options = {silent: true},
      command;

    // copy the ios test project to a temp directory
    shell.cp('-r', path.join(__dirname, 'projects', 'ios-config-xml'), test_dir);

    // copy the ios test plugin to a temp directory
    shell.cp('-r', path.join(__dirname, 'plugins', 'FaultyPlugin'), test_dir);

    command = util.format('%s %s --platform ios --project %s --plugin %s', shell.which('node'),
                                                                           plugman_exe,
                                                                           test_project_dir,
                                                                           test_plugin_dir
                         );
    var ret = shell.exec(command, options);
    test.equal(0, ret.code);
    //console.log(ret.output);
    test.notEqual("plugin installed\n", ret.output);

    test.ok(!fs.existsSync(srcDir + '/FaultyPlugin.m'))
    test.ok(!fs.existsSync(srcDir + '/FaultyPlugin.h'));

    test.done();
}

exports['should install and uninstall android plugin'] = function (test) {
  var test_project_dir = path.join(test_dir, 'android_two'),
      test_plugin_dir = path.join(test_dir, 'ChildBrowser'),
      options = {silent: true},
      command;

    // copy the ios test project to a temp directory
    shell.cp('-r', path.join(__dirname, 'projects', 'android_two'), test_dir);

    // copy the ios test plugin to a temp directory
    shell.cp('-r', path.join(__dirname, 'plugins', 'ChildBrowser'), test_dir);

    command = util.format('%s %s --platform android --project %s --plugin %s', shell.which('node'),
                                                                           plugman_exe,
                                                                           test_project_dir,
                                                                           test_plugin_dir
                         );
    var ret = shell.exec(command, options);
    test.equal(0, ret.code);
    test.equal("plugin installed\n", ret.output);

    command = util.format('%s %s --remove --platform android --project %s --plugin %s', shell.which('node'),
                                                                                plugman_exe,
                                                                                test_project_dir,
                                                                                test_plugin_dir
                         );
    var ret = shell.exec(command, options);
    test.equal(0, ret.code);
    test.equal("plugin uninstalled\n", ret.output);

    test.done();
}
<|endoftext|>


<commit_before>namespace('Sy');

Sy.kernel = new Sy.Kernel.Core();
Sy.kernel.getConfig().set({
    env: 'prod',
    controllers: {
        cache: true
    },
    logger: {
        level: {
            info: Sy.Lib.Logger.CoreLogger.prototype.INFO,
            debug: Sy.Lib.Logger.CoreLogger.prototype.DEBUG,
            error: Sy.Lib.Logger.CoreLogger.prototype.ERROR,
            log: Sy.Lib.Logger.CoreLogger.prototype.LOG,
        }
    },
    storage: {
        dbal: {
            defaultConnection: 'idb',
            connections: {
                idb: {
                    driver: 'indexeddb',
                    dbname: 'sy::app',
                    version: 1
                },
                local: {
                    driver: 'localstorage',
                    dbname: 'sy::app',
                    options: {
                        temporary: false
                    }
                },
                rest: {
                    driver: 'http',
                    version: 1,
                    options: {
                        pattern: '/{version}/{bundle}/{name}/',
                        headers: [
                            ['X-API-VERSION', 'vendor/1']
                        ]
                    }
                }
            }
        }
    }
});

Sy.kernel.getContainer()
    .setParameters(Sy.kernel.getConfig())
    .set({
        'sy::core::generator::uuid': {
            constructor: 'Sy.Lib.Generator.UUID'
        },
        'mediator': '@sy::core::mediator',
        'sy::core::mediator': {
            constructor: 'Sy.Lib.Mediator',
            calls: [
                ['setGenerator', ['@sy::core::generator::uuid']],
                ['setLogger', ['@sy::core::logger']]
            ]
        },
        'rest': '@sy::core::http::rest',
        'sy::core::http::rest': {
            constructor: 'Sy.HTTP.REST',
            calls: [
                ['setManager', ['@sy::core::http']]
            ]
        },
        'sy::core::registry::factory': {
            constructor: 'Sy.RegistryFactory'
        },
        'registry': '@sy::core::registry',
        'sy::core::registry': {
            constructor: 'Sy.Registry',
            factory: ['sy::core::registry::factory', 'make'],
            prototype: true
        }
        'sy::core::stateregistry::factory': {
            constructor: 'Sy.StateRegistryFactory',
            calls: [
                ['setRegistryFactory', ['@sy::core::registry::factory']]
            ]
        },
        'stateregistry': '@sy::core::stateregistry',
        'sy::core::stateregistry': {
            constructor: 'Sy.StateRegistry',
            fatory: ['sy::core::stateregistry::factory', 'make'],
            prototype: true
        }
        'sy::core::view::parser': {
            constructor: 'Sy.View.Parser'
        },
        'sy::core::view::factory::list': {
            constructor: 'Sy.View.ListFactory',
            calls: [
                ['setTemplateEngine', ['@sy::core::view::template::engine']]
            ]
        },
        'sy::core::view::factory::layout': {
            constructor: 'Sy.View.LayoutFactory',
            calls: [
                ['setParser', ['@sy::core::view::parser']],
                ['setTemplateEngine', ['@sy::core::view::template::engine']],
                ['setRegistryFactory', ['@sy::core::registry::factory']],
                ['setListFactory', ['@sy::core::view::factory::list']]
            ]
        },
        'sy::core::view::factory::viewscreen': {
            constructor: 'Sy.View.ViewScreenFactory',
            calls: [
                ['setParser', ['@sy::core::view::parser']],
                ['setTemplateEngine', ['@sy::core::view::template::engine']],
                ['setRegistryFactory', ['@sy::core::registry::factory']],
                ['setLayoutFactory', ['@sy::core::view::factory::layout']],
            ]
        },
        'sy::core::form': {
            constructor: 'Sy.Form.Builder',
            calls: [
                ['setValidator', ['@sy::core::validator']]
            ]
        },
        'logger': '@sy::core::logger',
        'sy::core::logger': {
            constructor: 'Sy.Lib.Logger.CoreLogger',
            calls: [
                ['setName', ['core']],
                ['setHandler', ['@sy::core::logger::handler::info', '%logger.level.info%']],
                ['setHandler', ['@sy::core::logger::handler::debug', '%logger.level.debug%']],
                ['setHandler', ['@sy::core::logger::handler::error', '%logger.level.error%']],
                ['setHandler', ['@sy::core::logger::handler::log', '%logger.level.log%']],
            ]
        },
        'sy::core::logger::handler::info': {
            constructor: 'Sy.Lib.Logger.Handler.Console',
            calls: [
                ['setLevel', ['%logger.level.info%']]
            ],
            private: true
        },
        'sy::core::logger::handler::debug': {
            constructor: 'Sy.Lib.Logger.Handler.Console',
            calls: [
                ['setLevel', ['%logger.level.debug%']]
            ],
            private: true
        },
        'sy::core::logger::handler::error': {
            constructor: 'Sy.Lib.Logger.Handler.Console',
            calls: [
                ['setLevel', ['%logger.level.error%']]
            ],
            private: true
        },
        'sy::core::logger::handler::log': {
            constructor: 'Sy.Lib.Logger.Handler.Console',
            calls: [
                ['setLevel', ['%logger.level.log%']]
            ],
            private: true
        },
        'http': '@sy::core::http',
        'sy::core::http': {
            constructor: 'Sy.HTTP.Manager',
            calls: [
                ['setParser', ['@sy::core::http::parser']],
                ['setGenerator', ['@sy::core::generator::uuid']],
                ['setRegistry', ['@sy::core::registry']],
                ['setLogger', ['@sy::core::logger']]
            ]
        },
        'sy::core::http::parser': {
            constructor: 'Sy.HTTP.HeaderParser',
            private: true
        },
        'translator': '@sy::core::translator',
        'sy::core::translator': {
            constructor: 'Sy.Translator',
            calls: [
                ['setRegistry', ['@sy::core::registry']],
                ['setStateRegistryFactory', ['@sy::core::stateregistry::factory']]
            ]
        },
        'sy::core::view::template::engine': {
            constructor: 'Sy.View.TemplateEngine',
            calls: [
                ['setRegistry', ['@sy::core::registry']],
                ['setGenerator', ['@sy::core::generator::uuid']]
            ]
        },
        'sy::core::viewport': {
            constructor: 'Sy.View.ViewPort',
            calls: [
                ['setNode', [document.querySelector('.viewport')]],
                ['setViewManager', ['@sy::core::view::manager']],
                ['setMediator', ['@sy::core::mediator']]
            ]
        },
        'sy::core::view::manager': {
            constructor: 'Sy.View.Manager',
            configurator: ['sy::core::view::managerconfigurator', 'configure']
            calls: [
                ['setViewsRegistry', ['@sy::core::registry']],
                ['setViewScreenFactory', ['@sy::core::view::factory::viewscreen']]
            ],
        },
        'sy::core::view::managerconfigurator': {
            constructor: 'Sy.View.ManagerConfigurator',
            calls: [
                ['setParser', ['@sy::core::view::parser']]
            ]
        },
        'validator': '@sy::core::validator',
        'sy::core::validator': {
            constructor: 'Sy.Validator.Core',
            calls: [
                ['setRulesRegistry', ['@sy::core::registry']],
                ['setContextFactory', ['@sy::core::validator::executioncontextfactory']],
                ['setConstraintFactory', ['@sy::core::validator::constraintfactory']]
            ]
        },
        'sy::core::validator::executioncontextfactory': {
            constructor: 'Sy.Validator.ExecutionContextFactory',
            calls: [
                ['setConstraintValidatorFactory', ['@sy::core::validator::constraintvalidatorfactory']]
            ]
            private: true
        },
        'sy::core::validator::constraintvalidatorfactory': {
            constructor: 'Sy.Validator.ConstraintValidatorFactory',
            private: true
        },
        'sy::core::validator::constraintfactory': {
            constructor: 'Sy.Validator.ConstraintFactory',
            private: true
        },
        'sy::core::storage::dbal::factory': {
            constructor: 'Sy.Storage.Dbal.Factory',
            private: true,
            calls: [
                ['setFactoriesRegistry', ['@sy::core::registry']],
                ['setDefaultConnectionName', ['%storage.dbal.defaultConnection%']],
                ['setConnections', ['%storage.dbal.connections%']]
            ]
        }
    });
<commit_msg>added the services definitions for the driver factories<commit_after>namespace('Sy');

Sy.kernel = new Sy.Kernel.Core();
Sy.kernel.getConfig().set({
    env: 'prod',
    controllers: {
        cache: true
    },
    logger: {
        level: {
            info: Sy.Lib.Logger.CoreLogger.prototype.INFO,
            debug: Sy.Lib.Logger.CoreLogger.prototype.DEBUG,
            error: Sy.Lib.Logger.CoreLogger.prototype.ERROR,
            log: Sy.Lib.Logger.CoreLogger.prototype.LOG,
        }
    },
    storage: {
        dbal: {
            defaultConnection: 'idb',
            connections: {
                idb: {
                    driver: 'indexeddb',
                    dbname: 'sy::app',
                    version: 1
                },
                local: {
                    driver: 'localstorage',
                    dbname: 'sy::app',
                    options: {
                        temporary: false
                    }
                },
                rest: {
                    driver: 'http',
                    version: 1,
                    options: {
                        pattern: '/{version}/{bundle}/{name}/',
                        headers: [
                            ['X-API-VERSION', 'vendor/1']
                        ]
                    }
                }
            }
        }
    }
});

Sy.kernel.getContainer()
    .setParameters(Sy.kernel.getConfig())
    .set({
        'sy::core::generator::uuid': {
            constructor: 'Sy.Lib.Generator.UUID'
        },
        'mediator': '@sy::core::mediator',
        'sy::core::mediator': {
            constructor: 'Sy.Lib.Mediator',
            calls: [
                ['setGenerator', ['@sy::core::generator::uuid']],
                ['setLogger', ['@sy::core::logger']]
            ]
        },
        'rest': '@sy::core::http::rest',
        'sy::core::http::rest': {
            constructor: 'Sy.HTTP.REST',
            calls: [
                ['setManager', ['@sy::core::http']]
            ]
        },
        'sy::core::registry::factory': {
            constructor: 'Sy.RegistryFactory'
        },
        'registry': '@sy::core::registry',
        'sy::core::registry': {
            constructor: 'Sy.Registry',
            factory: ['sy::core::registry::factory', 'make'],
            prototype: true
        }
        'sy::core::stateregistry::factory': {
            constructor: 'Sy.StateRegistryFactory',
            calls: [
                ['setRegistryFactory', ['@sy::core::registry::factory']]
            ]
        },
        'stateregistry': '@sy::core::stateregistry',
        'sy::core::stateregistry': {
            constructor: 'Sy.StateRegistry',
            fatory: ['sy::core::stateregistry::factory', 'make'],
            prototype: true
        }
        'sy::core::view::parser': {
            constructor: 'Sy.View.Parser'
        },
        'sy::core::view::factory::list': {
            constructor: 'Sy.View.ListFactory',
            calls: [
                ['setTemplateEngine', ['@sy::core::view::template::engine']]
            ]
        },
        'sy::core::view::factory::layout': {
            constructor: 'Sy.View.LayoutFactory',
            calls: [
                ['setParser', ['@sy::core::view::parser']],
                ['setTemplateEngine', ['@sy::core::view::template::engine']],
                ['setRegistryFactory', ['@sy::core::registry::factory']],
                ['setListFactory', ['@sy::core::view::factory::list']]
            ]
        },
        'sy::core::view::factory::viewscreen': {
            constructor: 'Sy.View.ViewScreenFactory',
            calls: [
                ['setParser', ['@sy::core::view::parser']],
                ['setTemplateEngine', ['@sy::core::view::template::engine']],
                ['setRegistryFactory', ['@sy::core::registry::factory']],
                ['setLayoutFactory', ['@sy::core::view::factory::layout']],
            ]
        },
        'sy::core::form': {
            constructor: 'Sy.Form.Builder',
            calls: [
                ['setValidator', ['@sy::core::validator']]
            ]
        },
        'logger': '@sy::core::logger',
        'sy::core::logger': {
            constructor: 'Sy.Lib.Logger.CoreLogger',
            calls: [
                ['setName', ['core']],
                ['setHandler', ['@sy::core::logger::handler::info', '%logger.level.info%']],
                ['setHandler', ['@sy::core::logger::handler::debug', '%logger.level.debug%']],
                ['setHandler', ['@sy::core::logger::handler::error', '%logger.level.error%']],
                ['setHandler', ['@sy::core::logger::handler::log', '%logger.level.log%']],
            ]
        },
        'sy::core::logger::handler::info': {
            constructor: 'Sy.Lib.Logger.Handler.Console',
            calls: [
                ['setLevel', ['%logger.level.info%']]
            ],
            private: true
        },
        'sy::core::logger::handler::debug': {
            constructor: 'Sy.Lib.Logger.Handler.Console',
            calls: [
                ['setLevel', ['%logger.level.debug%']]
            ],
            private: true
        },
        'sy::core::logger::handler::error': {
            constructor: 'Sy.Lib.Logger.Handler.Console',
            calls: [
                ['setLevel', ['%logger.level.error%']]
            ],
            private: true
        },
        'sy::core::logger::handler::log': {
            constructor: 'Sy.Lib.Logger.Handler.Console',
            calls: [
                ['setLevel', ['%logger.level.log%']]
            ],
            private: true
        },
        'http': '@sy::core::http',
        'sy::core::http': {
            constructor: 'Sy.HTTP.Manager',
            calls: [
                ['setParser', ['@sy::core::http::parser']],
                ['setGenerator', ['@sy::core::generator::uuid']],
                ['setRegistry', ['@sy::core::registry']],
                ['setLogger', ['@sy::core::logger']]
            ]
        },
        'sy::core::http::parser': {
            constructor: 'Sy.HTTP.HeaderParser',
            private: true
        },
        'translator': '@sy::core::translator',
        'sy::core::translator': {
            constructor: 'Sy.Translator',
            calls: [
                ['setRegistry', ['@sy::core::registry']],
                ['setStateRegistryFactory', ['@sy::core::stateregistry::factory']]
            ]
        },
        'sy::core::view::template::engine': {
            constructor: 'Sy.View.TemplateEngine',
            calls: [
                ['setRegistry', ['@sy::core::registry']],
                ['setGenerator', ['@sy::core::generator::uuid']]
            ]
        },
        'sy::core::viewport': {
            constructor: 'Sy.View.ViewPort',
            calls: [
                ['setNode', [document.querySelector('.viewport')]],
                ['setViewManager', ['@sy::core::view::manager']],
                ['setMediator', ['@sy::core::mediator']]
            ]
        },
        'sy::core::view::manager': {
            constructor: 'Sy.View.Manager',
            configurator: ['sy::core::view::managerconfigurator', 'configure']
            calls: [
                ['setViewsRegistry', ['@sy::core::registry']],
                ['setViewScreenFactory', ['@sy::core::view::factory::viewscreen']]
            ],
        },
        'sy::core::view::managerconfigurator': {
            constructor: 'Sy.View.ManagerConfigurator',
            calls: [
                ['setParser', ['@sy::core::view::parser']]
            ]
        },
        'validator': '@sy::core::validator',
        'sy::core::validator': {
            constructor: 'Sy.Validator.Core',
            calls: [
                ['setRulesRegistry', ['@sy::core::registry']],
                ['setContextFactory', ['@sy::core::validator::executioncontextfactory']],
                ['setConstraintFactory', ['@sy::core::validator::constraintfactory']]
            ]
        },
        'sy::core::validator::executioncontextfactory': {
            constructor: 'Sy.Validator.ExecutionContextFactory',
            calls: [
                ['setConstraintValidatorFactory', ['@sy::core::validator::constraintvalidatorfactory']]
            ]
            private: true
        },
        'sy::core::validator::constraintvalidatorfactory': {
            constructor: 'Sy.Validator.ConstraintValidatorFactory',
            private: true
        },
        'sy::core::validator::constraintfactory': {
            constructor: 'Sy.Validator.ConstraintFactory',
            private: true
        },
        'sy::core::storage::dbal::factory': {
            constructor: 'Sy.Storage.Dbal.Factory',
            private: true,
            calls: [
                ['setFactoriesRegistry', ['@sy::core::registry']],
                ['setDefaultConnectionName', ['%storage.dbal.defaultConnection%']],
                ['setConnections', ['%storage.dbal.connections%']]
            ]
        },
        'sy::core::storage::dbal::driver_factory::indexeddb': {
            constructor: 'Sy.Storage.Dbal.IndexedDBFactory',
            private: true,
            calls: [
                ['setEntitiesMeta', ['%app.meta.entities%']],
                ['setLogger', ['@sy::core::logger']]
            ],
            tags: [
                {name: 'storage.driver_factory', alias: 'indexeddb'}
            ]
        },
        'sy::core::storage::dbal::driver_factory::localstorage': {
            constructor: 'Sy.Storage.Dbal.LocalstorageFactory',
            private: true,
            calls: [
                ['setEntitiesMeta', ['%app.meta.entities%']],
                ['setLogger', ['@sy::core::logger']]
            ],
            tags: [
                {name: 'storage.driver_factory', alias: 'localstorage'}
            ]
        },
        'sy::core::storage::dbal::driver_factory::rest': {
            constructor: 'Sy.Storage.Dbal.RestFactory',
            private: true,
            calls: [
                ['setEntitiesMeta', ['%app.meta.entities%']],
                ['setLogger', ['@sy::core::logger']]
            ],
            tags: [
                {name: 'storage.driver_factory', alias: 'rest'}
            ]
        }
    });
<|endoftext|>