<commit_before>/**
* Copyright 2017 HUAWEI. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* @file Implementation of default test client process.
*/

'use strict'

/* global variables */
var tape  = require('tape');
var _test = require('tape-promise');
var test  = _test(tape);
var path  = require('path');
var bc    = require('../../src/comm/blockchain.js');

/**
 * Message handler
 */
process.on('message', function(message) {
    if(message.hasOwnProperty('type')) {
        try {
            var result;
            switch(message.type) {
                case 'queryNewTx':
                    queryNewTxInfo()
                    .then((queryResult) => {
                         process.send({type: 'queryResult', session: message.session, data: queryResult});
                    });
                    break;
                case 'test':
                    doTest(message)
                    .then((testResult) => {
                        result = testResult;
                        return queryNewTxInfo();
                    })
                    .then((queryResult) => {
                         process.send({type: 'queryResult', session: 'final', data: queryResult});
                         return sleep(200);
                    })
                    .then(() => {
                         process.send({type: 'testResult', data: result});
                    });
                    break;
                default:
                    process.send({type: 'error', data: 'unknown message type'});
            }
        }
        catch(err) {
            process.send({type: 'error', data: err});
        };
    }
    else {
         process.send({cmd: 'error', data: 'message type was missed'});
    }
})

var blockchain;
var results = [];
var txNum   = 0;
function queryNewTxInfo() {
    var tmpArray = results.slice();
    var tmpNum   = txNum;
    results = [];
    txNum   = 0;
    var stats = blockchain.getDefaultTxStats(tmpArray);
    return Promise.resolve({submitted: tmpNum, committed:stats});
}

function doTest(msg) {
    blockchain = new bc(msg.config);
    var cb = require(path.join(__dirname, '../../', msg.cb));
    var bcContext;
    var bcResults;

    return blockchain.getContext(msg.cmd)
    .then((context) => {
        bcContext = context;
        var rounds   = Array(msg.numb).fill(0);
        var promises = [];
        var idx       = 0;
        var start     = process.uptime();
        var sleepTime = (msg.tps > 0) ? 1000/msg.tps : 0;

        console.log('start client ' + process.pid +  (cb.info ? (':' + cb.info) : ''));

        return rounds.reduce(function(prev, item) {
            return prev.then( () => {
                txNum++;        // TODO: fix, wrong number if run() creates multiple transactions
                promises.push(cb.run().then((result)=>{
                    results.push(result);
                    return Promise.resolve(result);
                }));
                idx++;
                return rateControl(sleepTime, start, idx);
            });
        }, cb.init(blockchain, context, msg.args))
        .then( () => {
            return Promise.all(promises);
        })
        .then( (result) => {
            bcResults = result;
            return blockchain.releaseContext(bcContext);
        })
        .then( () => {
            return cb.end(bcResults);
        })
        .then( (out) => {
            var stats = blockchain.getDefaultTxStats(bcResults);
            if(msg.hasOwnProperty('out') && typeof out !== 'undefined') {
                stats.out = { key: msg['out'], value : out};
            }
            return Promise.resolve(stats);
        });
    })
    .catch( (err) => {
        console.log('Client ' + process.pid + ': error ' + (err.stack ? err.stack : err));
        return Promise.reject(err);
    });
}

/**
* Sleep a suitable time according to the required transaction generation time
* @timePerTx {number}, time interval for transaction generation
* @start {number}, generation time of the first transaction
* @txSeq {number}, sequence number of the current transaction
* @return {promise}
*/
function rateControl(timePerTx, start, txSeq) {
    if(timePerTx === 0) {
        return Promise.resolve();
    }
    var diff = Math.floor(timePerTx * txSeq - (process.uptime() - start)*1000);
    if( diff > 10) {
        return sleep(diff);
    }
    else {
        return Promise.resolve();
    }
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}<commit_msg>fix the message name<commit_after>/**
* Copyright 2017 HUAWEI. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* @file Implementation of default test client process.
*/

'use strict'

/* global variables */
var tape  = require('tape');
var _test = require('tape-promise');
var test  = _test(tape);
var path  = require('path');
var bc    = require('../../src/comm/blockchain.js');

/**
 * Message handler
 */
process.on('message', function(message) {
    if(message.hasOwnProperty('type')) {
        try {
            var result;
            switch(message.type) {
                case 'queryNewTx':
                    queryNewTxInfo()
                    .then((queryResult) => {
                         process.send({type: 'queryResult', session: message.session, data: queryResult});
                    });
                    break;
                case 'test':
                    doTest(message)
                    .then((testResult) => {
                        result = testResult;
                        return queryNewTxInfo();
                    })
                    .then((queryResult) => {
                         process.send({type: 'queryResult', session: 'final', data: queryResult});
                         return sleep(200);
                    })
                    .then(() => {
                         process.send({type: 'testResult', data: result});
                    });
                    break;
                default:
                    process.send({type: 'error', data: 'unknown message type'});
            }
        }
        catch(err) {
            process.send({type: 'error', data: err});
        };
    }
    else {
         process.send({type: 'error', data: 'unknown message type'});
    }
})

var blockchain;
var results = [];
var txNum   = 0;
function queryNewTxInfo() {
    var tmpArray = results.slice();
    var tmpNum   = txNum;
    results = [];
    txNum   = 0;
    var stats = blockchain.getDefaultTxStats(tmpArray);
    return Promise.resolve({submitted: tmpNum, committed:stats});
}

function doTest(msg) {
    blockchain = new bc(msg.config);
    var cb = require(path.join(__dirname, '../../', msg.cb));
    var bcContext;
    var bcResults;

    return blockchain.getContext(msg.cmd)
    .then((context) => {
        bcContext = context;
        var rounds   = Array(msg.numb).fill(0);
        var promises = [];
        var idx       = 0;
        var start     = process.uptime();
        var sleepTime = (msg.tps > 0) ? 1000/msg.tps : 0;

        console.log('start client ' + process.pid +  (cb.info ? (':' + cb.info) : ''));

        return rounds.reduce(function(prev, item) {
            return prev.then( () => {
                txNum++;        // TODO: fix, wrong number if run() creates multiple transactions
                promises.push(cb.run().then((result)=>{
                    results.push(result);
                    return Promise.resolve(result);
                }));
                idx++;
                return rateControl(sleepTime, start, idx);
            });
        }, cb.init(blockchain, context, msg.args))
        .then( () => {
            return Promise.all(promises);
        })
        .then( (result) => {
            bcResults = result;
            return blockchain.releaseContext(bcContext);
        })
        .then( () => {
            return cb.end(bcResults);
        })
        .then( (out) => {
            var stats = blockchain.getDefaultTxStats(bcResults);
            if(msg.hasOwnProperty('out') && typeof out !== 'undefined') {
                stats.out = { key: msg['out'], value : out};
            }
            return Promise.resolve(stats);
        });
    })
    .catch( (err) => {
        console.log('Client ' + process.pid + ': error ' + (err.stack ? err.stack : err));
        return Promise.reject(err);
    });
}

/**
* Sleep a suitable time according to the required transaction generation time
* @timePerTx {number}, time interval for transaction generation
* @start {number}, generation time of the first transaction
* @txSeq {number}, sequence number of the current transaction
* @return {promise}
*/
function rateControl(timePerTx, start, txSeq) {
    if(timePerTx === 0) {
        return Promise.resolve();
    }
    var diff = Math.floor(timePerTx * txSeq - (process.uptime() - start)*1000);
    if( diff > 10) {
        return sleep(diff);
    }
    else {
        return Promise.resolve();
    }
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}<|endoftext|>


<commit_before>/*jshint node: true, -W106 */
'use strict';

/*
* Name : pageList.js
* Module : Lib::DataTier::Middleware::MiddlewareLoader
* Location : /lib/controller/middleware
*
* History :
*
* Version         Date           Programmer
* =================================================
* 0.0.1          2014-05-11      Filippo Rampado
* -------------------------------------------------
* Codifica modulo.
* =================================================
*/

var PageList = function(name) {
	if (typeof name !== 'string') {
		return null;
	}
	var _name=name;
	var _pages=[];

	PageList.prototype.addPage = function(page){
		if (page){
			_pages.push(page);
			return true;
		}
		return false;
	};

	PageList.prototype.getName = function(){
		return _name;
	};

	PageList.prototype.getData = function(){
		return {};
	};
};

module.export = PageList;<commit_msg>add function getData to pageList<commit_after>/*jshint node: true, -W106 */
'use strict';

/*
* Name : pageList.js
* Module : Lib::DataTier::Middleware::MiddlewareLoader
* Location : /lib/controller/middleware
*
* History :
*
* Version         Date           Programmer
* =================================================
* 0.0.1          2014-05-11      Filippo Rampado
* -------------------------------------------------
* Codifica modulo.
* =================================================
*/

var PageList = function(name) {
	if (typeof name !== 'string') {
		return null;
	}
	var _name=name;
	var _pages=[];

	PageList.prototype.addPage = function(page){
		if ( page ){
			_pages.push(page);
			return true;
		}
		return false;
	};

	PageList.prototype.getName = function(){
		return _name;
	};

	PageList.prototype.getData = function(){
		var pageListdata=[];
		var pages=_pages.length;

		//build pageListData by iterating every page in _pages
		for(var i=0; i<pages; i++){
			var pageData=_pages[i].getData();
			var graphs=pageData.graphs.length;
			var graphsData=[];

			//build graphsData by iterating every graph in this page
			for (var j=0; j<graphs; j++){
				graphsData[j]={
					ID: pageData.graphs[j].ID,
					title: pageData.graphs[j].title,
					type: pageData.graphs[j].type,
				};
			}//for graphs

			pageListdata[i]={
				ID: pageData.ID,
				name: pageData.name,
				description: pageData.description,
				graphs: graphsData
			};
		}//for pages

		return {
			name: _name,
			data: pageListdata
		};
	};
};

module.export = PageList;<|endoftext|>


<commit_before>// @flow

import Command, {flags} from 'cli-engine-command'
import Plugins from '../plugins'

export default class Commands extends Command {
  static topic = 'commands'
  static hidden = true
  static flags = {json: flags.boolean()}

  async run () {
    this.out.warn('heroku-cli: This CLI is deprecated. Please reinstall from https://cli.heroku.com')
    let plugins = new Plugins(this.out)
    await plugins.load()
    let topics = plugins.topics.filter(t => !t.hidden)
    let commands = plugins.commands.map(c => ({
      command: c.command,
      topic: c.topic,
      usage: c.usage,
      description: c.description,
      help: c.help,
      fullHelp: c.help,
      hidden: c.hidden
    }))
    this.out.styledJSON({topics, commands})
  }
}
<commit_msg>add v6 hack<commit_after>// @flow

import Command, {flags} from 'cli-engine-command'
import Plugins from '../plugins'
import fs from 'fs-extra'
import path from 'path'

export default class Commands extends Command {
  static topic = 'commands'
  static hidden = true
  static flags = {json: flags.boolean()}

  async run () {
    this.out.warn('heroku-cli: This CLI is deprecated. Please reinstall from https://cli.heroku.com')
    await this.addV6Hack()
    let plugins = new Plugins(this.out)
    await plugins.load()
    let topics = plugins.topics.filter(t => !t.hidden)
    let commands = plugins.commands.map(c => ({
      command: c.command,
      topic: c.topic,
      usage: c.usage,
      description: c.description,
      help: c.help,
      fullHelp: c.help,
      hidden: c.hidden
    }))
    this.out.styledJSON({topics, commands})
  }

  async addV6Hack () {
    try {
      const hack = `### begin v6 v.1
begin
  pluginsDir = File.join(Dir.home, ".heroku", "plugins")
  bin = File.join(Dir.home, ".local", "share", "heroku", "client", "bin", "heroku")
  if File.exists?(bin) && (!Dir.exists?(pluginsDir) || Dir.entries(pluginsDir).count <= 2)
    puts "Running: #{bin}" if ENV['HEROKU_DEBUG'] || ENV['DEBUG']
    system bin, *ARGV
    status = $?.exitstatus
    exit status
  end
rescue => e
  puts e
end
### end v6 v.1

`
      if (this.config.windows) return
      let cliRB = path.join(this.config.home, '.heroku', 'client', 'lib', 'heroku', 'cli.rb')
      if (!(await fs.exists(cliRB))) return
      let contents = await fs.readFile(cliRB, 'utf8')
      if (contents.startsWith('### begin v6 v.1')) return
      await fs.outputFile(cliRB, hack + contents)
    } catch (err) {
      this.out.debug(err)
    }
  }
}
<|endoftext|>


<commit_before>var dom = require('@nymag/dom'),
  _ = require('lodash'),
  AddController = require('./add-component-controller'),
  references = require('references'),
  utils = require('../services/utils'),
  removeService = require('../services/remove-service'),
  createService = require('../services/create-service'),
  logicReadoutService = require('../services/logic-readout-service'),
  statusService = require('../services/status-service'),
  proto = BrowseController.prototype;

/**
 * Launch the controller to browse a Space.
 *
 * @param {Element} el
 * @param {Object}  callbacks
 * @param {Boolean} invisible
 */
function BrowseController(el, callbacks, invisible) {
  /**
   * The Space element
   *
   * @type {Element}
   */
  this.el = el;

  /**
   * The ref to the Space element
   *
   * @type {String}
   */
  this.spaceRef = el.getAttribute(references.referenceAttribute);

  /**
   * An object of callback functions
   *
   * @type {Object}
   */
  this.callbacks = callbacks;

  /**
   * Children Space Logic componens
   * @type {Array}
   */
  this.childComponents = [];

  /**
   * The list of components in the Space in
   * organized to be used in a filterable list
   * @type {Array}
   */
  this.componentList = [];

  /**
   * Boolean flag for whether or not you're launching
   * a browse pane for an invisble Space
   *
   * @type {Boolean}
   */
  this.invisible = invisible;

  this.findChildrenMakeList(this.el);
  // Launch the pane
  this.launchPane();
}

/**
 * Open the settings pane
 */
proto.launchPane = function () {
  var paneContent = this.markActiveInList(utils.createFilterableList(this.componentList, {
    click: this.invisible ? _.noop : this.listItemClick.bind(this),
    reorder: this.reorder.bind(this),
    settings: this.settings.bind(this),
    remove: this.remove.bind(this),
    add: this.addComponent.bind(this),
    addTitle: 'Add Component To Space'
  }));

  if (this.invisible) {
    this.addInTarget(paneContent);
  } else {
    this.swapInTargetIcon(paneContent);
  }

  debugger;
  references.pane.open([{ header: this.el.getAttribute(references.dataPaneTitle) || 'Browse Space', content: paneContent }]);
};

/**
 * Add in a separate target button for invisible lists.
 * This is important because the settings still need
 * to point to the original component.
 *
 * @param {Element} content
 */
proto.addInTarget = function (content) {
  var settingsIcons = dom.findAll(content, '.filtered-item-settings'),
    targetSpaceBtn = references.tpl.get('.target-space');

  // Whenever you append a document fragment like `targetSpaceBtn` into the DOM
  // you are taking the content out of the document fragment. If you use `appendChild`
  // then the HTML of the fragment is returned, but since we want to insert this
  // icon before another icon we don't get that benefit. For this reason we have
  // to clone the node (`cloneNode`) so that we can iterate through mutliple and
  // items and apply the button.
  _.forEach(settingsIcons, (icon) => {
    var targetBtn,
      targetSpaceBtnClone = targetSpaceBtn.cloneNode(true);

    // Add in button
    dom.insertBefore(icon, targetSpaceBtnClone);
    // Find the button
    targetBtn = dom.find(icon.parentElement, '.space-target');
    // Attach event listeners
    targetBtn.addEventListener('click', this.targetBtnClick.bind(this));
  });
};

/**
 * Event handler for the target button click event
 *
 * @param  {Object} e
 */
proto.targetBtnClick = function (e) {
  var componentUri = dom.closest(e.target, '[data-item-id]').getAttribute('data-item-id'),
    component = dom.find(`[${references.referenceAttribute}="${componentUri}"]`);

  this.settings(component.parentElement.getAttribute(references.referenceAttribute));
};

/**
 * Replace the settings icon with the target icon because
 * accurate iconography is important
 *
 * @param  {Element} content
 */
proto.swapInTargetIcon = function (content) {
  var settingsBtnSvgs = dom.findAll(content, '.filtered-item-settings svg'),
    targetSvg = references.tpl.get('.target-space-svg');

  _.forEach(settingsBtnSvgs, function (icon) {
    dom.replaceElement(icon, targetSvg.cloneNode(true));
  });
};

/**
 * Launch the AddController
 */
proto.addComponent = function () {
  AddController(this.el, this.callbacks.add, this.invisible);
};

/**
 * [findChildrenMakeList description]
 * @param  {Element} el
 */
proto.findChildrenMakeList = function (el) {
  this.childComponents = utils.findAllLogic(el);
  this.componentList = this.makeList(this.childComponents);
};

/**
 * Determine if it's the Logic or the child that should be
 * used based on whether or not the Space is invisible
 *
 * @param  {Element} logic
 * @return {Element}
 */
proto.logicOrEmbedded = function (logic) {
  return this.invisible ? dom.find(logic, `[${references.referenceAttribute}]`) : logic;
};

/**
 * Apply active styling to the proper item in the filterable list
 *
 * @param  {element} listHtml
 * @returns {element}
 */
proto.markActiveInList = function (listHtml) {
  if (!this.invisible) { // An 'active' item isn't a thing when you can't see it...right?
    _.each(this.childComponents, (logicComponent) => {
      var targetUri = this.logicOrEmbedded(logicComponent).getAttribute(references.referenceAttribute);

      if (statusService.isEditing(logicComponent)) {
        dom.find(listHtml, `[data-item-id="${targetUri}"]`).classList.add('active');
      }
    });
  }
  return listHtml;
};

proto.logicFromChildRef = function (ref) {
  return dom.closest(dom.find(`[${references.referenceAttribute}="${ref}"]`), '[data-logic]');
};

/**
 * Delete a component from a space
 *
 * @param  {string} id The `id` value of the item in the filterable list that was clicked
 */
proto.remove = function (id) {
  var logicUri = this.invisible ? this.logicFromChildRef(id).getAttribute(references.referenceAttribute) : id;

  removeService.removeLogic(logicUri, this.el)
    .then(() => {
      // Make new component list from the returned HTML
      this.findChildrenMakeList(this.el);

      // Invoke the callback
      this.callbacks.remove(this.el);
      // Close the old pane
      references.pane.close();
      // Launch new pane with updated components
      this.launchPane();
    });
};

/**
 * [makeList description]
 * @param  {Array} components
 * @return {Object}
 */
proto.makeList = function (components) {
  return _.map(components, (item) => {
    var childComponentUri = dom.find(item, `[${references.referenceAttribute}]`).getAttribute(references.referenceAttribute), // Get the child of the logic
      componentType = references.getComponentNameFromReference(childComponentUri), // Get it's name
      componentTitle = references.label(componentType), // Make the title
      readouts = logicReadoutService(item), // Get logic readouts
      returnId = this.invisible ? childComponentUri : item.getAttribute(references.referenceAttribute);

    componentTitle = readouts ? componentTitle + readouts : componentTitle; // Great the title

    // Return an object representing the Logic and it's child
    return {
      title: componentTitle,
      id: returnId
    };
  });
};

/**
 * [reorder description]
 * @param  {string} id
 * @param  {number} newIndex
 * @param  {number} oldIndex
 */
proto.reorder = function (id, newIndex, oldIndex) {

  var data = { _ref: this.spaceRef },
    content = _.map(this.componentList, (item) => {
      var id = item.id;

      // If invisible then `item.id` will be the direct component's
      // uri and not its parent Logic's. Need to fix that.
      if (this.invisible) {
        id = this.logicFromChildRef(id).getAttribute(references.referenceAttribute);
      }

      return { _ref: id };
    });

  content.splice(newIndex, 0, content.splice(oldIndex, 1)[0]); // reorder the array
  data.content = content;

  // Save the space
  references.edit.savePartial(data)
    .then((newHtml) => {
      var newChildHtmlPromises;

      // Make new component list from the returned HTML
      this.findChildrenMakeList(newHtml);

      // Make an array of promises for the updated children HTML
      newChildHtmlPromises = _.map(this.childComponents, function (logicComponent) {
        var query = { currentUrl: window.location.href };

        return references.edit.getHTMLWithQuery(logicComponent.getAttribute(references.referenceAttribute), query);
      });

      return Promise.all(newChildHtmlPromises)
        .then(this.renderUpdatedSpace.bind(this))
        .then(createService.attachHandlersAndFocus);

    });
};

/**
 * [renderUpdatedSpace description]
 * @param  {Element} resp
 * @return {Element}
 */
proto.renderUpdatedSpace = function (resp) {
  var space = this.el,
    spaceChildren = utils.findAllLogic(this.el),
    spaceOnPage = dom.find(document, `[${references.referenceAttribute}="${this.spaceRef}"]`);

  _.forEach(spaceChildren, function (child) {
    child.parentNode.removeChild(child);
  });

  _.forEach(resp, function (logicComponent) {
    space.appendChild(logicComponent);
  });

  dom.replaceElement(spaceOnPage, space);
  this.findChildrenMakeList(space);

  return space;
};

/**
 * When an item in the browse pane is clicked we want to be able
 * to edit that selected component. Remove the editing attribute
 * from all Logic's and then add it back to the one that was
 * selected. Let's then close the pane and focus on that selected
 * component.
 *
 * @param {string} id
 */
proto.listItemClick = function (id) {
  var newActive = dom.find(this.el, `[${references.referenceAttribute}="${id}"]`);

  // Remove editing status from each Logic
  _.each(this.childComponents, function (el) {
    statusService.removeEditing(el);
  });
  // Set the selected Logic to active
  statusService.setEditing(newActive);
  // Close pane
  references.pane.close();
  // Focus on the Logic's embedded component
  references.focus.focus(dom.find(newActive, `[${references.referenceAttribute}]`));
};

/**
 * Open the settings window for the logic
 *
 * @param  {string} id
 */
proto.settings = function (id) {
  references.focus.unfocus().then(function () {
    return references.forms.open(id, document.body);
  }).catch(_.noop);
};

/**
 * Make a new Space Settings instance
 *
 * @param  {Object}  el
 * @param  {Object}  callbacks
 * @param  {Boolean} invisible
 * @return {BrowseController}
 */
function spaceSettings(el, callbacks = {}, invisible = false) {
  return new BrowseController(el, callbacks, invisible);
};

module.exports = spaceSettings;

<commit_msg>Removing debugger<commit_after>var dom = require('@nymag/dom'),
  _ = require('lodash'),
  AddController = require('./add-component-controller'),
  references = require('references'),
  utils = require('../services/utils'),
  removeService = require('../services/remove-service'),
  createService = require('../services/create-service'),
  logicReadoutService = require('../services/logic-readout-service'),
  statusService = require('../services/status-service'),
  proto = BrowseController.prototype;

/**
 * Launch the controller to browse a Space.
 *
 * @param {Element} el
 * @param {Object}  callbacks
 * @param {Boolean} invisible
 */
function BrowseController(el, callbacks, invisible) {
  /**
   * The Space element
   *
   * @type {Element}
   */
  this.el = el;

  /**
   * The ref to the Space element
   *
   * @type {String}
   */
  this.spaceRef = el.getAttribute(references.referenceAttribute);

  /**
   * An object of callback functions
   *
   * @type {Object}
   */
  this.callbacks = callbacks;

  /**
   * Children Space Logic componens
   * @type {Array}
   */
  this.childComponents = [];

  /**
   * The list of components in the Space in
   * organized to be used in a filterable list
   * @type {Array}
   */
  this.componentList = [];

  /**
   * Boolean flag for whether or not you're launching
   * a browse pane for an invisble Space
   *
   * @type {Boolean}
   */
  this.invisible = invisible;

  this.findChildrenMakeList(this.el);
  // Launch the pane
  this.launchPane();
}

/**
 * Open the settings pane
 */
proto.launchPane = function () {
  var paneContent = this.markActiveInList(utils.createFilterableList(this.componentList, {
    click: this.invisible ? _.noop : this.listItemClick.bind(this),
    reorder: this.reorder.bind(this),
    settings: this.settings.bind(this),
    remove: this.remove.bind(this),
    add: this.addComponent.bind(this),
    addTitle: 'Add Component To Space'
  }));

  if (this.invisible) {
    this.addInTarget(paneContent);
  } else {
    this.swapInTargetIcon(paneContent);
  }

  references.pane.open([{ header: this.el.getAttribute(references.dataPaneTitle) || 'Browse Space', content: paneContent }]);
};

/**
 * Add in a separate target button for invisible lists.
 * This is important because the settings still need
 * to point to the original component.
 *
 * @param {Element} content
 */
proto.addInTarget = function (content) {
  var settingsIcons = dom.findAll(content, '.filtered-item-settings'),
    targetSpaceBtn = references.tpl.get('.target-space');

  // Whenever you append a document fragment like `targetSpaceBtn` into the DOM
  // you are taking the content out of the document fragment. If you use `appendChild`
  // then the HTML of the fragment is returned, but since we want to insert this
  // icon before another icon we don't get that benefit. For this reason we have
  // to clone the node (`cloneNode`) so that we can iterate through mutliple and
  // items and apply the button.
  _.forEach(settingsIcons, (icon) => {
    var targetBtn,
      targetSpaceBtnClone = targetSpaceBtn.cloneNode(true);

    // Add in button
    dom.insertBefore(icon, targetSpaceBtnClone);
    // Find the button
    targetBtn = dom.find(icon.parentElement, '.space-target');
    // Attach event listeners
    targetBtn.addEventListener('click', this.targetBtnClick.bind(this));
  });
};

/**
 * Event handler for the target button click event
 *
 * @param  {Object} e
 */
proto.targetBtnClick = function (e) {
  var componentUri = dom.closest(e.target, '[data-item-id]').getAttribute('data-item-id'),
    component = dom.find(`[${references.referenceAttribute}="${componentUri}"]`);

  this.settings(component.parentElement.getAttribute(references.referenceAttribute));
};

/**
 * Replace the settings icon with the target icon because
 * accurate iconography is important
 *
 * @param  {Element} content
 */
proto.swapInTargetIcon = function (content) {
  var settingsBtnSvgs = dom.findAll(content, '.filtered-item-settings svg'),
    targetSvg = references.tpl.get('.target-space-svg');

  _.forEach(settingsBtnSvgs, function (icon) {
    dom.replaceElement(icon, targetSvg.cloneNode(true));
  });
};

/**
 * Launch the AddController
 */
proto.addComponent = function () {
  AddController(this.el, this.callbacks.add, this.invisible);
};

/**
 * [findChildrenMakeList description]
 * @param  {Element} el
 */
proto.findChildrenMakeList = function (el) {
  this.childComponents = utils.findAllLogic(el);
  this.componentList = this.makeList(this.childComponents);
};

/**
 * Determine if it's the Logic or the child that should be
 * used based on whether or not the Space is invisible
 *
 * @param  {Element} logic
 * @return {Element}
 */
proto.logicOrEmbedded = function (logic) {
  return this.invisible ? dom.find(logic, `[${references.referenceAttribute}]`) : logic;
};

/**
 * Apply active styling to the proper item in the filterable list
 *
 * @param  {element} listHtml
 * @returns {element}
 */
proto.markActiveInList = function (listHtml) {
  if (!this.invisible) { // An 'active' item isn't a thing when you can't see it...right?
    _.each(this.childComponents, (logicComponent) => {
      var targetUri = this.logicOrEmbedded(logicComponent).getAttribute(references.referenceAttribute);

      if (statusService.isEditing(logicComponent)) {
        dom.find(listHtml, `[data-item-id="${targetUri}"]`).classList.add('active');
      }
    });
  }
  return listHtml;
};

proto.logicFromChildRef = function (ref) {
  return dom.closest(dom.find(`[${references.referenceAttribute}="${ref}"]`), '[data-logic]');
};

/**
 * Delete a component from a space
 *
 * @param  {string} id The `id` value of the item in the filterable list that was clicked
 */
proto.remove = function (id) {
  var logicUri = this.invisible ? this.logicFromChildRef(id).getAttribute(references.referenceAttribute) : id;

  removeService.removeLogic(logicUri, this.el)
    .then(() => {
      // Make new component list from the returned HTML
      this.findChildrenMakeList(this.el);

      // Invoke the callback
      this.callbacks.remove(this.el);
      // Close the old pane
      references.pane.close();
      // Launch new pane with updated components
      this.launchPane();
    });
};

/**
 * [makeList description]
 * @param  {Array} components
 * @return {Object}
 */
proto.makeList = function (components) {
  return _.map(components, (item) => {
    var childComponentUri = dom.find(item, `[${references.referenceAttribute}]`).getAttribute(references.referenceAttribute), // Get the child of the logic
      componentType = references.getComponentNameFromReference(childComponentUri), // Get it's name
      componentTitle = references.label(componentType), // Make the title
      readouts = logicReadoutService(item), // Get logic readouts
      returnId = this.invisible ? childComponentUri : item.getAttribute(references.referenceAttribute);

    componentTitle = readouts ? componentTitle + readouts : componentTitle; // Great the title

    // Return an object representing the Logic and it's child
    return {
      title: componentTitle,
      id: returnId
    };
  });
};

/**
 * [reorder description]
 * @param  {string} id
 * @param  {number} newIndex
 * @param  {number} oldIndex
 */
proto.reorder = function (id, newIndex, oldIndex) {

  var data = { _ref: this.spaceRef },
    content = _.map(this.componentList, (item) => {
      var id = item.id;

      // If invisible then `item.id` will be the direct component's
      // uri and not its parent Logic's. Need to fix that.
      if (this.invisible) {
        id = this.logicFromChildRef(id).getAttribute(references.referenceAttribute);
      }

      return { _ref: id };
    });

  content.splice(newIndex, 0, content.splice(oldIndex, 1)[0]); // reorder the array
  data.content = content;

  // Save the space
  references.edit.savePartial(data)
    .then((newHtml) => {
      var newChildHtmlPromises;

      // Make new component list from the returned HTML
      this.findChildrenMakeList(newHtml);

      // Make an array of promises for the updated children HTML
      newChildHtmlPromises = _.map(this.childComponents, function (logicComponent) {
        var query = { currentUrl: window.location.href };

        return references.edit.getHTMLWithQuery(logicComponent.getAttribute(references.referenceAttribute), query);
      });

      return Promise.all(newChildHtmlPromises)
        .then(this.renderUpdatedSpace.bind(this))
        .then(createService.attachHandlersAndFocus);

    });
};

/**
 * [renderUpdatedSpace description]
 * @param  {Element} resp
 * @return {Element}
 */
proto.renderUpdatedSpace = function (resp) {
  var space = this.el,
    spaceChildren = utils.findAllLogic(this.el),
    spaceOnPage = dom.find(document, `[${references.referenceAttribute}="${this.spaceRef}"]`);

  _.forEach(spaceChildren, function (child) {
    child.parentNode.removeChild(child);
  });

  _.forEach(resp, function (logicComponent) {
    space.appendChild(logicComponent);
  });

  dom.replaceElement(spaceOnPage, space);
  this.findChildrenMakeList(space);

  return space;
};

/**
 * When an item in the browse pane is clicked we want to be able
 * to edit that selected component. Remove the editing attribute
 * from all Logic's and then add it back to the one that was
 * selected. Let's then close the pane and focus on that selected
 * component.
 *
 * @param {string} id
 */
proto.listItemClick = function (id) {
  var newActive = dom.find(this.el, `[${references.referenceAttribute}="${id}"]`);

  // Remove editing status from each Logic
  _.each(this.childComponents, function (el) {
    statusService.removeEditing(el);
  });
  // Set the selected Logic to active
  statusService.setEditing(newActive);
  // Close pane
  references.pane.close();
  // Focus on the Logic's embedded component
  references.focus.focus(dom.find(newActive, `[${references.referenceAttribute}]`));
};

/**
 * Open the settings window for the logic
 *
 * @param  {string} id
 */
proto.settings = function (id) {
  references.focus.unfocus().then(function () {
    return references.forms.open(id, document.body);
  }).catch(_.noop);
};

/**
 * Make a new Space Settings instance
 *
 * @param  {Object}  el
 * @param  {Object}  callbacks
 * @param  {Boolean} invisible
 * @return {BrowseController}
 */
function spaceSettings(el, callbacks = {}, invisible = false) {
  return new BrowseController(el, callbacks, invisible);
};

module.exports = spaceSettings;

<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

// Ballista polyfill.
// Author: Matt Giuca <mgiuca@chromium.org>
//
// Implements a (partial) Ballista API for demonstration purposes. Note: This
// API, by design, does things that aren't possible in a polyfill, so this will
// be broken without specific browser hacks. All work-in-progress; don't get too
// excited about it.
"use strict";

(function() {

// Portions are copied (and heavily modified) from tha navigator-connect
// polyfill by mkruisselbrink and reillyeon:
// https://github.com/mkruisselbrink/navigator-connect

// The URL of the proxy app, which provides the handler chooser and routes
// actions through to the chosen handler.
// TODO(mgiuca): Move this to a fixed public location.
var kChooseUrl = 'http://localhost:8080/choose';

// The URL of the proxy app, which provides registration UI for handlers.
var kRegisterUrl = 'http://localhost:8080/register';

var kProxyUrlSuffix = '?actions-handler-proxy';

// DOM element for the <div> containing the chooser UI (hosted by the proxy
// site) in an iframe. Only non-null while choosing is taking place.
var chooserDiv = null;

// The handler's service worker provides a special proxy page that will be
// embedded in an iframe in the requester (by connectToHandler).
//
// This page proxies messages between the requester foreground page and the
// handler service worker.
function onFetch(event) {
  var targetUrl = event.request.url;
  // Detect whether kProxyUrlSuffix appears in the query string.
  var proxySuffixIndex = targetUrl.indexOf(kProxyUrlSuffix);
  if (proxySuffixIndex === -1 ||
      (proxySuffixIndex + kProxyUrlSuffix.length != targetUrl.length &&
       targetUrl[proxySuffixIndex + kProxyUrlSuffix.length] != '&')) {
    // Not a navigator-connect attempt
    return;
  }
  // In the real world this should not reply to all fetches.
  event.respondWith(fetch('polyfill/proxy-iframe.html'));
  event.stopImmediatePropagation();
}

function onMessage(event) {
  if (typeof event != 'object')
    return;

  var data = event.data;

  if (data === undefined)
    return;

  if (data.type == 'connect') {
    data.port.postMessage({connected: true});
    data.port.onmessage = e => {
      onMessageReceived(e.data, data.port);
    }
    event.stopImmediatePropagation();
    return;
  } else if (data.type == 'sendPortToProxy') {
    sendPortToProxy(data.port, data.options);
  } else if (data.type == 'closeChooser') {
    closeChooser();
  }
}

var findLastTopLevelClient = undefined;

// Listen for 'fetch' and 'message', if in a service worker.
if (self.WorkerNavigator !== undefined) {
  self.addEventListener('fetch', onFetch);
  self.addEventListener('message', onMessage);

  // Finds the most recently opened top-level client belonging to this service
  // worker. Returns a promise. Rejects the promise if none found.
  findLastTopLevelClient = () => {
    return clients.matchAll().then(allClients => {
      for (var i = allClients.length - 1; i >= 0; i--) {
        if (allClients[i].frameType == 'top-level') {
          return allClients[i];
        }
      }
      throw new Error('No available clients; please open a tab.');
    });
  }
} else {
  // Receive messages from the service worker.
  navigator.serviceWorker.addEventListener('message', onMessage);
}

// Creates an iframe at |url|, and shows it in a popup window. Returns a promise
// that resolves once the iframe has loaded, with the iframe DOM element.
function createIframePopup(url) {
  chooserDiv = document.createElement('div');
  chooserDiv.style.zIndex = 100;
  chooserDiv.style.position = 'absolute';
  chooserDiv.style.left = '200px';
  chooserDiv.style.top = '200px';
  chooserDiv.style.width = '640px';
  chooserDiv.style.height = '480px';

  var iframe = document.createElement('iframe');
  return new Promise((resolve, reject) => {
    iframe.onload = e => resolve(iframe);

    iframe.setAttribute('src', url);
    iframe.style.width = '100%';
    iframe.style.height = '100%';

    chooserDiv.appendChild(iframe);
    document.body.appendChild(chooserDiv);
  });
}

// Establishes a connection with the polyfill handler (via the proxy, by
// embedding a page from the proxy's domain in an iframe), and posts a
// MessagePort object to it. |options| is a dictionary of various fields used to
// identify which handlers can be used. Asynchronous; no return value.
function sendPortToProxy(port, options) {
  if (self.document === undefined) {
    // We are in a service worker. No way to create an iframe here (needed to
    // establish a connection to handler service worker), so connect to a random
    // foreground page and establish a connection there (it does not need to be
    // the same foreground page that initiated the action, as long as it is
    // running this polyfill code). If there is no open requester foreground
    // page, this will fail. This *should not* happen in the real API, but there
    // is no way around this in the polyfill.
    findLastTopLevelClient().then(client => {
      client.postMessage(
          {type: 'sendPortToProxy', port: port, options: options}, [port]);
    });
    return;
  }

  createIframePopup(kChooseUrl)
      .then(iframe => {
        iframe.contentWindow.postMessage({port: port, options: options}, '*',
                                         [port]);
      });
}

// Destroy the chooser div.
function closeChooser() {
  if (self.document === undefined) {
    // We are in a service worker. Tell the foreground page to destroy the
    // chooser.
    findLastTopLevelClient().then(client => {
      client.postMessage({type: 'closeChooser'});
    });
    return;
  }

  document.body.removeChild(chooserDiv);
}

// Prompts the user for a handler app and establishes a connection with the
// chosen handler. |options| is a dictionary of various fields used to identify
// which handlers can be used. Returns (in a promise) a MessagePort on succesful
// connection to the handler.
function connectToHandler(options) {
  return new Promise(function(resolve, reject) {
    var channel = new MessageChannel();
    channel.port1.onmessage = e => {
      closeChooser();

      if (e.data.connected) {
        channel.port1.onmessage = null;
        resolve(channel.port1);
      } else if (e.data.connected == false) {
        reject(new Error("User cancelled dialog."));
      } else {
        reject(new Error("Received unexpected response from handler."));
      }
    };
    sendPortToProxy(channel.port2, options);
  });
};

// Polyfill Cache.addAll.
// Not necessary in Chrome 45 with --enable-experimental-web-platform-features.
if (Cache.prototype.addAll === undefined) {
  Cache.prototype.addAll = function(urls) {
    return Promise.all(urls.map(url => this.add(url)));
  }
}

// TODO(mgiuca): Rewrite prototype-based "classes" using actual ES6 classes.
// This is blocked on Firefox implementing the "super" keyword.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1066239

// A base class that implements the EventTarget interface.
function CustomEventTarget() {
  // Map from event type to list of listeners.
  this._listeners = {};
}

CustomEventTarget.prototype.addEventListener = function(type, listener,
                                                        useCapture) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[type];
  if (listeners_of_type === undefined) {
    listeners[type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    if (listeners_of_type[i] === listener) return;
  }

  listeners_of_type.push(listener);
};

CustomEventTarget.prototype.removeEventListener = function(type, listener,
                                                           useCapture) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[type];
  if (listeners_of_type === undefined) {
    listeners[type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    if (listeners_of_type[i] === listener) {
      listeners_of_type.splice(i, 1);

      if (listeners_of_type.length == 0) delete listeners[type];

      return;
    }
  }
};

CustomEventTarget.prototype.dispatchEvent = function(evt) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[evt.type];
  if (listeners_of_type === undefined) {
    listeners[evt.type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    var listener = listeners_of_type[i];
    if (listener.handleEvent !== undefined) {
      listener.handleEvent(evt);
    } else {
      listener.call(evt.target, evt);
    }
  }
};

// An Action is an object representing a web action in flight.
// Each Action has an integer |id|, which is unique among all actions from the
// requester that created it (different requesters can have actions of the same
// ID). This id is exposed to the client.
function Action(id) {
  this.id = id;
}

// A map from action IDs to MessagePort objects. Handler only.
var actionMap = new Map;

// The next action ID to use.
var nextActionId = 0;

var newHandleEvent = null;
var newUpdateEvent = null;

// Polyfill navigator.actions.
if (navigator.actions === undefined) {
  var NavigatorActions = function() {
    CustomEventTarget.call(this);
  };
  NavigatorActions.prototype = Object.create(CustomEventTarget.prototype);
  var actions = new NavigatorActions();
  navigator.actions = actions;

  var event_or_extendable_event = Event;

  // HandleEvent is only available when the global scope is a
  // ServiceWorkerGlobalScope.
  if (self.ExtendableEvent !== undefined) {
    newHandleEvent = function(id, options, data) {
      var event = new ExtendableEvent('handle');
      event.id = id;
      event.options = options;
      event.data = data;
      event.reject = err => {
        // TODO(mgiuca): Cause the promise to be rejected.
        throw err;
      }
      return event;
    }

    event_or_extendable_event = ExtendableEvent;
  }

  // UpdateEvent is an ExtendableEvent when the global scope is a
  // ServiceWorkerGlobalScope; otherwise it is just an Event.
  newUpdateEvent = function(id, data, done) {
    var event = new event_or_extendable_event('update');
    event.id = id;
    event.data = data;
    event.done = done;
    return event;
  }

  // Performs an action with a given |options| and |data|. |options| is either
  // a verb (string) or a dictionary of various fields used to identify which
  // handlers can be used. |data| is an arbitrary object to be passed to the
  // handler.
  //
  // Returns a Promise<Action> with an action object allowing further
  // interaction with the handler. Fails with AbortError if a connection could
  // not be made.
  actions.performAction = function(options, data) {
    if (typeof options == 'string')
      options = {verb: options};

    return connectToHandler(options).then(port => {
      var id = nextActionId++;
      var action = new Action(id);

      // Send the options and data payload to the handler.
      var message = {type: 'action', options: options, data: data, id: id};
      port.postMessage(message);
      port.onmessage = m => onMessageReceived(m.data, null);

      return action;
    });
  };

  // Sends an updated version of the data payload associated with an action back
  // to the requester. This may be called multiple times per action, but should
  // send a complete copy of the data on each call (this is not a stream
  // protocol). |id| is the id of the action.
  actions.update = function(id, data, done) {
    if (!actionMap.has(id))
      throw new Error('No such action id: ' + id);

    var port = actionMap.get(id);
    var message = {type: 'update', data: data, id: id, done: done == true};
    port.postMessage(message);
  };
}

// Called when a message is received (on both the handler and requester).
// |port| is a MessagePort on the handler; null on the requester.
function onMessageReceived(data, port) {
  if (data.type == 'action') {
    if (newHandleEvent === null) {
      throw new Error(
          'navigator.actions requests must go to a service worker.');
    }

    actionMap.set(data.id, port);

    // Forward the event as a 'handle' event to the global object.
    var handleEvent = newHandleEvent(data.id, data.options, data.data);
    actions.dispatchEvent(handleEvent);
  } else if (data.type == 'update') {
    // Forward the event as an 'update' event to navigator.actions.
    var updateEvent = newUpdateEvent(data.id, data.data, data.done);
    actions.dispatchEvent(updateEvent);
  } else {
    console.log('Received unknown message:', data);
  }
}

// Registers a handler with the proxy server.
function registerHandler(handler) {
  // This channel will be used to get back a "close" signal from the proxy.
  var channel = new MessageChannel();
  channel.port1.onmessage = e => {
    if (e.data.close) {
      closeChooser();
    } else {
      throw new Error("Received unexpected response from proxy.");
    }
  };

  // Show the registration UI.
  createIframePopup(kRegisterUrl)
      .then(iframe => {
        iframe.contentWindow.postMessage(
            {handler: handler, port: channel.port2}, '*', [channel.port2]);
      });
}

// Parses the webapp manifest, and if there are any action handlers, prompts the
// user to register with the proxy server.
function performHandlerRegistration() {
  var links = document.querySelectorAll('link[rel=manifest]');
  for (var i = 0; i < links.length; i++) {
    var manifestUrl = links[i].href;
    fetch(manifestUrl).then(response => {
      if (!response.ok)
        return;

      response.json().then(json => {
        if (json.actions == undefined || json.actions.length == 0)
          return;

        // We have a manifest with action handlers. Attempt to register this
        // handler with the proxy server.
        var name = json.name || document.title;
        var url = json.scope || document.location.href;
        var verbs = [];
        for (var verb in json.actions)
          verbs.push(verb);
        registerHandler({name: name, url: url, verbs: verbs});
      });
    });
  }
}

addEventListener('load', performHandlerRegistration, false);

})();
<commit_msg>polyfill: Avoid duplicating the proxy site URL.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

// Ballista polyfill.
// Author: Matt Giuca <mgiuca@chromium.org>
//
// Implements a (partial) Ballista API for demonstration purposes. Note: This
// API, by design, does things that aren't possible in a polyfill, so this will
// be broken without specific browser hacks. All work-in-progress; don't get too
// excited about it.
"use strict";

(function() {

// Portions are copied (and heavily modified) from tha navigator-connect
// polyfill by mkruisselbrink and reillyeon:
// https://github.com/mkruisselbrink/navigator-connect

// TODO(mgiuca): Move this to a fixed public location.
var kProxySite = 'http://localhost:8080';

// The URL of the proxy app, which provides the handler chooser and routes
// actions through to the chosen handler.
var kChooseUrl = kProxySite + '/choose';

// The URL of the proxy app, which provides registration UI for handlers.
var kRegisterUrl = kProxySite + '/register';

var kProxyUrlSuffix = '?actions-handler-proxy';

// DOM element for the <div> containing the chooser UI (hosted by the proxy
// site) in an iframe. Only non-null while choosing is taking place.
var chooserDiv = null;

// The handler's service worker provides a special proxy page that will be
// embedded in an iframe in the requester (by connectToHandler).
//
// This page proxies messages between the requester foreground page and the
// handler service worker.
function onFetch(event) {
  var targetUrl = event.request.url;
  // Detect whether kProxyUrlSuffix appears in the query string.
  var proxySuffixIndex = targetUrl.indexOf(kProxyUrlSuffix);
  if (proxySuffixIndex === -1 ||
      (proxySuffixIndex + kProxyUrlSuffix.length != targetUrl.length &&
       targetUrl[proxySuffixIndex + kProxyUrlSuffix.length] != '&')) {
    // Not a navigator-connect attempt
    return;
  }
  // In the real world this should not reply to all fetches.
  event.respondWith(fetch('polyfill/proxy-iframe.html'));
  event.stopImmediatePropagation();
}

function onMessage(event) {
  if (typeof event != 'object')
    return;

  var data = event.data;

  if (data === undefined)
    return;

  if (data.type == 'connect') {
    data.port.postMessage({connected: true});
    data.port.onmessage = e => {
      onMessageReceived(e.data, data.port);
    }
    event.stopImmediatePropagation();
    return;
  } else if (data.type == 'sendPortToProxy') {
    sendPortToProxy(data.port, data.options);
  } else if (data.type == 'closeChooser') {
    closeChooser();
  }
}

var findLastTopLevelClient = undefined;

// Listen for 'fetch' and 'message', if in a service worker.
if (self.WorkerNavigator !== undefined) {
  self.addEventListener('fetch', onFetch);
  self.addEventListener('message', onMessage);

  // Finds the most recently opened top-level client belonging to this service
  // worker. Returns a promise. Rejects the promise if none found.
  findLastTopLevelClient = () => {
    return clients.matchAll().then(allClients => {
      for (var i = allClients.length - 1; i >= 0; i--) {
        if (allClients[i].frameType == 'top-level') {
          return allClients[i];
        }
      }
      throw new Error('No available clients; please open a tab.');
    });
  }
} else {
  // Receive messages from the service worker.
  navigator.serviceWorker.addEventListener('message', onMessage);
}

// Creates an iframe at |url|, and shows it in a popup window. Returns a promise
// that resolves once the iframe has loaded, with the iframe DOM element.
function createIframePopup(url) {
  chooserDiv = document.createElement('div');
  chooserDiv.style.zIndex = 100;
  chooserDiv.style.position = 'absolute';
  chooserDiv.style.left = '200px';
  chooserDiv.style.top = '200px';
  chooserDiv.style.width = '640px';
  chooserDiv.style.height = '480px';

  var iframe = document.createElement('iframe');
  return new Promise((resolve, reject) => {
    iframe.onload = e => resolve(iframe);

    iframe.setAttribute('src', url);
    iframe.style.width = '100%';
    iframe.style.height = '100%';

    chooserDiv.appendChild(iframe);
    document.body.appendChild(chooserDiv);
  });
}

// Establishes a connection with the polyfill handler (via the proxy, by
// embedding a page from the proxy's domain in an iframe), and posts a
// MessagePort object to it. |options| is a dictionary of various fields used to
// identify which handlers can be used. Asynchronous; no return value.
function sendPortToProxy(port, options) {
  if (self.document === undefined) {
    // We are in a service worker. No way to create an iframe here (needed to
    // establish a connection to handler service worker), so connect to a random
    // foreground page and establish a connection there (it does not need to be
    // the same foreground page that initiated the action, as long as it is
    // running this polyfill code). If there is no open requester foreground
    // page, this will fail. This *should not* happen in the real API, but there
    // is no way around this in the polyfill.
    findLastTopLevelClient().then(client => {
      client.postMessage(
          {type: 'sendPortToProxy', port: port, options: options}, [port]);
    });
    return;
  }

  createIframePopup(kChooseUrl)
      .then(iframe => {
        iframe.contentWindow.postMessage({port: port, options: options}, '*',
                                         [port]);
      });
}

// Destroy the chooser div.
function closeChooser() {
  if (self.document === undefined) {
    // We are in a service worker. Tell the foreground page to destroy the
    // chooser.
    findLastTopLevelClient().then(client => {
      client.postMessage({type: 'closeChooser'});
    });
    return;
  }

  document.body.removeChild(chooserDiv);
}

// Prompts the user for a handler app and establishes a connection with the
// chosen handler. |options| is a dictionary of various fields used to identify
// which handlers can be used. Returns (in a promise) a MessagePort on succesful
// connection to the handler.
function connectToHandler(options) {
  return new Promise(function(resolve, reject) {
    var channel = new MessageChannel();
    channel.port1.onmessage = e => {
      closeChooser();

      if (e.data.connected) {
        channel.port1.onmessage = null;
        resolve(channel.port1);
      } else if (e.data.connected == false) {
        reject(new Error("User cancelled dialog."));
      } else {
        reject(new Error("Received unexpected response from handler."));
      }
    };
    sendPortToProxy(channel.port2, options);
  });
};

// Polyfill Cache.addAll.
// Not necessary in Chrome 45 with --enable-experimental-web-platform-features.
if (Cache.prototype.addAll === undefined) {
  Cache.prototype.addAll = function(urls) {
    return Promise.all(urls.map(url => this.add(url)));
  }
}

// TODO(mgiuca): Rewrite prototype-based "classes" using actual ES6 classes.
// This is blocked on Firefox implementing the "super" keyword.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1066239

// A base class that implements the EventTarget interface.
function CustomEventTarget() {
  // Map from event type to list of listeners.
  this._listeners = {};
}

CustomEventTarget.prototype.addEventListener = function(type, listener,
                                                        useCapture) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[type];
  if (listeners_of_type === undefined) {
    listeners[type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    if (listeners_of_type[i] === listener) return;
  }

  listeners_of_type.push(listener);
};

CustomEventTarget.prototype.removeEventListener = function(type, listener,
                                                           useCapture) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[type];
  if (listeners_of_type === undefined) {
    listeners[type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    if (listeners_of_type[i] === listener) {
      listeners_of_type.splice(i, 1);

      if (listeners_of_type.length == 0) delete listeners[type];

      return;
    }
  }
};

CustomEventTarget.prototype.dispatchEvent = function(evt) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[evt.type];
  if (listeners_of_type === undefined) {
    listeners[evt.type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    var listener = listeners_of_type[i];
    if (listener.handleEvent !== undefined) {
      listener.handleEvent(evt);
    } else {
      listener.call(evt.target, evt);
    }
  }
};

// An Action is an object representing a web action in flight.
// Each Action has an integer |id|, which is unique among all actions from the
// requester that created it (different requesters can have actions of the same
// ID). This id is exposed to the client.
function Action(id) {
  this.id = id;
}

// A map from action IDs to MessagePort objects. Handler only.
var actionMap = new Map;

// The next action ID to use.
var nextActionId = 0;

var newHandleEvent = null;
var newUpdateEvent = null;

// Polyfill navigator.actions.
if (navigator.actions === undefined) {
  var NavigatorActions = function() {
    CustomEventTarget.call(this);
  };
  NavigatorActions.prototype = Object.create(CustomEventTarget.prototype);
  var actions = new NavigatorActions();
  navigator.actions = actions;

  var event_or_extendable_event = Event;

  // HandleEvent is only available when the global scope is a
  // ServiceWorkerGlobalScope.
  if (self.ExtendableEvent !== undefined) {
    newHandleEvent = function(id, options, data) {
      var event = new ExtendableEvent('handle');
      event.id = id;
      event.options = options;
      event.data = data;
      event.reject = err => {
        // TODO(mgiuca): Cause the promise to be rejected.
        throw err;
      }
      return event;
    }

    event_or_extendable_event = ExtendableEvent;
  }

  // UpdateEvent is an ExtendableEvent when the global scope is a
  // ServiceWorkerGlobalScope; otherwise it is just an Event.
  newUpdateEvent = function(id, data, done) {
    var event = new event_or_extendable_event('update');
    event.id = id;
    event.data = data;
    event.done = done;
    return event;
  }

  // Performs an action with a given |options| and |data|. |options| is either
  // a verb (string) or a dictionary of various fields used to identify which
  // handlers can be used. |data| is an arbitrary object to be passed to the
  // handler.
  //
  // Returns a Promise<Action> with an action object allowing further
  // interaction with the handler. Fails with AbortError if a connection could
  // not be made.
  actions.performAction = function(options, data) {
    if (typeof options == 'string')
      options = {verb: options};

    return connectToHandler(options).then(port => {
      var id = nextActionId++;
      var action = new Action(id);

      // Send the options and data payload to the handler.
      var message = {type: 'action', options: options, data: data, id: id};
      port.postMessage(message);
      port.onmessage = m => onMessageReceived(m.data, null);

      return action;
    });
  };

  // Sends an updated version of the data payload associated with an action back
  // to the requester. This may be called multiple times per action, but should
  // send a complete copy of the data on each call (this is not a stream
  // protocol). |id| is the id of the action.
  actions.update = function(id, data, done) {
    if (!actionMap.has(id))
      throw new Error('No such action id: ' + id);

    var port = actionMap.get(id);
    var message = {type: 'update', data: data, id: id, done: done == true};
    port.postMessage(message);
  };
}

// Called when a message is received (on both the handler and requester).
// |port| is a MessagePort on the handler; null on the requester.
function onMessageReceived(data, port) {
  if (data.type == 'action') {
    if (newHandleEvent === null) {
      throw new Error(
          'navigator.actions requests must go to a service worker.');
    }

    actionMap.set(data.id, port);

    // Forward the event as a 'handle' event to the global object.
    var handleEvent = newHandleEvent(data.id, data.options, data.data);
    actions.dispatchEvent(handleEvent);
  } else if (data.type == 'update') {
    // Forward the event as an 'update' event to navigator.actions.
    var updateEvent = newUpdateEvent(data.id, data.data, data.done);
    actions.dispatchEvent(updateEvent);
  } else {
    console.log('Received unknown message:', data);
  }
}

// Registers a handler with the proxy server.
function registerHandler(handler) {
  // This channel will be used to get back a "close" signal from the proxy.
  var channel = new MessageChannel();
  channel.port1.onmessage = e => {
    if (e.data.close) {
      closeChooser();
    } else {
      throw new Error("Received unexpected response from proxy.");
    }
  };

  // Show the registration UI.
  createIframePopup(kRegisterUrl)
      .then(iframe => {
        iframe.contentWindow.postMessage(
            {handler: handler, port: channel.port2}, '*', [channel.port2]);
      });
}

// Parses the webapp manifest, and if there are any action handlers, prompts the
// user to register with the proxy server.
function performHandlerRegistration() {
  var links = document.querySelectorAll('link[rel=manifest]');
  for (var i = 0; i < links.length; i++) {
    var manifestUrl = links[i].href;
    fetch(manifestUrl).then(response => {
      if (!response.ok)
        return;

      response.json().then(json => {
        if (json.actions == undefined || json.actions.length == 0)
          return;

        // We have a manifest with action handlers. Attempt to register this
        // handler with the proxy server.
        var name = json.name || document.title;
        var url = json.scope || document.location.href;
        var verbs = [];
        for (var verb in json.actions)
          verbs.push(verb);
        registerHandler({name: name, url: url, verbs: verbs});
      });
    });
  }
}

addEventListener('load', performHandlerRegistration, false);

})();
<|endoftext|>


<commit_before>/*global define:true */
define(['spec/support/spec_helper', 'aura/aura', 'components/underscore/underscore'], function (helper, aura) {

  "use strict";
  /*jshint devel: true, browser: true */
  /*global describe:true, it:true, before: true, sinon: true, define: true */

  var delay = function () {
    var args = arguments;
    var slice = Array.prototype.slice;
    setTimeout(function () {
      var argsArray = slice.call(args);
      argsArray.shift().apply(null, argsArray);
    }, parseInt(Math.random() * 2, 10));
  };

  var config = {
    appId: "fakeId",
    orgUrl: "orgUrl"
  };
  var mustFail = false;

  var easyXDMMock = {
    Rpc: function (a1, a2) {
      if (mustFail) {
        delay(a2.local.message.bind(this), {error:"Fail"});
      } else {
        delay(a2.local.ready, {services:{types:{auth:[]}},data: {me: {name: "test"}, app: {name: "test", org: {name: "test"}}}});
      }
      mustFail = false;
    }
  };

  easyXDMMock.Rpc.prototype.message = function (conf, successCb, errorCb) {
    var cb;
    if (conf.error) {
      cb = errorCb;
      conf = conf.error;
    } else if (conf.path.indexOf('error') === 0) {
      cb = errorCb;
    } else {
      cb = successCb;
    }
    delay(cb, { response: conf, headers: {} }, {});
  };

  define('easyXDM', function () { return easyXDMMock; });

  xdescribe("API specs", function () {
    var env, api, batch, app = aura(config);

    var extension = {
      initialize: function (appEnv) {
        env = appEnv;
        app.core = app.core || {};
        app.core.mvc = window.Backbone;
      }
    };

    app
      .use(extension)
      .use('lib/client/api');

    before(function(done) {
      app.start().then(function () {
        api = app.sandboxes.create().data.api;
        batch = api.batch;
        done();
      });
    });

    it('should be available in the environment', function () {
      env.sandbox.data.should.contain.keys('api');
      api.should.be.a('function');
    });

    describe("initializing the API client", function () {
      it("should reject if there's an error", function (done) {
        require(['lib/api'], function (apiClient) {
          mustFail = true;
          apiClient({appId: "please", orgUrl: "fail"}).then(
            function () {done(new Error('Error'));},
            function () {done();}
          );
        });
      });
    });

    describe("Basic API requests", function () {
      it("should reject promise and execute failure callback with an invalid request", function (done) {
        var spySuccess = sinon.spy();
        var spyFailure = sinon.spy();
        var ret = api("error", spySuccess, spyFailure);
        ret.fail(spyFailure);
        ret.done(spySuccess);
        ret.always(function () {
          spySuccess.should.not.have.been.called;
          spyFailure.should.have.been.calledTwice;
          done();
        });
      });

      it("should resolve promise and execute success callback with a valid request", function (done) {
        var spySuccess = sinon.spy();
        var spyFailure = sinon.spy();
        var ret = api("success", spySuccess, spyFailure);
        ret.done(spySuccess);
        ret.fail(spyFailure);
        ret.always(function () {
          spySuccess.should.have.been.calledTwice;
          spyFailure.should.not.have.been.called;
          done();
        });
      });
    });

    describe('batching requests', function () {
      var spySuccess, spyFailure;
      beforeEach(function () {
        spySuccess = sinon.spy();
        spyFailure = sinon.spy();
      });

      it('should throw if more than 2 functions are defined', function () {
        batch.bind(undefined, function () {}, function () {}, function () {}).should.throw(Error);
      });
      it('should only accept Arrays apart from callback/errback', function () {
        batch.bind(undefined, 'url').should.throw(Error);
        batch.bind(undefined, {}).should.throw(Error);
        batch.bind(undefined, 123).should.throw(Error);
        batch.bind(undefined, null).should.throw(Error);
        batch.bind(undefined, true).should.throw(Error);
        batch.bind(undefined, []).should.not.throw(Error);
      });
      it('should execute callback when successful', function (done) {
        var ret = batch(['success'], spySuccess, spyFailure);
        ret.always(function () {
          spySuccess.should.have.been.called;
          spyFailure.should.not.have.been.called;
          done();
        });
      });
      it('should execute errback when failed', function (done) {
        var ret = batch(['error'], spySuccess, spyFailure);
        ret.always(function () {
          spySuccess.should.not.have.been.called;
          spyFailure.should.have.been.called;
          done();
        });
      });
      it('should execute callback when all requests succeed', function (done) {
        var ret = batch(['success'], ['success'], spySuccess, spyFailure);
        ret.always(function () {
          spySuccess.should.have.been.called;
          spyFailure.should.not.have.been.called;
          done();
        });
      });
      it('should execute errback when one request failed', function (done) {
        var ret = batch(['error'], ['success'], spySuccess, spyFailure);
        ret.always(function () {
          spySuccess.should.not.have.been.called;
          spyFailure.should.have.been.called;
          done();
        });
      });
      describe('individuals callbacks', function () {
        it('should call individual callbacks on success', function (done) {
          var spySuccess2 = sinon.spy(),
              spyFailure2 = sinon.spy(),
              ret = batch(['success', spySuccess, spyFailure], ['success', spySuccess2, spyFailure2]);
          ret.always(function () {
            spySuccess.should.have.been.called;
            spyFailure.should.not.have.been.called;
            spySuccess2.should.have.been.called;
            spyFailure2.should.not.have.been.called;
            done();
          });
        });
        it('should call individual errbacks on failure', function (done) {
          var ret = batch(['error', spySuccess, spyFailure]);
          ret.always(function () {
            spySuccess.should.not.have.been.called;
            spyFailure.should.have.been.called;
            done();
          });
        });
      });
    });

    describe('Models', function () {
      it("should be provided an id", function () {
        api.model.bind(api, 'anId').should.not.throw(Error);
        api.model.bind(api, {_id: 'anId'}).should.not.throw(Error);
        api.model.bind(api, {}).should.throw(Error);
      });

      it("should not be fetched", function () {
        var model = api.model(_.uniqueId());
        model._fetched.should.be.false;
      });
      it("should not provide an attached deferred to the model", function () {
        var model = api.model(_.uniqueId());
        expect(model.deferred).to.be.undefined;
      });

      it("should trigger the `sync` event when the model has been fetched", function (done) {
        var model = api.model(_.uniqueId());
        model.on('error', function (m) {
          m.should.be.equal(model);
          done();
        });
        model.on('sync', function (m) {
          m.should.be.equal(model);
          done();
        });
      });
    });

    describe('Tracking API', function () {
      it('proxies to the `track` provider', function () {
        var orig = env.core.data.api;
        var spy = env.core.data.api = sinon.spy();
        env.core.track('test');
        spy.should.have.been.called;
        spy.args[0][0].should.have.keys(['provider', 'path']);
        spy.args[0][0].provider.should.equal('track');
        spy.args[0][0].path.should.equal('test');
        spy.args[0][1].should.equal('post');
        env.core.data.api = orig;
      });
    });
  });
});

<commit_msg>+ specs<commit_after>/*global define:true */
define(['spec/support/spec_helper', 'aura/aura', 'components/underscore/underscore'], function (helper, aura) {

  "use strict";
  /*jshint devel: true, browser: true */
  /*global describe:true, it:true, before: true, sinon: true, define: true */

  var delay = function () {
    var args = arguments;
    var slice = Array.prototype.slice;
    setTimeout(function () {
      var argsArray = slice.call(args);
      argsArray.shift().apply(null, argsArray);
    }, parseInt(Math.random() * 2, 10));
  };

  var config = {
    appId: "fakeId",
    orgUrl: "orgUrl"
  };
  var mustFail = false;

  var easyXDMMock = {
    Rpc: function (a1, a2) {
      if (mustFail) {
        delay(a2.local.message.bind(this), {error:"Fail"});
      } else {
        delay(a2.local.ready, {services:{types:{auth:[]}},data: {me: {name: "test"}, app: {name: "test", org: {name: "test"}}}});
      }
      mustFail = false;
    }
  };

  easyXDMMock.Rpc.prototype.message = function (conf, successCb, errorCb) {
    var cb;
    if (conf.error) {
      cb = errorCb;
      conf = conf.error;
    } else if (conf.path.indexOf('error') === 0) {
      cb = errorCb;
    } else {
      cb = successCb;
    }
    delay(cb, { response: conf, headers: {} }, {});
  };

  define('easyXDM', function () { return easyXDMMock; });

  describe("API specs", function () {
    var env, api, batch, app = aura(config);

    var extension = {
      initialize: function (appEnv) {
        env = appEnv;
        app.core = app.core || {};
        app.core.mvc = window.Backbone;
      }
    };

    app
      .use(extension)
      .use('lib/client/api');

    before(function(done) {
      app.start().then(function () {
        api = app.sandboxes.create().data.api;
        batch = api.batch;
        done();
      });
    });

    describe('API exposition', function () {
      it('should be available in the environment', function () {
        env.sandbox.data.should.contain.keys('api');
        api.should.be.a('function');
      });

      it('should have method-specific functions', function () {
        env.sandbox.data.api.should.contain.keys('get', 'post', 'put', 'delete');
        env.sandbox.data.api.get.should.be.a('function');
        env.sandbox.data.api.put.should.be.a('function');
        env.sandbox.data.api.post.should.be.a('function');
        env.sandbox.data.api.delete.should.be.a('function');
      });
    });

    xdescribe("initializing the API client", function () {
      it("should reject if there's an error", function (done) {
        require(['lib/api'], function (apiClient) {
          mustFail = true;
          apiClient({appId: "please", orgUrl: "fail"}).then(
            function () {done(new Error('Error'));},
            function () {done();}
          );
        });
      });
    });

    xdescribe("Basic API requests", function () {
      it("should reject promise and execute failure callback with an invalid request", function (done) {
        var spySuccess = sinon.spy();
        var spyFailure = sinon.spy();
        var ret = api("error", spySuccess, spyFailure);
        ret.fail(spyFailure);
        ret.done(spySuccess);
        ret.always(function () {
          spySuccess.should.not.have.been.called;
          spyFailure.should.have.been.calledTwice;
          done();
        });
      });

      it("should resolve promise and execute success callback with a valid request", function (done) {
        var spySuccess = sinon.spy();
        var spyFailure = sinon.spy();
        var ret = api("success", spySuccess, spyFailure);
        ret.done(spySuccess);
        ret.fail(spyFailure);
        ret.always(function () {
          spySuccess.should.have.been.calledTwice;
          spyFailure.should.not.have.been.called;
          done();
        });
      });
    });

    xdescribe('batching requests', function () {
      var spySuccess, spyFailure;
      beforeEach(function () {
        spySuccess = sinon.spy();
        spyFailure = sinon.spy();
      });

      it('should throw if more than 2 functions are defined', function () {
        batch.bind(undefined, function () {}, function () {}, function () {}).should.throw(Error);
      });
      it('should only accept Arrays apart from callback/errback', function () {
        batch.bind(undefined, 'url').should.throw(Error);
        batch.bind(undefined, {}).should.throw(Error);
        batch.bind(undefined, 123).should.throw(Error);
        batch.bind(undefined, null).should.throw(Error);
        batch.bind(undefined, true).should.throw(Error);
        batch.bind(undefined, []).should.not.throw(Error);
      });
      it('should execute callback when successful', function (done) {
        var ret = batch(['success'], spySuccess, spyFailure);
        ret.always(function () {
          spySuccess.should.have.been.called;
          spyFailure.should.not.have.been.called;
          done();
        });
      });
      it('should execute errback when failed', function (done) {
        var ret = batch(['error'], spySuccess, spyFailure);
        ret.always(function () {
          spySuccess.should.not.have.been.called;
          spyFailure.should.have.been.called;
          done();
        });
      });
      it('should execute callback when all requests succeed', function (done) {
        var ret = batch(['success'], ['success'], spySuccess, spyFailure);
        ret.always(function () {
          spySuccess.should.have.been.called;
          spyFailure.should.not.have.been.called;
          done();
        });
      });
      it('should execute errback when one request failed', function (done) {
        var ret = batch(['error'], ['success'], spySuccess, spyFailure);
        ret.always(function () {
          spySuccess.should.not.have.been.called;
          spyFailure.should.have.been.called;
          done();
        });
      });
      describe('individuals callbacks', function () {
        it('should call individual callbacks on success', function (done) {
          var spySuccess2 = sinon.spy(),
              spyFailure2 = sinon.spy(),
              ret = batch(['success', spySuccess, spyFailure], ['success', spySuccess2, spyFailure2]);
          ret.always(function () {
            spySuccess.should.have.been.called;
            spyFailure.should.not.have.been.called;
            spySuccess2.should.have.been.called;
            spyFailure2.should.not.have.been.called;
            done();
          });
        });
        it('should call individual errbacks on failure', function (done) {
          var ret = batch(['error', spySuccess, spyFailure]);
          ret.always(function () {
            spySuccess.should.not.have.been.called;
            spyFailure.should.have.been.called;
            done();
          });
        });
      });
    });

    xdescribe('Models', function () {
      it("should be provided an id", function () {
        api.model.bind(api, 'anId').should.not.throw(Error);
        api.model.bind(api, {_id: 'anId'}).should.not.throw(Error);
        api.model.bind(api, {}).should.throw(Error);
      });

      it("should not be fetched", function () {
        var model = api.model(_.uniqueId());
        model._fetched.should.be.false;
      });
      it("should not provide an attached deferred to the model", function () {
        var model = api.model(_.uniqueId());
        expect(model.deferred).to.be.undefined;
      });

      it("should trigger the `sync` event when the model has been fetched", function (done) {
        var model = api.model(_.uniqueId());
        model.on('error', function (m) {
          m.should.be.equal(model);
          done();
        });
        model.on('sync', function (m) {
          m.should.be.equal(model);
          done();
        });
      });
    });

    xdescribe('Tracking API', function () {
      it('proxies to the `track` provider', function () {
        var orig = env.core.data.api;
        var spy = env.core.data.api = sinon.spy();
        env.core.track('test');
        spy.should.have.been.called;
        spy.args[0][0].should.have.keys(['provider', 'path']);
        spy.args[0][0].provider.should.equal('track');
        spy.args[0][0].path.should.equal('test');
        spy.args[0][1].should.equal('post');
        env.core.data.api = orig;
      });
    });
  });
});

<|endoftext|>


<commit_before>/**
 * @flow
 */

import { register } from '../accounts';

export default (program: any) => {
  program
    .command('signup')
    .description('Sign up for a new Exponent account')
    .option('--github', 'Sign up with a GitHub account')
    .asyncAction(register);
};
<commit_msg>Update help text for register command.<commit_after>/**
 * @flow
 */

import { register } from '../accounts';

export default (program: any) => {
  program
    .command('register')
    .description('Sign up for a new Exponent account')
    .option('--github', 'Sign up with a GitHub account')
    .asyncAction(register);
};
<|endoftext|>


<commit_before>import { merge as mergeFactory } from '../services/merge';
import { compile } from '../services/path';
import { getType, resolveType } from '../services/convert';
import { TextColumnModel } from '../column-type/text.column';
import { assignWith, isUndefined, noop, startCase } from '../utility/kit';
import { columnFactory } from '../column/column.factory';
import { AppError } from '../infrastructure/error';

function merge(left, right, force = false) {
	let canAssign;
	if (force) {
		canAssign = (source, target) => !isUndefined(target) && target !== null ? target : source;
	}
	else {
		canAssign = (source, target) => !isUndefined(target) && target !== null && source === null ? target : source;
	}

	const doMerge = mergeFactory({
		equals: (l, r) => l.key === r.key,
		update: (l, r) => assignWith(l, r, canAssign),
		insert: (r, left) => left.push(r),
		remove: noop
	});

	return doMerge(left, right);
}

function hasChanges(statistics) {
	return statistics.some(st => st.inserted || st.update);
}

export function generateFactory(model) {
	const data = model.data;
	const createColumn = columnFactory(model);
	return () => {
		const { rows } = data();
		const htmlColumns = model.columnList().columns;

		const spawnColumns = [];
		const { generation } = model.columnList();
		if (generation) {
			let settings = {
				rows,
				columnFactory: createColumn,
				deep: false,
				cohort: false
			};

			switch (generation) {
				case 'shallow': {
					break;
				}
				case 'deep': {
					settings.deep = true;
					break;
				}
				case 'cohort': {
					settings.deep = true;
					settings.cohort = true;
					break;
				}
				default:
					throw new AppError(
						'column.list.generate',
						`Invalid generation mode "${generation}"`
					);
			}

			spawnColumns.push(...generate(settings));
		}

		const columns = Array.from(data().columns);

		const statistics = [];
		if (spawnColumns.length) {
			statistics.push(merge(columns, spawnColumns, false));
		}

		if (htmlColumns.length) {
			statistics.push(merge(columns, htmlColumns, true));
		}

		return {
			columns,
			statistics,
			hasChanges: hasChanges(statistics)
		};
	};
}

export function generate(settings) {
	const context = assignWith({
		deep: true,
		cohort: false,
		rows: [],
		columnFactory: () => new TextColumnModel(),
		title: startCase,
		testNumber: 10
	}, settings);

	if (context.rows.length) {
		return build(
			context.rows[0],
			[],
			context.columnFactory,
			context.deep,
			context.cohort,
			context.title,
			context.rows.slice(0, context.testNumber)
		);
	}

	return [];
}

function build(graph, pathParts, columnFactory, deep, cohort, title, rows) {
	const props = Object.getOwnPropertyNames(graph);
	return props.reduce((memo, prop) => {
		const propParts = [...pathParts, prop];
		const propValue = compile(propParts);
		const propPath = propParts.join('.');

		const subject = graph[prop];
		const type = resolveType(rows.map(propValue));

		switch (type) {
			case 'array': {
				const column = columnFactory(type).model;
				column.key = propPath;
				column.title = title(propPath, graph, column.length);
				column.value = propValue;
				column.source = 'generation';
				if (subject.length) {
					column.itemType = getType(subject[0]);
					switch (column.itemType) {
						case 'date': {
							column.itemFormat = columnFactory('date').model.format;
							break;
						}
					}
				}

				memo.push(column);
				break;
			}
			case 'collection': {
				break;
			}
			case 'object': {
				if (deep) {
					const columns = build(
						subject,
						propParts,
						columnFactory,
						deep,
						cohort,
						title,
						rows
					);

					if (cohort) {
						const column = columnFactory('cohort').model;
						column.key = propPath;
						column.title = title(propPath, graph, column.length);
						column.value = propValue;
						column.source = 'generation';
						column.children.push(...columns);
						memo.push(column);
					} else {
						memo.push(...columns);
					}

				}
				break;
			}
			default: {
				const column = columnFactory(type).model;
				column.key = propPath;
				column.title = title(propPath, graph, column.length);
				column.value = propValue;
				column.source = 'generation';
				memo.push(column);
				break;
			}
		}

		return memo;
	}, []);
}
<commit_msg>rename rows to testRows<commit_after>import { merge as mergeFactory } from '../services/merge';
import { compile } from '../services/path';
import { getType, resolveType } from '../services/convert';
import { TextColumnModel } from '../column-type/text.column';
import { assignWith, isUndefined, noop, startCase } from '../utility/kit';
import { columnFactory } from '../column/column.factory';
import { AppError } from '../infrastructure/error';

function merge(left, right, force = false) {
	let canAssign;
	if (force) {
		canAssign = (source, target) => !isUndefined(target) && target !== null ? target : source;
	}
	else {
		canAssign = (source, target) => !isUndefined(target) && target !== null && source === null ? target : source;
	}

	const doMerge = mergeFactory({
		equals: (l, r) => l.key === r.key,
		update: (l, r) => assignWith(l, r, canAssign),
		insert: (r, left) => left.push(r),
		remove: noop
	});

	return doMerge(left, right);
}

function hasChanges(statistics) {
	return statistics.some(st => st.inserted || st.update);
}

export function generateFactory(model) {
	const data = model.data;
	const createColumn = columnFactory(model);
	return () => {
		const { rows } = data();
		const htmlColumns = model.columnList().columns;

		const spawnColumns = [];
		const { generation } = model.columnList();
		if (generation) {
			let settings = {
				rows,
				columnFactory: createColumn,
				deep: false,
				cohort: false
			};

			switch (generation) {
				case 'shallow': {
					break;
				}
				case 'deep': {
					settings.deep = true;
					break;
				}
				case 'cohort': {
					settings.deep = true;
					settings.cohort = true;
					break;
				}
				default:
					throw new AppError(
						'column.list.generate',
						`Invalid generation mode "${generation}"`
					);
			}

			spawnColumns.push(...generate(settings));
		}

		const columns = Array.from(data().columns);

		const statistics = [];
		if (spawnColumns.length) {
			statistics.push(merge(columns, spawnColumns, false));
		}

		if (htmlColumns.length) {
			statistics.push(merge(columns, htmlColumns, true));
		}

		return {
			columns,
			statistics,
			hasChanges: hasChanges(statistics)
		};
	};
}

export function generate(settings) {
	const context = assignWith({
		deep: true,
		cohort: false,
		rows: [],
		columnFactory: () => new TextColumnModel(),
		title: startCase,
		testNumber: 10
	}, settings);

	if (context.rows.length) {
		return build(
			context.rows[0],
			[],
			context.columnFactory,
			context.deep,
			context.cohort,
			context.title,
			context.rows.slice(0, context.testNumber)
		);
	}

	return [];
}

function build(graph, pathParts, columnFactory, deep, cohort, title, testRows) {
	const props = Object.getOwnPropertyNames(graph);
	return props.reduce((memo, prop) => {
		const propParts = [...pathParts, prop];
		const propValue = compile(propParts);
		const propPath = propParts.join('.');

		const subject = graph[prop];
		const type = resolveType(testRows.map(propValue));

		switch (type) {
			case 'array': {
				const column = columnFactory(type).model;
				column.key = propPath;
				column.title = title(propPath, graph, column.length);
				column.value = propValue;
				column.source = 'generation';
				if (subject.length) {
					column.itemType = getType(subject[0]);
					switch (column.itemType) {
						case 'date': {
							column.itemFormat = columnFactory('date').model.format;
							break;
						}
					}
				}

				memo.push(column);
				break;
			}
			case 'collection': {
				break;
			}
			case 'object': {
				if (deep) {
					const columns = build(
						subject,
						propParts,
						columnFactory,
						deep,
						cohort,
						title,
						testRows
					);

					if (cohort) {
						const column = columnFactory('cohort').model;
						column.key = propPath;
						column.title = title(propPath, graph, column.length);
						column.value = propValue;
						column.source = 'generation';
						column.children.push(...columns);
						memo.push(column);
					} else {
						memo.push(...columns);
					}

				}
				break;
			}
			default: {
				const column = columnFactory(type).model;
				column.key = propPath;
				column.title = title(propPath, graph, column.length);
				column.value = propValue;
				column.source = 'generation';
				memo.push(column);
				break;
			}
		}

		return memo;
	}, []);
}
<|endoftext|>