import fs from 'fs'

import { promisifyAll } from 'bluebird'
import ejs from 'ejs'
import _ from 'lodash'
import nodemailer from 'nodemailer'
import logger from 'winston'

import { load as configLoader } from '../config/config'

promisifyAll(fs)
const config = configLoader()

export default class Mailer {
  static formatUsername(name, email) {
    return `${name} <${email}>`
  }

  static async sendMail(recipient, subject, locals, file) {
    const template = await fs.readFileAsync(file, 'utf8')

    locals.config = config
    const plainText = ejs.render(template, locals)

    const message = {
      to:      Mailer.formatUsername(recipient.screenName, recipient.email),
      subject: _.truncate(subject, 50),
      text:    plainText,
      from:    Mailer.formatUsername(config.mailer.fromName, config.mailer.fromEmail),
      headers: { 'X-Laziness-level': 1000 }
    }

    logger.info(`Sending Mail to ${recipient.email}…`)

    const transporter = nodemailer.createTransport(config.mailer.transport(config.mailer.options))

    try {
      await transporter.sendMail(message)
      logger.info('Message sent successfully.')
    } catch (e) {
      logger.info('Error occured!')
      logger.info(e.message)
    } finally {
      transporter.close()
    }
  }
}

import fs from 'fs'

import { promisifyAll } from 'bluebird'
import ejs from 'ejs'
import _ from 'lodash'
import nodemailer from 'nodemailer'
import logger from 'winston'

import { load as configLoader } from '../config/config'

promisifyAll(fs)
const config = configLoader()

export default class Mailer {
  static formatUsername(name, email) {
    return `${name} <${email}>`
  }

  static async sendMail(recipient, subject, locals, file, sendAsHtml = false) {
    const template = await fs.readFileAsync(file, 'utf8')

    locals.config = config
    const plainText = ejs.render(template, locals)

    const message = {
      to:      Mailer.formatUsername(recipient.screenName, recipient.email),
      subject: _.truncate(subject, 50),
      text:    plainText,
      from:    Mailer.formatUsername(config.mailer.fromName, config.mailer.fromEmail),
      headers: { 'X-Laziness-level': 1000 }
    }

    if (sendAsHtml) {
      delete message.text;
      message.html = plainText;
    }

    logger.info(`Sending Mail to ${recipient.email}…`)

    const transporter = nodemailer.createTransport(config.mailer.transport(config.mailer.options))

    try {
      await transporter.sendMail(message)
      logger.info('Message sent successfully.')
    } catch (e) {
      logger.info('Error occured!')
      logger.info(e.message)
    } finally {
      transporter.close()
    }
  }
}

Implement ability to send html emails.


var endGame = function(game){
  if ( missBall(game.ball, game.player)){
    if (game.player.lives == 0) {
      console.log('game over!')
      alert('Game Over!');
    }
  }
}

var missBall = function(ball, player){
  if ( ball.bottomEdge().y == canvas.height){
    return player.loseLife()
  }
  return false
}

var endGame = function(game){

  if (game.player.lives > 0) {
    missBall(game.ball, game.player)
  } else if (game.player.lives <= 0 ){
    console.log('game over!')
    alert('Game Over!');
  }
}

var missBall = function(ball, player){
  if ( ball.bottomEdge().y == canvas.height){
    return player.loseLife()
  }
  return false
}

Fix bug where alert wouldn't fire if lives equaled zero


import * as React from "react";
import {BuildSnapshot} from "./BuildSnapshot";

/**
 * Container Component for BuildSnapshots.
 *
 * @param {*} props input property containing an array of build data to be
 * rendered through BuildSnapshot.
 */
export const BuildSnapshotContainer = React.memo((props) =>
  {
    // Number of entries to be pulled.
    const NUMBER = 2;

    // The starting point from which the entries are selected.
    // Relative to the starting index.
    const OFFSET = 0;

    const SOURCE = `/builders/number=${NUMBER}/offset=${OFFSET}`;

    const [data, setData] = React.useState([]);

    /**
     * Fetch data when component is mounted.
     * Pass in empty array as second paramater to prevent
     * infinite callbacks as component refreshes.
     * @see Fetching
     */
    React.useEffect(() => {
      fetch(SOURCE)
        .then(res => res.json())
        .then(json => setData(json))
        .catch(setData([]));
    }, []);

    return (

        {data.map(snapshotData => <BuildSnapshot buildData={snapshotData}/>)}

    );
  }
);

import * as React from "react";
import {BuildSnapshot} from "./BuildSnapshot";

/**
 * Container Component for BuildSnapshots.
 *
 * @param {*} props input property containing an array of build data to be
 * rendered through BuildSnapshot.
 */
export const BuildSnapshotContainer = React.memo((props) =>
  {
    // Number of entries to be pulled.
    const NUMBER = 2;

    // The starting point from which the entries are selected.
    // Relative to the starting index.
    const OFFSET = NUMBER * 0;

    const SOURCE = `/builders/number=${NUMBER}/offset=${OFFSET}`;

    const [data, setData] = React.useState([]);

    /**
     * Fetch data when component is mounted.
     * Pass in empty array as second paramater to prevent
     * infinite callbacks as component refreshes.
     * @see Fetching
     */
    React.useEffect(() => {
      fetch(SOURCE)
        .then(res => res.json())
        .then(json => setData(json))
        .catch(setData([]));
    }, []);

    return (

        {data.map(snapshotData => <BuildSnapshot buildData={snapshotData}/>)}

    );
  }
);

Set offset to multiple of NUMBER


module.exports = function(gulp, config) {

	return function(done) {
		var c = require('better-console')
		c.info('watch reload')

		var browsersync = require('browser-sync')
		var fs = require('fs')
		var watch = require('gulp-watch')
		var filter = require('gulp-filter')

		var options = {}

		if(config.proxy) {
			options.proxy = config.proxy
		} else {
			options.server = { baseDir: config.dist_folder }
		}

		browsersync(options)

		var glob = [config.dist_folder + '/**/*.*']
		if(config.watch) glob.concat(config.watch)

		var handleReloadFile = function(file) {
			// files only
			if(!fs.existsSync(file.path) || !fs.lstatSync(file.path).isFile()) {
				return false
			}
			c.log('- handling reload', file.path)
			browsersync.reload(file.path)
		}

		return watch(glob, { base: config.dir }).pipe(filter(handleReloadFile))
	}
}

module.exports = function(gulp, config) {

	return function(done) {
		var c = require('better-console')
		c.info('watch reload')

		var browsersync = require('browser-sync')
		var fs = require('fs')
		var watch = require('gulp-watch')
		var filter = require('gulp-filter')

		var options = {}

		if(config.proxy) {
			options.proxy = config.proxy
		} else {
			options.server = { baseDir: config.dist_folder }
		}

		browsersync(options)

		var glob = [config.dist_folder + '/**/*.*']
		if(config.watch) {
			glob = glob.concat(config.watch)
		}

		var handleReloadFile = function(file) {
			// files only
			if(!fs.existsSync(file.path) || !fs.lstatSync(file.path).isFile()) {
				return false
			}
			c.log('- handling reload', file.path)
			browsersync.reload(file.path)
		}

		return watch(glob, { base: config.dir }).pipe(filter(handleReloadFile))
	}
}

Fix concating watch glob masks


//= require libs/jquery/jquery-ui-1.8.16.custom.min
//= require libs/jquery/plugins/jquery.base64
//= require libs/jquery/plugins/jquery.mustache.js
//= require core
//= require devolution
//= require popup
//= require geo-locator
//= require welcome
//= require browse
//= require search
//= require jquery.history
//= require jquery.tabs
//= require mobile
//= require govuk_toolkit

//= require libs/jquery/jquery-ui-1.8.16.custom.min
//= require libs/jquery/plugins/jquery.base64
//= require libs/jquery/plugins/jquery.mustache.js
//= require core
//= require devolution
//= require popup
//= require geo-locator
//= require welcome
//= require browse
//= require search
//= require jquery.history
//= require jquery.tabs
//= require mobile
//= require stop-related-scrolling

Revert to local related scrolling js


'use strict';

const chai = require('chai');
const execa = require('execa');
const path = require('path');

const busted = /v4|v6/.test(process.version) ? '--old-and-busted' : '';
const cli = (args, opts) => {
  const cliPath = path.join(cwd, 'cli');
  const params = [cliPath].concat(args, busted);

  return execa(process.execPath, params, opts);
};
const cwd = path.dirname(__dirname);
const expect = chai.expect;
const pkg = require(path.join(cwd, 'package.json'));

describe('mocha-chrome binary', () => {

  it('should report version', async () => {
    const { stdout } = await cli(['--version'], {cwd});

    expect(stdout).to.equal(pkg.version);
  });

  it('should run a successful test', async () => {
    const { code } = await cli(['test/html/test.html'], {cwd});
    expect(code).to.equal(0);
  });

  it('should run a failing test', (done) => {
    cli(['test/html/fail.html'], {cwd}).catch(err => {
      expect(err.code).to.equal(1);
      expect(err.stdout).to.match(/1 failing/);
      done();
    });
  });

});

'use strict';

const chai = require('chai');
const execa = require('execa');
const path = require('path');

const busted = /v4|v6/.test(process.version) ? '--old-and-busted' : '';
const cli = (args, opts) => {
  const cliPath = path.join(cwd, 'cli');
  const params = [cliPath].concat(args, busted);

  return execa(process.execPath, params, opts);
};
const cwd = path.dirname(__dirname);
const expect = chai.expect;
const pkg = require(path.join(cwd, 'package.json'));

describe('mocha-chrome binary', () => {

  it('should report version', async () => {
    const { stdout } = await cli(['--version'], {cwd});

    expect(stdout).to.equal(pkg.version);
  });

  it('should run a successful test', async () => {
    const { code } = await cli(['test/html/test.html'], {cwd});
    expect(code).to.equal(0);
  });

  it('should run a failing test', (done) => {
    cli(['test/html/fail.html'], {cwd}).catch(err => {
      expect(err.code).to.equal(1);
      expect(err.stdout).to.match(/1 failing/);
      done();
    });
  });

  it('should default to "spec" reporter', async () => {
    const { code, stdout } = await cli(['test/html/test.html'], {cwd});
    expect(stdout).to.match(/✓/);
  });

  it('should honor --spec parameter', async () => {
    const { code, stdout } = await cli(['--reporter', 'tap', 'test/html/test.html'], {cwd});
    expect(stdout).to.match(/ok/);
    expect(stdout).not.to.match(/✓/);
  });
});

Implement failing test for reporter parameter


// @flow

import { type ThreadInfo, threadInfoPropType } from 'lib/types/thread-types';
import type { MarkdownRules } from '../markdown/rules.react';

import PropTypes from 'prop-types';
import * as React from 'react';

export type MessageListParams = {|
  threadInfo: ThreadInfo,
|};

export const messageListRoutePropType = PropTypes.shape({
  key: PropTypes.string.isRequired,
  params: PropTypes.shape({
    threadInfo: threadInfoPropType.isRequired,
  }).isRequired,
});

export const messageListNavPropType = PropTypes.shape({
  navigate: PropTypes.func.isRequired,
  setParams: PropTypes.func.isRequired,
  setOptions: PropTypes.func.isRequired,
  dangerouslyGetParent: PropTypes.func.isRequired,
  isFocused: PropTypes.func.isRequired,
  popToTop: PropTypes.func.isRequired,
});

export type MessageListContextType = {|
  +getTextMessageMarkdownRules: (useDarkStyle: boolean) => MarkdownRules,
|};

export const MessageListContext = React.createContext<?MessageListContextType>();

// @flow

import { type ThreadInfo, threadInfoPropType } from 'lib/types/thread-types';
import type { MarkdownRules } from '../markdown/rules.react';
import { type UserInfo, userInfoPropType } from 'lib/types/user-types';

import PropTypes from 'prop-types';
import * as React from 'react';

export type MessageListParams = {|
  threadInfo: ThreadInfo,
  pendingPersonalThreadUserInfo?: UserInfo,
|};

export const messageListRoutePropType = PropTypes.shape({
  key: PropTypes.string.isRequired,
  params: PropTypes.shape({
    threadInfo: threadInfoPropType.isRequired,
    pendingPersonalThreadUserInfo: userInfoPropType,
  }).isRequired,
});

export const messageListNavPropType = PropTypes.shape({
  navigate: PropTypes.func.isRequired,
  setParams: PropTypes.func.isRequired,
  setOptions: PropTypes.func.isRequired,
  dangerouslyGetParent: PropTypes.func.isRequired,
  isFocused: PropTypes.func.isRequired,
  popToTop: PropTypes.func.isRequired,
});

export type MessageListContextType = {|
  +getTextMessageMarkdownRules: (useDarkStyle: boolean) => MarkdownRules,
|};

export const MessageListContext = React.createContext<?MessageListContextType>();

Add optional user info parameter to message list nav params


'use strict';

const linz = require('../');
const path = require('path');

/**
 * GET /admin
 * Admin route redirect.
 * @param  {Object} req Express Request object.
 * @param  {Object} res Express Response object.
 * @return {Void}       Redirects to the admin home.
 */
const route = (req, res) => {
	return res.redirect(307, path.join(linz.get('admin path'), linz.get('admin home')));
};

module.exports = route;

'use strict';

const linz = require('../');
const path = require('path');

/**
 * GET /admin
 * Admin route redirect.
 * @param  {Object} req Express Request object.
 * @param  {Object} res Express Response object.
 * @return {Void}       Redirects to the admin home.
 */
const route = (req, res) => {
	return res.redirect(307, path.posix.resolve(linz.get('admin path'), linz.get('admin home')));
};

module.exports = route;

Use a better path method


/**
 * Nanocloud turns any traditional software into a cloud solution, without
 * changing or redeveloping existing source code.
 *
 * Copyright (C) 2016 Nanocloud Software
 *
 * This file is part of Nanocloud.
 *
 * Nanocloud is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Nanocloud is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General
 * Public License
 * along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

import Ember from 'ember';

export default Ember.Route.extend({
  session: Ember.inject.service(),
  model() {
    return Ember.RSVP.hash({
      groups: this.store.findAll('group', { reload: true })
    });
  },
  beforeModel() {
    if (!this.get('session.user.isAdmin')) {
      this.toast.error('Permission denied');
      this.transitionTo('protected.dashboard');
    }
  }
});

/**
 * Nanocloud turns any traditional software into a cloud solution, without
 * changing or redeveloping existing source code.
 *
 * Copyright (C) 2016 Nanocloud Software
 *
 * This file is part of Nanocloud.
 *
 * Nanocloud is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Nanocloud is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General
 * Public License
 * along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

import Ember from 'ember';

export default Ember.Route.extend({
  session: Ember.inject.service(),
  model() {
    return Ember.RSVP.hash({
      groups: this.store.findAll('group', { reload: true })
    });
  },
  beforeModel() {
    if (!this.get('session').get('user').get('isAdmin')) {
      this.toast.error('Permission denied');
      this.transitionTo('protected.dashboard');
    }
  }
});

Fix Permission denied on Configuration tab for Administrator


'use strict';
/* globals window */

angular.module('vleApp', ['zeroclipboard'])
  .constant('_', window._)
  .constant('vl', window.vl)
  .constant('vg', window.vg)
  .constant('Papa', window.Papa)
  .config(['uiZeroclipConfigProvider', function(uiZeroclipConfigProvider) {

    // config ZeroClipboard
    uiZeroclipConfigProvider.setZcConf({
      swfPath: '../bower_components/zeroclipboard/dist/ZeroClipboard.swf'
    });
  }])

angular.module('facetedviz', ['vleApp', 'ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ui.router'])
  .constant('_', window._)
  .constant('jQuery', window.$)
  .constant('vl', window.vl)
  .constant('vg', window.vg)
  .constant('vr', window.vr)
  .constant('tv4', window.tv4)
  .constant('Papa', window.Papa)
  .config(function ($stateProvider, $urlRouterProvider) {
    $stateProvider
      .state('home', {
        url: '/',
        templateUrl: 'app/main/main.html',
        controller: 'MainCtrl'
      });

    $urlRouterProvider.otherwise('/');
  })
;

'use strict';
/* globals window */

angular.module('vleApp', ['zeroclipboard'])
  .constant('_', window._)
  .constant('vl', window.vl)
  .constant('vg', window.vg)
  .constant('Papa', window.Papa)
  .config(['uiZeroclipConfigProvider', function(uiZeroclipConfigProvider) {

    // config ZeroClipboard
    uiZeroclipConfigProvider.setZcConf({
      swfPath: 'bower_components/zeroclipboard/dist/ZeroClipboard.swf'
    });
  }]);

angular.module('facetedviz', ['vleApp', 'ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ui.router'])
  .constant('_', window._)
  .constant('jQuery', window.$)
  .constant('vl', window.vl)
  .constant('vg', window.vg)
  .constant('vr', window.vr)
  .constant('tv4', window.tv4)
  .constant('Papa', window.Papa)
  .config(function ($stateProvider, $urlRouterProvider) {
    $stateProvider
      .state('home', {
        url: '/',
        templateUrl: 'app/main/main.html',
        controller: 'MainCtrl'
      });

    $urlRouterProvider.otherwise('/');
  })
;

Fix ZeroClipboard.swf's path in dist/gh-pages (Previously, it worked in local `gulp serve:list` but not on github page)


import neo4jDriver from 'neo4j-driver'
import neo4jDriverLite from 'neo4j-driver-lite'

const isLite = (process.env.TEST_DRIVER_LITE || 'False').toUpperCase() in ['TRUE', '1']
const neo4j = isLite ? neo4jDriverLite : neo4jDriver

export default neo4j

import neo4jDriver from 'neo4j-driver'
import neo4jDriverLite from 'neo4j-driver-lite'

const isLite = ['TRUE', '1'].includes((process.env.TEST_DRIVER_LITE || 'False').toUpperCase())
const neo4j = isLite ? neo4jDriverLite : neo4jDriver

export default neo4j

Fix driver selection in the testkit-backend


(function() {

    angular
        .module('crowbarApp.upgrade')
        .constant('ADDONS_PRECHECK_MAP', {
            'ha': ['clusters_healthy'],
            'ceph': ['ceph_healthy']
        })
        .constant('UNEXPECTED_ERROR_DATA', {
            title: 'unexpected_error',
            errors: {
                unexpected_error: {
                    data: 'An unexpected error ocurred',
                    help: 'Please check logs for more details'
                }
            }
        })
        .constant('UPGRADE_LAST_STATE_KEY', 'crowbar.upgrade.lastUIState')
        .constant('PREPARE_TIMEOUT_INTERVAL', 1000)
        .constant('ADMIN_UPGRADE_ALLOWED_DOWNTIME', 30 * 60 * 1000)
        .constant('ADMIN_UPGRADE_TIMEOUT_INTERVAL', 5000)
        .constant('NODES_UPGRADE_TIMEOUT_INTERVAL', 5000)
        .constant('STOP_OPENSTACK_SERVICES_TIMEOUT_INTERVAL', 5000)
        .constant('OPENSTACK_BACKUP_TIMEOUT_INTERVAL', 1000)
        .constant('UPGRADE_MODES', {
            nondisruptive: 'non-disruptive',
            disruptive: 'disruptive',
            none: 'none',
        });
})();

(function() {

    angular
        .module('crowbarApp.upgrade')
        .constant('ADDONS_PRECHECK_MAP', {
            'ha': ['clusters_healthy'],
            'ceph': ['ceph_healthy']
        })
        .constant('UNEXPECTED_ERROR_DATA', {
            title: 'unexpected_error',
            errors: {
                unexpected_error: {
                    data: 'An unexpected error ocurred',
                    help: 'Please check logs for more details'
                }
            }
        })
        .constant('UPGRADE_LAST_STATE_KEY', 'crowbar.upgrade.lastUIState')
        .constant('PREPARE_TIMEOUT_INTERVAL', 1000)
        .constant('ADMIN_UPGRADE_ALLOWED_DOWNTIME', 30 * 60 * 1000)
        .constant('ADMIN_UPGRADE_TIMEOUT_INTERVAL', 5000)
        .constant('NODES_UPGRADE_TIMEOUT_INTERVAL', 30 * 1000)
        .constant('STOP_OPENSTACK_SERVICES_TIMEOUT_INTERVAL', 5000)
        .constant('OPENSTACK_BACKUP_TIMEOUT_INTERVAL', 1000)
        .constant('UPGRADE_MODES', {
            nondisruptive: 'non-disruptive',
            disruptive: 'disruptive',
            none: 'none',
        });
})();

Increase the time before the status call during the nodes upgrade.


import React from 'react';
import { connect } from 'react-redux';

class BackButton extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.context.history.goBack();
  }

  render() {
    const divStyle = {
      backgroundColor: 'white',
      opacity: '0.7',
      width: '75px',
      position: 'fixed',
      zIndex: 99,
      left: '90px',
      bottom: '10px',
      height: '75px',
      textAlign: 'center',
      borderRadius: '50%',
      boxShadow: '4px 4px 4px rgba(204, 197, 185, 0.4)',
      cursor: 'pointer',
    };
    const arrowStyle = {
      marginTop: '10px',
      fontSize: '2em',
      color: '#7AC29A',
      position: 'relative',
      top: '20px',
    };
    return (

    );
  }
}

BackButton.contextTypes = {
  history: React.PropTypes.object,
};

export default connect()(BackButton);

import React from 'react';
import { connect } from 'react-redux';

class BackButton extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.context.history.goBack();
  }

  render() {
    const divStyle = {
      backgroundColor: 'white',
      opacity: '0.7',
      width: '75px',
      position: 'fixed',
      zIndex: 99,
      left: '90px',
      bottom: '10px',
      height: '75px',
      textAlign: 'center',
      borderRadius: '50%',
      boxShadow: '5px 5px 5px rgba(204, 197, 185, 0.7)',
      cursor: 'pointer',
    };
    const arrowStyle = {
      marginTop: '10px',
      fontSize: '2em',
      color: '#7AC29A',
      position: 'relative',
      top: '20px',
    };
    return (

    );
  }
}

BackButton.contextTypes = {
  history: React.PropTypes.object,
};

export default connect()(BackButton);

Make back button more visible


var request = require('request');
var fs = require('fs');
var spawn = require('child_process').spawn;

module.exports = {

	// Write routes object to router file
	writeRoutes: function(routes) {
		fs.writeFileSync('config/routes.js', 'module.exports.routes = ' + JSON.stringify(routes));
	},

	// Starts sails server, makes request, returns response, kills sails server
	testRoute: function(method, options, callback) {

		// Prefix url with domain:port
		if (typeof options === 'string') {
			options = 'http://localhost:1337/' + options;
		} else {
			options.url = 'http://localhost:1337/'  + options.url;
		}

		// Start the sails server process
		var sailsprocess = spawn('../bin/sails.js', ['lift']);

		sailsprocess.stderr.on('data',function(data) {
			// Change buffer to string
			var dataString = data + '';

			// Make request once server has sucessfully started
			if (dataString.indexOf('http://localhost:1337') !== -1) {

				request[method](options, function(err, response) {
					if (err) callback(err);

					// Kill server process and return response
					sailsprocess.kill();
					callback(null, response);
				});
			}
		});
	}
};

var request = require('request');
var fs = require('fs');
var spawn = require('child_process').spawn;

module.exports = {

	// Write routes object to router file
	writeRoutes: function(routes) {
		fs.writeFileSync('config/routes.js', 'module.exports.routes = ' + JSON.stringify(routes));
	},

	// Starts sails server, makes request, returns response, kills sails server
	testRoute: function(method, options, callback) {

		// Prefix url with domain:port
		if (typeof options === 'string') {
			options = 'http://localhost:1337/' + options;
		} else {
			options.url = 'http://localhost:1337/'  + options.url;
		}

		// Start the sails server process
		var sailsprocess = spawn('../bin/sails.js', ['lift']);

		sailsprocess.stderr.on('data',function(data) {
			// Change buffer to string
			var dataString = data + '';

			// Make request once server has sucessfully started
			if (dataString.match(/Sails \S+ lifted/)) {

				request[method](options, function(err, response) {
					if (err) callback(err);

					// Kill server process and return response
					sailsprocess.kill();
					callback(null, response);
				});
			}
		});
	}
};

Fix router test server helper to listen for new lifted log line


import {
  moduleForComponent,
  test
} from 'ember-qunit';

moduleForComponent('stripe-checkout', 'StripeCheckoutComponent', {
  // specify the other units that are required for this test
  // needs: ['component:foo', 'helper:bar']
});

test('it renders', function() {
  expect(2);

  // creates the component instance
  var component = this.subject();
  equal(component._state, 'preRender');

  // appends the component to the page
  this.append();
  equal(component._state, 'inDOM');
});

test('it renders default text', function() {
  expect(1);

  var component = this.subject();
  this.append();

  equal($.trim($('.stripe-checkout').text()), "Pay with card");
});

import {
  moduleForComponent,
  test
} from 'ember-qunit';

moduleForComponent('stripe-checkout', 'StripeCheckoutComponent', {
  // specify the other units that are required for this test
  // needs: ['component:foo', 'helper:bar']
});

test('it renders', function() {
  expect(2);

  // creates the component instance
  var component = this.subject();
  equal(component._state, 'preRender');

  // appends the component to the page
  this.append();
  equal(component._state, 'inDOM');
});

test('it renders with the default text', function() {
  expect(1);

  var component = this.subject();
  this.append();

  equal($.trim($('.stripe-checkout').text()), "Pay with card");
});

/**
 * These tests are stubbed out until I'm able to learn how to test Ember
 * addons properly - documentation is sparse right now and I'm quite new
 * to qunit.
 * At the very least, these test stubs should help describe expected behaviour
 */
test('it sends the opened action when the Stripe modal opens', function() {
  expect(0);
  // TODO
});

test('it sends the closed action when the Stripe modal closes', function() {
  expect(0);
  // TODO
});

test('it sends the primary action when a Stripe checkout completes', function() {
  expect(0);
  // TODO
});

test('it displays the configured values in the Stripe modal', function() {
  expect(0);
  // TODO: Use all component configurations and validate they display in the Stripe modal
});

Add stubbed out tests so at least we have them to implement later when


$(function(){
  var width = parseInt($('.product_backlog').css('width'));
  console.log(width);
  $('.product_backlog').resizable({
    containment: '.content',
    minHeight: minheight,
    minWidth: width,
    maxWidth: width
  });
  $('.story').draggable({
    containment: '.product_backlog',
    stop: function() {
      var left = parseInt($(this).css('left'));
      var top = parseInt($(this).css('top'));
      var id = $(this).attr('id');
      var numid = id.substr(6);

      $.ajax(setCoordinatesUrl, {
        data: {'id': numid, 'left': left, 'top': top}
      });
    }
  });
});

$(function(){
  var width = parseInt($('.product_backlog').css('width'));
  console.log(width);
  $('.product_backlog').resizable({
    containment: '.content',
    minHeight: minheight,
    minWidth: width,
    maxWidth: width
  });
  $('.story').draggable({
    containment: '.product_backlog',
    stack: '.story',
    stop: function() {
      var left = parseInt($(this).css('left'));
      var top = parseInt($(this).css('top'));
      var id = $(this).attr('id');
      var numid = id.substr(6);

      $.ajax(setCoordinatesUrl, {
        data: {'id': numid, 'left': left, 'top': top}
      });
    }
  });
});

Allow stories to be stacked (dealing with z-index) Maybe we should save the z-index as long as the position


import React, {Component, PropTypes} from 'react';
import styles from './DatabaseDropdown.css';
import Select from 'react-select';

/*
    Displays in a dropdown menu all available databases/schemes
    using `ipc`.
    Permits to alter the `configuration` database parameter once
    user selects an option.
    Sends a preset query to show all tables within a database/
    scheme that was chosen in the dropdown using `ipcActions`.
*/

export default class DatabaseDropdown extends Component {
    constructor(props) {
        super(props);
    }

	render() {
        const {configuration, ipc, ipcActions, merge} = this.props;

        const ipcDatabases = ipc.get('databases');
        let databaseDropdownOptions;
        if (ipcDatabases) {
            databaseDropdownOptions = ipcDatabases.toJS().map(database => (
                {value: database.Database, label: database.Database}
            ));
        } else {
            databaseDropdownOptions = [
                {value: 'None', label: 'None Found', disabled: true}
            ];
        }

        function onSelectDatabase(database) {
            merge({database: database.value});
            ipcActions.useDatabase();
        }

        return (
            <div className={styles.dropdown}>
                <Select
                    name="form-field-name"
                    placeholder="Select Your Database"
                    options={databaseDropdownOptions}
                    onValueClick={onSelectDatabase}
                    value={configuration.get('database')}
                    resetValue="null"
                    matchPos="start"
                />
            </div>
        );
    }
}

import React, {Component, PropTypes} from 'react';
import styles from './DatabaseDropdown.css';
import Select from 'react-select';

/*
    Displays in a dropdown menu all available databases/schemes
    using `ipc`.
    Permits to alter the `configuration` database parameter once
    user selects an option.
    Sends a preset query to show all tables within a database/
    scheme that was chosen in the dropdown using `ipcActions`.
*/

export default class DatabaseDropdown extends Component {
    constructor(props) {
        super(props);
    }

	render() {
        const {configuration, ipc, ipcActions, merge} = this.props;

        const ipcDatabases = ipc.get('databases');
        let databaseDropdownOptions;
        if (ipcDatabases) {
            databaseDropdownOptions = ipcDatabases.toJS().map(database => (
                {value: database.Database, label: database.Database}
            ));
        } else {
            databaseDropdownOptions = [
                {value: 'None', label: 'None Found', disabled: true}
            ];
        }

        function onSelectDatabase(database) {
            merge({database: database.value});
            ipcActions.selectDatabase();
        }

        return (
            <div className={styles.dropdown}>
                <Select
                    name="form-field-name"
                    placeholder="Select Your Database"
                    options={databaseDropdownOptions}
                    onChange={onSelectDatabase}
                    value={configuration.get('database')}
                    resetValue="null"
                    matchPos="start"
                />
            </div>
        );
    }
}

Fix a :bug: due to changes in ipcActions from previosly


import apiClient from 'panoptes-client/lib/api-client';
import { config } from 'constants/config';
import * as type from 'constants/actions';

function workflowsRequested() {
  return { type: type.WORKFLOWS_REQUESTED };
}

function workflowsReceived(json) {
  return { type: type.WORKFLOWS_RECEIVED, json };
}

export function fetchWorkflows() {
  const fields = 'active,classifications_count,completeness,' +
    'display_name,finished_at,retirement,subjects_count';
  let page = 1;
  return dispatch => {
    dispatch(workflowsRequested());
    apiClient.type('workflows').get({ project_id: config.projectId, fields, page })
      .then(json => {
        dispatch(workflowsReceived(json));
        for (page = 2; page <= json[0]._meta.workflows.page_count; ++page) {
          apiClient.type('workflows').get({ project_id: config.projectId, fields, page })
            .then(ws => dispatch(workflowsReceived(ws)));
        }
      });
  };
}

import apiClient from 'panoptes-client/lib/api-client';
import { config } from 'constants/config';
import * as type from 'constants/actions';

function workflowsRequested() {
  return { type: type.WORKFLOWS_REQUESTED };
}

function workflowsReceived(json) {
  return { type: type.WORKFLOWS_RECEIVED, json };
}

export function fetchWorkflows() {
  const fields = 'active,classifications_count,completeness,' +
    'display_name,finished_at,retirement,subjects_count,created_at';
  let page = 1;
  return dispatch => {
    dispatch(workflowsRequested());
    apiClient.type('workflows').get({ project_id: config.projectId, fields, page })
      .then(json => {
        dispatch(workflowsReceived(json));
        for (page = 2; page <= json[0]._meta.workflows.page_count; ++page) {
          apiClient.type('workflows').get({ project_id: config.projectId, fields, page })
            .then(ws => dispatch(workflowsReceived(ws)));
        }
      });
  };
}

Add created_at to what we get for a workflow


Package.describe({
  name: 'konecty:methods-middleware',
  summary: 'Add more power to Meteor.methods with methods-middleware',
  version: '1.2.0',
  git: 'https://github.com/Konecty/methods-middleware.git'
});

Package.onUse(function(api) {
  api.versionsFrom('1.0');
  api.use('coffeescript');
  api.use('underscore');
  api.use('nooitaf:colors@0.0.2');
  api.addFiles('konecty:methods-middleware.coffee', ['server']);
});

Package.onTest(function(api) {
  api.use('tinytest');
  api.use('konecty:methods-middleware');
  api.use('coffeescript');
  api.use('nooitaf:colors@0.0.2');
  api.addFiles('konecty:methods-middleware-tests.coffee');
});

Package.describe({
  name: 'konecty:methods-middleware',
  summary: 'Add more power to Meteor.methods with methods-middleware',
  version: '1.2.1',
  git: 'https://github.com/Konecty/methods-middleware.git'
});

Package.onUse(function(api) {
  api.versionsFrom('1.0');
  api.use('coffeescript');
  api.use('underscore');
  api.use('nooitaf:colors@1.1.2_1');
  api.addFiles('konecty:methods-middleware.coffee', ['server']);
});

Package.onTest(function(api) {
  api.use('tinytest');
  api.use('konecty:methods-middleware');
  api.use('coffeescript');
  api.use('nooitaf:colors@0.0.2');
  api.addFiles('konecty:methods-middleware-tests.coffee');
});

Update nooitaf:colors dependency to 1.1.2_1


'use strict';

const dateHelper = require(`${__dirname}/../helpers/dateHelper`);
const requestHelper = require(`${__dirname}/../helpers/requestHelper`);

exports.getRowRequest = (field, size, req, res) => {
  if (dateHelper.isValidDate(req.query.from) && dateHelper.isValidDate(req.query.to)) {
    requestHelper.makeAllRowRequest(field, size, req.query.from, req.query.to, res);
  } else {
    res.json({ success: false, message: 'Bad date formatting' });
  }
};

exports.getGameRequest = (type, req, res) => {
	requestHelper.makeGameRequest(type, req, res);
};

exports.getTodaysRequests = (req, res) => {
	requestHelper.makeTodayCountRequests(req, res);
};

exports.getTopTargets = (req, res) => {
	requestHelper.makeTopTargetRequests(req, res);
};

exports.getUniqueTargets = (req, res) => {
	requestHelper.makeUniqueTargetsRequests(req, res);
};

exports.getCurrentlyActiveConnections = (req, res) => {
	requestHelper.makeCurrentlyActiveConnectionRequests(req, res);
};

exports.getStatusCodeList = (req, res) => {
	requestHelper.makeStatusCodeList(req, res);
};

Add controller function for status codes


core.Class("lowland.base.Events", {
  implement : [core.property.IEvent],

  members : {
    fireEvent : function(type, value, old) {
      var events = core.Class.getEvents(this.constructor);
      var cls = events[type];
      /*if (core.Env.getValue("debug")) {
        if (!cls || !core.Class.isClass(cls)) {
          throw new Error("Class " + this.construct + " has no event " + type);
        }
      }*/
      if (!cls) {
        lowland.events.EventManager.fireEvent(this, type, cls, [value, old]);
      }
    },

    addListener : function(event, callback, context) {
      lowland.events.EventManager.addListener(this, event, callback, context);
    },

    removeListener : function(event, callback, context) {
      lowland.events.EventManager.removeListener(this, event, callback, context);
    },

    dispose : function() {
      lowland.events.EventManager.removeAllListener(this);
    }
  }
});

core.Class("lowland.base.Events", {
  implement : [core.property.IEvent],

  members : {
    fireEvent : function(type, value, old) {
      var events = core.Class.getEvents(this.constructor);
      var cls = events[type];
      /*if (core.Env.getValue("debug")) {
        if (!cls || !core.Class.isClass(cls)) {
          throw new Error("Class " + this.construct + " has no event " + type);
        }
      }*/
      if (!cls) {
        lowland.events.EventManager.fireEvent(this, type, cls, [value, old]);
      }
    },

    addListener : function(event, callback, context) {
      lowland.events.EventManager.addListener(this, event, callback, context);
    },

    addListenerOnce : function(event, callback, context) {
      var self = this;
      var cb = function() {
        lowland.events.EventManager.removeListener(self, event, cb, context);
        callback.apply(context, arguments);
      };

      lowland.events.EventManager.addListener(this, event, cb, context);
    },

    removeListener : function(event, callback, context) {
      lowland.events.EventManager.removeListener(this, event, callback, context);
    },

    hasListener : function(event, callback, context) {
      return lowland.events.EventManager.hasListener(this, event, callback, context);
    },

    dispose : function() {
      lowland.events.EventManager.removeAllListener(this);
    }
  }
});

Add addListenerOnce and hasListener to event model


// Requiring modules

module.exports = function (native) {
  var methods = require('./methods');

  if (native) {
    require('./native')(methods);
  }

  return methods
};

// Requiring modules

module.exports = function (attach) {
  var methods = require('./methods');

  if (attach) {
    require('./native')(methods);
  }

  return methods
};

Change 'native' variable to 'attach'


import { Ember } from 'ember';
import { request } from 'ic-ajax';

export default Ember.Object.extend({
  setPrefilter: function() {
    var token = this.get('data').token;
    var preFilter = function(options, originalOptions, jqXHR) {
      return jqXHR.setRequestHeader('X-CSRF-Token', token );
    };
    $.ajaxPrefilter(preFilter);
  },
  setData: function(data) {
    var param = Object.keys(data)[0];
    this.set('data', { param: param, token: data[param] });
    this.setPrefilter();
  },
  fetchToken: function() {
    var setToken = this.setData.bind(this);
    if (!this.get('data')) {
      return request('api/v1/csrf').then(setToken);
    }
  }
});

import Ember from 'ember';
import { request } from 'ic-ajax';

export default Ember.Object.extend({
  setPrefilter: function() {
    var token = this.get('data').token;
    var preFilter = function(options, originalOptions, jqXHR) {
      return jqXHR.setRequestHeader('X-CSRF-Token', token );
    };
    $.ajaxPrefilter(preFilter);
  },
  setData: function(data) {
    var param = Object.keys(data)[0];
    this.set('data', { param: param, token: data[param] });
    this.setPrefilter();
  },
  fetchToken: function() {
    var setToken = this.setData.bind(this);
    if (!this.get('data')) {
      return request('api/v1/csrf').then(setToken);
    }
  }
});

Use default import for Ember.


window.addEventListener("load", function() {
   uTest.runAllTests();
   uTest.runTestGroup("FirstGroup");
   uTest.runTestGroup("SecondGroup");
   uTest.runTest("FirstTest");
   uTest.runTest({ group: "FirstGroup", name: "FirstTest" });
});

window.addEventListener("load", function() {
   uTest.runAllTests();
   uTest.runTestGroup("FirstGroup");
   uTest.runTestGroup("SecondGroup");
   uTest.runTest("FirstTest");
   uTest.runTest({ group: "FirstGroup", name: "FirstTest" });
   uTest.runTest("ThirdTest");
});

Add command to run ignored test


const Factories                 = use('core/factories');
const Config                    = use('config');

const NotificationSmsFactory = Factories('NotificationSms');

class NotificationSms {

    constructor() {
        this.provider = require(`./providers/${Config.smsGate.providers[Config.smsGate.active].name}`);
    }

    send({
            accounts_senders_id = null,
            sender_title = 'automatic message',
            body,
            recipient_phone,
            accounts_recipient_id = null,
            type = 'automatic'
        })
    {
        return this.provider.sendSms(recipient_phone, body)
            .then(response => {
                let saveData = {
                    status                  : response.status,
                    accounts_senders_id     : accounts_senders_id,
                    provider_sms_id         : response.providerSmsId,
                    body                    : body,
                    recipient_phone         : recipient_phone,
                    accounts_recipient_id   : accounts_recipient_id,
                    type                    : type,
                    sender_title            : sender_title
                };
                return NotificationSmsFactory
                    .new(saveData)
                    .then((notificationSms) => notificationSms.save());
            });
    }

    getStatus(id) {
        return this.provider.getSmsStatus(id)
            .then(response => NotificationSmsFactory.get({provider_sms_id: id}))
            .then((notificationSms) => {
                notificationSms.status = response[0].status;
                return notificationSms.save();
            });
    }

    getSmsBalance() {
        return this.provider.getSmsBalance();
    }
}

module.exports = NotificationSms;

const Factories                 = use('core/factories');
const Config                    = use('config');

const NotificationSmsFactory = Factories('NotificationSms');

class NotificationSms {

    constructor() {
        this.provider = require(`./providers/${Config.smsGate.providers[Config.smsGate.active].name}`);
    }

    send({
        accounts_senders_id = null,
        sender_title = 'automatic message',
        body,
        recipient_phone,
        accounts_recipient_id = null,
        type = 'automatic'
    })
    {
        let saveData = {
            accounts_senders_id     : accounts_senders_id,
            body                    : body,
            recipient_phone         : recipient_phone,
            accounts_recipient_id   : accounts_recipient_id,
            type                    : type,
            sender_title            : sender_title
        };

        return NotificationSmsFactory.new(saveData)
            .then((notificationSms) => {
                return notificationSms.save();
            })
            .then((notificationSms)=>{
                return this.provider.sendSms(recipient_phone, body)
                    .then(response => {
                        notificationSms.status          = response.status;
                        notificationSms.provider_sms_id = response.providerSmsId;
                        return notificationSms.save();
                    });
            });
    }

    getStatus(id) {
        return this.provider.getSmsStatus(id)
            .then(response => NotificationSmsFactory.get({provider_sms_id: id}))
            .then((notificationSms) => {
                notificationSms.status = response[0].status;
                return notificationSms.save();
            });
    }

    getSmsBalance() {
        return this.provider.getSmsBalance();
    }
}

module.exports = NotificationSms;

Save message before send sms has been added


function ChapterStatsController(chapterJSON) {
  var ChapterStatsCtrl = this;
  ChapterStatsCtrl.chapter = chapterJSON.data;
  console.log(ChapterStatsCtrl.chapter);

  ChapterStatsCtrl.calculateAverage = function(total) {
    if (ChapterStatsCtrl.chapter.entries.length > 0) {
      return Math.round(total / ChapterStatsCtrl.chapter.entries.length)
    } else {
      return 0;
    }
  }
  ChapterStatsCtrl.firstEntryID = ChapterStatsCtrl.chapter.entries[ChapterStatsCtrl.chapter.entries.length - 1].id;
  ChapterStatsCtrl.firstMeasurement = ChapterStatsCtrl.chapter.measurements[ChapterStatsCtrl.chapter.measurements.length - 1];

}

angular
  .module('fitnessTracker')
    .controller('ChapterStatsController', ChapterStatsController);

function ChapterStatsController(chapterJSON) {
  var ChapterStatsCtrl = this;
  ChapterStatsCtrl.chapter = chapterJSON.data;
  console.log(ChapterStatsCtrl.chapter);

  ChapterStatsCtrl.calculateAverage = function(total) {
    if (ChapterStatsCtrl.chapter.entries.length > 0) {
      return Math.round(total / ChapterStatsCtrl.chapter.entries.length)
    } else {
      return 0;
    }
  }
  ChapterStatsCtrl.firstEntryID = ChapterStatsCtrl.chapter.entries[ChapterStatsCtrl.chapter.entries.length - 1].id;
  ChapterStatsCtrl.firstMeasurement = ChapterStatsCtrl.chapter.measurements[ChapterStatsCtrl.chapter.measurements.length - 1];

  ChapterStatsCtrl.thisWeekMeasurements = ChapterStatsCtrl.chapter.measurements.slice(0, 7);
  ChapterStatsCtrl.lastWeeksMeasurement = ChapterStatsCtrl.chapter.measurements.slice(7, 14);

}

angular
  .module('fitnessTracker')
    .controller('ChapterStatsController', ChapterStatsController);

Save thisWeekMeasurements and lastWeekMeasurements to ChapterStatsCtrl


'use strict';

const dateHelper = require(`${__dirname}/../helpers/dateHelper`);
const requestHelper = require(`${__dirname}/../helpers/requestHelper`);

exports.getRowRequest = (field, size, req, res) => {
  if (dateHelper.isValidDate(req.query.from) && dateHelper.isValidDate(req.query.to)) {
    requestHelper.makeAllRowRequest(field, size, req.query.from, req.query.to, res);
  } else {
    res.json({ success: false, message: 'Bad date formatting' });
  }
};

exports.getGameRequest = (type, req, res) => {
	requestHelper.makeGameRequest(type, req, res);
};

exports.getTodaysRequests = (req, res) => {
	requestHelper.makeTodayCountRequests(req, res);
};

exports.getTopTargets = (req, res) => {
	requestHelper.makeTopTargetRequests(req, res);
};

Add controller function for top targets


var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');

const ENV = process.env.NODE_ENV = process.env.ENV = 'production';

module.exports = webpackMerge(commonConfig, {
  devtool: 'source-map',

  output: {
    path: helpers.root('dist'),
    publicPath: '/',
    filename: '[name].[hash].js',
    chunkFilename: '[id].[hash].chunk.js'
  },

  plugins: [
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618
      mangle: {
        keep_fnames: true
      }
    }),
    new ExtractTextPlugin('[name].[hash].css'),
    new webpack.DefinePlugin({
      'process.env': {
        'ENV': JSON.stringify(ENV)
      }
    }),
    new webpack.LoaderOptionsPlugin({
      htmlLoader: {
        minimize: false // workaround for ng2
      }
    })
  ]
});

var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');
var version = require('../package.json').version;

const ENV = process.env.NODE_ENV = process.env.ENV = 'production';

module.exports = webpackMerge(commonConfig, {
  devtool: 'source-map',

  output: {
    path: helpers.root('dist'),
    publicPath: '/',
    filename: `[name].js?${version}`,
    chunkFilename: `[id].chunk.js?${version}`
  },

  plugins: [
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618
      mangle: {
        keep_fnames: true
      }
    }),
    new ExtractTextPlugin(`[name].css?${version}`),
    new webpack.DefinePlugin({
      'process.env': {
        'ENV': JSON.stringify(ENV)
      }
    }),
    new webpack.LoaderOptionsPlugin({
      htmlLoader: {
        minimize: false // workaround for ng2
      }
    })
  ]
});

Use version query string to cache bust instead of webpack hash


'use strict';

var db = require('../config/neo4j').db;
var List = require('../models/List.model');

exports.show = function(req, res) {
  List.show(req.params.id, function(err, data) {
    if (err) {
      return handleError(res, err);
    }
    res.status(200).json(data);
  })
}

// Use this to handle errors
var handleError = function(res, err) {
  return res.status(500).send(err);
};

'use strict';

var db = require('../config/neo4j').db;
var List = require('../models/List.model');

exports.show = function(req, res) {
  List.show(req.params.id, function(err, data) {
    if (err) {
      return handleError(res, err);
    }
    res.status(200).json({itemArray: data});
  })
}

// Use this to handle errors
var handleError = function(res, err) {
  return res.status(500).send(err);
};

Refactor result data from list API route


var url = require('url')
var HttpClient = function() {}

HttpClient.prototype.fetch = function (requestOptions) {
  var self = this;
  var urlSplit = url.parse(requestOptions.url);
  var isHttps = urlSplit.protocol === 'https:'
  var http = require(isHttps ? 'https' : 'http');
  var options = {
    hostname: urlSplit.hostname,
    port: urlSplit.port || (isHttps ? 443 : 80),
    path: urlSplit.path,
    method: requestOptions.method,
    headers: requestOptions.headers
  };

  return new Promise(function(resolve, reject) {
    var req = http.request(options, function(res) {
      var statusCode = res.statusCode;
      var contentType = res.headers['content-type'];
      var responseData = '';
      res.on('data', function (chunk) {
        responseData += chunk;
      });
      res.on('end', function() {
        resolve({status: statusCode, type: contentType, data: responseData});
      });
      res.on('error', function () {
        reject('Unable to load data from request.');
      });
    });
    req.end()
  });
}

module.exports = HttpClient

var url = require('url')
var HttpClient = function() {}

HttpClient.prototype.fetch = function (requestOptions) {
  var self = this;
  var urlSplit = url.parse(requestOptions.url);
  var isHttps = urlSplit.protocol === 'https:'
  var http = require(isHttps ? 'https' : 'http');
  var options = {
    hostname: urlSplit.hostname,
    port: urlSplit.port || (isHttps ? 443 : 80),
    path: urlSplit.path,
    method: requestOptions.method,
    headers: requestOptions.headers
  };

  return new Promise(function(resolve, reject) {
    var req = http.request(options, function(res) {
      var statusCode = res.statusCode;
      var contentType = res.headers['content-type'];
      var responseData = '';
      res.on('data', function (chunk) {
        responseData += chunk;
      });
      res.on('end', function() {
        resolve({status: statusCode, type: contentType, data: responseData});
      });
      res.on('error', function () {
        reject('Unable to load data from request.');
      });
    });
    if (requestOptions.body) {
      req.write(requestOptions.body);
    }
    req.end()
  });
}

module.exports = HttpClient

Add Ability to forward Post Data


var myCanvas = null;

// Declare kinectron
var kinectron = null;

function setup() {
  myCanvas = createCanvas(512, 424);
  background(0);
  noStroke();

  // Define and create an instance of kinectron
  kinectron = new Kinectron("192.168.1.7");

  // CONNECT TO MIRCROSTUDIO
  //kinectron = new Kinectron("kinectron.itp.tsoa.nyu.edu");

  // Connect with application over peer
  kinectron.makeConnection();

  // Request right hand and set callback
  kinectron.startTrackedJoint(kinectron.HANDRIGHT, drawRightHand);
}

function draw() {

}

function drawRightHand(hand) {
  background(0);
  fill(255);

  // Normalize location based on canvas size
  ellipse(hand.depthX * myCanvas.width, hand.depthY * myCanvas.height, 50, 50);
}

// Declare Kinectron
var kinectron = null;

// Create P5 Canvas
var myCanvas = null;

// Create objects to store and access hands
var handColors = {};
var hands = {};

function setup() {
  myCanvas = createCanvas(512, 424);
  background(0);
  noStroke();

  // Create instance of Kinectron
  kinectron = new Kinectron("192.168.1.7");

  // CONNECT TO MIRCROSTUDIO
  //kinectron = new Kinectron("kinectron.itp.tsoa.nyu.edu");

  // Connect with application over peer
  kinectron.makeConnection();

  // Request right hand and set callback for received hand
  kinectron.startTrackedJoint(kinectron.HANDRIGHT, drawRightHand);
}

function draw() {

}

function drawRightHand(hand) {

  // Use handColors object to store unique colors for each hand

  // If we already have a color for incoming hand
  if (hand.trackingId in handColors) {
    // Create color property and give the hand its assigned color
    hand.color = handColors[hand.trackingId];
  } else {
    // If we don't have a color for the hand yet
    // Create a random RGB color
    var randomColor = [random(255), random(255), random(255)];
    // Create color property on the hand and assign it a random color
    hand.color = randomColor;
    // Add it to an array for easy look up
    handColors[hand.trackingId] = hand.color;
  }

  // Use hands object to store hands for drawing

  // Update or create the hand in the hands object
  hands[hand.trackingId] = hand;

  // Clear background
  background(0);

  // Draw an ellipse at each hand's location in its designated color
  for (var key in hands) {
    var trackedHand = hands[key];
    fill(trackedHand.color[0], trackedHand.color[1], trackedHand.color[2]);
    ellipse(trackedHand.depthX * myCanvas.width, trackedHand.depthY * myCanvas.height, 50, 50);
  }
}

Update joint example to include trackingid


import got from 'got'
import semver from 'semver'
import querystring from 'querystring'
import validate from 'validate-npm-package-name'

const exp = /^((@[^@\/]+)\/)?([^@\/]+)(@([^@\/]+))?$/

// Rename for PackageInfo
export default class InfoPackage {
  constructor(info) {
    const match = exp.exec(info)
    if (!info || !match) {
      this.isValid = false
      return this.isValid
    }

    this.scope = match[2] || null
    this.name = this.scope ? `${this.scope}/${match[3]}` : match[3]
    this.isValid = validate(this.name).validForOldPackages
    this.version = semver.valid(match[5], true)
    this.tag = !this.version ? match[5] || 'latest' : null
  }

  get urlPackage() {
    return `https://registry.npmjs.org/${querystring.escape(this.name)}`.toLowerCase()
  }

  async getInfo() {
    if (this._info)
      return this._info

    // verify if the response is ok, if the package exits
    const res = await got(this.urlPackage, {json: true})
    const version = this.version ? this.version : res.body['dist-tags'][this.tag]
    this._info = res.body.versions[version]

    return this._info || Promise.reject('package version does not exist')
  }

  async getStream() {
    const info = await this.getInfo()
    return got.stream(info.dist.tarball)
  }
}

import got from 'got'
import semver from 'semver'
import validate from 'validate-npm-package-name'

const exp = /^((@[^@\/]+)\/)?([^@\/]+)(@([^@\/]+))?$/

// Rename for PackageInfo
export default class InfoPackage {
  constructor(info) {
    const match = exp.exec(info)
    if (!info || !match) {
      this.isValid = false
      return this.isValid
    }

    this.scope = match[2] || null
    this.name = this.scope ? `${this.scope}/${match[3]}` : match[3]
    this.isValid = validate(this.name).validForOldPackages
    this.version = semver.valid(match[5], true)
    this.tag = !this.version ? match[5] || 'latest' : null
  }

  get urlPackage() {
    return `https://registry.npmjs.org/${encodeURIComponent(this.name)}`.toLowerCase()
  }

  async getInfo() {
    if (this._info)
      return this._info

    // verify if the response is ok, if the package exits
    const res = await got(this.urlPackage, {json: true})
    const version = this.version ? this.version : res.body['dist-tags'][this.tag]
    this._info = res.body.versions[version]

    return this._info || Promise.reject('package version does not exist')
  }

  async getStream() {
    const info = await this.getInfo()
    return got.stream(info.dist.tarball)
  }
}

Fix the encode url for the browser in InfoPackage


// Copyright 2015, EMC, Inc.

'use strict';

module.exports = {
    friendlyName: 'Bootstrap Ubuntu',
    injectableName: 'Task.Linux.Bootstrap.Ubuntu',
    implementsTask: 'Task.Base.Linux.Bootstrap',
    options: {
        kernelVersion: '3.19.0-56-generic',
        kernelFile: 'vmlinuz-{{ options.kernelVersion }}',
        initrdFile: 'initrd.img-{{ options.kernelVersion }}',
        kernelUri: '{{ api.server }}/common/{{ options.kernelFile }}',
        initrdUri: '{{ api.server }}/common/{{ options.initrdFile }}',
        basefs: 'common/base.trusty.{{ options.kernelVersion }}.squashfs.img',
        overlayfs: 'common/discovery.{{ options.kernelVersion }}.overlay.cpio.gz',
        profile: 'linux.ipxe',
        comport: 'ttyS0'
    },
    properties: {
        os: {
            linux: {
                distribution: 'ubuntu',
                release: 'trusty',
                kernel: '3.19.0-56-generic'
            }
        }
    }
};

// Copyright 2015, EMC, Inc.

'use strict';

module.exports = {
    friendlyName: 'Bootstrap Ubuntu',
    injectableName: 'Task.Linux.Bootstrap.Ubuntu',
    implementsTask: 'Task.Base.Linux.Bootstrap',
    options: {
        kernelFile: 'vmlinuz-3.16.0-25-generic',
        initrdFile: 'initrd.img-3.16.0-25-generic',
        kernelUri: '{{ api.server }}/common/{{ options.kernelFile }}',
        initrdUri: '{{ api.server }}/common/{{ options.initrdFile }}',
        basefs: 'common/base.trusty.3.16.0-25-generic.squashfs.img',
        overlayfs: 'common/discovery.overlay.cpio.gz',
        profile: 'linux.ipxe',
        comport: 'ttyS0'
    },
    properties: {
        os: {
            linux: {
                distribution: 'ubuntu',
                release: 'trusty',
                kernel: '3.16.0-25-generic'
            }
        }
    }
};

Revert "bootstrap file changed to reflect new kernel"


import h from "inferno-hyperscript";

const handleClick = fn => e => {
    // Verify left button.
    if (e.button !== 0) {
        return;
    }

    if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
        return;
    }

    if (e.defaultPrevented) {
        return;
    }

    let el = e.currentTarget;

    if (el.target) {
        return;
    }

    fn(el.pathname);

    e.preventDefault();
};

const historyPush = hist => newLoc => {
    let loc = hist.location;

    if (typeof newLoc === "string") {
        loc.pathname = newLoc;
    } else {
        Object.assign(loc, newLoc);
    }

    hist.push(loc);
};

export const Link = (props, {hist}) => (
    h("a", Object.assign(props, {onClick: handleClick(historyPush(hist))}))
);

import Component from "inferno-component";
import h from "inferno-hyperscript";

export class Link extends Component {
    constructor(props, context) {
        super(props, context);

        this.onClick = this.onClick.bind(this);
    }

    onClick(e) {
        // Verify left button.
        if (e.button !== 0) {
            return;
        }

        if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
            return;
        }

        if (e.defaultPrevented) {
            return;
        }

        let el = e.currentTarget;

        if (el.target) {
            return;
        }

        this.historyPush(el.pathname);

        e.preventDefault();
    }

    historyPush(newLoc) {
        let loc = this.context.hist.location;

        if (typeof newLoc === "string") {
            loc.pathname = newLoc;
        } else {
            Object.assign(loc, newLoc);
        }

        this.context.hist.push(loc);
    }

    render() {
        return h("a", Object.assign(this.props, {
            onClick: this.onClick,
        }));
    }
}

Convert Link to a class Component


module.exports = function(grunt) {
  grunt.initConfig({
    concat: {
      dist: {
        src: ['lib/osteo.js', 'lib/*.js', 'lib/mixins/*.js'],
        dest: 'osteo.js'
      }
    },

    jshint: {
      options: {
        jshintrc: true
      },
      beforeconcat: ['lib/**/*.js'],
      afterconcat:  ['osteo.js']
    },

    uglify: {
      options: {
        report: 'min'
      },
      dist: {
        src: 'osteo.js',
        dest: 'osteo.min.js'
      }
    },

    mocha: {
      src: 'test/test.html',
      options: {
        bail: true,
        log: true,
        run: true,
        timeout: 1000,
        mocha: {
          ignoreLeaks: true
        }
      }
    },

    watch: {
      files: ['lib/**/*.js'],
      tasks: ['concat', 'jshint']
    }
  });

  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-mocha');

  grunt.registerTask('test',    ['concat', 'mocha']);
  grunt.registerTask('default', ['test']);
  grunt.registerTask('release', ['concat', 'mocha', 'uglify']);
};

module.exports = function(grunt) {
  grunt.initConfig({
    concat: {
      dist: {
        src: ['lib/osteo.js', 'lib/*.js', 'lib/**/*.js'],
        dest: 'osteo.js'
      }
    },

    jshint: {
      options: {
        jshintrc: true
      },
      beforeconcat: ['lib/**/*.js'],
      afterconcat:  ['osteo.js']
    },

    uglify: {
      options: {
        report: 'min'
      },
      dist: {
        src: 'osteo.js',
        dest: 'osteo.min.js'
      }
    },

    mocha: {
      src: 'test/test.html',
      options: {
        bail: true,
        log: true,
        run: true,
        timeout: 1000,
        mocha: {
          ignoreLeaks: true
        }
      }
    },

    watch: {
      files: ['lib/**/*.js'],
      tasks: ['concat', 'jshint']
    }
  });

  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-mocha');

  grunt.registerTask('test',    ['concat', 'mocha']);
  grunt.registerTask('default', ['test']);
  grunt.registerTask('release', ['concat', 'mocha', 'uglify']);
};

Concatenate all subdirs, not just mixins


import { assert } from 'chai';
import Keyboard from '../src/Keyboard';

class KeyboardTest extends Keyboard(HTMLElement) {
  keydown(event) {
    if (super.keydown) { super.keydown(event); }
    if (this.keydownHook) {
      this.keydownHook(event);
    }
  }
}
document.registerElement('keyboard-test', KeyboardTest);

describe("Keyboard mixin", () => {

  let container;

  before(() => {
    container = document.getElementById('container');
  });

  afterEach(() => {
    container.innerHTML = '';
  });

  it("listens to keydown and fires the keydown() method", done => {
    let fixture = document.createElement('keyboard-test');
    fixture.keydownHook = (event) => {
      assert.equal(event.keyCode, 13);
      done();
    };
    container.appendChild(fixture);
    var event = new KeyboardEvent('keydown');
    // HACK: Polyfill fails to wrap the event on its own.
    event = wrap(event);
    Object.defineProperties(event, {
      key: { value: 'Enter' },
      keyCode: { value: 13 },
      which: { value: 13 }
    });
    fixture.dispatchEvent(event);
  });

});

import { assert } from 'chai';
import Keyboard from '../src/Keyboard';

class KeyboardTest extends Keyboard(HTMLElement) {
  keydown(event) {
    if (super.keydown) { super.keydown(event); }
    if (this.keydownHook) {
      this.keydownHook(event);
    }
  }
}
document.registerElement('keyboard-test', KeyboardTest);

describe("Keyboard mixin", () => {

  let container;

  before(() => {
    container = document.getElementById('container');
  });

  afterEach(() => {
    container.innerHTML = '';
  });

  it("listens to keydown and fires the keydown() method", done => {
    let fixture = document.createElement('keyboard-test');
    fixture.keydownHook = (event) => {
      assert.equal(event.keyCode, 13);
      done();
    };
    container.appendChild(fixture);
    let event;
    try {
      event = new window.KeyboardEvent('keydown');
    } catch (e) {
      // Stupid IE 11 doesn't support the KeyboardEvent constructor.
      event = document.createEvent('KeyboardEvent');
      event.initKeyboardEvent(
        'keydown',
        true, // bubbles
        true, // cancelable
        window, // view
        'Enter', // key
        0, // location
        '', // modifiers
        false, // repeat
        '' // locale
      );
    }
    // HACK: Polyfill fails to wrap the event on its own.
    event = window.wrap(event);
    Object.defineProperties(event, {
      key: { value: 'Enter' },
      keyCode: { value: 13 },
      which: { value: 13 }
    });
    fixture.dispatchEvent(event);
  });

});

Fix Keyboard unit test for stupid, stupid IE 11.


var common = require('../common');
var assert = common.assert;
var CombinedStream = common.CombinedStream;
var fs = require('fs');

var FILE1 = common.dir.fixture + '/file1.txt';
var FILE2 = common.dir.fixture + '/file2.txt';
var EXPECTED = fs.readFileSync(FILE1) + fs.readFileSync(FILE2);

(function testDelayedStreams() {
  var combinedStream = CombinedStream.create();
  combinedStream.append(fs.createReadStream(FILE1));
  combinedStream.append(fs.createReadStream(FILE2));

  var stream1 = combinedStream._streams[0];
  var stream2 = combinedStream._streams[1];

  stream1.on('end', function() {
    assert.equal(stream2.dataSize, 0);
  });

  var tmpFile = common.dir.tmp + '/combined.txt';
  var dest = fs.createWriteStream(tmpFile);
  combinedStream.pipe(dest);

  dest.on('end', function() {
    var written = fs.readFileSync(tmpFile, 'utf8');
    assert.strictEqual(written, EXPECTED);
  });
})();

var common = require('../common');
var assert = common.assert;
var CombinedStream = common.CombinedStream;
var fs = require('fs');

var FILE1 = common.dir.fixture + '/file1.txt';
var FILE2 = common.dir.fixture + '/file2.txt';
var EXPECTED = fs.readFileSync(FILE1) + fs.readFileSync(FILE2);
var GOT;

(function testDelayedStreams() {
  var combinedStream = CombinedStream.create();
  combinedStream.append(fs.createReadStream(FILE1));
  combinedStream.append(fs.createReadStream(FILE2));

  var stream1 = combinedStream._streams[0];
  var stream2 = combinedStream._streams[1];

  stream1.on('end', function() {
    assert.equal(stream2.dataSize, 0);
  });

  var tmpFile = common.dir.tmp + '/combined.txt';
  var dest = fs.createWriteStream(tmpFile);
  combinedStream.pipe(dest);

  dest.on('end', function() {
    GOT = fs.readFileSync(tmpFile, 'utf8');
  });
})();

process.on('exit', function() {
  assert.strictEqual(GOT, EXPECTED);
});

Fix delayed stream test to always check


/*
 *  Lexer UTF-8 support.
 */

/*===
äö
变量
1 number
2 number
3 number
===*/

// These chars are in UTF-8 format in the source file
print('äö');
print('变量');

/* Note: smjs fails the tests below (Rhino does not) */

$äöå = 1;
print($äöå, typeof $äöå);

äöå = 2;
print(äöå, typeof äöå);

my_变量 = 3;
print(my_变量, typeof my_变量);

/*
 *  Lexer UTF-8 support.
 */

/*===
äö
变量
1 number
2 number
3 number
hello world!
===*/

// These chars are in UTF-8 format in the source file
print('äö');
print('变量');

/* Note: smjs fails the tests below (Rhino does not) */

$äöå = 1;
print($äöå, typeof $äöå);

äöå = 2;
print(äöå, typeof äöå);

my_变量 = 3;
print(my_变量, typeof my_变量);

obj = {};
obj.变量1='hello';
print(obj.变量1, 'world!');

Add CJK property name test


/**
 * Store
 * Used to provide default values for a store configuration
 */

exports.reduce = function (stores, state, transformer, initial) {
  return stores.reduce(function(next, item) {
    let key   = item[0]
    let store = item[1]

    next[key] = transformer(store, state[key], key)

    return next
  }, initial || {})
}

exports.getInitialState = function (store) {
  return store.getInitialState? store.getInitialState() : undefined
}

exports.serialize = function (store, state) {
  return store.serialize? store.serialize(state) : state
}

exports.deserialize = function (store, raw) {
  return store.deserialize? store.deserialize(raw) : raw
}

exports.send = function (store, state, { payload, type }) {
  let pool = store.register? store.register() : null

  if (!pool || type in pool === false) {
    return state
  }

  let handler = pool[type]

  return typeof handler === 'function' ? handler.call(store, state, payload) : handler
}

/**
 * Store
 * Used to provide default values for a store configuration
 */

exports.reduce = function (stores, state, transformer, initial) {
  return stores.reduce(function(next, item) {
    let key   = item[0]
    let store = item[1]

    next[key] = transformer(store, state[key], key)

    return next
  }, initial || {})
}

exports.getInitialState = function (store) {
  return store.getInitialState? store.getInitialState() : undefined
}

exports.serialize = function (store, state) {
  return store.serialize? store.serialize(state) : state
}

exports.deserialize = function (store, raw) {
  return store.deserialize? store.deserialize(raw) : raw
}

exports.send = function (store, state, { payload, type }) {
  let pool = typeof store.register === 'function' ? store.register() : null

  if (!pool || type in pool === false) {
    return state
  }

  let handler = pool[type]

  return typeof handler === 'function' ? handler.call(store, state, payload) : handler
}

Use typeof to type check registration


const { execSync } = require("child_process");

const getPkgPermissions = userOrOrg => {
  const bufferResponse = execSync(`npm access ls-packages ${userOrOrg}`);
  const stringResponse = bufferResponse.toString();
  return JSON.parse(stringResponse);
};

const truffleSuiteOrgPermissionsObject = getPkgPermissions("trufflesuite");

const getNpmUsername = () => {
  const bufferResponse = execSync("npm whoami");
  return bufferResponse.toString();
};

const username = getNpmUsername();

const userPermissionsObject = getPkgPermissions(username);

for (const pkg in truffleSuiteOrgPermissionsObject) {
  if (!userPermissionsObject[pkg])
    throw new Error(`You don't have permissions to publish ${pkg}`);
  if (truffleSuiteOrgPermissionsObject[pkg] !== userPermissionsObject[pkg])
    throw new Error(`Missing correct 'read-write' access to ${pkg}`);
}

const { execSync } = require("child_process");

const getPkgPermissions = userOrOrg => {
  const bufferResponse = execSync(`npm access ls-packages ${userOrOrg}`);
  const stringResponse = bufferResponse.toString();
  return JSON.parse(stringResponse);
};

const orgs = ["trufflesuite", "truffle"];

for (let org of orgs) {
  const permissions = getPkgPermissions(org);

  const getNpmUsername = () => {
    const bufferResponse = execSync("npm whoami");
    return bufferResponse.toString();
  };

  const username = getNpmUsername();

  const userPermissionsObject = getPkgPermissions(username);

  for (const pkg in permissions) {
    if (!userPermissionsObject[pkg])
      throw new Error(`You don't have permissions to publish ${pkg}`);
    if (permissions[pkg] !== userPermissionsObject[pkg])
      throw new Error(`Missing correct 'read-write' access to ${pkg}`);
  }
}

Add truffle org to permission checking


require("./_util").updateMain("index.js");
require("child_process").execSync(__dirname + "/../../../node_modules/.bin/browserify -s babylon -e " + __dirname + "/../lib/index.js -o " + __dirname + "/../index.js", { encoding: "utf8" });

require("./_util").updateMain("index.js");
require("child_process").execSync(
  __dirname + "/../../../node_modules/.bin/browserify " +
  "--standalone babylon " +
  "--entry " + __dirname + "/../lib/index.js " +
  "--plugin bundle-collapser/plugin " +
  "--plugin derequire/plugin " +
  "--outfile " + __dirname + "/../index.js", { encoding: "utf8" });

Clean up babylon bundle to allow it to be re-bundled - fixes T6930


import 'babel-polyfill';
import $ from 'jquery';
import PatternEditor from './pattern_editor';

import { state } from './state';

$('body').keydown((event) => {
  state.set({
    cursor: {
      item: state.cursor.item + 1,
    }
  });
});

const PE = new PatternEditor();
PE.render($('#container'));

import 'babel-polyfill';
import $ from 'jquery';
import PatternEditor from './pattern_editor';

import { state } from './state';
import song from '../data/song.json';

$('body').keydown((event) => {
  switch (event.key) {
    case "ArrowUp": {
      let row = state.cursor.get("row") - 1;
      if (row < 0) {
        row = song.patterns[0].rows - 1;
      }
      state.set({
        cursor: {
          row,
        }
      });
      break;
    }
    case "ArrowDown": {
      let row = state.cursor.get("row") + 1;
      if (row >= song.patterns[0].rows) {
        row = 0;
      }
      state.set({
        cursor: {
          row,
        }
      });
      break;
    }
    case "ArrowRight": {
      let item = state.cursor.get("item");
      let track = state.cursor.get("track");
      let column = state.cursor.get("column");
      item += 1;
      if (item > 5 ) {
        item = 0;
        column += 1;
        if (column >= song.tracks[track].notecolumns) {
          column = 0;
          track += 1;
          if (track >= song.tracks.length) {
            track = 0;
          }
        }
      }
      state.set({
        cursor: {
          track,
          column,
          item,
        }
      });
      break;
    }
    case "ArrowLeft": {
      let item = state.cursor.get("item");
      let track = state.cursor.get("track");
      let column = state.cursor.get("column");
      item -= 1;
      if (item < 0 ) {
        item = 5;
        column -= 1;
        if (column < 0) {
          track -= 1;
          if (track < 0) {
            track = song.tracks.length - 1;
          }
          column = song.tracks[track].notecolumns - 1;
        }
      }
      state.set({
        cursor: {
          track,
          column,
          item,
        }
      });
      break;
    }
    default:
      return;
  }
  event.preventDefault();
});

const PE = new PatternEditor();
PE.render($('#container'));

Implement cursor movement with keyboard.


import { _ERROR } from 'constants'
const pendingIds = {} // requestIDs to timeoutIDs
let nextRequestID = 0

export default function createSocketMiddleware (socket, prefix) {
  return ({ dispatch }) => {
    // dispatch incoming actions sent by the server
    socket.on('action', dispatch)

    return next => action => {
      const { type, meta } = action

      // only apply to socket.io requests
      if (!type || type.indexOf(prefix) !== 0) {
        return next(action)
      }

      const requestID = nextRequestID++

      // fire request action
      next(action)

      // error action if socket.io callback timeout
      if (!(meta && meta.requireAck === false)) {
        pendingIds[requestID] = setTimeout(() => {
          next({
            type: type + _ERROR,
            meta: {
              error: `No response from server; check network connection (on action ${type})`,
            }
          })
        }, 2000)
      }

      // emit with callback method (3rd arg) that is
      // called using ctx.acknowledge(action) on the server
      socket.emit('action', action, responseAction => {
        // cancel dead man's timer
        clearTimeout(pendingIds[requestID])

        // action should typically have type *_SUCCESS or *_ERROR
        return next(responseAction)
      })
    }
  }
}

import { _ERROR } from 'constants'
const pendingIds = {} // requestIDs to timeoutIDs
let nextRequestID = 0

export default function createSocketMiddleware (socket, prefix) {
  return ({ dispatch }) => {
    // dispatch incoming actions sent by the server
    socket.on('action', dispatch)

    return next => action => {
      const { type, meta } = action

      // only apply to socket.io requests
      if (!type || type.indexOf(prefix) !== 0) {
        return next(action)
      }

      const requestID = nextRequestID++

      // fire request action
      next(action)

      // error action if socket.io callback timeout
      if (!(meta && meta.requireAck === false)) {
        pending[requestID] = setTimeout(() => {
          next({
            type: type + _ERROR,
            meta: {
              error: `Server didn't respond; check network [${type}]`,
            }
          })

          delete pending[requestID]
        }, 1000)
      }

      // emit along with our acknowledgement callback (3rd arg)
      // that receives data when the server calls ctx.acknowledge(data)
      // in our case the data should be a *_SUCCESS or *_ERROR action
      socket.emit('action', action, responseAction => {
        clearTimeout(pending[requestID])
        delete pending[requestID]

        next(responseAction)
      })
    }
  }
}

Delete request ids when we're done with them; refactor


import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { checkA11y } from 'storybook-addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withKnobs, boolean } from '@storybook/addon-knobs/react';
import styles from '@sambego/storybook-styles';
import { Store, State } from '@sambego/storybook-state';
import { RadioGroup, RadioButton } from '../components';
import { baseStyles, centerStyles } from '../.storybook/styles';

const sizes = ['small', 'medium', 'large'];

const store = new Store({
  value: 'option1',
});
const updateState = value => {
  store.set({ value });
  action(`Selected: ${value}`)();
};

storiesOf('Radio', module)
  .addDecorator((story, context) => withInfo('common info')(story)(context))
  .addDecorator(checkA11y)
  .addDecorator(withKnobs)
  .addDecorator(styles({ ...baseStyles, ...centerStyles }))
  .add('Sizes', () => (
    <State store={store}>
      <RadioGroup name="stringValue" onChange={updateState}>
        {sizes.map((size, key) => (
          <RadioButton
            key={key}
            size={size}
            marginVertical={3}
            label={`Option ${key+1}`}
            value={`option${key+1}`}
            disabled={boolean('Disabled', false)}
          />
        ))}
      </RadioGroup>
    </State>
  ));

import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { checkA11y } from 'storybook-addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withKnobs, boolean } from '@storybook/addon-knobs/react';
import styles from '@sambego/storybook-styles';
import { Store, State } from '@sambego/storybook-state';
import { RadioGroup, RadioButton } from '../components';
import { baseStyles, centerStyles } from '../.storybook/styles';

const sizes = ['small', 'medium', 'large'];

const store = new Store({
  value: 'small',
});
const updateState = value => {
  store.set({ value });
  action(`Selected: ${value}`)();
};

storiesOf('Radio', module)
  .addDecorator((story, context) => withInfo('common info')(story)(context))
  .addDecorator(checkA11y)
  .addDecorator(withKnobs)
  .addDecorator(styles({ ...baseStyles, ...centerStyles }))
  .add('Sizes', () => (
    <State store={store}>
      <RadioGroup name="stringValue" onChange={updateState}>
        {sizes.map((size, key) => (
          <RadioButton
            key={key}
            size={size}
            marginVertical={3}
            label={`I'm a ${size} radio`}
            value={size}
            disabled={boolean('Disabled', false)}
          />
        ))}
      </RadioGroup>
    </State>
  ));

Make stories a little more consistent


/*global describe, it, sinon */

define([ 'js/app' ], function(main) {
  'use strict';

  describe('Main', function() {
    describe('Main init', function() {
      it('should be main equal Test', function() {
        main.should.be.a('object');
      });
    });
  });
});

/*global describe, it, sinon */

define([ 'js/app' ], function(app) {
  'use strict';

  describe('Main', function() {
    describe('Main init', function() {
      it('should be main equal Test', function() {
        app.should.be.a('object');
      });
    });
  });
});

Update just to recheck travis build


class ServerError extends Error {
  constructor ({ statusCode, reason, error }, query) {
    super(`Cubic-client encountered an error while requesting ${query}: ${statusCode} - ${reason}`)
    this.statusCode = statusCode
    this.reason = reason
    this.error = error
  }
}

export default ServerError

class ServerError extends Error {
  constructor ({ statusCode, body }, query) {
    super(`Cubic-client encountered an error while requesting ${query}: ${statusCode} - ${body.error} (${body.reason})`)
    this.statusCode = statusCode
    this.reason = body.reason
    this.error = body.error
  }
}

export default ServerError

Fix ServerErrors returning `undefined` as reason.


import path from 'path';
import { ipcMain } from 'electron';
import Log from 'electron-log';
import { daedalusLogger } from './remoteLog';
import ensureDirectoryExists from './ensureDirectoryExists';
import { pubLogsFolderPath, APP_NAME } from '../config';

export const setupLogging = () => {
  const logFilePath = path.join(pubLogsFolderPath, APP_NAME + '.log');
  ensureDirectoryExists(pubLogsFolderPath);

  Log.transports.console.level = 'warn';
  Log.transports.file.level = 'debug';
  Log.transports.file.maxSize = 20 * 1024 * 1024;
  Log.transports.file.file = logFilePath;

  try {
    let sendLogsToRemoteServer;
    ipcMain.on('send-logs-choice', (event, sendLogs) => {
      sendLogsToRemoteServer = sendLogs;
    });
    ipcMain.on('log-to-remote', (event, logEntry) => {
      if (sendLogsToRemoteServer) daedalusLogger.info(logEntry);
    });
  } catch (error) {
    Log.error('Error setting up log logging to remote server', error);
  }
};

import path from 'path';
import { ipcMain } from 'electron';
import Log from 'electron-log';
import { daedalusLogger } from './remoteLog';
import ensureDirectoryExists from './ensureDirectoryExists';
import { pubLogsFolderPath, APP_NAME } from '../config';

export const setupLogging = () => {
  const logFilePath = path.join(pubLogsFolderPath, APP_NAME + '.log');
  ensureDirectoryExists(pubLogsFolderPath);

  Log.transports.console.level = 'warn';
  Log.transports.rendererConsole.level = 'warn';
  Log.transports.file.level = 'debug';
  Log.transports.file.maxSize = 20 * 1024 * 1024;
  Log.transports.file.file = logFilePath;
  Log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms} {z}] [{level}] {text}';

  try {
    let sendLogsToRemoteServer;
    ipcMain.on('send-logs-choice', (event, sendLogs) => {
      sendLogsToRemoteServer = sendLogs;
    });
    ipcMain.on('log-to-remote', (event, logEntry) => {
      if (sendLogsToRemoteServer) daedalusLogger.info(logEntry);
    });
  } catch (error) {
    Log.error('Error setting up log logging to remote server', error);
  }
};

Add UTC offset to Daedalus logs, Remove logging output from the web console


Occasion.Modules.push(function(library) {
  library.Transaction = class Transaction extends library.Base {};

  library.Transaction.className = 'Transaction';
  library.Transaction.queryName = 'transactions';

  library.Transaction.attributes('amount');

  library.Transaction.belongsTo('order', { inverseOf: 'answers' });
  library.Transaction.belongsTo('paymentMethod', { polymorphic: true });
});

Occasion.Modules.push(function(library) {
  library.Transaction = class Transaction extends library.Base {};

  library.Transaction.className = 'Transaction';
  library.Transaction.queryName = 'transactions';

  library.Transaction.attributes('amount');

  library.Transaction.belongsTo('order', { inverseOf: 'transactions' });
  library.Transaction.belongsTo('paymentMethod', { polymorphic: true });
});

Fix Transaction belongsTo order inverseOf declaration


if (jasy.Env.isSet("runtime", "browser"))
{
  var suite = new core.test.Suite("BOM/Text");

  suite.test("Basics", function()
  {
    this.identical(typeof core.bom.Text.measure("hello world"), "object");
    this.identical(typeof core.bom.Text.measure("hello world").width, "number");
    this.identical(typeof core.bom.Text.measure("hello world").height, "number");

    this.identical(typeof core.bom.Text.measure("hello world foo bar baz", null, 40), "object");
    this.identical(core.bom.Text.measure("hello world foo bar baz", null, 40).width, 40);
  });
}

Use runtime permutation to disable on NodeJS.


"use strict";

/**
 * Takes a method and transforms it into a function by binding this to the first argument
 * passed to the resulting function.
 *
 * `this` gets bound to null when the method is called.
 *
 * @see methodify
 *
 * @function demethodify
 *
 * @example
 * const { demethodify } = require("helpbox");
 *
 * function Person(age) {
 *     this.age = age;
 * }
 *
 * Person.prototype.tellMyAge = function () {
 *     console.log(this.age);
 * }
 *
 * const person        = new Person(3);
 * const tellPersonAge = demethodify(Person.prototype.tellMyAge);
 *
 * // Prints 3.
 * tellPersonAge(person);
 *
 * @param λ {Function} The function to methodify.
 *
 * @return {Function} The function in its methodified form.
 */
module.exports = λ => {
    return function () {
        return λ.apply(arguments[0], Array.prototype.slice.call(arguments, 1));
    };
};

"use strict";

/**
 * Takes a method and transforms it into a function by binding this to the first argument
 * passed to the resulting function.
 *
 * `this` gets bound to null when the method is called.
 *
 * @see methodify
 *
 * @function demethodify
 *
 * @example
 * const { demethodify } = require("helpbox");
 *
 * function Person(age) {
 *     this.age = age;
 * }
 *
 * Person.prototype.tellMyAge = function () {
 *     console.log(this.age);
 * }
 *
 * const person        = new Person(3);
 * const tellPersonAge = demethodify(Person.prototype.tellMyAge);
 *
 * // Prints 3.
 * tellPersonAge(person);
 *
 * @param λ {Function} The function to demethodify.
 *
 * @return {Function} The function in its demethodified form.
 */
module.exports = λ => {
    return function () {
        return λ.apply(arguments[0], Array.prototype.slice.call(arguments, 1));
    };
};

Fix copy/pasting error in comment


export const updateElementInWorkspace = (workspace, payload) => {
  const { selectionState } = workspace

  if(!selectionState.dragStarted) {
    return workspace
  }

  const { currentMousePosition } = payload
  const {
    elementId,
    startElementPosition,
    startMousePosition
  } = selectionState
  const element = workspace.entities[elementId]

  return Object.assign({}, workspace, {
    ...workspace,
    entities: {
      ...workspace.entities,
      [elementId]: {
        ...element,
        position: {
          x: startElementPosition.x + currentMousePosition.x - startMousePosition.x,
          y: startElementPosition.y + currentMousePosition.y - startMousePosition.y,
        }
      }
    }
  })
}

export const addDragStartedToWorkspace = (workspace, payload) => {
  let { elementId, elementPosition, mousePosition } = payload

  return setDragStateToWorkspace(
    workspace,
    {
      dragStarted: true,
      elementId: elementId,
      startElementPosition: elementPosition,
      startMousePosition: mousePosition
    }
  )
}

export const addDragStoppedToWorkspace = (workspace, payload) => {
  return setDragStateToWorkspace(
    workspace,
    {
      dragStarted: false
    }
  )
}

export const setDragStateToWorkspace = (workspace, dragState) => {
  return Object.assign({}, workspace, {
    ...workspace,
    selectionState: {
      ...workspace.selectionState,
      ...dragState
    }
  })
}

export const updateElementInWorkspace = (workspace, payload) => {
  const { selectionState } = workspace

  if(!selectionState.dragStarted) {
    return workspace
  }

  const { currentMousePosition } = payload
  const {
    elementId,
    startElementPosition,
    startMousePosition
  } = selectionState
  const element = workspace.entities[elementId]

  return Object.assign({}, workspace, {
    ...workspace,
    entities: {
      ...workspace.entities,
      [elementId]: {
        ...element,
        position: {
          x: startElementPosition.x + currentMousePosition.x - startMousePosition.x,
          y: startElementPosition.y + currentMousePosition.y - startMousePosition.y,
        }
      }
    }
  })
}

export const addDragStartedToWorkspace = (workspace, payload) => {
  const  {
    elementId,
    elementPosition,
    mousePosition
  } = payload

  return setDragStateToWorkspace(
    workspace,
    {
      dragStarted: true,
      elementId: elementId,
      startElementPosition: elementPosition,
      startMousePosition: mousePosition
    }
  )
}

export const addDragStoppedToWorkspace = (workspace, payload) => {
  return setDragStateToWorkspace(
    workspace,
    {
      dragStarted: false
    }
  )
}

export const setDragStateToWorkspace = (workspace, dragState) => {
  return Object.assign({}, workspace, {
    ...workspace,
    selectionState: {
      ...workspace.selectionState,
      ...dragState
    }
  })
}

Change unused 'let' to 'const'


import React, {Component, PropTypes} from 'react';
import lodash from 'lodash';

export function listeningTo(storeNames, getter) {
    return decorator;

    function decorator(ChildComponent) {
        class ListeningContainerComponent extends Component {
            static contextTypes = {
                dependencies: PropTypes.object.isRequired
            }

            static Original = ChildComponent

            componentDidMount() {
                const {dependencies} = this.context;
                const stores = lodash.map(storeNames, name => dependencies[name]);

                lodash.each(stores, store => {
                    store.on('change', this.setStateFromStores);
                });
            }

            componentWillUnmount() {
                const {dependencies} = this.context;
                const stores = lodash.map(storeNames, name => dependencies[name]);

                lodash.each(stores, store => {
                    store.removeListener('change', this.setStateFromStores);
                });
            }

            constructor(props, context) {
                super(props, context);

                const {dependencies} = this.context;

                this.state = {
                    childProps: getter(dependencies)
                };

                this.setStateFromStores = () => {
                    this.setState({
                        childProps: getter(dependencies)
                    });
                };
            }

            render() {
                const {childProps} = this.state;

                return <ChildComponent {...this.props} {...childProps}/>;
            }
        }

        return ListeningContainerComponent;
    }
}

import React, {Component, PropTypes} from 'react';
import lodash from 'lodash';

export function listeningTo(storeTokens, getter) {
    return decorator;

    function decorator(ChildComponent) {
        class ListeningContainerComponent extends Component {
            static contextTypes = {
                dependencyCache: PropTypes.instanceOf(Map)
            }

            static Original = ChildComponent

            getStores() {
                const {dependencyCache} = this.context;

                return lodash.map(storeTokens, name => {
                    if (typeof this.props[name] === 'string') {
                        return this.props[name];
                    } else {
                        return dependencyCache.get([name]);
                    }
                });
            }

            componentDidMount() {
                lodash.each(this.stores, store => {
                    store.on('change', this.setStateFromStores);
                });
            }

            componentWillUnmount() {
                lodash.each(this.stores, store => {
                    store.removeListener('change', this.setStateFromStores);
                });
            }

            constructor(props, context) {
                super(props, context);

                this.stores = this.getStores();

                this.state = {
                    childProps: getter(this.props)
                };

                this.setStateFromStores = () => {
                    this.setState({
                        childProps: getter(this.props)
                    });
                };
            }

            render() {
                const {childProps} = this.state;

                return <ChildComponent {...this.props} {...childProps}/>;
            }
        }

        return ListeningContainerComponent;
    }
}

Refactor @listeningTo to use dependency cache


import Ember from 'ember';

export default Ember.Component.extend({
	classNames: ['loading-indicator'],

	layout: Ember.Handlebars.compile(' '),
	size: 'small',

	didInsertElement: function() {
		this.$().spin(this.get('size'));
	}
});

import Ember from 'ember';

export default Ember.Component.extend({
	classNames: ['loading-indicator'],

	layout: Ember.Handlebars.compile(' '),
	size: 'small',

	didInsertElement: function() {
		var size = this.get('size');
		Ember.$.fn.spin.presets[size].zIndex = 'auto';
		this.$().spin(size);
	}
});

Stop loading spinner from appearing at the top


var test = require('tape');
var robot = require('..');

//TODO: Need tests for keyToggle, typeString, typeStringDelayed, and setKeyboardDelay.

test('Tap a key.', function(t)
{
	t.plan(4);
	t.ok(robot.keyTap("a") === 1, 'successfully tapped "a".');
	t.ok(robot.keyTap("a", "control") === 1, 'successfully tapped "ctrl+a".');

	t.throws(function()
	{
		robot.keyTap();
	}, /Invalid number/, 'tap nothing.');

	t.throws(function()
	{
		robot.keyTap();
	}, /Invalid number/, 'tap "ctrl+a" with an extra argument.');
});

// This test won't fail if there's an issue, but it will help you identify an issue if ran locally.
test('Tap all keys', function(t)
{
	var chars = 'abcdefghijklmnopqrstuvwxyz1234567890,./;\'[]\\'.split('');

	for (var x in chars)
	{
		t.ok(robot.keyTap(chars[x]), 'tap ' + chars[x] + '.');
	}

	t.end();
});

Add new test to help identify keycode issues.


"strict"

import fs from "fs"

if (fs.existsSync(__dirname + "/../../.env")) {
  require("dotenv").config({path: __dirname + "/../../.env"})
}

const config = {
	crt: process.env.CERTIFICATE  || "./test/keys/certificate_tbk.crt",
	key: process.env.KEY || "./test/keys/llavecomercio.pem",
	url: process.env.URL || "https://tbk.orangepeople.cl/WSWebpayTransaction/cxf/WSWebpayService?wsdl",
	commerceid: process.env.COMMERCEID || "123456789",
	returnurl: process.env.RETURNURL || "http://localhost:3000/payment/webpaydetails",
	finalurl: process.env.FINALURL || "http://localhost:3000/payment/webpayresult"
}

export default config

"strict"

import fs from "fs"
import path from "path"

if (fs.existsSync(__dirname + "/../../.env")) {
  require("dotenv").config({path: __dirname + "/../../.env"})
}

const config = {
	crt: process.env.CERTIFICATE  || path.join(__dirname, "..", "test", "keys", "oneclick.crt"),
	key: process.env.KEY || path.join(__dirname, "..", "test", "keys", "oneclick.key"),
	url: process.env.URL || "https://tbk.orangepeople.cl/WSWebpayTransaction/cxf/WSWebpayService?wsdl",
	commerceid: process.env.COMMERCEID || "123456789",
	returnurl: process.env.RETURNURL || "http://localhost:3000/payment/webpaydetails",
	finalurl: process.env.FINALURL || "http://localhost:3000/payment/webpayresult"
}

export default config

Fix path cert and key