<commit_before>/* Copyright (c) 2017 Dmitri Kourennyi */
/* See the file COPYING for copying permission. */
/* exported contrast */
/* globals getDefaultComputedStyle:false color:false */
'use strict';

const contrast = {
  kInputElems: [
    'INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'TOOLBARBUTTON',
  ],
  kInvisibleElems: [
    'HEAD', 'TITLE', 'META', 'SCRIPT', 'IMG', 'STYLE', 'BR', 'LINK',
    '#text',
    'FRAMESET',
  ],

  IFRAME_DELAY: 500,

  userInverted: false,

  checkDoc() {
    // Check from root recursively
    this.checkElement(document.documentElement, {recurse: true});

    // Other checks required when browser is in quirks mode
    if (document.compatMode === 'BackCompat') {
      // Tables don't inherit color
      const tables = document.getElementsByTagName('table');

      for (let i = 0; i < tables.length; i += 1) {
        if (getComputedStyle(tables[i]).color ===
          getDefaultComputedStyle(tables[i]).color) {
          // If color has not been set explicitly, then force inherit
          tables[i].style.color = 'inherit';
        }
      }
    }
  },

  checkElement(element, {recurse, delay, parentFg, parentBg} = {
    recurse:  false,
    delay:    false,
    parentFg: null,
    parentBg: null,
  }) {
    if (element == null) {
      return;
    }

    // If element has already been examined before, don't do any processing
    if (element.dataset._extensionTextContrast != null) {
      return;
    }

    const fg_color_defined = this.is_fg_defined(element) || parentFg != null;
    const bg_color_defined = this.is_bg_defined(element) || parentBg != null;
    const bg_img_defined = this.is_bg_img_defined(element);

    if (fg_color_defined && bg_color_defined) {
      // Both colors explicitely defined, nothing to do
      element.dataset._extensionTextContrast = '';
      this.fix_embeds(element, 'std');

      return;
    } else if (!fg_color_defined && bg_color_defined) {
      // Only set fg if original contrast is poor
      parentFg = parentFg || color.to_rgb(getComputedStyle(element).color);
      parentBg = parentBg ||
        color.to_rgb(getComputedStyle(element).backgroundColor);

      if (color.is_transparent(parentBg) ||
        !color.is_contrasty(parentFg, parentBg)) {
        element.dataset._extensionTextContrast = 'fg';
        this.fix_embeds(element, 'std');

        return;
      }
    } else if (fg_color_defined && !bg_color_defined) {
      // Only set bg if it will improve contrast
      parentFg = parentFg || color.to_rgb(getComputedStyle(element).color);
      parentBg = parentBg ||
        color.to_rgb(getComputedStyle(element).backgroundColor);

      if (!color.is_contrasty(parentFg, parentBg)) {
        element.dataset._extensionTextContrast = 'bg';
        this.fix_embeds(element, 'std');

        return;
      }
    } else if (bg_img_defined) {
      const default_bg =
        color.to_rgb(getDefaultComputedStyle(element).backgroundColor);

      if (!this.isInputNode(element) && color.is_transparent(default_bg)) {
        // If the background is supposed to be transparent, keep the
        // transparency and only fix foreground. Don't do this for input
        // elements as they have an inverted style from the get-go.
        element.dataset._extensionTextContrast = 'fg';
      } else {
        // No FG or BG color, but may have a transparent bg image. BG color is
        // not transparent, so need to set both colors.
        element.dataset._extensionTextContrast = 'both';
      }
      this.fix_embeds(element, 'std');

      return;
    }

    if (recurse === true) {
      const {children} = element;
      const len = children.length;

      for (let i = 0; i < len; i += 1) {
        // Don't look at non-renderable elements
        if (!this.isInVisibleNode(element.children[i])) {
          this.checkElement(element.children[i],
                            {recurse, delay, parentFg, parentBg});
        }
      }

      if (this.is_subdoc(element)) {
        if (delay) {
          setTimeout(() => {
            this.fix_embeds(element, 'fix');
          }, this.IFRAME_DELAY);
        } else {
          this.fix_embeds(element, 'fix');
        }
      }
    }
  },

  checkInputs(elem) {
    // Check all input elements under elem
    const nodeIterator = document.createNodeIterator(elem,
      NodeFilter.SHOW_ELEMENT, {acceptNode: this.isInputNode.bind(this)});

    // Can't use for-in loop because a NodeIterator is not an iterator. Thanks
    // Javascript.
    let node; // eslint-disable-line init-declarations

    while ((node = nodeIterator.nextNode()) != null) {
      this.checkElement(node);
    }
  },

  clear_overrides(doc) {
    const elems = doc.querySelectorAll('[data-_extension-text-contrast]');

    for (const e of elems) {
      e.removeAttribute('data-_extension-text-contrast');
    }
  },

  fix_embeds(e, mode) {
    const nodeIterator = document.createNodeIterator(
      e, NodeFilter.SHOW_ELEMENT, {acceptNode: this.is_subdoc});

    // Can't use for-in loop because a NodeIterator is not an iterator. Thanks
    // Javascript.
    let node; // eslint-disable-line init-declarations

    while ((node = nodeIterator.nextNode()) != null) {
      if (node.contentWindow != null) {
        if (mode === 'std') {
          node.contentWindow.postMessage('_tcfdt_subdoc_std', '*');
        } else if (mode === 'fix') {
          node.contentWindow.postMessage('_tcfdt_subdoc_fix', '*');
        } else if (mode === 'clr') {
          node.contentWindow.postMessage('_tcfdt_subdoc_clr', '*');
        }
      } else if (node.getSVGDocument != null && node.getSVGDocument() != null &&
        mode === 'std') {
        this.clear_overrides(node.getSVGDocument().documentElement);
        // Node is an <embed> SVG file, which will use the local stylesheet, so
        // set its dataset directly.
        node.getSVGDocument().documentElement.dataset._extensionTextContrast =
          'default';
      }
    }
  },

  get_subdoc(node) {
    if (node.contentDocument != null) {
      return node.contentDocument.documentElement;
    }

    if (node.getSVGDocument != null && node.getSVGDocument() != null) {
      return node.getSVGDocument().documentElement;
    }

    return null;
  },

  is_fg_defined(e) {
    return getComputedStyle(e).color !== getDefaultComputedStyle(e).color;
  },

  is_bg_defined(e) {
    return getComputedStyle(e).backgroundColor !==
      getDefaultComputedStyle(e).backgroundColor;
  },

  is_bg_img_defined(e) {
    return getComputedStyle(e).backgroundImage !== 'none';
  },

  isInputNode(node) {
    return this.kInputElems.indexOf(node.nodeName) > -1;
  },

  isInVisibleNode(node) {
    return this.kInvisibleElems.indexOf(node.nodeName) > -1;
  },

  is_subdoc(node) {
    if (node.contentWindow != null ||
      node.getSVGDocument != null && node.getSVGDocument() != null) {
      return true;
    }

    return false;
  },

  recolor_parent_check(elem) {
    if (this.userInverted === true) {
      let parent = elem.parentElement;
      let defined = false;

      while (parent !== null) {
        if (parent.dataset._extensionTextContrast != null) {
          // If any parents' were already handled,
          // new elements don't need recolor.
          defined = true;
          break;
        }
        parent = parent.parentElement;
      }
      if (defined) {
        // Color was set, adjust any child embeds
        setTimeout(() => {
          this.fix_embeds(elem, 'std');
        }, this.IFRAME_DELAY);
      } else {
        this.checkElement(elem, {recurse: true, delay: true});
      }
    }
  },

  updateUserInverted() {
    const defaultFg = color.to_rgb(getDefaultComputedStyle(
      document.documentElement).color);

    if (!color.is_contrasty(defaultFg, {r: 255, g: 255, b: 255, a: 1})) {
      // Contrast check against what sites will assume to be default
      // (black fg, white bg) failed, so user most likely has 'Use system
      // colors' on
      contrast.userInverted = true;
    }
  },
};
<commit_msg>Fix 37af71c6 accidentally breaking the transparent background image case.<commit_after>/* Copyright (c) 2017 Dmitri Kourennyi */
/* See the file COPYING for copying permission. */
/* exported contrast */
/* globals getDefaultComputedStyle:false color:false */
'use strict';

const contrast = {
  kInputElems: [
    'INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'TOOLBARBUTTON',
  ],
  kInvisibleElems: [
    'HEAD', 'TITLE', 'META', 'SCRIPT', 'IMG', 'STYLE', 'BR', 'LINK',
    '#text',
    'FRAMESET',
  ],

  IFRAME_DELAY: 500,

  userInverted: false,

  checkDoc() {
    // Check from root recursively
    this.checkElement(document.documentElement, {recurse: true});

    // Other checks required when browser is in quirks mode
    if (document.compatMode === 'BackCompat') {
      // Tables don't inherit color
      const tables = document.getElementsByTagName('table');

      for (let i = 0; i < tables.length; i += 1) {
        if (getComputedStyle(tables[i]).color ===
          getDefaultComputedStyle(tables[i]).color) {
          // If color has not been set explicitly, then force inherit
          tables[i].style.color = 'inherit';
        }
      }
    }
  },

  checkElement(element, {recurse, delay, parentFg, parentBg} = {
    recurse:  false,
    delay:    false,
    parentFg: null,
    parentBg: null,
  }) {
    if (element == null) {
      return;
    }

    // If element has already been examined before, don't do any processing
    if (element.dataset._extensionTextContrast != null) {
      return;
    }

    const fg_color_defined = this.is_fg_defined(element);
    const bg_color_defined = this.is_bg_defined(element);
    const bg_img_defined = this.is_bg_img_defined(element);

    if (fg_color_defined && bg_color_defined) {
      // Both colors explicitely defined, nothing to do
      element.dataset._extensionTextContrast = '';
      this.fix_embeds(element, 'std');

      return;
    } else if (!fg_color_defined && bg_color_defined) {
      // Only set fg if original contrast is poor
      parentFg = parentFg || color.to_rgb(getComputedStyle(element).color);
      parentBg = parentBg ||
        color.to_rgb(getComputedStyle(element).backgroundColor);

      if (color.is_transparent(parentBg) ||
        !color.is_contrasty(parentFg, parentBg)) {
        element.dataset._extensionTextContrast = 'fg';
        this.fix_embeds(element, 'std');

        return;
      }
    } else if (fg_color_defined && !bg_color_defined) {
      // Only set bg if it will improve contrast
      parentFg = parentFg || color.to_rgb(getComputedStyle(element).color);
      parentBg = parentBg ||
        color.to_rgb(getComputedStyle(element).backgroundColor);

      if (!color.is_contrasty(parentFg, parentBg)) {
        element.dataset._extensionTextContrast = 'bg';
        this.fix_embeds(element, 'std');

        return;
      }
    } else if (bg_img_defined) {
      const default_bg =
        color.to_rgb(getDefaultComputedStyle(element).backgroundColor);

      if (!this.isInputNode(element) && color.is_transparent(default_bg)) {
        // If the background is supposed to be transparent, keep the
        // transparency and only fix foreground. Don't do this for input
        // elements as they have an inverted style from the get-go.
        element.dataset._extensionTextContrast = 'fg';
      } else {
        // No FG or BG color, but may have a transparent bg image. BG color is
        // not transparent, so need to set both colors.
        element.dataset._extensionTextContrast = 'both';
      }
      this.fix_embeds(element, 'std');

      return;
    }

    if (recurse === true) {
      const {children} = element;
      const len = children.length;

      for (let i = 0; i < len; i += 1) {
        // Don't look at non-renderable elements
        if (!this.isInVisibleNode(element.children[i])) {
          this.checkElement(element.children[i],
                            {recurse, delay, parentFg, parentBg});
        }
      }

      if (this.is_subdoc(element)) {
        if (delay) {
          setTimeout(() => {
            this.fix_embeds(element, 'fix');
          }, this.IFRAME_DELAY);
        } else {
          this.fix_embeds(element, 'fix');
        }
      }
    }
  },

  checkInputs(elem) {
    // Check all input elements under elem
    const nodeIterator = document.createNodeIterator(elem,
      NodeFilter.SHOW_ELEMENT, {acceptNode: this.isInputNode.bind(this)});

    // Can't use for-in loop because a NodeIterator is not an iterator. Thanks
    // Javascript.
    let node; // eslint-disable-line init-declarations

    while ((node = nodeIterator.nextNode()) != null) {
      this.checkElement(node);
    }
  },

  clear_overrides(doc) {
    const elems = doc.querySelectorAll('[data-_extension-text-contrast]');

    for (const e of elems) {
      e.removeAttribute('data-_extension-text-contrast');
    }
  },

  fix_embeds(e, mode) {
    const nodeIterator = document.createNodeIterator(
      e, NodeFilter.SHOW_ELEMENT, {acceptNode: this.is_subdoc});

    // Can't use for-in loop because a NodeIterator is not an iterator. Thanks
    // Javascript.
    let node; // eslint-disable-line init-declarations

    while ((node = nodeIterator.nextNode()) != null) {
      if (node.contentWindow != null) {
        if (mode === 'std') {
          node.contentWindow.postMessage('_tcfdt_subdoc_std', '*');
        } else if (mode === 'fix') {
          node.contentWindow.postMessage('_tcfdt_subdoc_fix', '*');
        } else if (mode === 'clr') {
          node.contentWindow.postMessage('_tcfdt_subdoc_clr', '*');
        }
      } else if (node.getSVGDocument != null && node.getSVGDocument() != null &&
        mode === 'std') {
        this.clear_overrides(node.getSVGDocument().documentElement);
        // Node is an <embed> SVG file, which will use the local stylesheet, so
        // set its dataset directly.
        node.getSVGDocument().documentElement.dataset._extensionTextContrast =
          'default';
      }
    }
  },

  get_subdoc(node) {
    if (node.contentDocument != null) {
      return node.contentDocument.documentElement;
    }

    if (node.getSVGDocument != null && node.getSVGDocument() != null) {
      return node.getSVGDocument().documentElement;
    }

    return null;
  },

  is_fg_defined(e) {
    return getComputedStyle(e).color !== getDefaultComputedStyle(e).color;
  },

  is_bg_defined(e) {
    return getComputedStyle(e).backgroundColor !==
      getDefaultComputedStyle(e).backgroundColor;
  },

  is_bg_img_defined(e) {
    return getComputedStyle(e).backgroundImage !== 'none';
  },

  isInputNode(node) {
    return this.kInputElems.indexOf(node.nodeName) > -1;
  },

  isInVisibleNode(node) {
    return this.kInvisibleElems.indexOf(node.nodeName) > -1;
  },

  is_subdoc(node) {
    if (node.contentWindow != null ||
      node.getSVGDocument != null && node.getSVGDocument() != null) {
      return true;
    }

    return false;
  },

  recolor_parent_check(elem) {
    if (this.userInverted === true) {
      let parent = elem.parentElement;
      let defined = false;

      while (parent !== null) {
        if (parent.dataset._extensionTextContrast != null) {
          // If any parents' were already handled,
          // new elements don't need recolor.
          defined = true;
          break;
        }
        parent = parent.parentElement;
      }
      if (defined) {
        // Color was set, adjust any child embeds
        setTimeout(() => {
          this.fix_embeds(elem, 'std');
        }, this.IFRAME_DELAY);
      } else {
        this.checkElement(elem, {recurse: true, delay: true});
      }
    }
  },

  updateUserInverted() {
    const defaultFg = color.to_rgb(getDefaultComputedStyle(
      document.documentElement).color);

    if (!color.is_contrasty(defaultFg, {r: 255, g: 255, b: 255, a: 1})) {
      // Contrast check against what sites will assume to be default
      // (black fg, white bg) failed, so user most likely has 'Use system
      // colors' on
      contrast.userInverted = true;
    }
  },
};
<|endoftext|>


<commit_before>const {Menu, app} = require('electron');

module.exports = (hostipc) => {

  app.once('ready', () => {
    const template = [
      {
        label: 'Datei',
        submenu: [
          {
            label: 'Neu',
            accelerator: 'CmdOrCtrl+N'
          },
          {
            label: 'Öffnen ...',
            accelerator: 'CmdOrCtrl+O',
            click: hostipc.open
          },
          {
            label: 'Speichern',
            accelerator: 'CmdOrCtrl+S',
            click: hostipc.save
          },
          {
            label: 'Speichern unter ...',
            accelerator: 'CmdOrCtrl+Shift+S',
            click: hostipc.saveAs
          },
          {
            type: 'separator'
          },
          {
            label: 'Daten senden ...',
            click: () => hostipc.sendData(false)
          },
          {
            label: 'Übertragung testen ...',
            click: () => hostipc.sendData(true)
          },
          {
            type: 'separator'
          },
          {
            label: 'Beenden',
            accelerator: 'CmdOrCtrl+Q',
            role: 'close'
          }
        ]
      },
      {
        label: 'Bearbeiten',
        submenu: [
          {
            label: 'Einstellungen',
            accelerator: 'CmdOrCtrl+,'
          },
        ]
      },
      {
        label: 'Werkzeuge',
        submenu: [
          {
            label: 'Dauerfristverlängerung ...',
            click: hostipc.showUStSVzA
          },
          {
            label: 'Protokoll nachdrucken ...',
            click: hostipc.reprintProtocol
          },
          {
            type: 'separator'
          },
          {
            label: 'Entwickler-Tools anzeigen',
            accelerator: (function() {
              if (process.platform === 'darwin')
                return 'Alt+Command+I';
              else
                return 'Ctrl+Shift+I';
            })(),
            click: function(item, focusedWindow) {
              if (focusedWindow)
                focusedWindow.toggleDevTools();
            }
          },
        ]
      },
      {
        label: 'Hilfe',
        role: 'help',
        submenu: [
          {
            label: 'Info',
            role: 'about',
            click: hostipc.showAboutDialog
          },
        ]
      },
    ];

    if (process.platform === 'darwin') {
      const name = app.getName();
      template.unshift({
        label: name,
        submenu: [
          {
            label: 'About ' + name,
            role: 'about'
          },
          {
            type: 'separator'
          },
          {
            label: 'Services',
            role: 'services',
            submenu: []
          },
          {
            type: 'separator'
          },
          {
            label: 'Hide ' + name,
            accelerator: 'Command+H',
            role: 'hide'
          },
          {
            label: 'Hide Others',
            accelerator: 'Command+Shift+H',
            role: 'hideothers'
          },
          {
            label: 'Show All',
            role: 'unhide'
          },
          {
            type: 'separator'
          },
          {
            label: 'Quit',
            accelerator: 'Command+Q',
            click: function() { app.quit(); }
          },
        ]
      });
    }

    const menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu)
  });
};
<commit_msg>hide preferences menu<commit_after>const {Menu, app} = require('electron');

module.exports = (hostipc) => {

  app.once('ready', () => {
    const template = [
      {
        label: 'Datei',
        submenu: [
          {
            label: 'Neu',
            accelerator: 'CmdOrCtrl+N'
          },
          {
            label: 'Öffnen ...',
            accelerator: 'CmdOrCtrl+O',
            click: hostipc.open
          },
          {
            label: 'Speichern',
            accelerator: 'CmdOrCtrl+S',
            click: hostipc.save
          },
          {
            label: 'Speichern unter ...',
            accelerator: 'CmdOrCtrl+Shift+S',
            click: hostipc.saveAs
          },
          {
            type: 'separator'
          },
          {
            label: 'Daten senden ...',
            click: () => hostipc.sendData(false)
          },
          {
            label: 'Übertragung testen ...',
            click: () => hostipc.sendData(true)
          },
          {
            type: 'separator'
          },
          {
            label: 'Beenden',
            accelerator: 'CmdOrCtrl+Q',
            role: 'close'
          }
        ]
      },
      /* {
        label: 'Bearbeiten',
        submenu: [
          {
            label: 'Einstellungen',
            accelerator: 'CmdOrCtrl+,'
          },
        ]
      }, */
      {
        label: 'Werkzeuge',
        submenu: [
          {
            label: 'Dauerfristverlängerung ...',
            click: hostipc.showUStSVzA
          },
          {
            label: 'Protokoll nachdrucken ...',
            click: hostipc.reprintProtocol
          },
          {
            type: 'separator'
          },
          {
            label: 'Entwickler-Tools anzeigen',
            accelerator: (function() {
              if (process.platform === 'darwin')
                return 'Alt+Command+I';
              else
                return 'Ctrl+Shift+I';
            })(),
            click: function(item, focusedWindow) {
              if (focusedWindow)
                focusedWindow.toggleDevTools();
            }
          },
        ]
      },
      {
        label: 'Hilfe',
        role: 'help',
        submenu: [
          {
            label: 'Info',
            role: 'about',
            click: hostipc.showAboutDialog
          },
        ]
      },
    ];

    if (process.platform === 'darwin') {
      const name = app.getName();
      template.unshift({
        label: name,
        submenu: [
          {
            label: 'About ' + name,
            role: 'about'
          },
          {
            type: 'separator'
          },
          {
            label: 'Services',
            role: 'services',
            submenu: []
          },
          {
            type: 'separator'
          },
          {
            label: 'Hide ' + name,
            accelerator: 'Command+H',
            role: 'hide'
          },
          {
            label: 'Hide Others',
            accelerator: 'Command+Shift+H',
            role: 'hideothers'
          },
          {
            label: 'Show All',
            role: 'unhide'
          },
          {
            type: 'separator'
          },
          {
            label: 'Quit',
            accelerator: 'Command+Q',
            click: function() { app.quit(); }
          },
        ]
      });
    }

    const menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu)
  });
};
<|endoftext|>


<commit_before>import _ from 'underscore';

/**
 * A service to handle events from context and remap the event data for tracking
 * purposes.
 *
 * @class Tracking-Registry
 */
class Service {

	/**
	 * Creates an instance of the registry service.
	 *
	 * @constructor
	 * @param {object} options - The settings for the service.
	 * @param {context} options.context - The reference to the
	 *		backbone.geppetto context.
	 */
	constructor(options) {
		if (!options.context) {
			throw new Error('Tracking registry needs a context');
		}

		this._context = options.context;
		this._events = {};

		_.bindAll(this, '_onEvent');
	}

	/**
	 * This function adds a listener at the context for a given event name. If
	 * this event appears, it fires an other event and remaps the received event
	 * data for the new event. This can be used to wire a lot of events in an
	 * application into a single event. The event data will be updated to be
	 * used in a single backbone.geppetto command.
	 *
	 * @param {string} fire - The event name which will be fired after the
	 *		registered event appeared.
	 * @param {string} eventName - The event name to listen and remap
	 * @param {object} mapping - Defines the values which should be given to the
	 *		track event call. Define here all values for the track event
	 *		call (category, action, label, value). The registry can handle
	 *		predefined values and dynamic value from functions or
	 *		properties. To use functions or properties use a leading ".".
	 *		The event data object must support those functions or properties.
	 * @example
	 *		register('event:to:fire', 'event:to:wait:for' {
	 *			category: 'some-category',
	 *			action: 'some-action',
	 *			label: '.getLabel',
	 *			value: '.getValue'
	 *		});
	 * @example
	 *		// You can also use function references here by passing the
	 *		// function into a specific property to add custom logic.
	 *		register('event:to:fire', 'event:to:wait:for' {
	 *			category: 'some-category',
	 *			action: function(eventData, thisMapping) {
	 *				return evenData.foo.bar() ? 'omg' : 'wtf';
	 *			}
	 *		});
	 */
	register(fire, eventName, mapping) {
		if (!_.isObject(mapping)) {
			throw new Error('Missing mapping config for "' + eventName + '"');
		}

		if (!this._events[eventName]) {
			this._context.vent.on(eventName, this._onEvent);
		}

		this._events[eventName] = this._events[eventName] || [];
		this._events[eventName].push({
			fire: fire,
			mapping: mapping
		});
	}

	/**
	 * Register a track event call to a specific context event. This is a
	 * shorthand for .register()-call with settings for eventtacking in
	 * google analytics.
	 *
	 * @param {string} eventName - Is the specific event name.
	 * @param {object} mapping - Defines the values which should be given to the
	 *		track event call. Define here all values for the track event
	 *		call (category, action, label, value). The registry can handle
	 *		predefined values and dynamic value from functions or
	 *		properties. To use functions or properties use a leading ".".
	 *		The event data object must support those functions or properties.
	 * @example
	 *		registerEvent('some:event', {
	 *			category: 'some-category',
	 *			action: 'some-action',
	 *			label: '.getLabel',
	 *			value: '.getValue'
	 *		});
	 * @example
	 *		// You can also use function references here by passing the
	 *		//function into a specific property to add custom logic.
	 *		registerEvent('some:event', {
	 *			category: 'some-category',
	 *			action: function(eventData, thisMapping) {
	 *				return evenData.foo.bar() ? 'omg' : 'wtf';
	 *			}
	 *		});
	 */
	registerEvent(eventName, mapping) {
		this.register('tracking:trackevent', eventName, mapping);
	}

	/**
	 * Register a track pageview call to a specific context event. This is a
	 * shorthand for .register()-call with settings for pageviewtacking in
	 * google analytics.
	 *
	 * @param {string} eventName - Is the specific event name.
	 * @param {object} mapping - Defines the values which should be given to the
	 *		track event call. Define here all values for the track event
	 *		call (category, action, label, value). The registry can handle
	 *		predefined values and dynamic value from functions or
	 *		properties. To use functions or properties use a leading ".".
	 *		The event data object must support those functions or properties.
	 * @example
	 *		registerPageview('some:event', {
	 *			category: 'some-category',
	 *			action: 'some-action',
	 *			label: '.getLabel',
	 *			value: '.getValue'
	 *		});
	 * @example
	 *		// You can also use function references here by passing the
	 *		//function into a specific property to add custom logic.
	 *		registerPageview('some:event', {
	 *			category: 'some-category',
	 *			action: function(eventData, thisMapping) {
	 *				return evenData.foo.bar() ? 'omg' : 'wtf';
	 *			}
	 *		});
	 */
	registerPageview(eventName, mapping) {
		this.register('tracking:trackpageview', eventName, mapping);
	}

	/**
	 * Register a track social call to a specific context event. This is a
	 * shorthand for .register()-call with settings for socialtracking in
	 * google analytics.
	 *
	 * @param {string} eventName - Is the specific event name.
	 * @param {object} mapping - Defines the values which should be given to the
	 *		track event call. Define here all values for the track event
	 *		call (category, action, label, value). The registry can handle
	 *		predefined values and dynamic value from functions or
	 *		properties. To use functions or properties use a leading ".".
	 *		The event data object must support those functions or properties.
	 * @example
	 *		registerSocial('some:event', {
	 *			category: 'some-category',
	 *			action: 'some-action',
	 *			label: '.getLabel',
	 *			value: '.getValue'
	 *		});
	 * @example
	 *		// You can also use function references here by passing the
	 *		//function into a specific property to add custom logic.
	 *		registerSocial('some:event', {
	 *			category: 'some-category',
	 *			action: function(eventData, thisMapping) {
	 *				return evenData.foo.bar() ? 'omg' : 'wtf';
	 *			}
	 *		});
	 */
	registerSocial(eventName, mapping) {
		this.register('tracking:tracksocial', eventName, mapping);
	}

	_build(target, mapping) {
		var data = {};
		// Build tracking data depending on mapping:
		_.each(mapping, function(value, key) {
			if (_.isString(value)) {
				if (value.indexOf('.') === 0) {
					value = value.substr(1);
					if (_.isFunction(target[value])) {
						data[key] = target[value]();
					} else if (target[value]) {
						data[key] = target[value];
					}
				} else {
					data[key] = value;
				}
			} else if (_.isFunction(value)) {
				data[key] = value.call(this, target, mapping);
			}
		}, this);

		return data;
	}

	_onEvent(event) {
		_.each(this._events[event.eventName], function(settings) {
			this._context.dispatch(
				settings.fire,
				this._build(event, settings.mapping)
			);
		}, this);
	}
}

/**
 * @module picnic/tracking-registry/services/Registry
 */
export default Service;
<commit_msg>Update documentation<commit_after>import _ from 'underscore';

/**
 * A service to handle events from context and remap the event data for tracking
 * purposes.
 *
 * @class Tracking-Registry
 */
class Service {

	/**
	 * Creates an instance of the registry service.
	 *
	 * @constructor
	 * @param {object} options The settings for the service.
	 * @param {context} options.context The reference to the
	 *		backbone.geppetto context.
	 */
	constructor(options) {
		if (!options.context) {
			throw new Error('Tracking registry needs a context');
		}

		this._context = options.context;
		this._events = {};

		_.bindAll(this, '_onEvent');
	}

	/**
	 * This function adds a listener at the context for a given event name. If
	 * this event appears, it fires an other event and remaps the received event
	 * data for the new event. This can be used to wire a lot of events in an
	 * application into a single event. The event data will be updated to be
	 * used in a single backbone.geppetto command.
	 *
	 * @param {string} fire The event name which will be fired after the
	 *		registered event appeared.
	 * @param {string} eventName The event name to listen and remap
	 * @param {object} mapping Defines the values which should be given to the
	 *		track event call. Define here all values for the track event
	 *		call (category, action, label, value). The registry can handle
	 *		predefined values and dynamic value from functions or
	 *		properties. To use functions or properties use a leading ".".
	 *		The event data object must support those functions or properties.
	 * @example
	 *		register('event:to:fire', 'event:to:wait:for' {
	 *			category: 'some-category',
	 *			action: 'some-action',
	 *			label: '.getLabel',
	 *			value: '.getValue'
	 *		});
	 * @example
	 *		// You can also use function references here by passing the
	 *		// function into a specific property to add custom logic.
	 *		register('event:to:fire', 'event:to:wait:for' {
	 *			category: 'some-category',
	 *			action: function(eventData, thisMapping) {
	 *				return evenData.foo.bar() ? 'omg' : 'wtf';
	 *			}
	 *		});
	 */
	register(fire, eventName, mapping) {
		if (!_.isObject(mapping)) {
			throw new Error('Missing mapping config for "' + eventName + '"');
		}

		if (!this._events[eventName]) {
			this._context.vent.on(eventName, this._onEvent);
		}

		this._events[eventName] = this._events[eventName] || [];
		this._events[eventName].push({
			fire: fire,
			mapping: mapping
		});
	}

	/**
	 * Register a track event call to a specific context event. This is a
	 * shorthand for .register()-call with settings for eventtacking in
	 * google analytics.
	 *
	 * @param {string} eventName Is the specific event name.
	 * @param {object} mapping Defines the values which should be given to the
	 *		track event call. Define here all values for the track event
	 *		call (category, action, label, value). The registry can handle
	 *		predefined values and dynamic value from functions or
	 *		properties. To use functions or properties use a leading ".".
	 *		The event data object must support those functions or properties.
	 * @example
	 *		registerEvent('some:event', {
	 *			category: 'some-category',
	 *			action: 'some-action',
	 *			label: '.getLabel',
	 *			value: '.getValue'
	 *		});
	 * @example
	 *		// You can also use function references here by passing the
	 *		//function into a specific property to add custom logic.
	 *		registerEvent('some:event', {
	 *			category: 'some-category',
	 *			action: function(eventData, thisMapping) {
	 *				return evenData.foo.bar() ? 'omg' : 'wtf';
	 *			}
	 *		});
	 */
	registerEvent(eventName, mapping) {
		this.register('tracking:trackevent', eventName, mapping);
	}

	/**
	 * Register a track pageview call to a specific context event. This is a
	 * shorthand for .register()-call with settings for pageviewtacking in
	 * google analytics.
	 *
	 * @param {string} eventName Is the specific event name.
	 * @param {object} mapping Defines the values which should be given to the
	 *		track event call. Define here all values for the track event
	 *		call (category, action, label, value). The registry can handle
	 *		predefined values and dynamic value from functions or
	 *		properties. To use functions or properties use a leading ".".
	 *		The event data object must support those functions or properties.
	 * @example
	 *		registerPageview('some:event', {
	 *			category: 'some-category',
	 *			action: 'some-action',
	 *			label: '.getLabel',
	 *			value: '.getValue'
	 *		});
	 * @example
	 *		// You can also use function references here by passing the
	 *		//function into a specific property to add custom logic.
	 *		registerPageview('some:event', {
	 *			category: 'some-category',
	 *			action: function(eventData, thisMapping) {
	 *				return evenData.foo.bar() ? 'omg' : 'wtf';
	 *			}
	 *		});
	 */
	registerPageview(eventName, mapping) {
		this.register('tracking:trackpageview', eventName, mapping);
	}

	/**
	 * Register a track social call to a specific context event. This is a
	 * shorthand for .register()-call with settings for socialtracking in
	 * google analytics.
	 *
	 * @param {string} eventName Is the specific event name.
	 * @param {object} mapping Defines the values which should be given to the
	 *		track event call. Define here all values for the track event
	 *		call (category, action, label, value). The registry can handle
	 *		predefined values and dynamic value from functions or
	 *		properties. To use functions or properties use a leading ".".
	 *		The event data object must support those functions or properties.
	 * @example
	 *		registerSocial('some:event', {
	 *			category: 'some-category',
	 *			action: 'some-action',
	 *			label: '.getLabel',
	 *			value: '.getValue'
	 *		});
	 * @example
	 *		// You can also use function references here by passing the
	 *		//function into a specific property to add custom logic.
	 *		registerSocial('some:event', {
	 *			category: 'some-category',
	 *			action: function(eventData, thisMapping) {
	 *				return evenData.foo.bar() ? 'omg' : 'wtf';
	 *			}
	 *		});
	 */
	registerSocial(eventName, mapping) {
		this.register('tracking:tracksocial', eventName, mapping);
	}

	_build(target, mapping) {
		var data = {};
		// Build tracking data depending on mapping:
		_.each(mapping, function(value, key) {
			if (_.isString(value)) {
				if (value.indexOf('.') === 0) {
					value = value.substr(1);
					if (_.isFunction(target[value])) {
						data[key] = target[value]();
					} else if (target[value]) {
						data[key] = target[value];
					}
				} else {
					data[key] = value;
				}
			} else if (_.isFunction(value)) {
				data[key] = value.call(this, target, mapping);
			}
		}, this);

		return data;
	}

	_onEvent(event) {
		_.each(this._events[event.eventName], function(settings) {
			this._context.dispatch(
				settings.fire,
				this._build(event, settings.mapping)
			);
		}, this);
	}
}

/**
 * @module picnic/tracking-registry/services/Registry
 */
export default Service;
<|endoftext|>


<commit_before>/*global define*/
define([
        './Cartesian2',
        './Cartographic',
        './defaultValue',
        './defined',
        './DeveloperError',
        './GeographicProjection',
        './Intersect',
        './Rectangle'
    ], function(
        Cartesian2,
        Cartographic,
        defaultValue,
        defined,
        DeveloperError,
        GeographicProjection,
        Intersect,
        Rectangle) {
    'use strict';

    /**
     * A bounding rectangle given by a corner, width and height.
     * @alias BoundingRectangle
     * @constructor
     *
     * @param {Number} [x=0.0] The x coordinate of the rectangle.
     * @param {Number} [y=0.0] The y coordinate of the rectangle.
     * @param {Number} [width=0.0] The width of the rectangle.
     * @param {Number} [height=0.0] The height of the rectangle.
     *
     * @see BoundingSphere
     * @see Packable
     */
    function BoundingRectangle(x, y, width, height) {
        /**
         * The x coordinate of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.x = defaultValue(x, 0.0);

        /**
         * The y coordinate of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.y = defaultValue(y, 0.0);

        /**
         * The width of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.width = defaultValue(width, 0.0);

        /**
         * The height of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.height = defaultValue(height, 0.0);
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    BoundingRectangle.packedLength = 4;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {BoundingRectangle} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    BoundingRectangle.pack = function(value, array, startingIndex) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        //>>includeEnd('debug');

        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value.x;
        array[startingIndex++] = value.y;
        array[startingIndex++] = value.width;
        array[startingIndex] = value.height;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {BoundingRectangle} [result] The object into which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.unpack = function(array, startingIndex, result) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        //>>includeEnd('debug');

        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new BoundingRectangle();
        }
        result.x = array[startingIndex++];
        result.y = array[startingIndex++];
        result.width = array[startingIndex++];
        result.height = array[startingIndex];
        return result;
    };

    /**
     * Computes a bounding rectangle enclosing the list of 2D points.
     * The rectangle is oriented with the corner at the bottom left.
     *
     * @param {Cartesian2[]} positions List of points that the bounding rectangle will enclose.  Each point must have x and y properties.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.fromPoints = function(positions, result) {
        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        if (!defined(positions) || positions.length === 0) {
            result.x = 0;
            result.y = 0;
            result.width = 0;
            result.height = 0;
            return result;
        }

        var length = positions.length;

        var minimumX = positions[0].x;
        var minimumY = positions[0].y;

        var maximumX = positions[0].x;
        var maximumY = positions[0].y;

        for ( var i = 1; i < length; i++) {
            var p = positions[i];
            var x = p.x;
            var y = p.y;

            minimumX = Math.min(x, minimumX);
            maximumX = Math.max(x, maximumX);
            minimumY = Math.min(y, minimumY);
            maximumY = Math.max(y, maximumY);
        }

        result.x = minimumX;
        result.y = minimumY;
        result.width = maximumX - minimumX;
        result.height = maximumY - minimumY;
        return result;
    };

    var defaultProjection = new GeographicProjection();
    var fromRectangleLowerLeft = new Cartographic();
    var fromRectangleUpperRight = new Cartographic();
    /**
     * Computes a bounding rectangle from an rectangle.
     *
     * @param {Rectangle} rectangle The valid rectangle used to create a bounding rectangle.
     * @param {Object} [projection=GeographicProjection] The projection used to project the rectangle into 2D.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.fromRectangle = function(rectangle, projection, result) {
        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        if (!defined(rectangle)) {
            result.x = 0;
            result.y = 0;
            result.width = 0;
            result.height = 0;
            return result;
        }

        projection = defaultValue(projection, defaultProjection);

        var lowerLeft = projection.project(Rectangle.southwest(rectangle, fromRectangleLowerLeft));
        var upperRight = projection.project(Rectangle.northeast(rectangle, fromRectangleUpperRight));

        Cartesian2.subtract(upperRight, lowerLeft, upperRight);

        result.x = lowerLeft.x;
        result.y = lowerLeft.y;
        result.width = upperRight.x;
        result.height = upperRight.y;
        return result;
    };

    /**
     * Duplicates a BoundingRectangle instance.
     *
     * @param {BoundingRectangle} rectangle The bounding rectangle to duplicate.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided. (Returns undefined if rectangle is undefined)
     */
    BoundingRectangle.clone = function(rectangle, result) {
        if (!defined(rectangle)) {
            return undefined;
        }

        if (!defined(result)) {
            return new BoundingRectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
        }

        result.x = rectangle.x;
        result.y = rectangle.y;
        result.width = rectangle.width;
        result.height = rectangle.height;
        return result;
    };

    /**
     * Computes a bounding rectangle that is the union of the left and right bounding rectangles.
     *
     * @param {BoundingRectangle} left A rectangle to enclose in bounding rectangle.
     * @param {BoundingRectangle} right A rectangle to enclose in a bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.union = function(left, right, result) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(left)) {
            throw new DeveloperError('left is required.');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required.');
        }
        //>>includeEnd('debug');

        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        var lowerLeftX = Math.min(left.x, right.x);
        var lowerLeftY = Math.min(left.y, right.y);
        var upperRightX = Math.max(left.x + left.width, right.x + right.width);
        var upperRightY = Math.max(left.y + left.height, right.y + right.height);

        result.x = lowerLeftX;
        result.y = lowerLeftY;
        result.width = upperRightX - lowerLeftX;
        result.height = upperRightY - lowerLeftY;
        return result;
    };

    /**
     * Computes a bounding rectangle by enlarging the provided rectangle until it contains the provided point.
     *
     * @param {BoundingRectangle} rectangle A rectangle to expand.
     * @param {Cartesian2} point A point to enclose in a bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.expand = function(rectangle, point, result) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(rectangle)) {
            throw new DeveloperError('rectangle is required.');
        }
        if (!defined(point)) {
            throw new DeveloperError('point is required.');
        }
        //>>includeEnd('debug');

        result = BoundingRectangle.clone(rectangle, result);

        var width = point.x - result.x;
        var height = point.y - result.y;

        if (width > result.width) {
            result.width = width;
        } else if (width < 0) {
            result.width -= width;
            result.x = point.x;
        }

        if (height > result.height) {
            result.height = height;
        } else if (height < 0) {
            result.height -= height;
            result.y = point.y;
        }

        return result;
    };

    /**
     * Determines if two rectangles intersect.
     *
     * @param {BoundingRectangle} left A rectangle to check for intersection.
     * @param {BoundingRectangle} right The other rectangle to check for intersection.
     * @returns {Intersect} Intersect.INTESECTING if the rectangles intersect, Intersect.OUTSIDE otherwise.
     */
    BoundingRectangle.intersect = function(left, right) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(left)) {
            throw new DeveloperError('left is required.');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required.');
        }
        //>>includeEnd('debug');

        var leftX = left.x;
        var leftY = left.y;
        var rightX = right.x;
        var rightY = right.y;
        if (!(leftX > rightX + right.width ||
              leftX + left.width < rightX ||
              leftY + left.height < rightY ||
              leftY > rightY + right.height)) {
            return Intersect.INTERSECTING;
        }

        return Intersect.OUTSIDE;
    };

    /**
     * Compares the provided BoundingRectangles componentwise and returns
     * true if they are equal, false otherwise.
     *
     * @param {BoundingRectangle} [left] The first BoundingRectangle.
     * @param {BoundingRectangle} [right] The second BoundingRectangle.
     * @returns {Boolean} true if left and right are equal, false otherwise.
     */
    BoundingRectangle.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (left.x === right.x) &&
                (left.y === right.y) &&
                (left.width === right.width) &&
                (left.height === right.height));
    };

    /**
     * Duplicates this BoundingRectangle instance.
     *
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.prototype.clone = function(result) {
        return BoundingRectangle.clone(this, result);
    };

    /**
     * Determines if this rectangle intersects with another.
     *
     * @param {BoundingRectangle} right A rectangle to check for intersection.
     * @returns {Intersect} Intersect.INTESECTING if the rectangles intersect, Intersect.OUTSIDE otherwise.
     */
    BoundingRectangle.prototype.intersect = function(right) {
        return BoundingRectangle.intersect(this, right);
    };

    /**
     * Compares this BoundingRectangle against the provided BoundingRectangle componentwise and returns
     * true if they are equal, false otherwise.
     *
     * @param {BoundingRectangle} [right] The right hand side BoundingRectangle.
     * @returns {Boolean} true if they are equal, false otherwise.
     */
    BoundingRectangle.prototype.equals = function(right) {
        return BoundingRectangle.equals(this, right);
    };

    return BoundingRectangle;
});
<commit_msg>Replace DeveloperError with Check in BoundingRectangle<commit_after>/*global define*/
define([
        './Cartesian2',
        './Cartographic',
        './Check',
        './defaultValue',
        './defined',
        './GeographicProjection',
        './Intersect',
        './Rectangle'
    ], function(
        Cartesian2,
        Cartographic,
        Check,
        defaultValue,
        defined,
        GeographicProjection,
        Intersect,
        Rectangle) {
    'use strict';

    /**
     * A bounding rectangle given by a corner, width and height.
     * @alias BoundingRectangle
     * @constructor
     *
     * @param {Number} [x=0.0] The x coordinate of the rectangle.
     * @param {Number} [y=0.0] The y coordinate of the rectangle.
     * @param {Number} [width=0.0] The width of the rectangle.
     * @param {Number} [height=0.0] The height of the rectangle.
     *
     * @see BoundingSphere
     * @see Packable
     */
    function BoundingRectangle(x, y, width, height) {
        /**
         * The x coordinate of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.x = defaultValue(x, 0.0);

        /**
         * The y coordinate of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.y = defaultValue(y, 0.0);

        /**
         * The width of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.width = defaultValue(width, 0.0);

        /**
         * The height of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.height = defaultValue(height, 0.0);
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    BoundingRectangle.packedLength = 4;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {BoundingRectangle} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    BoundingRectangle.pack = function(value, array, startingIndex) {
        //>>includeStart('debug', pragmas.debug);
        Check.typeOf.object(value, 'value');
        Check.defined(array, 'array');
        //>>includeEnd('debug');

        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value.x;
        array[startingIndex++] = value.y;
        array[startingIndex++] = value.width;
        array[startingIndex] = value.height;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {BoundingRectangle} [result] The object into which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.unpack = function(array, startingIndex, result) {
        //>>includeStart('debug', pragmas.debug);
        Check.defined(array, 'array');
        //>>includeEnd('debug');

        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new BoundingRectangle();
        }
        result.x = array[startingIndex++];
        result.y = array[startingIndex++];
        result.width = array[startingIndex++];
        result.height = array[startingIndex];
        return result;
    };

    /**
     * Computes a bounding rectangle enclosing the list of 2D points.
     * The rectangle is oriented with the corner at the bottom left.
     *
     * @param {Cartesian2[]} positions List of points that the bounding rectangle will enclose.  Each point must have x and y properties.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.fromPoints = function(positions, result) {
        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        if (!defined(positions) || positions.length === 0) {
            result.x = 0;
            result.y = 0;
            result.width = 0;
            result.height = 0;
            return result;
        }

        var length = positions.length;

        var minimumX = positions[0].x;
        var minimumY = positions[0].y;

        var maximumX = positions[0].x;
        var maximumY = positions[0].y;

        for ( var i = 1; i < length; i++) {
            var p = positions[i];
            var x = p.x;
            var y = p.y;

            minimumX = Math.min(x, minimumX);
            maximumX = Math.max(x, maximumX);
            minimumY = Math.min(y, minimumY);
            maximumY = Math.max(y, maximumY);
        }

        result.x = minimumX;
        result.y = minimumY;
        result.width = maximumX - minimumX;
        result.height = maximumY - minimumY;
        return result;
    };

    var defaultProjection = new GeographicProjection();
    var fromRectangleLowerLeft = new Cartographic();
    var fromRectangleUpperRight = new Cartographic();
    /**
     * Computes a bounding rectangle from an rectangle.
     *
     * @param {Rectangle} rectangle The valid rectangle used to create a bounding rectangle.
     * @param {Object} [projection=GeographicProjection] The projection used to project the rectangle into 2D.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.fromRectangle = function(rectangle, projection, result) {
        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        if (!defined(rectangle)) {
            result.x = 0;
            result.y = 0;
            result.width = 0;
            result.height = 0;
            return result;
        }

        projection = defaultValue(projection, defaultProjection);

        var lowerLeft = projection.project(Rectangle.southwest(rectangle, fromRectangleLowerLeft));
        var upperRight = projection.project(Rectangle.northeast(rectangle, fromRectangleUpperRight));

        Cartesian2.subtract(upperRight, lowerLeft, upperRight);

        result.x = lowerLeft.x;
        result.y = lowerLeft.y;
        result.width = upperRight.x;
        result.height = upperRight.y;
        return result;
    };

    /**
     * Duplicates a BoundingRectangle instance.
     *
     * @param {BoundingRectangle} rectangle The bounding rectangle to duplicate.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided. (Returns undefined if rectangle is undefined)
     */
    BoundingRectangle.clone = function(rectangle, result) {
        if (!defined(rectangle)) {
            return undefined;
        }

        if (!defined(result)) {
            return new BoundingRectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
        }

        result.x = rectangle.x;
        result.y = rectangle.y;
        result.width = rectangle.width;
        result.height = rectangle.height;
        return result;
    };

    /**
     * Computes a bounding rectangle that is the union of the left and right bounding rectangles.
     *
     * @param {BoundingRectangle} left A rectangle to enclose in bounding rectangle.
     * @param {BoundingRectangle} right A rectangle to enclose in a bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.union = function(left, right, result) {
        //>>includeStart('debug', pragmas.debug);
        Check.typeOf.object(left, 'left');
        Check.typeOf.object(right, 'right');
        //>>includeEnd('debug');

        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        var lowerLeftX = Math.min(left.x, right.x);
        var lowerLeftY = Math.min(left.y, right.y);
        var upperRightX = Math.max(left.x + left.width, right.x + right.width);
        var upperRightY = Math.max(left.y + left.height, right.y + right.height);

        result.x = lowerLeftX;
        result.y = lowerLeftY;
        result.width = upperRightX - lowerLeftX;
        result.height = upperRightY - lowerLeftY;
        return result;
    };

    /**
     * Computes a bounding rectangle by enlarging the provided rectangle until it contains the provided point.
     *
     * @param {BoundingRectangle} rectangle A rectangle to expand.
     * @param {Cartesian2} point A point to enclose in a bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.expand = function(rectangle, point, result) {
        //>>includeStart('debug', pragmas.debug);
        Check.typeOf.object(rectangle, 'rectangle');
        Check.typeOf.object(point, 'point');
        //>>includeEnd('debug');

        result = BoundingRectangle.clone(rectangle, result);

        var width = point.x - result.x;
        var height = point.y - result.y;

        if (width > result.width) {
            result.width = width;
        } else if (width < 0) {
            result.width -= width;
            result.x = point.x;
        }

        if (height > result.height) {
            result.height = height;
        } else if (height < 0) {
            result.height -= height;
            result.y = point.y;
        }

        return result;
    };

    /**
     * Determines if two rectangles intersect.
     *
     * @param {BoundingRectangle} left A rectangle to check for intersection.
     * @param {BoundingRectangle} right The other rectangle to check for intersection.
     * @returns {Intersect} Intersect.INTESECTING if the rectangles intersect, Intersect.OUTSIDE otherwise.
     */
    BoundingRectangle.intersect = function(left, right) {
        //>>includeStart('debug', pragmas.debug);
        Check.typeOf.object(left, 'left');
        Check.typeOf.object(right, 'right');
        //>>includeEnd('debug');

        var leftX = left.x;
        var leftY = left.y;
        var rightX = right.x;
        var rightY = right.y;
        if (!(leftX > rightX + right.width ||
              leftX + left.width < rightX ||
              leftY + left.height < rightY ||
              leftY > rightY + right.height)) {
            return Intersect.INTERSECTING;
        }

        return Intersect.OUTSIDE;
    };

    /**
     * Compares the provided BoundingRectangles componentwise and returns
     * true if they are equal, false otherwise.
     *
     * @param {BoundingRectangle} [left] The first BoundingRectangle.
     * @param {BoundingRectangle} [right] The second BoundingRectangle.
     * @returns {Boolean} true if left and right are equal, false otherwise.
     */
    BoundingRectangle.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (left.x === right.x) &&
                (left.y === right.y) &&
                (left.width === right.width) &&
                (left.height === right.height));
    };

    /**
     * Duplicates this BoundingRectangle instance.
     *
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.prototype.clone = function(result) {
        return BoundingRectangle.clone(this, result);
    };

    /**
     * Determines if this rectangle intersects with another.
     *
     * @param {BoundingRectangle} right A rectangle to check for intersection.
     * @returns {Intersect} Intersect.INTESECTING if the rectangles intersect, Intersect.OUTSIDE otherwise.
     */
    BoundingRectangle.prototype.intersect = function(right) {
        return BoundingRectangle.intersect(this, right);
    };

    /**
     * Compares this BoundingRectangle against the provided BoundingRectangle componentwise and returns
     * true if they are equal, false otherwise.
     *
     * @param {BoundingRectangle} [right] The right hand side BoundingRectangle.
     * @returns {Boolean} true if they are equal, false otherwise.
     */
    BoundingRectangle.prototype.equals = function(right) {
        return BoundingRectangle.equals(this, right);
    };

    return BoundingRectangle;
});
<|endoftext|>


<commit_before>var React = require('react');

// Enable React Touch Events
React.initializeTouchEvents(true);

function getTouchProps(touch) {
	if (!touch) return {};
	return {
		pageX: touch.pageX,
		pageY: touch.pageY,
		clientX: touch.clientX,
		clientY: touch.clientY
	};
}

/**
 * Tappable Component
 * ==================
 */

module.exports = React.createClass({

	displayName: 'Tappable',

	propTypes: {

		component: React.PropTypes.any,              // component to create
		className: React.PropTypes.string,           // optional className
		classBase: React.PropTypes.string,           // base for generated classNames

		moveThreshold: React.PropTypes.number,       // pixels to move before cancelling tap
		pressDelay: React.PropTypes.number,          // ms to wait before detecting a press
		pressMoveThreshold: React.PropTypes.number,  // pixels to move before cancelling press
		preventDefault: React.PropTypes.bool,        // whether to preventDefault on all events
		stopPropagation: React.PropTypes.bool,       // whether to stopPropagation on all events

		onTap: React.PropTypes.func,                 // fires when a tap is detected
		onPress: React.PropTypes.func,               // fires when a press is detected
		onTouchStart: React.PropTypes.func,          // pass-through touch event
		onTouchMove: React.PropTypes.func,           // pass-through touch event
		onTouchEnd: React.PropTypes.func,            // pass-through touch event
		onMouseDown: React.PropTypes.func,           // pass-through mouse event
		onMouseUp: React.PropTypes.func,             // pass-through mouse event
		onMouseMove: React.PropTypes.func,           // pass-through mouse event
		onMouseOut: React.PropTypes.func             // pass-through mouse event

	},

	getDefaultProps: function() {
		return {
			component: 'span',
			classBase: 'Tappable',
			moveThreshold: 100,
			pressDelay: 1000,
			pressMoveThreshold: 5
		};
	},

	getInitialState: function() {
		return {
			isActive: false,
			touchActive: false
		};
	},

	componentWillUnmount: function() {
		this.cleanupScrollDetection();
		this.cancelPressDetection();
	},

	processEvent: function(event) {
		if (this.props.preventDefault) event.preventDefault();
		if (this.props.stopPropagation) event.stopPropagation();
	},

	onTouchStart: function(event) {
		if (this.props.onTouchStart && this.props.onTouchStart(event) === false) return;
		this.processEvent(event);
		window._blockMouseEvents = true;
		this._initialTouch = this._lastTouch = getTouchProps(event.touches[0]);
		this.initScrollDetection();
		this.initPressDetection(this.endTouch);
		this.setState({
			isActive: true
		});
	},

	initScrollDetection: function() {
		this._scrollParents = [];
		this._scrollPos = { top: 0, left: 0 };
		var node = this.getDOMNode();
		while (node) {
			if (node.scrollHeight > node.offsetHeight || node.scrollWidth > node.offsetWidth) {
				this._scrollParents.push(node);
				this._scrollPos.top += node.scrollTop;
				this._scrollPos.left += node.scrollLeft;
			}
			node = node.parentNode;
		}
	},

	calculateMovement: function(touch) {
		return {
			x: Math.abs(touch.clientX - this._initialTouch.clientX),
			y: Math.abs(touch.clientY - this._initialTouch.clientY)
		};
	},

	detectScroll: function() {
		var currentScrollPos = { top: 0, left: 0 };
		for (var i = 0; i < this._scrollParents.length; i++) {
			currentScrollPos.top += this._scrollParents[i].scrollTop;
			currentScrollPos.left += this._scrollParents[i].scrollLeft;
		}
		return !(currentScrollPos.top === this._scrollPos.top && currentScrollPos.left === this._scrollPos.left);
	},

	cleanupScrollDetection: function() {
		this._scrollParents = undefined;
		this._scrollPos = undefined;
	},

	initPressDetection: function(callback) {
		if (!this.props.onPress) return;
		this._pressTimeout = setTimeout(function() {
			this.props.onPress();
			callback();
		}.bind(this), this.props.pressDelay);
	},

	cancelPressDetection: function() {
		clearTimeout(this._pressTimeout);
	},

	onTouchMove: function(event) {
		if (!this._initialTouch) return;
		this.processEvent(event);
		if (this.detectScroll()) {
			return this.endTouch(event);
		}
		this.props.onTouchMove && this.props.onTouchMove(event);
		this._lastTouch = getTouchProps(event.touches[0]);
		var movement = this.calculateMovement(this._lastTouch);
		if (movement.x > this.props.pressMoveThreshold || movement.y > this.props.pressMoveThreshold) {
			this.cancelPressDetection();
		}
		if (movement.x > this.props.moveThreshold || movement.y > this.props.moveThreshold) {
			if (this.state.isActive) {
				this.setState({
					isActive: false
				});
			}
		} else {
			if (!this.state.isActive) {
				this.setState({
					isActive: true
				});
			}
		}
	},

	onTouchEnd: function(event) {
		if (!this._initialTouch) return;
		this.processEvent(event);
		var movement = this.calculateMovement(this._lastTouch);
		if (movement.x <= this.props.moveThreshold && movement.y <= this.props.moveThreshold) {
			this.props.onTap && this.props.onTap(event);
		}
		this.endTouch(event);
	},

	endTouch: function() {
		this.cancelPressDetection();
		this.props.onTouchEnd && this.props.onTouchEnd(event);
		this._initialTouch = null;
		this._lastTouch = null;
		this.setState({
			isActive: false
		});
	},

	onMouseDown: function(event) {
		if (window._blockMouseEvents) {
			window._blockMouseEvents = false;
			return;
		}
		if (this.props.onMouseDown && this.props.onMouseDown(event) === false) return;
		this.processEvent(event);
		this.initPressDetection(this.endMouseEvent);
		this._mouseDown = true;
		this.setState({
			isActive: true
		});
	},

	onMouseMove: function(event) {
		if (window._blockMouseEvents || !this._mouseDown) return;
		this.processEvent(event);
		this.props.onMouseMove && this.props.onMouseMove(event);
	},

	onMouseUp: function(event) {
		if (window._blockMouseEvents || !this._mouseDown) return;
		this.processEvent(event);
		this.props.onMouseUp && this.props.onMouseUp(event);
		this.props.onTap && this.props.onTap(event);
		this.endMouseEvent();
	},

	onMouseOut: function(event) {
		if (window._blockMouseEvents || !this._mouseDown) return;
		this.processEvent(event);
		this.props.onMouseOut && this.props.onMouseOut(event);
		this.endMouseEvent();
	},

	endMouseEvent: function() {
		this.cancelPressDetection();
		this._mouseDown = false;
		this.setState({
			isActive: false
		});
	},

	render: function() {

		var className = this.props.classBase + (this.state.isActive ? '-active' : '-inactive');
		if (this.props.className) {
			className += ' ' + this.props.className;
		}

		var style = {
			WebkitTapHighlightColor: 'rgba(0,0,0,0)',
			WebkitTouchCallout: 'none',
			WebkitUserSelect: 'none',
			KhtmlUserSelect: 'none',
			MozUserSelect: 'none',
			msUserSelect: 'none',
			userSelect: 'none',
			cursor: 'pointer'
		};
		return React.createElement(this.props.component, {
			style: style,
			className: className,
			onTouchStart: this.onTouchStart,
			onTouchMove: this.onTouchMove,
			onTouchEnd: this.onTouchEnd,
			onMouseDown: this.onMouseDown,
			onMouseMove: this.onMouseMove,
			onMouseUp: this.onMouseUp,
			onMouseOut: this.onMouseOut
		}, this.props.children);

	}

});
<commit_msg>Add support for disabled prop<commit_after>var React = require('react');

// Enable React Touch Events
React.initializeTouchEvents(true);

function getTouchProps(touch) {
	if (!touch) return {};
	return {
		pageX: touch.pageX,
		pageY: touch.pageY,
		clientX: touch.clientX,
		clientY: touch.clientY
	};
}

/**
 * Tappable Component
 * ==================
 */

module.exports = React.createClass({

	displayName: 'Tappable',

	propTypes: {

		component: React.PropTypes.any,              // component to create
		className: React.PropTypes.string,           // optional className
		classBase: React.PropTypes.string,           // base for generated classNames
		disabled: React.PropTypes.bool,              // only applies to buttons

		moveThreshold: React.PropTypes.number,       // pixels to move before cancelling tap
		pressDelay: React.PropTypes.number,          // ms to wait before detecting a press
		pressMoveThreshold: React.PropTypes.number,  // pixels to move before cancelling press
		preventDefault: React.PropTypes.bool,        // whether to preventDefault on all events
		stopPropagation: React.PropTypes.bool,       // whether to stopPropagation on all events

		onTap: React.PropTypes.func,                 // fires when a tap is detected
		onPress: React.PropTypes.func,               // fires when a press is detected
		onTouchStart: React.PropTypes.func,          // pass-through touch event
		onTouchMove: React.PropTypes.func,           // pass-through touch event
		onTouchEnd: React.PropTypes.func,            // pass-through touch event
		onMouseDown: React.PropTypes.func,           // pass-through mouse event
		onMouseUp: React.PropTypes.func,             // pass-through mouse event
		onMouseMove: React.PropTypes.func,           // pass-through mouse event
		onMouseOut: React.PropTypes.func             // pass-through mouse event

	},

	getDefaultProps: function() {
		return {
			component: 'span',
			classBase: 'Tappable',
			moveThreshold: 100,
			pressDelay: 1000,
			pressMoveThreshold: 5
		};
	},

	getInitialState: function() {
		return {
			isActive: false,
			touchActive: false
		};
	},

	componentWillUnmount: function() {
		this.cleanupScrollDetection();
		this.cancelPressDetection();
	},

	processEvent: function(event) {
		if (this.props.preventDefault) event.preventDefault();
		if (this.props.stopPropagation) event.stopPropagation();
	},

	onTouchStart: function(event) {
		if (this.props.onTouchStart && this.props.onTouchStart(event) === false) return;
		this.processEvent(event);
		window._blockMouseEvents = true;
		this._initialTouch = this._lastTouch = getTouchProps(event.touches[0]);
		this.initScrollDetection();
		this.initPressDetection(this.endTouch);
		this.setState({
			isActive: true
		});
	},

	initScrollDetection: function() {
		this._scrollParents = [];
		this._scrollPos = { top: 0, left: 0 };
		var node = this.getDOMNode();
		while (node) {
			if (node.scrollHeight > node.offsetHeight || node.scrollWidth > node.offsetWidth) {
				this._scrollParents.push(node);
				this._scrollPos.top += node.scrollTop;
				this._scrollPos.left += node.scrollLeft;
			}
			node = node.parentNode;
		}
	},

	calculateMovement: function(touch) {
		return {
			x: Math.abs(touch.clientX - this._initialTouch.clientX),
			y: Math.abs(touch.clientY - this._initialTouch.clientY)
		};
	},

	detectScroll: function() {
		var currentScrollPos = { top: 0, left: 0 };
		for (var i = 0; i < this._scrollParents.length; i++) {
			currentScrollPos.top += this._scrollParents[i].scrollTop;
			currentScrollPos.left += this._scrollParents[i].scrollLeft;
		}
		return !(currentScrollPos.top === this._scrollPos.top && currentScrollPos.left === this._scrollPos.left);
	},

	cleanupScrollDetection: function() {
		this._scrollParents = undefined;
		this._scrollPos = undefined;
	},

	initPressDetection: function(callback) {
		if (!this.props.onPress) return;
		this._pressTimeout = setTimeout(function() {
			this.props.onPress();
			callback();
		}.bind(this), this.props.pressDelay);
	},

	cancelPressDetection: function() {
		clearTimeout(this._pressTimeout);
	},

	onTouchMove: function(event) {
		if (!this._initialTouch) return;
		this.processEvent(event);
		if (this.detectScroll()) {
			return this.endTouch(event);
		}
		this.props.onTouchMove && this.props.onTouchMove(event);
		this._lastTouch = getTouchProps(event.touches[0]);
		var movement = this.calculateMovement(this._lastTouch);
		if (movement.x > this.props.pressMoveThreshold || movement.y > this.props.pressMoveThreshold) {
			this.cancelPressDetection();
		}
		if (movement.x > this.props.moveThreshold || movement.y > this.props.moveThreshold) {
			if (this.state.isActive) {
				this.setState({
					isActive: false
				});
			}
		} else {
			if (!this.state.isActive) {
				this.setState({
					isActive: true
				});
			}
		}
	},

	onTouchEnd: function(event) {
		if (!this._initialTouch) return;
		this.processEvent(event);
		var movement = this.calculateMovement(this._lastTouch);
		if (movement.x <= this.props.moveThreshold && movement.y <= this.props.moveThreshold) {
			this.props.onTap && this.props.onTap(event);
		}
		this.endTouch(event);
	},

	endTouch: function() {
		this.cancelPressDetection();
		this.props.onTouchEnd && this.props.onTouchEnd(event);
		this._initialTouch = null;
		this._lastTouch = null;
		this.setState({
			isActive: false
		});
	},

	onMouseDown: function(event) {
		if (window._blockMouseEvents) {
			window._blockMouseEvents = false;
			return;
		}
		if (this.props.onMouseDown && this.props.onMouseDown(event) === false) return;
		this.processEvent(event);
		this.initPressDetection(this.endMouseEvent);
		this._mouseDown = true;
		this.setState({
			isActive: true
		});
	},

	onMouseMove: function(event) {
		if (window._blockMouseEvents || !this._mouseDown) return;
		this.processEvent(event);
		this.props.onMouseMove && this.props.onMouseMove(event);
	},

	onMouseUp: function(event) {
		if (window._blockMouseEvents || !this._mouseDown) return;
		this.processEvent(event);
		this.props.onMouseUp && this.props.onMouseUp(event);
		this.props.onTap && this.props.onTap(event);
		this.endMouseEvent();
	},

	onMouseOut: function(event) {
		if (window._blockMouseEvents || !this._mouseDown) return;
		this.processEvent(event);
		this.props.onMouseOut && this.props.onMouseOut(event);
		this.endMouseEvent();
	},

	endMouseEvent: function() {
		this.cancelPressDetection();
		this._mouseDown = false;
		this.setState({
			isActive: false
		});
	},

	render: function() {

		var className = this.props.classBase + (this.state.isActive ? '-active' : '-inactive');
		if (this.props.className) {
			className += ' ' + this.props.className;
		}

		var style = {
			WebkitTapHighlightColor: 'rgba(0,0,0,0)',
			WebkitTouchCallout: 'none',
			WebkitUserSelect: 'none',
			KhtmlUserSelect: 'none',
			MozUserSelect: 'none',
			msUserSelect: 'none',
			userSelect: 'none',
			cursor: 'pointer'
		};
		return React.createElement(this.props.component, {
			style: style,
			className: className,
			disabled: this.props.disabled,
			onTouchStart: this.onTouchStart,
			onTouchMove: this.onTouchMove,
			onTouchEnd: this.onTouchEnd,
			onMouseDown: this.onMouseDown,
			onMouseMove: this.onMouseMove,
			onMouseUp: this.onMouseUp,
			onMouseOut: this.onMouseOut
		}, this.props.children);

	}

});
<|endoftext|>


<commit_before>var baseConvert = require('./_baseConvert');

/**
 * Converts `lodash` to an immutable auto-curried iteratee-first data-last
 * version with conversion `options` applied.
 *
 * @param {Function} lodash The lodash function to convert.
 * @param {Object} [options] The options object. See `baseConvert` for more details.
 * @returns {Function} Returns the converted `lodash`.
 */
function browserConvert(lodash, options) {
  return baseConvert(lodash, lodash, options);
}

if (typeof _ == 'function') {
  _ = browserConvert(_.runInContext());
}
module.exports = browserConvert;
<commit_msg>Update public/libs/bower_components/lodash/fp/_convertBrowser.js<commit_after><|endoftext|>


<commit_before>/*
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

import WatchGroup from './watch-group.js';
import ArcsUtils from '../lib/arcs-utils.js';
import Xen from '../../components/xen/xen.js';
const db = window.db;

const log = Xen.Base.logFactory('PersistentArc', '#a30000');

class PersistentArc extends Xen.Base {
  static get observedAttributes() { return ['key', 'metadata']; }
  _getInitialState() {
    return {
      watch: new WatchGroup(),
      db: db.child('arcs')
    };
  }
  // Allow overriding search params for unit tests.
  _getSearchParams() {
    return (new URL(document.location)).searchParams;
  }
  _getExternalManifest() {
    const params = this._getSearchParams();
    // Prioritize manifest over solo, semi-arbitrarily, since usually we'll
    // only see one or the other.
    return params.get('solo') || params.get('manifest');
  }
  _update({key, metadata}, state, lastProps) {
    if (key === '*' && lastProps.key != key) {
      this._createKey(state.db);
    }
    if (key && key !== '*') {
      if (key !== lastProps.key) {
        state.watch.watches = [this._watchKey(state.db, key)];
      }
      if (metadata) {
        // Typical developer workflow involves creating a new arc and
        // subsequently modifying the url to include a specific recipe via a
        // solo or manifest query param, thus we have to look for such a param
        // at arc update time. Alternatively we could have the developer
        // include the param in the main launcher page and have the app shell
        // pass it along to the 'New Arc' url, but that is not the current
        // state of the world.
        const externalManifest = this._getExternalManifest();
        if (externalManifest != metadata.externalManifest) {
          metadata.externalManifest = externalManifest;
        }
        if (this._hasMetadataChanged(metadata)) {
          log('WRITING (update) metadata', metadata);
          state.db.child(key).child('metadata').update(metadata);
        }
      }
      this._fire('key', key);
    }
  }
  _hasMetadataChanged(metadata) {
    const state = this._state;
    const serial = JSON.stringify(metadata);
    if (serial !== state.serial) {
      log('metadata changed');
      //PersistentArc.groupCollapsed('metadata changed');
      //  log('new meta:', metadata); //serial);
      //  log('old meta:', state.serial ? JSON.parse(state.serial) : state.serial);
      console.groupEnd();
      state.serial = serial;
      return true;
    }
  }
  _createKey(db) {
    let data = {
      description: ArcsUtils.randomName(),
      externalManifest: this._getExternalManifest()
    };
    this._assignColors(data);
    let key = db.push({'metadata': data}).key;
    this._setState({key});
    log('providing key (_createKey)', key);
    this._fire('key', key);
  }
  _assignColors(metadata) {
    let bgs = ['#5EF4BD', '#20E7FF', '#607D8B', '#FF7364', '#2FADE6', '#FFB843', '#FFF153', '#17C497'];
    let colors = ['#212121', '#212121', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#212121', '#212121', '#FFFFFF'];
    let choice = Math.floor(Math.random()*colors.length);
    metadata.color = colors[choice];
    metadata.bg = bgs[choice];
  }
  _watchKey(db, key) {
    let arcMetadata = db.child(key).child('metadata');
    log('watching', String(arcMetadata));
    const state = this._state;
    return {
      node: arcMetadata,
      handler: snap => {
        log('READING', String(arcMetadata));
        let metadata = snap.val();
        if (this._hasMetadataChanged(metadata)) {
          this._fire('metadata', metadata);
        }
      }
    };
  }
}
customElements.define('persistent-arc', PersistentArc);
<commit_msg>use Debug mixin, modulate additional logging slightly<commit_after>/*
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

import WatchGroup from './watch-group.js';
import ArcsUtils from '../lib/arcs-utils.js';
import Xen from '../../components/xen/xen.js';
const db = window.db;

const log = Xen.Base.logFactory('PersistentArc', '#a30000');

class PersistentArc extends Xen.Debug(Xen.Base, log) {
  static get observedAttributes() { return ['key', 'metadata']; }
  _getInitialState() {
    return {
      watch: new WatchGroup(),
      db: db.child('arcs')
    };
  }
  // Allow overriding search params for unit tests.
  _getSearchParams() {
    return (new URL(document.location)).searchParams;
  }
  _getExternalManifest() {
    const params = this._getSearchParams();
    // Prioritize manifest over solo, semi-arbitrarily, since usually we'll
    // only see one or the other.
    return params.get('solo') || params.get('manifest');
  }
  _update({key, metadata}, state, lastProps) {
    if (key === '*' && lastProps.key != key) {
      this._createKey(state.db);
    }
    if (key && key !== '*') {
      if (key !== lastProps.key) {
        state.watch.watches = [this._watchKey(state.db, key)];
      }
      if (metadata) {
        // Typical developer workflow involves creating a new arc and
        // subsequently modifying the url to include a specific recipe via a
        // solo or manifest query param, thus we have to look for such a param
        // at arc update time. Alternatively we could have the developer
        // include the param in the main launcher page and have the app shell
        // pass it along to the 'New Arc' url, but that is not the current
        // state of the world.
        const externalManifest = this._getExternalManifest();
        if (externalManifest != metadata.externalManifest) {
          metadata.externalManifest = externalManifest;
        }
        if (this._hasMetadataChanged(metadata)) {
          log('WRITING (update) metadata', metadata);
          state.db.child(key).child('metadata').update(metadata);
        }
      }
      this._fire('key', key);
    }
  }
  _hasMetadataChanged(metadata) {
    const state = this._state;
    const serial = JSON.stringify(metadata);
    if (serial !== state.serial) {
      log('metadata changed');
      //PersistentArc.groupCollapsed('metadata changed');
      //  log('new meta:', metadata); //serial);
      //  log('old meta:', state.serial ? JSON.parse(state.serial) : state.serial);
      //console.groupEnd();
      state.serial = serial;
      return true;
    }
  }
  _createKey(db) {
    let data = {
      description: ArcsUtils.randomName(),
      externalManifest: this._getExternalManifest()
    };
    this._assignColors(data);
    let key = db.push({'metadata': data}).key;
    this._setState({key});
    log('providing key (_createKey)', key);
    this._fire('key', key);
  }
  _assignColors(metadata) {
    let bgs = ['#5EF4BD', '#20E7FF', '#607D8B', '#FF7364', '#2FADE6', '#FFB843', '#FFF153', '#17C497'];
    let colors = ['#212121', '#212121', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#212121', '#212121', '#FFFFFF'];
    let choice = Math.floor(Math.random()*colors.length);
    metadata.color = colors[choice];
    metadata.bg = bgs[choice];
  }
  _watchKey(db, key) {
    let arcMetadata = db.child(key).child('metadata');
    log('watching', String(arcMetadata));
    const state = this._state;
    return {
      node: arcMetadata,
      handler: snap => {
        //log('READING', String(arcMetadata));
        let metadata = snap.val();
        if (this._hasMetadataChanged(metadata)) {
          log('READING', metadata);
          this._fire('metadata', metadata);
        }
      }
    };
  }
}
customElements.define('persistent-arc', PersistentArc);
<|endoftext|>