import React, { Component } from 'react';
import {
  NetInfo
} from 'react-native';

class ConnectionStatus extends Component {
  componentDidMount() {
    NetInfo.isConnected.addEventListener('change', this.handleConnectionChange);
    NetInfo.isConnected.fetch()
      .done((isConnected) => {
        this.props.setIsConnected(isConnected);
      });
  }

  componentWillUnmount() {
    NetInfo.isConnected.removeEventListener('change', this.handleConnectionChange);
  }

  handleConnectionChange = (isConnected) => {
    this.props.setIsConnected(isConnected);
  };

  render() {
    return (null);
  }
}

ConnectionStatus.propTypes = {
  setIsConnected: React.PropTypes.func.isRequired
};

export default ConnectionStatus;

import React, { Component } from 'react';
import {
  NetInfo
} from 'react-native';

class ConnectionStatus extends Component {
  componentDidMount() {
    NetInfo.isConnected.addEventListener('change', this.handleConnectionChange);
    NetInfo.isConnected.fetch()
      .done((isConnected) => {
        this.props.setIsConnected(isConnected);
      });
  }

  handleConnectionChange = (isConnected) => {
    this.props.setIsConnected(isConnected);
  };

  render() {
    return (null);
  }
}

ConnectionStatus.propTypes = {
  setIsConnected: React.PropTypes.func.isRequired
};

export default ConnectionStatus;

Remove willUnmount from connection status


import BinaryOpPatcher from './BinaryOpPatcher.js';

export default class FloorDivideOpPatcher extends BinaryOpPatcher {
  /**
   * LEFT '//' RIGHT
   */
  patchAsExpression() {
    let operator = this.getOperatorToken();
    // `a // b` → `Math.floor(a // b`
    //             ^^^^^^^^^^^
    this.insert(this.contentStart, 'Math.floor(');

    // Patch LEFT and RIGHT.
    super.patchAsExpression();

    // `Math.floor(a // b` → `Math.floor(a // b)`
    //                                         ^
    this.insert(this.contentEnd, ')');
    // `Math.floor(a // b)` → `Math.floor(a / b)`
    //               ^^                     ^
    this.overwrite(operator.start, operator.end, '/');
  }
}

import BinaryOpPatcher from './BinaryOpPatcher.js';

export default class FloorDivideOpPatcher extends BinaryOpPatcher {
  /**
   * LEFT '//' RIGHT
   */
  patchAsExpression() {
    let operator = this.getOperatorToken();

    // `a // b` → `Math.floor(a // b`
    //             ^^^^^^^^^^^
    this.insert(this.contentStart, 'Math.floor(');

    this.left.patch();

    // `Math.floor(a // b)` → `Math.floor(a / b)`
    //               ^^                     ^
    this.overwrite(operator.start, operator.end, '/');

    this.right.patch();

    // `Math.floor(a // b` → `Math.floor(a // b)`
    //                                         ^
    this.insert(this.contentEnd, ')');
  }
}

Patch parts of `FloorDivideOp` in source order.


import React from 'react';
import styles from './Slide.css';
import {hashHistory} from 'react-router'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import NeufundLogo from '../../../images/NeuFund_icon_light.png';

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

    onClick() {
        hashHistory.push(this.props.linkTo);
    }

    render() {
        return (
            <MuiThemeProvider>

                        About
                        FAQ
                        Support

                        {this.props.children}

            </MuiThemeProvider>
        )
    }
}

export default Slide;

import React from 'react';
import styles from './Slide.css';
import {hashHistory} from 'react-router'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import NeufundLogo from '../../../images/NeuFund_icon_light.png';

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

    onClick() {
        if (this.props.linkTo) {
            hashHistory.push(this.props.linkTo);
        }
    }

    render() {
        return (
            <MuiThemeProvider>

                        About
                        FAQ
                        Support

                        {this.props.children}

            </MuiThemeProvider>
        )
    }
}

export default Slide;

Make link property optional on a slide


const winston = require('winston');
const env = require('./env');
const GITLAB_TOKEN = env.GITLAB_TOKEN || Math.random(); // Seriously, you must have it set
const GITLAB_EVENT_NAME = 'Push Hook';

module.exports = {
  /**
   * Middleware for processing Gitlab Merge Hook
   *
   * @param {object} req - Contains request information from express
   * @param {object} res - Express object with interface for communicating back
   * to client user
   * @return {undefined} Should not be consumed
   */
  gitlab: function(req, res, next) {
    if (req.headers['x-gitlab-token'] !== GITLAB_TOKEN) {
      winston.error(new Error('Token does not match team\'s '));
      return res.sendStatus(500);
    }

    if (req.headers['x-gitlab-event'] !== GITLAB_EVENT_NAME) {
      winston.error(new Error('Missing or invalid X-Gitlab-Event header'));
      return res.sendStatus(500);
    }

    next();
  }
};

const winston = require('winston');
const env = require('./env');
const GITLAB_TOKEN = env.GITLAB_TOKEN || Math.random(); // Seriously, you must have it set
const GITLAB_EVENT_NAME = 'Merge Request Hook';

module.exports = {
  /**
   * Middleware for processing Gitlab Merge Hook
   *
   * @param {object} req - Contains request information from express
   * @param {object} res - Express object with interface for communicating back
   * to client user
   * @return {undefined} Should not be consumed
   */
  gitlab: function(req, res, next) {
    if (req.headers['x-gitlab-token'] !== GITLAB_TOKEN) {
      winston.error(new Error('Token does not match team\'s '));
      return res.sendStatus(500);
    }

    if (req.headers['x-gitlab-event'] !== GITLAB_EVENT_NAME) {
      winston.error(new Error('Missing or invalid X-Gitlab-Event header'));
      return res.sendStatus(500);
    }

    next();
  }
};

Correct name of event header


let ServiceConfig = {
  BLACKLIST: [
    'uris',
    'ports',
    'version',
    'versions',
    'versionInfo',
    'deployments',
    'tasks',
    'taskStats',
    'tasksHealthy',
    'tasksRunning',
    'tasksStaged',
    'tasksUnhealthy'
  ]
};

module.exports = ServiceConfig;

let ServiceConfig = {
  BLACKLIST: [
    'uris',
    'ports',
    'version',
    'versions',
    'versionInfo',
    'deployments',
    'queue',
    'tasks',
    'taskStats',
    'tasksHealthy',
    'tasksRunning',
    'tasksStaged',
    'tasksUnhealthy'
  ]
};

module.exports = ServiceConfig;

Add queue to the blacklist for service configs


'use strict';

var markovDictionaryBuilder = (function() {

	function buildDict(wordSet, chainSize) {
		console.log("building dictionary from " + wordSet.length + " words with a chain size of " + chainSize);

		var dict = [];
		for (var i = 0, len = wordSet.length - chainSize; i < len; i++) {

			var end = i + parseFloat(chainSize);
			var workingSet = wordSet.slice(i, end+1);
			var k = workingSet.slice(0, workingSet.length-1);
			var n = [workingSet.slice(-1)];

			var dictItem = {
				'key' : k.join('/').toLowerCase(),
				'words' : k,
				'next' : n
			};

			var match = getDictItemByKey(dict, dictItem.key);

			if (match !== null) {
				var index = dict.indexOf(match);
				dict[index].next.push(dictItem.next[0]);
			} else {
				dict.push(dictItem);
			}
		}
		//console.log(dict);
		return dict;
	}

	function getDictItemByKey(dict, key) {
		for (var i = 0, len = dict.length; i < len; i++) {
			if (dict[i].key === key) return dict[i];
		}
		return null;
	}

	return { buildDict : buildDict  };

})();

'use strict';

var markovDictionaryBuilder = (function() {

	function buildDict(wordSet, chainSize) {

		console.log("building dictionary from " + wordSet.length + " words with a chain size of " + chainSize);

		var map = [];
		var dict = [];
		for (var i = 0, len = wordSet.length - chainSize; i < len; i++) {

			var end = i + parseFloat(chainSize);
			var workingSet = wordSet.slice(i, end+1);
			var k = workingSet.slice(0, workingSet.length-1);
			var n = workingSet.slice(-1);

			var dictItem = {
				'key' : k.join('/').toLowerCase(),
				'words' : k,
				'next' : n
			};

			var mi = map[dictItem.key];
			if (typeof(mi) === 'undefined') {
				map[dictItem.key] = dict.length;
				dict.push(dictItem);
			} else {
				dict[mi].next.push(dictItem.next[0]);
			}
		}
		return dict;
	}

	return {
		buildDict : buildDict
	};

})();

Store the dictItem keys in a map to avoid searching the dict for objects with matching keys (this cut performance tests from 5000ms to 150ms)


import axios from 'axios'

let links

export default async function AJAX({
  url,
  resource,
  id,
  method = 'GET',
  data = {},
  params = {},
  headers = {},
}) {
  try {
    const basepath = window.basepath || ''
    let response

    url = `${basepath}${url}`

    if (!links) {
      const linksRes = (response = await axios({
        url: `${basepath}/chronograf/v1`,
        method: 'GET',
      }))
      links = linksRes.data
    }

    if (resource) {
      url = id
        ? `${basepath}${links[resource]}/${id}`
        : `${basepath}${links[resource]}`
    }

    response = await axios({
      url,
      method,
      data,
      params,
      headers,
    })

    const {auth} = links

    return {
      ...response,
      auth: {links: auth},
      logout: links.logout,
    }
  } catch (error) {
    const {response} = error

    const {auth} = links
    throw {...response, auth: {links: auth}, logout: links.logout} // eslint-disable-line no-throw-literal
  }
}

import axios from 'axios'

let links

export default async function AJAX({
  url,
  resource,
  id,
  method = 'GET',
  data = {},
  params = {},
  headers = {},
}) {
  try {
    const basepath = window.basepath || ''
    let response

    url = `${basepath}${url}`

    if (!links) {
      const linksRes = (response = await axios({
        url: `${basepath}/chronograf/v1`,
        method: 'GET',
      }))
      links = linksRes.data
    }

    if (resource) {
      url = id
        ? `${basepath}${links[resource]}/${id}`
        : `${basepath}${links[resource]}`
    }

    response = await axios({
      url,
      method,
      data,
      params,
      headers,
    })

    const {auth} = links

    return {
      ...response,
      auth: {links: auth},
      logoutLink: links.logout,
    }
  } catch (error) {
    const {response} = error

    const {auth} = links
    throw {...response, auth: {links: auth}, logout: links.logout} // eslint-disable-line no-throw-literal
  }
}

Fix missing renaming of logout => logoutLink


'use strict';

var app = require('./../../../config/express')();
var request = require('supertest');

describe('File API endpoints: ', function () {

  describe('GET /api/v1/files', function () {

    it('should respond with json', function (done) {

      request(app)
        .get('/api/v1/files')
        .expect(200)
        .end(function (err, res) {
          if (err) {
            return done(err);
          }

          done();
        });
    });

  });

});

'use strict';

var app = require('./../../../config/express')();
var config = require('./../../../config/config')();
var mongoose = require('mongoose');
var request = require('supertest');
var FileModel = require('./../../models/file.model');

process.env.NODE_ENV = 'test';

describe('File API endpoints: ', function () {

  beforeEach(function (done) {

    function clearDB () {
      for (var i = 0; i < mongoose.connection.collections.length; i++) {
        mongoose.connection.collections[i].remove();
      }

      return done();
    }

    function reconnect () {
      mongoose.connect(config.db[process.env.NODE_ENV].uri, function (err) {
        if (err) {
          throw err;
        }

        return clearDB();
      });
    }

    function checkState() {
      switch(mongoose.connection.readyState) {
        case 0:
          reconnect();
          break;
        case 1:
          clearDB();
          break;
        default:
          process.nextTick(checkState);
      }
    }

    checkState();
  });

  describe('GET /api/v1/files', function () {

    it('should respond with json', function (done) {

      request(app)
        .get('/api/v1/files')
        .expect(200)
        .expect('Content-Type', /json/)
        .end(function (err, response) {
          expect(err).toBeNull();
          expect(response.body).toBeDefined();
          done();
        });
    });

  });

  describe('POST /api/v1/files', function () {

    var post_data = null;

    beforeEach(function () {
      post_data = {
        filename: "happy.jpg",
        created_at: new Date()
      };
    });

    it('should add a new file', function (done) {

      request(app)
        .post('/api/v1/files')
        .send(post_data)
        .expect(200)
        .end(function (err, response) {
          expect(err).toBeNull();
          FileModel.findOne(post_data, function (err, file) {
            expect(file).not.toBeNull();
          });
          done();
        });
    });

  });

  afterEach(function (done) {
    mongoose.disconnect();
    return done();
  });

});

Create integration tests for the API.


/**
* Plugin which transforms most Monoxide objects into promise returns
*/
var promisify = require('util').promisify;

module.exports = function(finish, o) {
	// Promisify all module methods
	[
		'connect',
		'disconnect',
		'create',
		'update',
		'delete',
		'meta',
		'runCommand',
		'save',
	].forEach(method => o[method] = promisify(o[method]));

	// Promisify all model methods
	o.on('modelCreate', (model, m) => {
		[
			'aggregate',
			'checkIndexes',
			'create',
			'distinct',
			'getIndexes',
			'getSchemaIndexes',
			'remove',
			'update',
		].forEach(method => m[method] = promisify(m[method]));
	});

	// Promisify all document methods
	o.on('documentCreate', doc => {
		[
			'delete',
			'populate',
			'remove',
			'save',
		].forEach(method => doc.__proto__[method] = promisify(doc.__proto__[method]));
	});

	finish();
};

/**
* Plugin which transforms most Monoxide objects into promise returns
*/
var promisify = require('util').promisify;

module.exports = function(finish, o) {
	// Promisify all module methods
	[
		'connect',
		'disconnect',
		'create',
		'update',
		'delete',
		'meta',
		'runCommand',
		'save',
	].forEach(method => o[method] = promisify(o[method]));

	// Promisify all model methods
	o.on('modelCreate', (model, m) => {
		[
			'aggregate',
			'checkIndexes',
			'create',
			'distinct',
			'getIndexes',
			'getSchemaIndexes',
			'remove',
			'update',
			'findOneByID',
		].forEach(method => m[method] = promisify(m[method]));

		m.findOneByID = id => new Promise((resolve, reject) => {
			if (!id) return reject('No ID specified');
			o.internal.query({$collection: model, $id: id}, (err, result) => {
				if (err) return reject(err);
				resolve(result);
			});
		});
	});

	// Promisify all document methods
	o.on('documentCreate', doc => {
		[
			'delete',
			'populate',
			'remove',
			'save',
		].forEach(method => doc.__proto__[method] = promisify(doc.__proto__[method]));
	});

	finish();
};

Revert "BUGFIX: Removed broken findOneByID wrapper"


define([
	"../resource/get",
	"../path/normalize"
], function( resourceGet, pathNormalize ) {

	return function( Cldr, locale ) {
		var parent;

		if ( locale === "root" ) {
			return;
		}

		// First, try to find parent on supplemental data.
		parent = resourceGet( Cldr._resolved, pathNormalize( [ "supplemental/parentLocales/parentLocale", locale ] ) );
		if ( parent ) {
			return parent;
		}

		// Or truncate locale.
		parent = locale.substr( 0, locale.lastIndexOf( "_" ) );
		if ( !parent ) {
			return "root";
		}

		return parent;
	};

});

define([
	"../resource/get",
	"../path/normalize"
], function( resourceGet, pathNormalize ) {

	return function( Cldr, locale ) {
		var normalizedPath, parent;

		if ( locale === "root" ) {
			return;
		}

		// First, try to find parent on supplemental data.
		normalizedPath = pathNormalize( [ "supplemental/parentLocales/parentLocale", locale ] );
		parent = resourceGet( Cldr._resolved, normalizedPath ) || resourceGet( Cldr._raw, normalizedPath );
		if ( parent ) {
			return parent;
		}

		// Or truncate locale.
		parent = locale.substr( 0, locale.lastIndexOf( "_" ) );
		if ( !parent ) {
			return "root";
		}

		return parent;
	};

});

Check for supplemental on both _resolved and _raw


import { clone, isString, isFunction, toLowerCase } from '../util';
import { isFunctionalComponent } from './functional-component';

/** Check if two nodes are equivalent.
 *	@param {Element} node
 *	@param {VNode} vnode
 *	@private
 */
export function isSameNodeType(node, vnode) {
	if (isString(vnode)) {
		return node instanceof Text;
	}
	if (isString(vnode.nodeName)) {
		return !node._componentConstructor && isNamedNode(node, vnode.nodeName);
	}
	if (isFunction(vnode.nodeName)) {
		return node._componentConstructor===vnode.nodeName || isFunctionalComponent(vnode);
	}
}

export function isNamedNode(node, nodeName) {
	return node.normalizedNodeName===nodeName || toLowerCase(node.nodeName)===toLowerCase(nodeName);
}

/**
 * Reconstruct Component-style `props` from a VNode.
 * Ensures default/fallback values from `defaultProps`:
 * Own-properties of `defaultProps` not present in `vnode.attributes` are added.
 * @param {VNode} vnode
 * @returns {Object} props
 */
export function getNodeProps(vnode) {
	let props = clone(vnode.attributes);
	props.children = vnode.children;

	let defaultProps = vnode.nodeName.defaultProps;
	if (defaultProps) {
		for (let i in defaultProps) {
			if (props[i]===undefined) {
				props[i] = defaultProps[i];
			}
		}
	}

	return props;
}

import { clone, isString, isFunction, toLowerCase } from '../util';
import { isFunctionalComponent } from './functional-component';

/** Check if two nodes are equivalent.
 *	@param {Element} node
 *	@param {VNode} vnode
 *	@private
 */
export function isSameNodeType(node, vnode) {
	if (isString(vnode)) {
		return node instanceof Text;
	}
	if (isString(vnode.nodeName)) {
		return !node._componentConstructor && isNamedNode(node, vnode.nodeName);
	}
	if (isFunction(vnode.nodeName)) {
		return node._componentConstructor ? node._componentConstructor===vnode.nodeName : true;
	}
}

export function isNamedNode(node, nodeName) {
	return node.normalizedNodeName===nodeName || toLowerCase(node.nodeName)===toLowerCase(nodeName);
}

/**
 * Reconstruct Component-style `props` from a VNode.
 * Ensures default/fallback values from `defaultProps`:
 * Own-properties of `defaultProps` not present in `vnode.attributes` are added.
 * @param {VNode} vnode
 * @returns {Object} props
 */
export function getNodeProps(vnode) {
	let props = clone(vnode.attributes);
	props.children = vnode.children;

	let defaultProps = vnode.nodeName.defaultProps;
	if (defaultProps) {
		for (let i in defaultProps) {
			if (props[i]===undefined) {
				props[i] = defaultProps[i];
			}
		}
	}

	return props;
}

Fix SSR hydration remounting issue. Thanks @paullewis for discovering it!


import React from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import Theme from '../../../config/theme';
import FloatingActionButton from 'material-ui/FloatingActionButton';
import UpIcon from 'material-ui/svg-icons/navigation/arrow-upward';
import Header from '../Header';
import News from '../News';
import styles from './index.css';

class App extends React.Component {

    render () {
        return (
            <MuiThemeProvider muiTheme={Theme}>
                <div>
                    <Header />
                    <News />
                </div>
            </MuiThemeProvider>
        )
    }

};

export default App;

import React from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import Theme from '../../../config/theme';
import FloatingActionButton from 'material-ui/FloatingActionButton';
import UpIcon from 'material-ui/svg-icons/navigation/arrow-upward';
import Header from '../Header';
import News from '../News';
import Sponsor from '../Sponsor';
import styles from './index.css';

class App extends React.Component {

    render () {
        return (
            <MuiThemeProvider muiTheme={Theme}>
                <div>
                    <Header />
                    <News />
                    <Sponsor />
                </div>
            </MuiThemeProvider>
        )
    }

};

export default App;

Add Sponsor component to home page


import React, { Component } from 'react'
import { Field, reduxForm } from 'redux-form'

class LoginForm extends Component {
  render() {
    return (
      <form onSubmit={this.props.handleSubmit}>

          Email:
          <Field name="email" component="input" type="text" />

          Password:
          <Field name="password" component="input" type="text" />

        <button type="submit">Login</button>
      </form>
    )
  }
}

export default reduxForm({
  form: 'signup'
})(LoginForm)

import React, { Component } from 'react'
import { Field, reduxForm } from 'redux-form'

class LoginForm extends Component {
  render() {
    return (
      <form onSubmit={this.props.handleSubmit}>

          Email:
          <Field name="email" component="input" type="text" />

          Password:
          <Field name="password" component="input" type="password" />

        <button type="submit">Login</button>
      </form>
    )
  }
}

export default reduxForm({
  form: 'signup'
})(LoginForm)

Correct input type for password in Login


'use strict'

var moment = require('moment')

var DEFAULT_WEEK_START_DAY = moment().startOf('week').format('d') * 1

module.exports = function getWeekDayNames(startDay){

    var names = moment.weekdaysShort()

    if (startDay !== undefined && DEFAULT_WEEK_START_DAY != startDay){
        var index = startDay

        while (index > DEFAULT_WEEK_START_DAY){
            names.push(names.shift())
            index--
        }
    }

    return names
}

'use strict'

var moment = require('moment')

var DEFAULT_WEEK_START_DAY = moment().startOf('week').format('d') * 1

module.exports = function getWeekDayNames(startDay){

    var names = moment.weekdaysShort()
    var index = startDay || DEFAULT_WEEK_START_DAY

    if (index) {
        while (index > 0){
            names.push(names.shift())
            index--
        }
    }

    return names
}

Adjust the order of week days based on the current locale


import { kebabCase } from "lodash";
import pluralize from "pluralize";

let modelNameMap = {
  j5Report: "assembly-report"
};

export const setModelLinkMap = map => {
  modelNameMap = map;
};

export default function modelNameToLink(modelName, id) {
  let link = modelNameMap[modelName] || kebabCase(modelName);
  link = "/" + pluralize(link);
  return id ? link + `/${id}` : link;
}

import { kebabCase } from "lodash";
import pluralize from "pluralize";

let modelNameMap = {
  j5Report: "assembly-report"
};

export const setModelLinkMap = map => {
  modelNameMap = map;
};

export default function modelNameToLink(modelNameOrRecord, maybeId) {
  let modelName,
    id = maybeId;
  if (typeof modelNameOrRecord === "string") {
    modelName = modelNameOrRecord;
  } else {
    modelName = modelNameOrRecord.__typename;
    id = modelNameOrRecord.id;
  }
  let link = modelNameMap[modelName] || kebabCase(modelName);
  link = "/" + pluralize(link);
  return id ? link + `/${id}` : link;
}

Add pass record to model name to link


'use strict';

var gulp = require('gulp');
var sass = require('gulp-sass');
var minifyCss = require('gulp-minify-css');
var rename = require('gulp-rename');
var autoprefixer = require('gulp-autoprefixer');

//gulp.task('sass', function () {
//    gulp.src('./sass/**/*.scss')
//        .pipe(sass().on('error', sass.logError))
//        .pipe(gulp.dest('./css'));
//});

//gulp.task('sass:watch', function () {
//    gulp.watch('./sass/**/*.scss', ['sass']);
//});

gulp.task('default', ['sass']);

gulp.task('sass', function(done) {
    var generatedStyleFolder =  './generated/';
    gulp.src('./style/won.scss')
        .pipe(sass({
            errLogToConsole: true
        }))
        .pipe(autoprefixer({
            browsers: ['last 3 versions'],
            cascade: false
        }))
        .pipe(gulp.dest(generatedStyleFolder))
        .pipe(minifyCss({
            keepSpecialComments: 0
        }))
        .pipe(rename({ extname: '.min.css' }))
        .pipe(gulp.dest(generatedStyleFolder))
        .on('end', done);
});

gulp.task('watch', function() {
    gulp.watch('./style/won.scss', ['sass']);
});

'use strict';

var gulp = require('gulp');
var sass = require('gulp-sass');
var minifyCss = require('gulp-minify-css');
var rename = require('gulp-rename');
var autoprefixer = require('gulp-autoprefixer');

//gulp.task('sass', function () {
//    gulp.src('./sass/**/*.scss')
//        .pipe(sass().on('error', sass.logError))
//        .pipe(gulp.dest('./css'));
//});

//gulp.task('sass:watch', function () {
//    gulp.watch('./sass/**/*.scss', ['sass']);
//});

gulp.task('default', ['sass']);

gulp.task('sass', function(done) {
    var generatedStyleFolder =  './generated/';
    gulp.src('./style/won.scss')
        .pipe(sass({
            errLogToConsole: true
        }))
        .pipe(autoprefixer({
            browsers: ['last 3 versions'],
            cascade: false
        }))
        .pipe(gulp.dest(generatedStyleFolder))
        .pipe(minifyCss({
            keepSpecialComments: 0
        }))
        .pipe(rename({ extname: '.min.css' }))
        .pipe(gulp.dest(generatedStyleFolder))
        .on('end', done);
});

gulp.task('watch', function() {
    gulp.watch('./style/**/*.scss', ['sass']);
});

Watch task on *all* scss files now.


{% load djblets_utils %}
  /*
   * Initial state from the server. These should all be thought of as
   * constants, not state.
   */
{% if not error %}
  var gBugTrackerURL = "{{review_request.repository.bug_tracker}}";
  var gReviewRequestPath = '{{review_request.get_absolute_url}}';
  var gReviewRequestId = "{{review_request.id}}";
  var gReviewRequestSummary = "{{review_request.summary}}";
  var gReviewPending = {% if review %}true{% else %}false{% endif %};
{% ifuserorperm review_request.submitter "reviews.can_edit_reviewrequest" %}
{% ifequal review_request.status 'P' %}
  var gEditable = true;
{% endifequal %}
{% endifuserorperm %}
{% else %}{# error #}
  var gReviewPending = false;
{% endif %}{# !error #}

  var gUserURL = "{% url user user %}";
  var gUserAuthenticated = {{user.is_authenticated|lower}};
{% if not user.is_anonymous %}
  var gUserFullName = "{{user|user_displayname}}";
{% endif %}
</script>

Fix a bug where an extra </script> tag was sitting inside a <script> block.


import React from 'react'
import { withRouter } from 'react-router-dom'
import Character from './character'
import SingleCharacterRolling from "./single-character-rolling";

class Game extends React.Component {
  constructor() {
    super()

    this.addCharacter = this.addCharacter.bind(this)

    this.state = {
      gameName: ''
    }
  }

  componentWillMount() {
    var path = this.props.history.location.pathname.split('/')
    this.setState({ gameName: path[path.length - 1]})
  }

  addCharacter(character) {
    const characters = {...this.props.games[this.state.gameName].characters}
    characters[character.name] = character
    this.props.addNewCharacter(this.state.gameName, character)
  }

  render() {
    return (

          {
            Object.keys(this.props.games[this.state.gameName].characters)
              .map(name => <Character key={name} character={this.props.games[this.state.gameName].characters[name]}/>)
          }

        <SingleCharacterRolling addCharacter={this.addCharacter}/>

    );
  }
}

export default withRouter(Game)

import React from 'react'
import { withRouter } from 'react-router-dom'
import Character from './character'
import SingleCharacterRolling from "./single-character-rolling";

class Game extends React.Component {
  constructor() {
    super()

    this.addCharacter = this.addCharacter.bind(this)

    this.state = {
      gameName: ''
    }
  }

  componentWillMount() {
    var path = this.props.history.location.pathname.split('/')
    this.setState({ gameName: path[path.length - 1]})
  }

  addCharacter(character) {
    const characters = {...this.props.games[this.state.gameName].characters}
    characters[character.name] = character
    this.props.addNewCharacter(this.state.gameName, character)
  }

  render() {
    return (

            <button type="button" className="btn btn-primary" onClick={() => {this.props.history.push('/')} }>Game List -></button>
            <SingleCharacterRolling addCharacter={this.addCharacter}/>

              {
                Object.keys(this.props.games[this.state.gameName].characters)
                  .map(name => <Character key={name} character={this.props.games[this.state.gameName].characters[name]}/>)
              }

    );
  }
}

export default withRouter(Game)

Add button to return to game list


import React, {PropTypes} from 'react';

const Icon = ({name}) => {
	return <svg className={`lexicon-icon lexicon-icon-${name}`}><use xlinkHref={`../bower_components/lexicon/release/images/icons/icons.svg#${name}`}></use></svg>;
};

Icon.propTypes = {
	name: PropTypes.string.isRequired
};

export default Icon;

import React, {PropTypes} from 'react';

const Icon = ({className, name}) => {
	return <svg className={`lexicon-icon lexicon-icon-${name} ${className}`}><use xlinkHref={`../bower_components/lexicon/release/images/icons/icons.svg#${name}`}></use></svg>;
};

Icon.propTypes = {
	className: PropTypes.string,
	name: PropTypes.string.isRequired
};

export default Icon;

Add className prop to icon


/*! @license ©2014 Ruben Verborgh - Multimedia Lab / iMinds / Ghent University */
/** A DistinctIterator emits the unique items from a source. */

var FilterIterator = require('./FilterIterator'),
    isEqual = require('underscore').isEqual;

// Creates a new DistinctIterator with the given filter
function DistinctIterator(source, options) {
  if (!(this instanceof DistinctIterator))
    return new DistinctIterator(source, options);
  FilterIterator.call(this, source, options);

  // The `window` parameter indicates how much items to keep for uniqueness testing
  var window = options && options.window;
  this._windowLength = isFinite(window) && window > 0 ? ~~window : Infinity;
  this._uniques = [];
}
FilterIterator.inherits(DistinctIterator);

// Filters distinct items from the source
DistinctIterator.prototype._filter = function (item) {
  // Reject the item if it is already in the list of uniques
  var uniques = this._uniques, length = uniques.length, i = length;
  while (i !== 0)
    if (isEqual(item, uniques[--i]))
      return false;
  // Shorten the list of uniques if we reach the window length
  (length === this._windowLength) && uniques.shift();
  // Add the item to the list of uniques
  return uniques.push(item);
};

module.exports = DistinctIterator;

/*! @license ©2014 Ruben Verborgh - Multimedia Lab / iMinds / Ghent University */
/** A DistinctIterator emits the unique items from a source. */

var FilterIterator = require('./FilterIterator'),
    isEqual = require('lodash').isEqual;

// Creates a new DistinctIterator with the given filter
function DistinctIterator(source, options) {
  if (!(this instanceof DistinctIterator))
    return new DistinctIterator(source, options);
  FilterIterator.call(this, source, options);

  // The `window` parameter indicates how much items to keep for uniqueness testing
  var window = options && options.window;
  this._windowLength = isFinite(window) && window > 0 ? ~~window : Infinity;
  this._uniques = [];
}
FilterIterator.inherits(DistinctIterator);

// Filters distinct items from the source
DistinctIterator.prototype._filter = function (item) {
  // Reject the item if it is already in the list of uniques
  var uniques = this._uniques, length = uniques.length, i = length;
  while (i !== 0)
    if (isEqual(item, uniques[--i]))
      return false;
  // Shorten the list of uniques if we reach the window length
  (length === this._windowLength) && uniques.shift();
  // Add the item to the list of uniques
  return uniques.push(item);
};

module.exports = DistinctIterator;

Use lodash in all modules.


/**
 * @file TimestampProvider component.
 */

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';

import getLogger from '../utils/logger';

const log = getLogger('TimestampContext');

export const TimestampContext = React.createContext({
  timestamp: null,
  fetchTimestamp: () => {},
});

export default class TimestampProvider extends PureComponent {
  static propTypes = {
    children: PropTypes.node,
  };

  state = {
    timestamp: null,
  };

  fetchTimestamp = async () => {
    try {
      const res = await axios.get('/home/timestamp');

      this.setState(() => ({
        timestamp: res.data.timestamp,
      }));
    } catch (error) {
      log.error(error);
    }
  };

  render() {
    const { children } = this.props;
    const { timestamp } = this.state;

    return (
      <TimestampContext.Provider
        value={{ timestamp, fetchTimestamp: this.fetchTimestamp }}
      >
        {children}
      </TimestampContext.Provider>
    );
  }
}

/**
 * @file TimestampProvider component.
 */

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';

import getLogger from '../utils/logger';

const log = getLogger('TimestampContext');

export const TimestampContext = React.createContext({
  timestamp: null,
  fetchTimestamp: () => {},
});

export default class TimestampProvider extends PureComponent {
  static propTypes = {
    children: PropTypes.node,
  };

  state = {
    // eslint-disable-next-line react/no-unused-state
    timestamp: null,
    // eslint-disable-next-line react/no-unused-state
    fetchTimestamp: () => this.fetchTimestamp(),
  };

  fetchTimestamp = async () => {
    try {
      const res = await axios.get('/home/timestamp');

      this.setState(() => ({
        timestamp: res.data.timestamp,
      }));
    } catch (error) {
      log.error(error);
    }
  };

  render() {
    const { children } = this.props;

    return (
      <TimestampContext.Provider value={this.state}>
        {children}
      </TimestampContext.Provider>
    );
  }
}

Optimize context value per official docs


"use strict";

const Promise = require('bluebird');
const Task = require('../Task');
const path = require('path');
const ProjectType = require('../../ProjectType');
const fs = require('fs');

class ViewImportTask extends Task {

    constructor(buildManager, taskRunner) {
        super(buildManager, taskRunner);
        this.availableTo = [ProjectType.FRONTEND];
    }

    action() {
        var _ = require('lodash');
        var views = require('../../RequireViews')({
            dirname: path.join(process.cwd(), this._buildManager.options.views),
            filter: /(.+)\.tsx$/,
            map: function (name) {
                return _.capitalize(name);
            }
        });
        const exportFolder = path.join(process.cwd(), this._buildManager.options.views, 'export.js');
        process.env.EXPORT_VIEWS_PATH = exportFolder;
        fs.writeFileSync(exportFolder, 'module.exports = ' + JSON.stringify(views).replace(/"/gmi, ''));
        return Promise.resolve();
    }
}

module.exports = ViewImportTask;

"use strict";

const Promise = require('bluebird');
const Task = require('../Task');
const path = require('path');
const ProjectType = require('../../ProjectType');
const fs = require('fs');

class ViewImportTask extends Task {

    constructor(buildManager, taskRunner) {
        super(buildManager, taskRunner);
        this.availableTo = [ProjectType.FRONTEND];
    }

    action() {
        var _ = require('lodash');
        var views = require('../../RequireViews')({
            dirname: path.join(process.cwd(), this._buildManager.options.views),
            filter: /(.+)\.tsx$/,
            map: function (name) {
                return name[0].toUpperCase() + name.slice(1);
            }
        });
        const exportFolder = path.join(process.cwd(), this._buildManager.options.views, 'export.js');
        process.env.EXPORT_VIEWS_PATH = exportFolder;
        fs.writeFileSync(exportFolder, 'module.exports = ' + JSON.stringify(views).replace(/"/gmi, ''));
        return Promise.resolve();
    }
}

module.exports = ViewImportTask;

Fix capitalization of views imported


function OnUpdate(doc, meta) {
	expiry = Math.round((new Date()).getTime() / 1000) + 300;
	docTimer(timerCallback, meta.id, expiry);
}
function timerCallback(docid) {
	var query = SELECT * FROM `bucket-1` USE KEYS[:docid];
	query.execQuery();
	query = UPSERT INTO `eventing-bucket-1` ( KEY, VALUE ) VALUES ( :docId, 'Hello World');
	query.execQuery();
}

function OnUpdate(doc, meta) {
	expiry = Math.round((new Date()).getTime() / 1000) + 300;
	docTimer(timerCallback, meta.id, expiry);
}
function timerCallback(docId) {
	var query = SELECT * FROM `bucket-1` USE KEYS[:docId];
	query.execQuery();
	query = UPSERT INTO `eventing-bucket-1` ( KEY, VALUE ) VALUES ( :docId, 'Hello World');
	query.execQuery();
}

Fix eventing timer n1ql application code for spelling


import groupBy from 'dummy/utils/group-by';
import { module, test } from 'qunit';

module('Unit | Utility | group by');

// Replace this with your real tests.
test('it works', function(assert) {
  let result = groupBy();
  assert.ok(result);
});

import { A as emberA } from '@ember/array';
import groupBy from 'dummy/utils/group-by';
import { module, test } from 'qunit';

module('Unit | Utility | group by');

test('It returns an empty object if array is empty', function (assert) {
  let group = groupBy(emberA(), '');
  assert.equal(typeof group, 'object');
});

test('It returns a promise proxy', function (assert) {
  let group = groupBy(emberA(), '');
  return group.then(function () {
    assert.ok(group.get('isFulfilled'));
  });
});

Add unit tests for groupBy util function


$(function () {
    var nav_offset = $('#navigation').offset().top;
    $(window).scroll(function () {
        $('#navigation').css('position', ($(window).scrollTop() - nav_offset > 0) ? 'fixed' : 'relative');
    });
    var info_offset = $('.info-float').offset().top;
    $(window).scroll(function () {
        $('.info-float').css('position', ($(window).scrollTop() - info_offset > 0) ? 'fixed' : 'relative');
    });
});

$(function () {
    var nav_offset = $('#navigation').offset().top;
    $(window).scroll(function () {
        $('#navigation').css('position', ($(window).scrollTop() - nav_offset > 0) ? 'fixed' : 'relative');
    });
    var float = $('.info-float');
    var info_offset = float.offset().top;
    var info_right = float.offset().right;
    $(window).scroll(function () {
        float.css('position', ($(window).scrollTop() - info_offset > 0) ? 'fixed' : 'relative');
        float.css('right', ($(window).scrollTop() - info_offset > 0) ? 0 : 'auto');
    });
});

Fix status table right align (attempt)


/* Test your fear implementation */

var tyf = {

    /** Show a slide and hide all others */
    showSlide(slideId) {
        if(!slideId.startsWith('#')) {
            slideId = '#' + slideId;
        }
        $('.slide').addClass('hidden');
        $(slideId).removeClass('hidden');
    },

    /** Setup the callback handlers for all buttons and slides */
    setupHandlers() {
        $('#ichhabeangst').click(function() {
            tyf.showSlide('schimpfwort');
            setTimeout(function() {
                tyf.showSlide('startscreen');
            }, 3000);
        });
    },

	init: function() {
        this.setupHandlers();
        this.showSlide('startscreen');
	}

};

$(document).ready(function() {
	tyf.init();
});

/* Test your fear implementation */

var tyf = {

    /** List of available curse words */
    shimpfwoerter: [
        'Du Weichei!',
        'Du Warmd- uscher!',
        'Du Angst- hase!',
        'Du feige Sau!',
        'Du Lulu!',
        'Du Security Freak!',
        'Du Drücke- berger!',
        'Du Wasch- lappen!',
        'Du Becken- rand- schwim- mer!',
        'Du Schisser!',
        'Du fade Nudl!',
        'Du Kakao- trinker!',
        'Du Karusell- bremser!',
        'Du Spielver- derber!',
        'Du Socken- sortierer!'
    ],

    /** Select a string at random from an array */
    selectRandomFromArray(arr) {
        var r = Math.floor(Math.random()*arr.length);
        return arr[r];
    },

    /** Select a curse word at random */
    selectSchimpfwort() {
        return this.selectRandomFromArray(this.shimpfwoerter);
    },

    /** Show a slide and hide all others */
    showSlide(slideId) {
        if(!slideId.startsWith('#')) {
            slideId = '#' + slideId;
        }
        $('.slide').addClass('hidden');
        $(slideId).removeClass('hidden');
    },

    /** Setup the callback handlers for all buttons and slides */
    setupHandlers() {
        $('#ichhabeangst').click(function() {
            $('#beschimpfung').html(tyf.selectSchimpfwort());
            tyf.showSlide('schimpfwort');
            setTimeout(function() {
                tyf.showSlide('startscreen');
            }, 3000);
        });
    },

	init: function() {
        this.setupHandlers();
        this.showSlide('startscreen');
	}

};

$(document).ready(function() {
	tyf.init();
});

Select curse word at random


var mongodb = require('mongodb');
var server = new mongodb.Server("127.0.0.1", 27017, {});
var Db = new mongodb.Db('zombiedb', server, {w:0});

function walkingDead() {

  Db.open( function (error, client) {

    if (error) throw error;

    var zombieCollection = new mongodb.Collection(client, 'zombies');

    var listOfZombies = zombieCollection.find();

    console.log('------------------');

    listOfZombies.each( function(err, zombie ) {

      if( zombie == null) {
        Db.close()
        }
      else {

        console.log(zombie._id, " strength: ", zombie.strength, " location: ", zombie.location );

        var randomlat = Math.floor((Math.random()*2)-1);
        var randomlon = Math.floor((Math.random()*2)-1);

        var latitude  = randomlat + zombie.location[0];
        var longitude = randomlon + zombie.location[1];

        zombieCollection.update( { _id : zombie._id },
            { $set : {  location : [ latitude, longitude ] } } );
        }

      });

    });

}

var timeInterval = 1500; // milliseconds
interval = setInterval(walkingDead, timeInterval);

var mongodb = require('mongodb');
var server = new mongodb.Server("127.0.0.1", 27017, {});
var Db = new mongodb.Db('zombiedb', server, {w:0});

function walkingDead() {

  Db.open( function (error, client) {

    if (error) throw error;

    var zombieCollection = new mongodb.Collection(client, 'zombies');

    var listOfZombies = zombieCollection.find();

    console.log('------------------');

    listOfZombies.each( function(err, zombie ) {

      if( zombie == null) {
        Db.close()
        }
      else {

        console.log(zombie._id, " strength: ", zombie.strength, " location: ", zombie.location );

        var randomlat = ( Math.random() * 2.0 ) - 1.0;
        var randomlon = ( Math.random() * 2.0 ) - 1.0;

        var latitude  = randomlat + zombie.location[0];
        var longitude = randomlon + zombie.location[1];

        zombieCollection.update( { _id : zombie._id },
            { $set : {  location : [ latitude, longitude ] } } );
        }

      });

    });

}

var timeInterval = 1500; // milliseconds
interval = setInterval(walkingDead, timeInterval);

Remove bias from random walk


var fs = require('fs');
var machine = require('../machine').machine;
var log=require('../log').logger("websocket");
var socketio = require('socket.io');
var clients_limit = 1;
var nb_clients=0;

function bind_status_report(socket){
	machine.on('status',function(status){
		socket.broadcast.emit('status',status);
	});
}

connect = function(socket) {
	log.debug("new client by websocket");
	nb_clients++;
	if (nb_clients<=clients_limit){ // avoid too many connection on the app.
		socket_main(socket);
	}
	socket.on('disconnect', function() {
		log.debug("client disconnected");
		nb_clients--;
	});
};

function socket_main(socket){
	bind_status_report(socket);
}

module.exports = function(server) {
	server.io.on('connection', connect);
};

var fs = require('fs');
var machine = require('../machine').machine;
var log=require('../log').logger("websocket");
var socketio = require('socket.io');
var clients_limit = 3;
var nb_clients=0;

function broadcast_status_report(clients_sockets){
	machine.on('status',function(status){
		clients_sockets.emit('status',status);
	});
}

connect = function(socket) {
	nb_clients++;
	if (nb_clients<=clients_limit){ // avoid too many connection on the app.
		socket_main(socket);
	}
	socket.on('disconnect', function() {
		socket_close(socket);
		nb_clients--;

	});
};

function socket_main(socket){
	log.debug("client connected");
}

function socket_close(socket){
	log.debug("client disconnected");
}

module.exports = function(server) {
	server.io.on('connection', connect);
	broadcast_status_report(server.io.sockets);
};

Fix event listener redundancy on machine "status" event.


$(document).ready( function () {
  $("button.upvote").click( function() {
    var commentId = $(".upvote").data("id");
    event.preventDefault();
    $.ajax({
      url: '/response/up_vote',
      method: 'POST',
      data: { id: commentId },
      dataType: 'JSON'
    }).done( function (response) {
    }).fail( function (response) {
        console.log(response);
    })
  })
})

$(document).ready( function () {
  $("button.upvote").click( function() {
    var commentId = $(".upvote").data("id");
    event.preventDefault();
    $.ajax({
      url: '/response/up_vote',
      method: 'POST',
      data: { id: commentId },
      dataType: 'JSON'
    }).done( function (response) {
      $("button.upvote").closest(".response").append("<p>" + response + "</p>")
    }).fail( function (response) {
        console.log("Failed. Here is the response:")
        console.log(response);
    })
  })
})

Add ajax to upvote a response.


(function() {
  'use strict';

  angular.module('app.core')
    .config(configure)
    .run(init);

  /** @ngInject */
  function configure($logProvider, $compileProvider) {
    $logProvider.debugEnabled(true);
    $compileProvider.debugInfoEnabled(false);
  }

  /** @ngInject */
  function init(logger) {
    logger.showToasts = false;
  }
})();

(function() {
  'use strict';

  angular.module('app.core')
    .config(configure)
    .run(init);

  var DEVEL_DOMAINS = [
    'localhost',
    '127.0.0.1',
    '[::1]',
  ];

  var isDevel = window._.includes(DEVEL_DOMAINS, window.location.hostname);

  /** @ngInject */
  function configure($logProvider, $compileProvider) {
    $logProvider.debugEnabled(isDevel);
    $compileProvider.debugInfoEnabled(isDevel);
  }

  /** @ngInject */
  function init(logger) {
    logger.showToasts = isDevel;
  }
})();

Enable $compile debugging info in DEV mode (+ add a concept of dev-mode)


const babel = require('rollup-plugin-babel');
const multiEntry = require('rollup-plugin-multi-entry');
const resolve = require('rollup-plugin-node-resolve');
const commonjs = require('rollup-plugin-commonjs');
const replace = require('rollup-plugin-replace');
const browserConfig = require('./browser-config.js');

const template = ({env}) => `${env}.js`;

module.exports = (userBabelConfig) => ({
  formats: ['iife'],
  template,
  plugins: [
    resolve({browser: true}),
    commonjs({include: 'node_modules/**'}),
    multiEntry({exports: false}),
    babel(Object.assign({}, browserConfig(userBabelConfig), {exclude: 'node_modules/**'})),
    replace({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
    }),
  ],
});

const babel = require('rollup-plugin-babel');
const multiEntry = require('rollup-plugin-multi-entry');
const resolve = require('rollup-plugin-node-resolve');
const commonjs = require('rollup-plugin-commonjs');
const replace = require('rollup-plugin-replace');
const browserConfig = require('./browser-config.js');

const template = ({env}) => `${env}.js`;

module.exports = (userBabelConfig) => ({
  formats: ['iife'],
  template,
  plugins: [
    resolve({browser: true}),
    commonjs({include: /node_modules/}),
    multiEntry({exports: false}),
    babel(Object.assign({}, browserConfig(userBabelConfig), {exclude: 'node_modules/**'})),
    replace({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
    }),
  ],
});

Allow CJS node_modules in test browser bundles


/**
 * Tests sit right alongside the file they are testing, which is more intuitive
 * and portable than separating `src` and `test` directories. Additionally, the
 * build process will exclude all `.spec.js` files from the build
 * automatically.
 */
describe( 'Home', function() {
  beforeEach( module( 'app' ) );

  it( 'should have a dummy test', inject( function() {
    expect( true ).toBeTruthy();
  }));
});

/**
 * Tests sit right alongside the file they are testing, which is more intuitive
 * and portable than separating `src` and `test` directories. Additionally, the
 * build process will exclude all `.spec.js` files from the build
 * automatically.
 */
describe( 'Home', function() {
  beforeEach( module( 'app' ) );

  beforeEach( inject( function( $controller, _$location_, $rootScope ) {
      $location = _$location_;
      $scope = $rootScope.$new();
      HomeCtrl = $controller( 'HomeController', { $location: $location, $scope: $scope });
   }));

  afterEach(inject(function($httpBackend, $rootScope) {
    // Force all of the http requests to respond.
    $httpBackend.flush();

    // Force all of the promises to resolve.
    // VERY IMPORTANT: If we do not resolve promises, none of the dependent
    // expectations will be called, and the spec may pass inadvertantly.
    $rootScope.$digest();

    // Check that we don't have anything else hanging.
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  }));

  it( 'should return drug names', inject( function() {
    // This will fail the spec if this http request does not happen.
    $httpBackend.expect('GET', baseUrl + '/api/drugs/suggestions?q=Tyl')
        .respond({response: {"id":"0027e3a2-862a-474d-8c33-dda1a2264b27","name":"Infants TYLENOL","indicator":"brand"});

    HomeCtrl.create(article).then(function(data) {
      expect(data.id).toBeGreatherThan(0);
      expect(data.title).toEqual(article.title);
    });
  }));
});

Test Case for getSuggestions of HomeController


Template.videoFlexTab.helpers({

});

Template.videoFlexTab.onCreated(function() {
	this.timeout = null;
	this.autorun(() => {

	});

	// Opening a PR so we can do this via the https://meet.jit.si/external_api.js
	$.getScript( "https://cdn.rawgit.com/geekgonecrazy/jitsi-meet/master/external_api.js" )
		.done(function( script, textStatus ) {
			var domain = "meet.jit.si"; // Need to get from config
			var room = "a124124124124124125125125"; // Need to calc from instance id and room id
			var width = 500;
			var height = 500;

			var configOverwrite = {};
			var interfaceConfigOverwrite = {};

			var api = new JitsiMeetExternalAPI(domain, room, width, height, document.getElementById('videoContainer'), configOverwrite, interfaceConfigOverwrite, true);

			api.executeCommand('displayName', [Meteor.user().name]);
		})
		.fail(function( jqxhr, settings, exception ) {
			// Show an error
		});

});

Template.videoFlexTab.events({

});

Template.videoFlexTab.helpers({

});

Template.videoFlexTab.onCreated(function() {
	this.timeout = null;
	this.autorun(() => {

	});

	// Opening a PR so we can do this via the https://meet.jit.si/external_api.js
	$.getScript( "https://cdn.rawgit.com/geekgonecrazy/jitsi-meet/master/external_api.js" )
		.done(function( script, textStatus ) {
			var domain = "meet.jit.si"; // Need to get from settings
			var room = "a124124124124124125125126"; // Need to calc from instance id and room id
			var width = 500;
			var height = 500;

			var configOverwrite = {};
			var interfaceConfigOverwrite = {};

			var api = new JitsiMeetExternalAPI(domain, room, width, height, document.getElementById('videoContainer'), configOverwrite, interfaceConfigOverwrite, true);

			// This for sure needs to be an onReady of some sort instead
			setTimeout(() => {
				api.executeCommand('displayName', [Meteor.user().name])
			}, 3000);

		})
		.fail(function( jqxhr, settings, exception ) {
			// Show an error
		});

});

Template.videoFlexTab.events({

});

Add timeout for name setting


import APIConstants from '../constants/APIConstants'
import randomInt from '../../app/helpers/random-int'
const fixture = require('../fixture-data')
import isSameDay from '../../app/helpers/is-same-day'
import daysBetween from './days-between'

var images = Array.apply(null, Array(7))
images.forEach((item, i) => (images[i] = `images/img_${i + 1}.jpg`))
images = images.reverse()

function getByDate(date) {
  return getByDateCall(date).then(data => {
    if (APIConstants.TEST_FAILURE) throw new Error('Error')
    else return data
  })
}

function getByDateCall(date) {
  var today = new Date()
  var day = Math.abs(daysBetween(today, date) % 7)

  fixture.hdurl = images[day]
  fixture.url = images[day]
  fixture.date = date

  let p = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(fixture)
    }, APIConstants.API_DELAY)
  })

  return p
}

module.exports = getByDate

import APIConstants from '../constants/APIConstants'
import randomInt from '../../app/helpers/random-int'
const fixture = require('../fixture-data')
import isSameDay from '../../app/helpers/is-same-day'
import daysBetween from './days-between'

var images = Array.apply(null, Array(7))
images.forEach((item, i) => (images[i] = `images/img_${i + 1}`))
images = images.reverse()

function getByDate(date) {
  return getByDateCall(date).then(data => {
    if (APIConstants.TEST_FAILURE) throw new Error('Error')
    else return data
  })
}

function getByDateCall(date) {
  var today = new Date()
  var day = Math.abs(daysBetween(today, date) % 7)

  fixture.hdurl = `${images[day]}_hd.jpg`
  fixture.url = `${images[day]}.jpg`
  fixture.date = date

  let p = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(fixture)
    }, APIConstants.API_DELAY)
  })

  return p
}

module.exports = getByDate

Update test helper file to handle hd images


/**
 * @class CM_FormField_Color
 * @extends CM_FormField_Abstract
 */
var CM_FormField_Color = CM_FormField_Abstract.extend({
	_class: 'CM_FormField_Color',

	ready: function() {
		var field = this;
		/*
		 * mColorPicker's "replace" option cannot be set to false here (too late)
		 * Set it within mColorPicker!
		 */
		$.fn.mColorPicker.init.replace = false;
		$.fn.mColorPicker.init.allowTransparency = false;
		$.fn.mColorPicker.init.showLogo = false;
		$.fn.mColorPicker.init.enhancedSwatches = false;
		this.$('input').mColorPicker({
			"imageFolder": cm.getUrlStatic() + '/img/jquery.mColorPicker/'
		});
		$("#mColorPickerFooter").remove();
		$("#mColorPicker").height(158);
		this.$('input').change(function() {
			field.trigger('change');
		});
	}
});

/**
 * @class CM_FormField_Color
 * @extends CM_FormField_Abstract
 */
var CM_FormField_Color = CM_FormField_Abstract.extend({
	_class: 'CM_FormField_Color',

	ready: function() {
		var field = this;
		/*
		 * mColorPicker's "replace" option cannot be set to false here (too late)
		 * Set it within mColorPicker!
		 */
		$.fn.mColorPicker.init.replace = false;
		$.fn.mColorPicker.init.allowTransparency = false;
		$.fn.mColorPicker.init.showLogo = false;
		$.fn.mColorPicker.init.enhancedSwatches = false;
		this.$('input').mColorPicker({
			"imageFolder": cm.getUrlResource('layout', 'img/jquery.mColorPicker/')
		});
		$("#mColorPickerFooter").remove();
		$("#mColorPicker").height(158);
		this.$('input').change(function() {
			field.trigger('change');
		});
	}
});

Move jquery.colorPicker images to layout resource


const Application = require('spectron').Application
const assert = require('assert')
const path = require('path')

describe('application launch', function() {
  this.timeout(10000)

  beforeEach(function() {
    this.app = new Application({
      path: path.join(__dirname, '..', 'node_modules', '.bin', 'electron'),
      args: [path.join(__dirname, '..', 'src')]
    })
    return this.app.start()
  })

  afterEach(function() {
    if (this.app && this.app.isRunning()) {
      return this.app.stop()
    }
  })

  it('shows an initial window', function() {
    return this.app.client.getWindowCount().then(function(count) {
      assert.equal(count, 1)
    })
  })

  it('renders sarah', function() {
    return this.app.client.getText('p').then(text => {
      assert.equal('sarah', text)
    })
  })
})

const Application = require('spectron').Application
const assert = require('assert')
const path = require('path')

describe('application launch', function() {
  this.timeout(10000)

  beforeEach(function() {
    this.app = new Application({
      path: path.join(__dirname, '..', 'node_modules', '.bin', 'electron'),
      args: [path.join(__dirname, '..', 'src', 'main.js')],
    })
    return this.app.start()
  })

  afterEach(function() {
    if (this.app && this.app.isRunning()) {
      return this.app.stop()
    }
  })

  it('shows an initial window', function() {
    return this.app.client.getWindowCount().then(function(count) {
      assert.equal(count, 1)
    })
  })

  it('renders sarah', function() {
    return this.app.client.getText('p').then(text => {
      assert.equal('sarah', text)
    })
  })
})

Fix bad test runner setup


define(function(require) {

  var test = require('../../../test')

  test.assert(typeof seajs.importStyle === 'function', 'seajs.importStyle')

  seajs.importStyle('.a { color: blue }')
  seajs.importStyle('.b { color: red }', 'b.css')
  seajs.importStyle('.c { color: green }', 'c.css')
  seajs.importStyle('.b { color: red }', 'b.css')

  var styleCount = document.getElementsByTagName('style').length
  test.assert(styleCount === 3, styleCount)
  test.assert(document.getElementById('b-css').innerText.indexOf('.b') >= 0, 'b.css')

  test.next()

})

define(function(require) {

  var test = require('../../../test')

  test.assert(typeof seajs.importStyle === 'function', 'seajs.importStyle')

  seajs.importStyle('.a { color: blue }')
  seajs.importStyle('.b { color: red }', 'b.css')
  seajs.importStyle('.c { color: green }', 'c.css')
  seajs.importStyle('.b { color: red }', 'b.css')

  var styleCount = document.getElementsByTagName('style').length
  test.assert(styleCount === 3, styleCount)

  var styleEl = document.getElementById('b-css')
  var cssText = styleEl.innerHTML || styleEl.styleSheet.cssText
  test.assert(cssText.indexOf('.b') >= 0, 'b.css')

  test.next()

})

Fix a bug in spec code


var loadPresentation = function() {
	var presentation = localStorage.getItem('preview-string');
	var config = JSON.parse(localStorage.getItem('preview-config'));

	if (presentation) {
		document.querySelector('#kine_ppt').innerHTML = presentation;
	//	document.body.className = config.surface + " " + document.body.className;
	}
};

var loadPresentation = function() {
	var presentation = localStorage.getItem('preview-string');
	var config = JSON.parse(localStorage.getItem('preview-config'));

	if (presentation) {
    document.querySelector('#kine_ppt').innerHTML = presentation;
    document.querySelector('#impress').setAttribute('data-transition-duration', 300);
	//	document.body.className = config.surface + " " + document.body.className;
	}
};

Reduce transition durations to 300ms


var $ = require('jquery');
var CoreView = require('backbone/core-view');
var iconTemplates = {};

var importAllIconTemplates = function () {
  var templates = require.context('./templates', false, /\.tpl$/);

  templates.keys().forEach(function (template) {
    iconTemplates[template] = templates(template);
  });
};

importAllIconTemplates();

module.exports = CoreView.extend({
  initialize: function (opts) {
    if (!opts || !opts.icon) throw new Error('An icon is required to render IconView');

    this.icon = opts && opts.icon;
    this.placeholder = opts && opts.placeholder;
  },

  render: function () {
    var currentIconTemplate = './' + this.icon + '.tpl';
    this.$el.html(iconTemplates[currentIconTemplate]());

    if (this.placeholder) {
      var placeholder = $(this.placeholder);
      this.$el.removeClass().addClass(placeholder.attr('class'));
      placeholder.replaceWith(this.$el);
    }

    return this;
  }
});

var $ = require('jquery');
var CoreView = require('backbone/core-view');
var iconTemplates = {};

var importAllIconTemplates = function () {
  var templates = require.context('./templates', false, /\.tpl$/);

  templates.keys().forEach(function (template) {
    iconTemplates[template] = templates(template);
  });
};

importAllIconTemplates();

module.exports = CoreView.extend({
  initialize: function (opts) {
    if (!opts || !opts.icon) throw new Error('An icon is required to render IconView');

    this.icon = opts.icon;
    this.iconTemplate = this._getIconTemplate(this.icon);

    if (!this.iconTemplate) {
      throw new Error('The selected icon does not have any available template');
    }

    this.placeholder = opts && opts.placeholder;
  },

  render: function () {
    this.$el.html(this.iconTemplate);

    if (this.placeholder) {
      var placeholder = $(this.placeholder);
      this.$el.removeClass().addClass(placeholder.attr('class'));
      placeholder.replaceWith(this.$el);
    }

    return this;
  },

  _getIconTemplate: function (icon) {
    var iconTemplate = './' + this.icon + '.tpl';
    return iconTemplates[iconTemplate];
  }
});

Check if icon template is available before rendering


/* @flow weak */
/* eslint-disable react/prefer-stateless-function */
import React, { Component, PropTypes } from 'react'

const generateDisplayName = (Comp) => {
  const displayName = Comp.displayName || Comp.name
  if (displayName) {
    return `Themed${displayName}`
  }

  return 'ConnectedFelaComponent'
}

export default function connect(mapStylesToProps) {
  return Comp => class EnhancedComponent extends Component {
    // reuse the initial displayName name
    static displayName = generateDisplayName(Comp);

    static contextTypes = {
      ...Comp.contextTypes,
      renderer: PropTypes.object,
      theme: PropTypes.object
    };

    render() {
      const { renderer, theme } = this.context

      const styles = mapStylesToProps({
        ...this.props,
        theme: theme || {}
      })(renderer)

      return <Comp {...this.props} styles={styles} />
    }
  }
}

/* @flow weak */
/* eslint-disable react/prefer-stateless-function */
import React, { Component, PropTypes } from 'react'

const generateDisplayName = (Comp) => {
  const displayName = Comp.displayName || Comp.name
  if (displayName) {
    return `Fela${displayName}`
  }

  return 'ConnectedFelaComponent'
}

export default function connect(mapStylesToProps) {
  return Comp => class EnhancedComponent extends Component {
    // reuse the initial displayName name
    static displayName = generateDisplayName(Comp);

    static contextTypes = {
      ...Comp.contextTypes,
      renderer: PropTypes.object,
      theme: PropTypes.object
    };

    render() {
      const { renderer, theme } = this.context

      const styles = mapStylesToProps({
        ...this.props,
        theme: theme || {}
      })(renderer)

      return <Comp {...this.props} styles={styles} />
    }
  }
}

Use Fela prefix instead of Themed


'use strict';

var Point = require('../../data/point'),
    Rect = require('../../data/rect'),
    TileGraphic = require('../../graphics/tile-graphic'),
    Animation = require('../../graphics/animation');

var url = '/assets/astrosheet.png',
    pointUrl = '/assets/point.png';

module.exports = function(engine, entity, data) {
  var graphics = new Animation({
    position: data.loc,
    direction: data.dir,
    url: url,
    width: 24,
    height: 24,
    frameTime: 100,
    frameMidpoint: new Point(12, 12),
    crop: new Rect(0, 0, 120, 120)
  });

  graphics.defineLoop(exports.STAND, [5]);
  graphics.defineLoop(exports.WALK, [0, 1, 2, 3]);
  graphics.defineLoop(exports.JUMP, [7]);
  graphics.defineLoop(exports.DRAW, [15]);
  graphics.defineLoop(exports.SHOOT, [16, 17]);
  graphics.playLoop(exports.STAND);

  engine.renderer.table.attach(entity, graphics);

  var pointGraphic = new TileGraphic(data.loc, data.dir, pointUrl, {
    x: 0, y: 0, width: 1, height: 1
  });

  engine.renderer.table.attach(entity, pointGraphic);

  return graphics;
};

exports.WALK = 'walk';
exports.STAND = 'stand';
exports.JUMP = 'jump';
exports.SHOOT = 'shoot';
exports.DRAW = 'draw';

'use strict';

var Point = require('../../data/point'),
    Rect = require('../../data/rect'),
    TileGraphic = require('../../graphics/tile-graphic'),
    Animation = require('../../graphics/animation');

var url = '/assets/astrosheet.png';

module.exports = function(engine, entity, data) {
  var graphics = new Animation({
    position: data.loc,
    direction: data.dir,
    url: url,
    width: 24,
    height: 24,
    frameTime: 100,
    frameMidpoint: new Point(12, 12),
    crop: new Rect(0, 0, 120, 120)
  });

  graphics.defineLoop(exports.STAND, [5]);
  graphics.defineLoop(exports.WALK, [0, 1, 2, 3]);
  graphics.defineLoop(exports.JUMP, [7]);
  graphics.defineLoop(exports.DRAW, [15]);
  graphics.defineLoop(exports.SHOOT, [16, 17]);
  graphics.playLoop(exports.STAND);

  engine.renderer.table.attach(entity, graphics);

  return graphics;
};

exports.WALK = 'walk';
exports.STAND = 'stand';
exports.JUMP = 'jump';
exports.SHOOT = 'shoot';
exports.DRAW = 'draw';

Remove extra point graphic from player


module.exports = {
  entry: './js/main.js',
  output: {
    path: __dirname,
    filename: './js/build/bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        loader: 'babel',
        query: {
          presets: ['es2015']
        }
      }
    ]
  }
};

module.exports = {
  entry: './js/main.js',
  output: {
    path: __dirname,
    filename: './js/build/bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        loader: 'babel',
        query: {
          cacheDirectory: true,
          presets: ['es2015']
        }
      }
    ]
  }
};

Fix query passed to babel-loader : enable cacheDirectry


var path = require("path");
var webpack = require("webpack");

module.exports = {
  // https://webpack.js.org/configuration/dev-server/
  devServer: {
    contentBase: "./public",
    compress: true,
    overlay: true,
    port: 3000
  },

  devtool: "cheap-module-source-map",

  entry: {
    widgets: ["babel-polyfill", "./src/widgets"]
  },

  module: {
    rules: [
      {
        include: path.resolve("./src"),
        test: /\.js$/,
        use: {
          loader: "babel-loader",
          options: {
            cacheDirectory: true
          }
        }
      }
    ]
  },

  output: {
    chunkFilename: "[id].[hash:5]-[chunkhash:7].js",
    devtoolModuleFilenameTemplate: "[absolute-resource-path]",
    filename: "[name].js",
    path: path.resolve("./build")
  },

  plugins: [
    new webpack.NamedModulesPlugin(),
    new webpack.DefinePlugin({
      "process.env": {
        NODE_ENV: JSON.stringify(process.env.NODE_ENV || "development")
      }
    })
  ]
};

var path = require("path");
var webpack = require("webpack");

module.exports = {
  // https://webpack.js.org/configuration/dev-server/
  devServer: {
    contentBase: "./public",
    compress: true,
    overlay: true,
    port: 3000
  },

  devtool: "cheap-module-source-map",

  entry: {
    widgets: ["babel-polyfill", "./src/widgets"]
  },

  module: {
    rules: [
      {
        include: path.resolve("./src"),
        test: /\.js$/,
        use: {
          loader: "babel-loader",
          options: {
            cacheDirectory: true
          }
        }
      }
    ]
  },

  output: {
    chunkFilename: "[id].[hash:5]-[chunkhash:7].js",
    devtoolModuleFilenameTemplate: "[absolute-resource-path]",
    filename: "[name].js",
    path: path.resolve("./build")
  }
};

Remove DefinePlugin (webpack -p) does this


/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

export { BottomConfirmModal } from './BottomConfirmModal';
export { BottomModal } from './BottomModal';
export { ByProgramModal } from './ByProgramModal';
export { ConfirmModal } from './ConfirmModal';
export { DemoUserModal } from './DemoUserModal';
export { FinaliseModal } from './FinaliseModal';
export { LoginModal } from './LoginModal';
export { PageContentModal } from './PageContentModal';
export { DataTablePageModal } from './DataTablePageModal';
export { BottomTextEditor } from './BottomTextEditor';
export { NewConfirmModal } from './NewConfirmModal';

/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

export { BottomConfirmModal } from './BottomConfirmModal';
export { BottomModal } from './BottomModal';
export { ByProgramModal } from './ByProgramModal';
export { ConfirmModal } from './ConfirmModal';
export { DemoUserModal } from './DemoUserModal';
export { FinaliseModal } from './FinaliseModal';
export { LoginModal } from './LoginModal';
export { DataTablePageModal } from './DataTablePageModal';
export { BottomTextEditor } from './BottomTextEditor';
export { NewConfirmModal } from './NewConfirmModal';

Add remove export of PageContentModal


function viewCollectionDetails(collectionName) {

  $.ajax({
    url: "/zebedee/collection/" + collectionName,
    type: "get",
    headers:{ "X-Florence-Token":accessToken() },
    crossDomain: true
  }).done(function (data) {

    var collection_summary =
      '
' + data.name + '
' +
      '
0 New pages (todo)
' +
      '
0 Edited pages (todo)
' +
      '
0 Deleted pages (todo)
' +
      '
' + data.inProgressUris.length + ' Pages awaiting review
' +
      '
' + data.approvedUris.length + ' Pages approved
';

    $('.fl-panel--collection-details-container').html(collection_summary);
  });

  $('.fl-work-on-collection-button').click(function () {
    document.cookie = "collection=" + collectionName + ";path=/";
    localStorage.setItem("collection", collectionName);
    viewController('workspace');
  });

  $('.fl-button--cancel').click(function () {
    //perhaps need to rethink this if we do decide to animate panel transitions within this view
    viewController('collections');
  });
}

function viewCollectionDetails(collectionName) {

  $.ajax({
    url: "/zebedee/collection/" + collectionName,
    type: "get",
    headers:{ "X-Florence-Token":accessToken() },
    crossDomain: true
  }).done(function (data) {

    var collection_summary =
      '
' + data.name + '
' +
      '
' + data.inProgressUris.length + ' Pages in progress
' +
      '
' + data.completeUris.length + ' Pages awaiting review
' +
      '
' + data.reviewedUris.length + ' Pages awaiting approval
';

    $('.fl-panel--collection-details-container').html(collection_summary);
  });

  $('.fl-work-on-collection-button').click(function () {
    document.cookie = "collection=" + collectionName + ";path=/";
    localStorage.setItem("collection", collectionName);
    viewController('workspace');
  });

  $('.fl-button--cancel').click(function () {
    //perhaps need to rethink this if we do decide to animate panel transitions within this view
    viewController('collections');
  });
}

Update view to include count of complete files awaiting review.


specialForms.set = (args, scope) => {
    if (args.length != 2 || args[0].type != "word") {
        throw new SyntaxError("Incorrect use of set");
    }
    let curScope = scope;

    // Would be nicer with a for loop to change scope
    while (!Object.prototype.hasOwnProperty.call(curScope, args[0].name)) {
        curScope = Object.getPrototypeOf(curScope);

        // Could probably use (!curScope) instead
        if (curScope === null) {
            throw new ReferenceError("Can't set non-existing binding " +
                                     `'${args[0].name}'`);
        }
    }
    let value = evaluate(args[1], scope);
    curScope[args[0].name] = value;
    return value;
}

specialForms.set = (args, scope) => {
	if (args.length != 2 || args[0].type != "word") {
		throw new SyntaxError("Incorrect use of set");
	}

	let name = args[0].name;
	let value = evaluate(args[1], scope);

	while (scope) {
		if (Object.prototype.hasOwnProperty.call(scope, name)) {
			scope[name] = value;
			return value;
		}

		scope = Object.getPrototypeOf(scope);
	};

	throw new ReferenceError(`Variable ${name} does not exist`);
};

Add chapter 12, exercise 4


import Ember from 'ember';

const { $, on } = Ember;

export default Ember.Component.extend({
  filterTerm: '',
  inputClass: 'js-input',
  localClassNames: ['canvas-block-filter'],

  focusInput: on('didInsertElement', function() {
    $(`.${this.get('inputClass')}`).focus();
  }),

  actions: {
    close() {
      this.get('onCloseFilter')();
      this.set('filterTerm', '');
    }
  }
});

import Ember from 'ember';

const { $, on } = Ember;

export default Ember.Component.extend({
  filterTerm: '',
  inputSelector: '.js-input',
  localClassNames: ['canvas-block-filter'],

  focusInput: on('didInsertElement', function() {
    $(`${this.get('inputSelector')}`).focus();
  }),

  actions: {
    close() {
      this.get('onCloseFilter')();
      this.set('filterTerm', '');
    }
  }
});

Use a selector for finding filter input


import Mixin from '@ember/object/mixin';

import { next } from '@ember/runloop';
import { get } from '@ember/object';
const isOutside = function(element, e) {
  const isRemoved = !e.target || !document.contains(e.target);
  const isInside = element === e.target || element.contains(e.target);
  return !isRemoved && !isInside;
};
const handler = function(e) {
  const el = get(this, 'element');
  if (isOutside(el, e)) {
    this.onblur(e);
  }
};
export default Mixin.create({
  init: function() {
    this._super(...arguments);
    this.handler = handler.bind(this);
  },
  onchange: function() {},
  onblur: function() {},
  didInsertElement: function() {
    this._super(...arguments);
    next(this, () => {
      document.addEventListener('click', this.handler);
    });
  },
  willDestroyElement: function() {
    this._super(...arguments);
    document.removeEventListener('click', this.handler);
  },
});

import Mixin from '@ember/object/mixin';

import { next } from '@ember/runloop';
import { get } from '@ember/object';
const isOutside = function(element, e) {
  if (element) {
    const isRemoved = !e.target || !document.contains(e.target);
    const isInside = element === e.target || element.contains(e.target);
    return !isRemoved && !isInside;
  } else {
    return false;
  }
};
const handler = function(e) {
  const el = get(this, 'element');
  if (isOutside(el, e)) {
    this.onblur(e);
  }
};
export default Mixin.create({
  init: function() {
    this._super(...arguments);
    this.handler = handler.bind(this);
  },
  onchange: function() {},
  onblur: function() {},
  didInsertElement: function() {
    this._super(...arguments);
    next(this, () => {
      document.addEventListener('click', this.handler);
    });
  },
  willDestroyElement: function() {
    this._super(...arguments);
    document.removeEventListener('click', this.handler);
  },
});

Fix tear down click handler problem in tests


KylinApp.service('MessageService', function (GraphBuilder) {
    var options = {
        extraClasses: 'messenger-fixed messenger-on-top messenger-on-right',
        theme: 'air'
    };

    this.sendMsg = function (msg, type, actions, sticky) {

        var data = {
            message: msg,
            type: angular.isDefined(type) ? type : 'info',
            actions: actions,
            showCloseButton: true
        };

        // Whether sticky the message, otherwise it will hide after a period.
        if (angular.isDefined(sticky) && sticky === true) {
            data.hideAfter = false;
        }

        Messenger(options).post(data);
    }
});

KylinApp.service('MessageService', function () {
    var options = {
        extraClasses: 'messenger-fixed messenger-on-top messenger-on-right',
        theme: 'air'
    };

    this.sendMsg = function (msg, type, actions, sticky) {

        var data = {
            message: msg,
            type: angular.isDefined(type) ? type : 'info',
            actions: actions,
            showCloseButton: true
        };

        // Whether sticky the message, otherwise it will hide after a period.
        if (angular.isDefined(sticky) && sticky === true) {
            data.hideAfter = false;
        }

        Messenger(options).post(data);
    }
});

Customize Messenger widget - remove useless param


var app = app || {};

(function($) {
  'use strict';

  app.JobSingleView = app.JobAbstractView.extend({

    template: _.template($('#job-template').html()),

    events: {
      'click .kill': 'killJob'
    },

    initialize: function() {
      this.listenTo(this.model, 'change', this._modelChanged);
    },

    _modelChanged: function() {
      this.render();
      window.scrollTo(0, document.body.scrollHeight);
    },

    render: function() {
      var job = this.model;
      job.set('statusColor', this.getStatusColor(job.get('status')));
      this.$el.html(this.template(job.toJSON())).find('.timeago').timeago();
    },

    killJob: function() {
      if (window.confirm("Kill this job?")) {
        $.post('/job/' + this.model.id + '/kill');
      }
    },

    close: function() {
      this.unbind();
      this.model.unbind("change", this._modelChanged);
      this.remove();
    }
  });

})(jQuery);

var app = app || {};

(function($) {
  'use strict';

  app.JobSingleView = app.JobAbstractView.extend({

    template: _.template($('#job-template').html()),

    events: {
      'click .kill': 'killJob'
    },

    initialize: function() {
      this.listenTo(this.model, 'change:status change:output', function() {
        this.render();
        window.scrollTo(0, document.body.scrollHeight);
      });
    },

    render: function() {
      var job = this.model;
      job.set('statusColor', this.getStatusColor(job.get('status')));
      this.$el.html(this.template(job.toJSON())).find('.timeago').timeago();
    },

    killJob: function() {
      if (window.confirm("Kill this job?")) {
        $.post('/job/' + this.model.id + '/kill');
      }
    },

    close: function() {
      this.unbind();
      this.model.unbind('change:status change:output');
      this.remove();
    }
  });

})(jQuery);

Change single job view model binding.


const loaders = require("./loaders");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
module.exports = {
    context: path.join(__dirname, ".."),
    entry: ['./src/index.ts'],
    output: {
        filename: 'build.js',
        path: path.resolve("./dist"),
        publicPath: "/"
    },
    resolve: {
        extensions: [
            '.ts',
            '.js',
            '.json'
        ]
    },
    resolveLoader: {
        modules: ["node_modules"]
    },
    devtool: "cheap-eval-source-map",
    devServer: {
        contentBase: "dist",
        port: 8080,
        host: "localhost",
        hot: true,
        stats: true
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            inject: 'body',
            hash: true
        }),
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery',
            'window.jQuery': 'jquery',
            'window.jquery': 'jquery'
        }),
        new webpack.NamedModulesPlugin(),
        new webpack.HotModuleReplacementPlugin()
    ],
    module:{
        loaders: loaders
    }
};

const loaders = require("./loaders");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack");
const path = require("path");
module.exports = {
    context: path.join(__dirname, ".."),
    entry: ["./src/index.ts"],
    output: {
        filename: "build.js",
        path: path.resolve("./dist"),
        publicPath: "/"
    },
    resolve: {
        extensions: [
            ".ts",
            ".js",
            ".json"
        ]
    },
    resolveLoader: {
        modules: ["node_modules"]
    },
    devtool: "cheap-eval-source-map",
    devServer: {
        contentBase: "dist",
        port: 8080,
        host: "localhost",
        hot: true,
        stats: true
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html",
            inject: "body",
            hash: true
        }),
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            "window.jQuery": "jquery",
            "window.jquery": "jquery"
        }),
        new webpack.NamedModulesPlugin(),
        new webpack.HotModuleReplacementPlugin()
    ],
    module:{
        loaders: loaders
    }
};

Replace single quotes -> double quotes


var assert = require('assert');
var RSVP = require('rsvp');
var fs = require('fs');
var AddonTestApp = require('ember-cli-addon-tests').AddonTestApp;

describe('Acceptance: development build', function() {
  this.timeout(300000);

  var app;

  before(function() {
    app = new AddonTestApp();
  });

  it('generates a development build', function() {
    return app
      .create('dummy', {
        fixturesPath: 'node-tests/acceptance/fixtures',
        // FIXME test fails in travis with latest version of ember-data. Seems like a bug in ember-cli-addon-tests
        emberDataVersion: '2.12.1'
      })
      .then(function() {
        return app.runEmberCommand('build', '-dev')
      })
      .then(assertFileExists(app, 'test.txt'))
  });

  it('generates a production build', function() {
    return app
      .create('dummy', {
        fixturesPath: 'node-tests/acceptance/fixtures',
        // FIXME test fails in travis with latest version of ember-data. Seems like a bug in ember-cli-addon-tests
        emberDataVersion: '2.12.1'
      })
      .then(function() {
        return app.runEmberCommand('build', '-prod')
      })
      .then(assertFileExists(app, 'test.txt'))
  });
});

function assertFileExists(app, filePath) {
  return function() {
    assert.ok(fs.existsSync(app.filePath(filePath)), 'file exists')
  };
}

var assert = require('assert');
var RSVP = require('rsvp');
var fs = require('fs');
var AddonTestApp = require('ember-cli-addon-tests').AddonTestApp;

describe('Acceptance: development build', function() {
  this.timeout(600000);

  var app;

  before(function() {
    app = new AddonTestApp();
  });

  it('generates a development build', function() {
    return app
      .create('dummy', {
        fixturesPath: 'node-tests/acceptance/fixtures',
        // FIXME test fails in travis with latest version of ember-data. Seems like a bug in ember-cli-addon-tests
        emberDataVersion: '2.12.1'
      })
      .then(function() {
        return app.runEmberCommand('build', '-dev')
      })
      .then(assertFileExists(app, 'test.txt'))
  });

  it('generates a production build', function() {
    return app
      .create('dummy', {
        fixturesPath: 'node-tests/acceptance/fixtures',
        // FIXME test fails in travis with latest version of ember-data. Seems like a bug in ember-cli-addon-tests
        emberDataVersion: '2.12.1'
      })
      .then(function() {
        return app.runEmberCommand('build', '-prod')
      })
      .then(assertFileExists(app, 'test.txt'))
  });
});

function assertFileExists(app, filePath) {
  return function() {
    assert.ok(fs.existsSync(app.filePath(filePath)), 'file exists')
  };
}

Increase node acceptance test timeout


var assert = require('assert');
   var h = require('./helpers');

var tests = {

  'before': function () {
    process.chdir('./test');
  },

 'after': function () {
    process.chdir('../');
  },

 'test default task': function (next) {
    h.exec('../bin/cli.js  -q -f Jakefile.publish publish', function (out) {
      var expected = [
            'Fetched remote tags.'
          , 'On branch v0.0'
          , 'Bumped version number to v0.0.2.'
          , 'Created package for zerb v0.0.2'
          , 'Publishing zerb v0.0.2'
          , './pkg/zerb-v0.0.2.tar.gz'
          , 'BOOM! Published.'
          , 'Cleaned up package'
          ].join('\n');
      assert.equal(expected, out);
      next();
    });
  }

};

module.exports = tests;

var assert = require('assert');
   var h = require('./helpers');

var tests = {

  /*
  'before': function () {
    process.chdir('./test');
  },

 'after': function () {
    process.chdir('../');
  },

 'test default task': function (next) {
    h.exec('../bin/cli.js  -q -f Jakefile.publish publish', function (out) {
      var expected = [
            'Fetched remote tags.'
          , 'On branch v0.0'
          , 'Bumped version number to v0.0.2.'
          , 'Created package for zerb v0.0.2'
          , 'Publishing zerb v0.0.2'
          , './pkg/zerb-v0.0.2.tar.gz'
          , 'BOOM! Published.'
          , 'Cleaned up package'
          ].join('\n');
      assert.equal(expected, out);
      next();
    });
  }
  */

};

module.exports = tests;

Comment out PublishTask test until fixed


Package.describe({
  summary: "JavaScript minifier",
  version: "1.2.16-beta.1"
});

Npm.depends({
  "uglify-js": "2.7.5"
});

Npm.strip({
  "uglify-js": ["test/"]
});

Package.onUse(function (api) {
  api.use('underscore', 'server');
  api.export(['UglifyJSMinify', 'UglifyJS']);
  api.addFiles(['minifier.js'], 'server');
});

Package.onTest(function (api) {
  api.use('minifier-js', 'server');
  api.use('tinytest');

  api.addFiles([
    'beautify-tests.js',
  ], 'server');
});

Package.describe({
  summary: "JavaScript minifier",
  version: "1.2.17"
});

Npm.depends({
  "uglify-js": "2.7.5"
});

Npm.strip({
  "uglify-js": ["test/"]
});

Package.onUse(function (api) {
  api.use('underscore', 'server');
  api.export(['UglifyJSMinify', 'UglifyJS']);
  api.addFiles(['minifier.js'], 'server');
});

Package.onTest(function (api) {
  api.use('minifier-js', 'server');
  api.use('tinytest');

  api.addFiles([
    'beautify-tests.js',
  ], 'server');
});

Remove -beta.n suffix from minifier-js before republishing.


var express = require('express');
var app = express();

app.listen(2222);

var express = require('express');
var app = express();

app.get('/api1/:n', function(req, res) {
  res.send([{test: 'test'}, {test: 'test1'}, {test: 'test2'}]);
});

app.listen(2222);

Add route for basic tests.


/* exported configureTripKeyForPath, getTripKeyFromUrl, setProgressBar */
/**
* Sets Progress Bar to correct location based on the page number
* @param {int} pageNumber
*/
function setProgressBar(pageNumber){
  let ul = document.getElementById("progressbar");
  let items = ul.getElementsByTagName("li");
  for(let i=0; i < pageNumber;i++){
    items[i].className = 'active';
  }
}

/**
 * Go through url to retrieve the trip key.
 * @return String of trip key
 */
function getTripKeyFromUrl() {
  return new URLSearchParams(location.search).get('tripKey');
}

/**
 * Uses the trip key passed in to create a modified path from
 * the path parameter. If the trip key is null, redirects the
 * user to the page where they can make a new trip and have
 * a valid trip key to reference.
 * @param {String} tripKey unique value for a trip
 * @param {String} path to send tripKey across
 * @return String of path with tripKey in query params
 */
function configureTripKeyForPath(tripKey, path) {
  if(tripKey === null) {
    // Send back to page where a trip can be made
    alert("tripKey not created! Create a new trip.");
    window.location.href = '/index.html';
    return "";
  } else {
    return path + "?" + new URLSearchParams({tripKey}).toString();
  }
}

/* exported configureTripKeyForPath, getTripKeyFromUrl, setProgressBar */
/**
* Sets Progress Bar to correct location based on the page number
* @param {int} pageNumber
*/
function setProgressBar(pageNumber){
  let ul = document.getElementById("progressbar");
  let items = ul.getElementsByTagName("li");
  for(let i=0; i < pageNumber;i++){
    items[i].className = 'active';
  }
}

/**
 * Go through url to retrieve the trip key. If the trip key is null,
 * redirects the user to the page where they can make a new trip
 * and have a valid trip key to reference.
 * @return String of trip key
 */
function getTripKeyFromUrl() {
  const url = new URLSearchParams(location.search).get('tripKey');
  if(url === null) {
    alert("tripKey not created! Create a new trip.");
    window.location.href = '/index.html';
    return "";
  } else {
    return url;
  }
}

/**
 * Uses the trip key passed in to create a modified path from
 * the path parameter.
 * @param {String} tripKey unique value for a trip
 * @param {String} path to send tripKey across
 * @return String of path with tripKey in query params
 */
function configureTripKeyForPath(tripKey, path) {
  return path + "?" + new URLSearchParams({tripKey}).toString();
}

Check for a valid trip ket when first searching in url


const config = require('../../../../config')
const authorisedRequest = require('../../../lib/authorised-request')

async function fetchEventAttendees ({ token, eventId, page = 1, sortby, limit = 10 }) {
  if (!eventId) {
    return null
  }

  const offset = limit * (page - 1)

  const eventAttendees = await authorisedRequest(token, {
    url: `${config.apiRoot}/v3/interaction`,
    qs: {
      limit,
      offset,
      sortby,
      event_id: eventId,
    },
  })

  return eventAttendees
}

module.exports = {
  fetchEventAttendees,
}

const { isNil, pickBy } = require('lodash')

const config = require('../../../../config')
const authorisedRequest = require('../../../lib/authorised-request')

async function fetchEventAttendees ({ token, eventId, page = 1, sortby, limit = 10, contactId }) {
  if (!eventId) {
    return null
  }

  const offset = limit * (page - 1)

  const eventAttendees = await authorisedRequest(token, {
    url: `${config.apiRoot}/v3/interaction`,
    qs: pickBy({
      limit,
      offset,
      sortby,
      event_id: eventId,
      contact_id: contactId,
    }, value => !isNil(value)),
  })

  return eventAttendees
}

module.exports = {
  fetchEventAttendees,
}

Add lookup attendees by contact as well as event


import React from "react";
import ReactDOMServer from "react-dom/server";
import { Route, StaticRouter } from "react-router";
import config from "config"; // Aliased through webpack
import paths from "../paths";
import BodyContent from "../BodyContent";

// TODO: what if a route isn't found?
module.exports = function renderPage(location, cb) {
  const allPages = paths.getAllPages(config);
  const page = paths.getPageForPath(location, allPages);

  const html = ReactDOMServer.renderToStaticMarkup(
    <StaticRouter location={location} context={{}}>
      <Route component={BodyContent(page, allPages)} />
    </StaticRouter>
  );

  cb(null, { html, page });
};

import React from "react";
import ReactDOMServer from "react-dom/server";
import { Route, StaticRouter } from "react-router";
import config from "config"; // Aliased through webpack
import paths from "../paths";
import BodyContent from "../BodyContent";

// TODO: what if a route isn't found?
module.exports = function renderPage(location, cb) {
  const allPages = paths.getAllPages(config);
  const page = paths.getPageForPath(location, allPages);

  (config.renderPage || renderDefault)(
    {
      location,
      content: BodyContent(page, allPages),
    },
    (err, html) => cb(err, { html, page })
  );
};

function renderDefault({ location, content }, cb) {
  cb(
    null,
    ReactDOMServer.renderToStaticMarkup(
      <StaticRouter location={location} context={{}}>
        <Route component={content} />
      </StaticRouter>
    )
  );
}

Add experimental `renderPage` hook for render customization


Package.describe({
  summary: "Additional tests for Spacebars",
  internal: true
});

// These tests are in a separate package to avoid a circular dependency
// between the `spacebars` and `templating` packages.
Package.on_test(function (api) {
  api.use('underscore');
  api.use('spacebars');
  api.use('tinytest');
  api.use('jquery');
  api.use('test-helpers');
  api.use('showdown');

  api.use('templating', 'client');
  api.add_files([
    'template_tests.html',
    'template_tests.js',
    'templating_tests.html',
    'templating_tests.js'
  ], 'client');

  api.add_files('template_tests_server.js', 'server');

  api.add_files([
    'assets/markdown_basic.html',
    'assets/markdown_if1.html',
    'assets/markdown_if2.html',
    'assets/markdown_each1.html',
    'assets/markdown_each2.html'
  ], 'server', { isAsset: true });
});

Package.describe({
  summary: "Additional tests for Spacebars",
  internal: true
});

// These tests are in a separate package to avoid a circular dependency
// between the `spacebars` and `templating` packages.
Package.on_test(function (api) {
  api.use('underscore');
  api.use('spacebars');
  api.use('tinytest');
  api.use('jquery');
  api.use('test-helpers');
  api.use('showdown');
  api.use('minimongo');

  api.use('templating', 'client');
  api.add_files([
    'template_tests.html',
    'template_tests.js',
    'templating_tests.html',
    'templating_tests.js'
  ], 'client');

  api.add_files('template_tests_server.js', 'server');

  api.add_files([
    'assets/markdown_basic.html',
    'assets/markdown_if1.html',
    'assets/markdown_if2.html',
    'assets/markdown_each1.html',
    'assets/markdown_each2.html'
  ], 'server', { isAsset: true });
});

Add mongo dependency back to template tests


var paddingMin = document.getElementById('slider-padding-value-min'),
	paddingMax = document.getElementById('slider-padding-value-max');

paddingSlider.noUiSlider.on('update', function ( values, handle ) {
	if ( handle ) {
		marginMax.innerHTML = values[handle];
	} else {
		marginMin.innerHTML = values[handle];
	}
});

var paddingMin = document.getElementById('slider-padding-value-min'),
	paddingMax = document.getElementById('slider-padding-value-max');

paddingSlider.noUiSlider.on('update', function ( values, handle ) {
	if ( handle ) {
		paddingMax.innerHTML = values[handle];
	} else {
		paddingMin.innerHTML = values[handle];
	}
});

Fix typo in new docs


import axios from 'axios';

const API_URL = '/api/v1/users';
/**
 * setAuthorizationToken - set token to request headers
 * @param  {string} token Authorization token
 * @return {void} no return or void
 */
export function setAuthorizationToken(token) {
  if (token) {
    axios.defaults.headers.common.Authorization = `Bearer ${token}`;
  } else {
    delete axios.defaults.headers.common.Authorization;
  }
}

export function getUserData(email) {
  return axios.post(`${API_URL}/getemail`, email)
    .then(response => response.data.user)
    .catch(error => error);
}

export function registerGoogleUser(userDetails) {
  return axios.post(`${API_URL}/signup`, userDetails)
    .then((res) => {
      const token = res.data.Token;
      localStorage.setItem('token', token);
      setAuthorizationToken(token);
      window.location.href = '/dashboard';
    });
}

export function editProfile(userId, userData) {
  return axios.put(`${API_URL}/edit/${userId}`, userData)
    .then(() => axios.get(`${API_URL}/userId`)
      .then(res => res.data.token))
    .catch(error => error.data.response);
}

Fix violated ESLINT rule on codeclimate


'use strict';

/*
* When adding modules to the namespace, ensure that they are added in alphabetical order according to module name.
*/

// MODULES //

var setReadOnly = require( '@stdlib/utils/define-read-only-property' );

// MAIN //

/**
* Top-level namespace.
*
* @namespace rules
*/
var rules = {};

/**
* @name empty-line-before-comment
* @memberof rules
* @readonly
* @type {Function}
* @see {@link module:@stdlib/_tools/eslint/rules/empty-line-before-comment}
*/
setReadOnly( rules, 'empty-line-before-comment', require( '@stdlib/_tools/eslint/rules/empty-line-before-comment') );

/**
* @name no-internal-require
* @memberof rules
* @readonly
* @type {Function}
* @see {@link module:@stdlib/_tools/eslint/rules/no-internal-require}
*/
setReadOnly( rules, 'no-internal-require', require( '@stdlib/_tools/eslint/rules/no-internal-require') );

// EXPORTS //

module.exports = rules;

'use strict';

/*
* When adding modules to the namespace, ensure that they are added in alphabetical order according to module name.
*/

// MODULES //

var setReadOnly = require( '@stdlib/utils/define-read-only-property' );

// MAIN //

/**
* Top-level namespace.
*
* @namespace rules
*/
var rules = {};

/**
* @name empty-line-before-comment
* @memberof rules
* @readonly
* @type {Function}
* @see {@link module:@stdlib/_tools/eslint/rules/empty-line-before-comment}
*/
setReadOnly( rules, 'empty-line-before-comment', require( '@stdlib/_tools/eslint/rules/empty-line-before-comment') );

/**
* @name no-internal-require
* @memberof rules
* @readonly
* @type {Function}
* @see {@link module:@stdlib/_tools/eslint/rules/no-internal-require}
*/
setReadOnly( rules, 'no-internal-require', require( '@stdlib/_tools/eslint/rules/no-internal-require') );

/**
* @name require-typed-arrays
* @memberof rules
* @readonly
* @type {Function}
* @see {@link module:@stdlib/_tools/eslint/rules/require-typed-arrays}
*/
setReadOnly( rules, 'require-typed-arrays', require( '@stdlib/_tools/eslint/rules/require-typed-arrays') );

// EXPORTS //

module.exports = rules;

Add lint rule to ESLint rule namespace


export const UPDATE_BRANCH_TITLES = 'UPDATE_BRANCH_TITLES';

export function updateBranchTitles(payload) {
  return {
    type: UPDATE_BRANCH_TITLES,
    // FIX ME put the default branch in here/ render info from whatever is click
    payload: '',
  };
}

export const UPDATE_BRANCH_TITLES = 'UPDATE_BRANCH_TITLES';

/* Cases to handle:

 * branch at initialState is clicked (move deeper into the tree)
 *  branches will come from state.POIs.X where X is
 *  hotel || restaurant || nightlife || point of interest || museum

 * root is clicked (move shallower out of the tree)
 *  branches will come from { initialBranches } from branchTitles_reducer.js

*/

const updateBranchTitles = payload => (
  {
    type: UPDATE_BRANCH_TITLES,
    payload,
  }
);

export default updateBranchTitles;

Clarify cases for updateBranchTitles action and ES6-ify