import urlQueryReducer from './urlQueryReducer';
import urlQueryConfig from '../urlQueryConfig';

/**
 * Middleware to handle updating the URL query params
 */
const urlQueryMiddleware = (options = {}) => ({ getState }) => next => (action) => {
  // if not a URL action, do nothing.
  if (!action.meta || !action.meta.urlQuery) {
    return next(action);
  }

  // otherwise, handle with URL handler -- doesn't go to Redux dispatcher
  // update the URL

  // use the default reducer if none provided
  const reducer = options.reducer || urlQueryConfig.reducer || urlQueryReducer;

  // if configured to read from the redux store (react-router-redux), do so and pass it to
  // the reducer
  const readLocationFromStore = options.readLocationFromStore == null ?
    urlQueryConfig.readLocationFromStore : options.readLocationFromStore;

  if (readLocationFromStore) {
    const location = readLocationFromStore(getState());
    return reducer(action, location);
  }

  return reducer(action);
};

export default urlQueryMiddleware;

import urlQueryReducer from './urlQueryReducer';
import urlQueryConfig from '../urlQueryConfig';

/**
 * Middleware to handle updating the URL query params
 */
const urlQueryMiddleware = (options = {}) => ({ getState }) => next => (action) => {
  // if not a URL action, do nothing.
  if (!action.meta || !action.meta.urlQuery) {
    return next(action);
  }

  // otherwise, handle with URL handler -- doesn't go to Redux dispatcher
  // update the URL

  // use the default reducer if none provided
  const reducer = options.reducer || urlQueryConfig.reducer || urlQueryReducer;

  // if configured to read from the redux store (react-router-redux), do so and pass it to
  // the reducer
  const readLocationFromStore = options.readLocationFromStore == null ?
    urlQueryConfig.readLocationFromStore : options.readLocationFromStore;

  if (readLocationFromStore) {
    const location = readLocationFromStore(getState());
    reducer(action, location);
  } else {
    reducer(action);
  }

  // shortcircuit by default (don't pass to redux store), unless explicitly set
  // to false.
  if (options.shortcircuit === false) {
    return next(action);
  }

  return undefined;
};

export default urlQueryMiddleware;

Make short-circuiting redux store optional


export function isArgumentObj(fn) {
	if (fn.toString().match(/^[^\(]*\(\s*\{/)) {
		return true;

	} else if (fn.toString().match(/^[^\(]*\(\s*_ref\s*\)/)) {
		return true;
	}

	return false;
}

export function isArgumentObj(fn) {
	if (fn.toString().match(/^[^\(]*\(\s*\{/)) {
		return true;

	} else if (fn.toString().match(/^[^\(]*\(\s*(_ref|plugin)\s*\)/)) {
		return true;
	}

	return false;
}

Change isArgumentObj to detect plugin variable name


import { empty } from './util';

/** Global options
 *	@public
 *	@namespace options {Object}
 */
export default {

	/** If `true`, `prop` changes trigger synchronous component updates.
	 *	@name syncComponentUpdates
	 *	@type Boolean
	 *	@default true
	 */
	//syncComponentUpdates: true,

	/** Processes all created VNodes.
	 *	@param {VNode} vnode	A newly-created VNode to normalize/process
	 */
	vnode: empty
};

/** Global options
 *	@public
 *	@namespace options {Object}
 */
export default {

	/** If `true`, `prop` changes trigger synchronous component updates.
	 *	@name syncComponentUpdates
	 *	@type Boolean
	 *	@default true
	 */
	//syncComponentUpdates: true,

	/** Processes all created VNodes.
	 *	@param {VNode} vnode	A newly-created VNode to normalize/process
	 */
	//vnode(vnode) { }
};

Remove default empty `vnode` hook (note: this is technically a breaking change, but very few people use this API and documentation has mentioned performing an existence check on the previous hook prior to calling it for a long time)


'use strict';

var Dispatcher = require('flux').Dispatcher;

module.exports = new Dispatcher();

'use strict';

var Dispatcher = require('flux').Dispatcher;

var ACTION_TYPES = require('../constants/action-types');

var dispatcher = new Dispatcher();

dispatcher.dispatch = function (action) {
    if (action.type in ACTION_TYPES) {
        return Object.getPrototypeOf(this).dispatch.call(this, action);
    }

    throw 'Dispatch error: invalid action type ' + action.type;
};

module.exports = dispatcher;

Throw exception if dispatching undefined action type


var AWS = require('aws-sdk');
var config;

try {
  config = require("./config.js");
} catch(e) {
  console.log(e);
}

AWS.Config({
  accessKeyId: process.env.ACCESS_KEY_ID || config.accessKeyId,
  secretAccessKey:  process.env.SECRET_ACCESS_KEY || config.secretAccessKey,
  region: 'us-west-2'
});

//AWS cloudsearchdomain configuration
exports.cloudsearchdomain = new AWS.CloudSearchDomain({
  endpoint: 'https://search-imorgo-axx2j6p35ftfdsmbwrupypvdy4.us-west-2.cloudsearch.amazonaws.com',
  apiVersion: '2013-01-01'
});

exports.domain = "imorgo-search";

var AWS = require('aws-sdk');
var config;

try {
  config = require("./config.js");
} catch(e) {
  console.log(e);
}

AWS.config.update({
  accessKeyId: process.env.ACCESS_KEY_ID || config.accessKeyId,
  secretAccessKey:  process.env.SECRET_ACCESS_KEY || config.secretAccessKey,
  region: 'us-west-2'
});

//AWS cloudsearchdomain configuration
exports.cloudsearchdomain = new AWS.CloudSearchDomain({
  endpoint: 'https://search-imorgo-axx2j6p35ftfdsmbwrupypvdy4.us-west-2.cloudsearch.amazonaws.com',
  apiVersion: '2013-01-01'
});

exports.domain = "imorgo-search";

Fix setting up AWS config.


import { plugins } from './rollup.config'
import istanbul from 'rollup-plugin-istanbul'

export default config => config.set({
  frameworks: ['mocha', 'chai', 'sinon', 'sinon-chai'],
  reporters: ['mocha', 'coverage'],
  preprocessors: {
    'test/**/*.spec.js': ['rollup']
  },
  files: [
    {
      pattern: 'test/**/*.spec.js',
      watched: false
    },
    {
      pattern: 'test/fixtures/**/*',
      included: false,
      served: true
    }
  ],
  rollupPreprocessor: {
    plugins: [
      ...plugins,
      istanbul({
        include: ['**/*.js'],
        exclude: ['node_modules/**', 'test/**']
      })
    ],
    output: {
      format: 'iife',
      sourcemap: 'inline'
    }
  },
  coverageReporter: {
    reporters: [
      process.env.CI
        ? { type: 'lcovonly', subdir: '.' }
        : { type: 'html' },
      { type: 'text-summary' }
    ]
  }
})

import { plugins } from './rollup.config'
import istanbul from 'rollup-plugin-istanbul'

export default config => config.set({
  client: {
    mocha: {
      timeout: process.env.CI ? 15000 : 3000
    }
  },
  frameworks: ['mocha', 'chai', 'sinon', 'sinon-chai'],
  reporters: ['mocha', 'coverage'],
  preprocessors: {
    'test/**/*.spec.js': ['rollup']
  },
  files: [
    {
      pattern: 'test/**/*.spec.js',
      watched: false
    },
    {
      pattern: 'test/fixtures/**/*',
      included: false,
      served: true
    }
  ],
  rollupPreprocessor: {
    plugins: [
      ...plugins,
      istanbul({
        include: ['**/*.js'],
        exclude: ['node_modules/**', 'test/**']
      })
    ],
    output: {
      format: 'iife',
      sourcemap: 'inline'
    }
  },
  coverageReporter: {
    reporters: [
      process.env.CI
        ? { type: 'lcovonly', subdir: '.' }
        : { type: 'html' },
      { type: 'text-summary' }
    ]
  }
})

Increase mocha timeout to reduce flakyness in CI


var GridStore = require('mongodb').GridStore;

/**
 * @param filename:  filename or ObjectId
 */
var SkinGridStore = exports.SkinGridStore = function(skinDb) {
  this.skinDb = skinDb;
}

/**
 * @param filename: filename or ObjectId
 *  callback(err, gridStoreObject)
 */
SkinGridStore.prototype.open = function(filename, mode, options, callback){
  if(!callback){
    callback = options;
    options = undefined;
  }
  this.skinDb.open(function(err, db) {
      new GridStore(db, filename, mode, options).open(callback);
  });
}

/**
 * @param filename: filename or ObjectId
 */
SkinGridStore.prototype.unlink = SkinGridStore.prototype.remove = function(filename, callback){
  this.skinDb.open(function(err, db) {
      GridStore.unlink(db, filename, callback);
  });
}

SkinGridStore.prototype.exist = function(filename, callback){
  this.skinDb.open(function(err, db) {
      GridStore.exist(db, filename, callback);
  });
}

exports.SkinGridStore = SkinGridStore;

var GridStore = require('mongodb').GridStore;

/**
 * @param filename:  filename or ObjectId
 */
var SkinGridStore = exports.SkinGridStore = function(skinDb) {
  this.skinDb = skinDb;
}

/**
 * @param filename: filename or ObjectId
 *  callback(err, gridStoreObject)
 */
SkinGridStore.prototype.open = function(filename, mode, options, callback){
  if(!callback){
    callback = options;
    options = undefined;
  }
  this.skinDb.open(function(err, db) {
      new GridStore(db, filename, mode, options).open(callback);
  });
}

/**
 * @param filename: filename or ObjectId
 */
SkinGridStore.prototype.unlink = SkinGridStore.prototype.remove = function(filename, callback){
  this.skinDb.open(function(err, db) {
      GridStore.unlink(db, filename, callback);
  });
}

SkinGridStore.prototype.exist = function(filename, rootCollection, callback){
  this.skinDb.open(function(err, db) {
      GridStore.exist(db, filename, rootCollection, callback);
  });
}

exports.SkinGridStore = SkinGridStore;

Add rootCollection option to SkinGridStore.exist


(function(codefest){
    console.log('Get Ready!');

    var canvas = document.getElementById('viewport');
    canvas.width = 640;
    canvas.height = 480;
    var context = canvas.getContext('2d');
    context.fillRect(0, 0, 640, 480);

    var bird = new codefest.Asset('assets/bird.png');
    bird.on('ready', function(){
        context.drawImage(this.image, 0, 0);
    });
})(codefest = codefest || {});

(function(codefest){
    console.log('Get Ready!');

    var canvas = document.getElementById('viewport');
    canvas.width = 640;
    canvas.height = 480;
    var context = canvas.getContext('2d');
    context.fillRect(0, 0, canvas.width, canvas.height);

    var bird = new codefest.Asset('assets/bird.png');
    bird.on('ready', function(){
        context.drawImage(this.image, 0, 0);
    });
})(codefest = codefest || {});

Use canvas width and height


(function(context) {
	var event = new Event('network-ready');
	context.ajax = function(url, data, callback) {
		if (typeof data === 'function') {
			callback = data;
			data = null;
		}
		var xhr = new XMLHttpRequest(),
			ajaxCallback = function() {
				callback(xhr.response, xhr.status, xhr);
				if (xhr.status != null) {
					window.dispatchEvent(event);
				}
			};

		xhr.onload = ajaxCallback;
		xhr.onerror = ajaxCallback;

		xhr.startedAt = new Date();
                xhr.startedAt.setTime(xhr.startedAt.getTime());
                saveTimeStamp(xhr.startedAt);

		xhr.open(data !== null ? 'POST' : 'GET', url);

		xhr.send(data || null);

	};
})(this);

function saveTimeStamp(date){
    var expTime = new Date();
    expTime.setTime(expTime.getTime() + (14*24*60*60*1000)); //14 days in ms.
    document.cookie = "LastAccess" + "=" + date.toUTCString() + "; " + "expires=" + expTime.toUTCString();;
}

function getLatestAccessTimeStamp() {
    var sKey = "LastAccess";
    return document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + sKey + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1") || null;;
}

(function(context) {
	var event = new Event('network-ready');
	context.ajax = function(url, data, callback) {
		if (typeof data === 'function') {
			callback = data;
			data = null;
		}
		var xhr = new XMLHttpRequest(),
			ajaxCallback = function() {
				callback(xhr.response, xhr.status, xhr);
				if (xhr.status !== null) {
					context.dispatchEvent(event);
				}
			};

		xhr.onload = ajaxCallback;
		xhr.onerror = ajaxCallback;

		xhr.startedAt = new Date();
		xhr.startedAt.setTime(xhr.startedAt.getTime());
		saveTimeStamp(xhr.startedAt);

		xhr.open(data !== null ? 'POST' : 'GET', url);

		xhr.send(data || null);

	};

	function saveTimeStamp (date){
		var expTime = new Date();
		expTime.setTime(expTime.getTime() + (14*24*60*60*1000)); //14 days in ms.
		document.cookie = "LastAccess" + "=" + date.toUTCString() + "; " + "expires=" + expTime.toUTCString();;
	}

	context.getLatestAccessTimeStamp = function() {
		var sKey = "LastAccess";
		return document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + sKey + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1") || null;;
	};
})(this);

Reformat library to pull functions in from global scope.


var Context = require('../context');

function getContext(obj) {
  if (!obj) {
    return;
  }

  if (obj instanceof Context) {
    return obj;
  }

  if (obj.context instanceof Context) {
    return obj.context;
  }

  if (obj.__context instanceof Context) {
    return obj.__context;
  }

  if (obj.context && obj.context.martyContext) {
    return obj.context.martyContext;
  }
}

module.exports = getContext;

var Context = require('../context');

function getContext(obj) {
  if (!obj) {
    return;
  }

  if (obj instanceof Context) {
    return obj;
  }

  if (obj.context instanceof Context) {
    return obj.context;
  }

  if (obj.context && obj.context.martyContext) {
    return obj.context.martyContext;
  }
}

module.exports = getContext;

Remove redundant check for obj.__context


'use strict';

class BottomTab extends HTMLElement{

  prepare(name) {
    this.name = name
    this.attached = false
    this.active = false
    this.classList.add('linter-tab')
    this.countSpan = document.createElement('span')
    this.countSpan.classList.add('count')
    this.countSpan.textContent = '0'
    this.innerHTML = this.name + ' '
    this.appendChild(this.countSpan)
    return this
  }

  attachedCallback() {
    this.attached = true
  }

  detachedCallback() {
    this.attached = false
  }

  get active() {
    return this._active
  }

  set active(value) {
    this._active = value
    if (value) {
      this.classList.add('active')
    } else {
      this.classList.remove('active')
    }
  }

  get count(){
    return this._count
  }

  set count(value) {
    this._count = value
    this.countSpan.textContent = value
  }

}

module.exports = BottomTab = document
  .registerElement(
    'linter-bottom-tab',
    {prototype: BottomTab.prototype}
  )

'use strict';

class BottomTab extends HTMLElement{

  prepare(name) {
    this.name = name
    this.attached = false
    this.active = false
    this.classList.add('linter-tab')
    this.countSpan = document.createElement('span')
    this.countSpan.classList.add('count')
    this.countSpan.textContent = '0'
    this.innerHTML = this.name + ' '
    this.appendChild(this.countSpan)
    this.count = 0
    return this
  }

  attachedCallback() {
    this.attached = true
  }

  detachedCallback() {
    this.attached = false
  }

  get active() {
    return this._active
  }

  set active(value) {
    this._active = value
    if (value) {
      this.classList.add('active')
    } else {
      this.classList.remove('active')
    }
  }

  get count(){
    return this._count
  }

  set count(value) {
    this._count = value
    this.countSpan.textContent = value
  }

}

module.exports = BottomTab = document
  .registerElement(
    'linter-bottom-tab',
    {prototype: BottomTab.prototype}
  )

Set a default count value of Bottom Tab


// Person class

var databank = require('databank'),
    Identifier = require('./Identifier'),
    DatabankObject = databank.DatabankObject;

var Person = DatabankObject.subClass("person");

Person.schema = {pkey: "uuid",
                 fields: [],
                 indices: []
                };

Person.fromIdentifier = function(id, callback) {
    Identifier.get(id, function(err, identifier) {
        if (err) {
            callback(err, null);
        } else {
            Person.get(identifier.person, function(err, person) {
                if (err) {
                    callback(err, null);
                } else {
                    callback(null, person);
                }
            });
        }
    });
};

Person.prototype.getIdentifiers = function(callback) {
    var person = this;

    Identifier.search({uuid: person.uuid}, function(err, identifiers) {
        if (err) {
            callback(err, null);
        } else {
            callback(null, identifiers);
        }
    });
};

exports.Person = Person;

// Person class

var databank = require("databank"),
    Identifier = require("./Identifier"),
    uuid = require("node-uuid"),
    DatabankObject = databank.DatabankObject;

var Person = DatabankObject.subClass("person");

Person.schema = {pkey: "uuid",
                 fields: [],
                 indices: []
                };

Person.beforeCreate = function(props, callback) {
    if (!props.uuid) {
        props.uuid = uuid.v4();
    }
    callback(null, props);
};

Person.fromIdentifier = function(id, callback) {
    Identifier.get(id, function(err, identifier) {
        if (err) {
            callback(err, null);
        } else {
            Person.get(identifier.person, function(err, person) {
                if (err) {
                    callback(err, null);
                } else {
                    callback(null, person);
                }
            });
        }
    });
};

Person.prototype.getIdentifiers = function(callback) {
    var person = this;

    Identifier.search({uuid: person.uuid}, function(err, identifiers) {
        if (err) {
            callback(err, null);
        } else {
            callback(null, identifiers);
        }
    });
};

exports.Person = Person;

Add a beforeCreate() hook to automatically add a UUID


angular.module('ls_battle')
.controller('MainController', mainCtrl);

function mainCtrl($http,$timeout,$scope){

  var self = this;

  self.grabData = function() {
    console.log("Getting Data")
    $http.get('http://localhost:8080/api/v1/results').success(function(data){
      self.teams = data.contestants;
      self.tweets = data.tweets;

      console.log('Teams: ', self.teams);
      console.log('Tweets: ', self.tweets);
    })

    $http.get('../data.json').success(function(data){
      self.pics = data.contestants[0].pic;
    })
  }

  setInterval(self.grabData,1000);
}

angular.module('ls_battle')
.controller('MainController', mainCtrl);

function mainCtrl($http,$timeout,$scope){

  var self = this;

  self.grabData = function() {
    console.log("Getting Data")
    $http.get('http://localhost:8080/api/v1/results').success(function(data){
      self.teams = data.contestants;
      self.tweets = data.tweets;

      console.log('Teams: ', self.teams);
      console.log('Tweets: ', self.tweets);
    })

    $http.get('../data.json').success(function(data){
      self.pics = data.contestants[0].pic;
    })
  }

  // run once immediately then start interval
  self.grabData();
  setInterval(self.grabData, 5000);
}

Increase interval and run the initial service call as soon as the controller loads.


module.exports = function(gulp, settings) {
    var appBundlerFn = require("./util-bundlers.js")(gulp, settings).appBundlerFn;

    /**
     * Run all test build tasks.
     **/
    gulp.task('build-tests', ['copy-test-lib', 'bundle-test-lib', 'ui-test'], function () {
        // Lastly, bundle the test code (app/code/test/...).
        return appBundlerFn({
            entries: './app/code/test/testIndex.js',
            paths: 'app/code/util/',
            isDebug: settings.isDebug,
            vendorLibraries: settings.libraryModules,
            destinationName: 'test.bundle.js',
            destinationDir: settings.testOutputPath
        });
    });
};

module.exports = function(gulp, settings) {
    var appBundlerFn = require("./util-bundlers.js")(gulp, settings).appBundlerFn;

    /**
     * Run all test build tasks.
     **/
    gulp.task('build-tests', ['copy-test-lib', 'bundle-test-lib', 'ui-test'], function () {
        // Lastly, bundle the test code (app/code/test/...).
        return appBundlerFn({
            entries: './app/code/test/testIndex.js',
            paths: settings.extraPaths,
            isDebug: settings.isDebug,
            vendorLibraries: settings.libraryModules,
            destinationName: 'test.bundle.js',
            destinationDir: settings.testOutputPath
        });
    });
};

Use extraPaths configuration for browserify


import assert from 'assert';

// We don't have to name functions to call them
assert((x => x + 5)(10) === 15);

// We can assign function values to variables
let square = x => x * x;
let odd = x => Math.abs(x % 2) === 1;
let lessThanTen = x => x < 10;
let twice = (f, x) => f(f(x));

// We can pass function values
assert(twice(square, -3) === 81);
assert(twice(x => x + 1, 5) === 7);

// We can create and return new functions on the fly
function compose(f, g) {
  return x => f(g(x));
}
let isOddWhenSquared = compose(odd, square);
assert(isOddWhenSquared(7));
assert(!isOddWhenSquared(0));

// Several built-in array functions accept functions
let a = [9, 7, 4, -1, 8];
assert(!a.every(odd));
assert(a.some(odd));
assert(a.every(lessThanTen));
assert.deepEqual(a.filter(odd), [9, 7, -1]);
assert.deepEqual(a.map(square), [81, 49, 16, 1, 64]);

import assert from 'assert';

// We don't have to name functions to call them
assert((x => x + 5)(10) === 15);

// There are two styles of function values
let square = x => x * x;
let odd = x => Math.abs(x % 2) === 1;
let lessThanTen = function (x) {return x < 10};
let twice = (f, x) => f(f(x));

// We can pass function values to other functions
assert(twice(square, -3) === 81);
assert(twice(x => x + 1, 5) === 7);

// We can create and return new functions on the fly
function compose(f, g) {
  return x => f(g(x));
}
let isOddWhenSquared = compose(odd, square);
assert(isOddWhenSquared(7));
assert(!isOddWhenSquared(0));

// Array functions help us replace loops
let a = [9, 7, 4, -1, 8];
assert(!a.every(odd));
assert(a.some(odd));
assert(a.every(lessThanTen));
assert.deepEqual(a.filter(odd), [9, 7, -1]);
assert.deepEqual(a.map(square), [81, 49, 16, 1, 64]);

Clean up function examples in JS


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

const GlobalWorkerOptions = Object.create(null);

/**
 * Defines global port for worker process. Overrides the `workerSrc` option.
 * @var {Object}
 */
GlobalWorkerOptions.workerPort =
  GlobalWorkerOptions.workerPort === undefined
    ? null
    : GlobalWorkerOptions.workerPort;

/**
 * A string containing the path and filename of the worker file.
 *
 * NOTE: The `workerSrc` option should always be set, in order to prevent any
 *       issues when using the PDF.js library.
 * @var {string}
 */
GlobalWorkerOptions.workerSrc =
  GlobalWorkerOptions.workerSrc === undefined
    ? ""
    : GlobalWorkerOptions.workerSrc;

export { GlobalWorkerOptions };

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

/**
 * @typedef {Object} GlobalWorkerOptionsType
 * @property {Worker | null} workerPort - Defines global port for worker
 *   process. Overrides the `workerSrc` option.
 * @property {string} workerSrc - A string containing the path and filename
 *   of the worker file.
 *
 *   NOTE: The `workerSrc` option should always be set, in order to prevent any
 *         issues when using the PDF.js library.
 */

/** @type {GlobalWorkerOptionsType} */
const GlobalWorkerOptions = Object.create(null);

GlobalWorkerOptions.workerPort =
  GlobalWorkerOptions.workerPort === undefined
    ? null
    : GlobalWorkerOptions.workerPort;

GlobalWorkerOptions.workerSrc =
  GlobalWorkerOptions.workerSrc === undefined
    ? ""
    : GlobalWorkerOptions.workerSrc;

export { GlobalWorkerOptions };

Use typedef to define the type of GlobalWorkerOptions.


/**
 * Storage (save data to localStorage)
 */

var config = {};

var storage = {
  set : function (key, value, stringify) {
    var isChanged = config.hasOwnProperty(key) && (config[key] !== value);

    if(isChanged) {
      config[key] = value;

      value = stringify ? JSON.stringify(value) : value;
      localStorage.setItem(key, value);
    }
  },

  get : function (key, type) {
    var value;

    if(!config.hasOwnProperty(key)) {
      value = localStorage.getItem(key);
      switch(type) {
        case 'boolean':
          value = value === 'true' ? true : false;
          break;
        case 'object':
          value = JSON.parse(val);
          break;
      }

      config[key] = value;
    }

    return config[key];
  },

  setFile : function (file) {
    if(!file || !file.path) return;
    config.file = file.path;
  },

  getFile : function () {
    return config.file;
  },

  rmFile : function () {
    config.file = null;
  },

  formConfig : function () {
    return extend(config, {});
  }
};

module.exports = storage;

/**
 * Simple extend function for key-value pairs
 */
function extend(a, b) {
  var obj = {};

  Object.getOwnPropertyNames(a).forEach(function (prop) {
    obj[prop] = a[prop];
  });

  Object.getOwnPropertyNames(b).forEach(function (prop) {
    obj[prop] = b[prop];
  });

  return obj;
}

/**
 * Storage (save data to localStorage)
 */

var config = {};

var storage = {
  set : function (key, value, stringify) {
    var isChanged = config.hasOwnProperty(key) && (config[key] !== value);

    if(isChanged) {
      config[key] = value;

      value = stringify ? JSON.stringify(value) : value;
      localStorage.setItem(key, value);
    }
  },

  get : function (key, type) {
    var value;

    if(!config.hasOwnProperty(key)) {
      value = localStorage.getItem(key);
      switch(type) {
        case 'boolean':
          value = value === 'true' ? true : false;
          break;
        case 'object':
          value = JSON.parse(val);
          break;
      }

      config[key] = value;
    }

    return config[key];
  },

  setFile : function (file) {
    if(!file || !file.path) return;
    config.file = file.path;
  },

  getFile : function () {
    return config.file;
  },

  rmFile : function () {
    config.file = null;
  },

  formConfig : function () {
    return extend(config, { filter : '1' });
  }
};

module.exports = storage;

/**
 * Simple extend function for key-value pairs
 */
function extend(a, b) {
  var obj = {};

  Object.getOwnPropertyNames(a).forEach(function (prop) {
    obj[prop] = a[prop];
  });

  Object.getOwnPropertyNames(b).forEach(function (prop) {
    obj[prop] = b[prop];
  });

  return obj;
}

Include dummy filter value to the config


import SaveCommand from './SaveCommand'

export default {
  name: 'persistence',
  configure: function(config) {
    config.addCommand('save', SaveCommand, {
      commandGroup: 'persistence'
    })
    config.addIcon('save', { 'fontawesome': 'fa-save' })
    config.addLabel('save', {
      en: 'Save',
      de: 'Speichern'
    })
  },
  SaveCommand: SaveCommand
}

import SaveCommand from './SaveCommand'

export default {
  name: 'persistence',
  configure: function(config) {
    config.addCommand('save', SaveCommand, {
      commandGroup: 'persistence'
    })
    config.addIcon('save', { 'fontawesome': 'fa-save' })
    config.addLabel('save', {
      en: 'Save',
      de: 'Speichern'
    })
    config.addKeyboardShortcut('CommandOrControl+S', { command: 'save' })

  },
  SaveCommand: SaveCommand
}

Add keyboard shortcut for saving.


module.exports = {
  browsers: [
    // These are the browsers we actually are attempting to support:
    'last 2 Chrome versions',
    'last 1 Firefox version',
    'Firefox ESR',
    'last 1 Safari version',
    'last 1 iOS version',
    'last 1 Edge version',
    'last 1 UCAndroid version',
    'last 1 years',
  ],
};

module.exports = {
  browsers: [
    // These are the browsers we actually are attempting to support:
    'last 2 Chrome versions',
    'last 1 Firefox version',
    'Firefox ESR',
    'last 1 Safari version',
    'last 1 iOS version',
    'last 1 Edge version',
    'last 1 UCAndroid version',
    'last 1 years',
    // Edge for Android is currently (2021-08-18) using an outdated Chromium version (v77),
    // which is not recognized by `browserslist` and `caniuse`. This can be removed once
    // Edge for Android has promoted their "Edge for Android Beta" app to production, which
    // is using a more up-to-date Chromium version.
    // (see https://github.com/rust-lang/crates.io/issues/3838)
    'Chrome 77',
  ],
};

Fix "Edge for Android" compatibility


'use strict';

const _ = require('lodash');
const Fun = require('./function');
const pathToControllers = process.env.NODE_ENV === 'test' ? '../test/fixtures/controllers/' : '../../app/controllers/';
const pathToRouteConfig = process.env.NODE_ENV === 'test' ? '../test/fixtures/config/routes' : '../config/routes';
const routes = require(pathToRouteConfig);

module.exports = {
  bindRoute: function (app) {
    let routesGroupedByController = _.transform(routes, (result, value, key) => {
      let controller = value.split('.')[0];
      let action = value.split('.')[1];
      let method = key.split(' ')[0];
      let path = key.split(' ')[1];
      let route = {
        method: method,
        path: path,
        action: require(pathToControllers + controller)[action]
      };

      if (_.isArray(result[controller])) {
        result[controller].push(route);
      } else {
        result[controller] = [route];
      }
      return result;
    });

    _.forIn(routesGroupedByController, (routeConfigs) => {
      routeConfigs.forEach((config) => {
        app[config.method](config.path, Fun.generatorToCallback(config.action));
      });
    });
  }
};

'use strict';

const _ = require('lodash');
const Fun = require('./function');

module.exports = {
  bindRoute: function (app) {
    const pathToControllers = process.env.NODE_ENV === 'test' ? '../test/fixtures/controllers/' : '../../app/controllers/';
    const pathToRouteConfig = process.env.NODE_ENV === 'test' ? '../test/fixtures/config/routes' : '../config/routes';
    let routes = require(pathToRouteConfig);

    let routesGroupedByController = _.transform(routes, (result, value, key) => {
      let controller = value.split('.')[0];
      let action = value.split('.')[1];
      let method = key.split(' ')[0];
      let path = key.split(' ')[1];
      let route = {
        method: method,
        path: path,
        action: require(pathToControllers + controller)[action]
      };

      if (_.isArray(result[controller])) {
        result[controller].push(route);
      } else {
        result[controller] = [route];
      }
      return result;
    });

    _.forIn(routesGroupedByController, (routeConfigs) => {
      routeConfigs.forEach((config) => {
        app[config.method](config.path, Fun.generatorToCallback(config.action));
      });
    });
  }
};

Define paths inside of function.


'use strict'

let routes = function(server, app) {
  server.route({
    method: 'POST',
    path: '/alexa',
    config: {
      handler: function(request, response) {
        let retVal = app.handleRequest(request)
console.warn("RET VAL", retVal)
        response({
          version: "0.1.0",
          sessionAttributes: {},
          response: {
            outputSpeech: {
              type: "PlainText",
              text: "Done"
            }
          }
        })
      }
    }
  })
}

module.exports = routes

'use strict'

let routes = function(server, app) {
  server.route({
    method: 'POST',
    path: '/alexa',
    config: {
      handler: function(request, response) {
        let retVal = app.handleRequest(request)
console.warn("RET VAL", retVal) // TODO - Remove once you actually make use of the response
        response({
          version: "0.1.0",
          sessionAttributes: {},
          response: {
            outputSpeech: {
              type: "PlainText",
              text: "Done"
            }
          }
        })
      }
    }
  })
}

module.exports = routes

Add comment to debug line


const Promise = require('bluebird');
const pathLib = require('path');
const express = require('express');
const compression = require('compression');
const tiles = require('./tiles');
const info = require('./info');

module.exports.init = function init(opts) {
  return Promise.try(() => {
    const router = express.Router();
    const handlers = opts.requestHandlers || [];

    handlers.unshift(tiles, info);
    return Promise.mapSeries(handlers, reqHandler => reqHandler(opts.core, router)).return(router);
  }).then((router) => {
    // Add before static to prevent disk IO on each tile request
    const { app } = opts;
    const staticOpts = {
      setHeaders(res) {
        if (app.conf.cache) {
          res.header('Cache-Control', app.conf.cache);
        }
        if (res.req.originalUrl.endsWith('.pbf')) {
          res.header('Content-Encoding', 'gzip');
        }
      },
    };

    app.use('/', router);

    // Compression is nativelly handled by the tiles, so only statics need its
    app.use(compression());
    app.use('/', express.static(pathLib.resolve(__dirname, '../static'), staticOpts));
    app.use('/leaflet', express.static(pathLib.dirname(require.resolve('leaflet')), staticOpts));

    opts.core.metrics.increment('init');
  });
};

const Promise = require('bluebird');
const pathLib = require('path');
const express = require('express');
const compression = require('compression');
const tiles = require('./tiles');
const info = require('./info');

module.exports.init = function init(opts) {
  return Promise.try(() => {
    const router = express.Router();
    const handlers = opts.requestHandlers || [];

    handlers.unshift(tiles, info);
    return Promise.mapSeries(handlers, reqHandler => reqHandler(opts.core, router)).return(router);
  }).then((router) => {
    // Add before static to prevent disk IO on each tile request
    const { app } = opts;
    const staticOpts = {
      setHeaders(res) {
        if (app.conf.cache) {
          res.header('Cache-Control', app.conf.cache);
        }
        if (res.req.originalUrl.endsWith('.pbf')) {
          res.header('Content-Encoding', 'gzip');
        }
      },
    };

    app.use('/', router);

    // Compression is nativelly handled by the tiles, so only statics need its
    app.use(compression({ threshold: 0 }));
    app.use('/', express.static(pathLib.resolve(__dirname, '../static'), staticOpts));
    app.use('/leaflet', express.static(pathLib.dirname(require.resolve('leaflet')), staticOpts));

    opts.core.metrics.increment('init');
  });
};

Compress even if payload is small


var vows = require('vows')
  , assert = require('assert')
  , mongoose = require('mongoose')
  , timestamp = require('../lib/addCreatedAndModified')
  , util = require('util')

// DB setup
mongoose.connect('mongodb://localhost/mongoose_troop')

// Setting up test schema
var Schema = mongoose.Schema
  , ObjectId = Schema.ObjectId

var BlogPost = new Schema({
    author : String
  , title  : String
  , body   : String
})

// Registering the timestamp plugin with mongoose
// Note: Must be defined before creating schema object
mongoose.plugin(timestamp,{debug: true})

var Blog = mongoose.model('BlogPost',BlogPost)

vows.describe('Add create and modified').addBatch({
  'when this plugin registered by default':{
    topic: function(){
      var blog = new Blog()
      blog.author = "butu5"
      blog.title = "Mongoose troops!!! timestamp plugin "
      blog.save(this.callback)
    },

    'it should create created and modified attribute': function(topic){
      assert.equal(util.isDate(topic.created),true)
      assert.equal(util.isDate(topic.modified),true)
    }
  }
}).run()

var vows = require('vows')
  , assert = require('assert')
  , mongoose = require('mongoose')
  , timestamp = require('../lib/addCreatedAndModified')
  , util = require('util')

// DB setup
mongoose.connect('mongodb://localhost/mongoose_troop')

// Setting up test schema
var Schema = mongoose.Schema
  , ObjectId = Schema.ObjectId

var BlogPost = new Schema({
    author : String
  , title  : String
  , body   : String
})

// Registering the addCreatedAndModified plugin with mongoose
// Note: Must be defined before creating schema object
mongoose.plugin(timestamp,{debug: true})

var Blog = mongoose.model('BlogPost',BlogPost)

vows.describe('Add create and modified').addBatch({
  'when this plugin registered by default':{
    topic: function(){
      var blog = new Blog()
      blog.author = "butu5"
      blog.title = "Mongoose troops!!! timestamp plugin "
      blog.save(this.callback)
    },

    'it should create created and modified attribute': function(topic){
      assert.equal(util.isDate(topic.created),true)
      assert.equal(util.isDate(topic.modified),true)
    }
  }
}).run()

Change the comment from timestamp plugin to addCreatedAndModified plugin


//When page loadded, read data and URL state, if any
$( document ).ready(load_data());

function page_main(){
  add_goals_sidebar();
  add_goals();
}

function add_goals_sidebar(){
  $('#sidebar').addClass("active");
  var sdgList = document.getElementById("sidebar");
  var goals=sdgs.goals.goals;
  for (var i in goals){
    var goal= goals[i];
    append('a',sdgList,"",""," "+goal.goal+": "+goal.short+".","#goal-"+goal.goal,"");
  }
}

function add_goals(){
  var sdgList = document.getElementById("dashboard-content");
  var goals=sdgs.goals.goals;
  for (var i in goals){
    var goal= goals[i];
    append('div',sdgList,"goal-"+goal.goal,"h3"," "+goal.goal+": "+goal.title+".");
  }
}

//When page loadded, read data and URL state, if any
$( document ).ready(load_data());

function page_main(){
  add_goals_sidebar();
  add_goals();
  load_vizrefs();
}

function add_goals_sidebar(){
  $('#sidebar').addClass("active");
  var sdgList = document.getElementById("sidebar");
  var goals=sdgs.goals.goals;
  for (var i in goals){
    var goal= goals[i];
    append('a',sdgList,"",""," "+goal.goal+": "+goal.short+".","#goal-"+goal.goal,"");
  }
}

function add_goals(){
  var sdgList = document.getElementById("dashboard-content");
  var goals=sdgs.goals.goals;
  for (var i in goals){
    var goal= goals[i];
    append('div',sdgList,"goal-"+goal.goal,"h3"," "+goal.goal+": "+goal.title+".");
  }
}

var visualizations={};
function load_vizrefs(){
  // Load Visualizations references from the json file
  d3.json("/web/assets/viz-101.json", function (error, data) {
    var viz=data.visualizations[0];
    var vizHook = document.getElementById("goal-"+viz.goal);
    console.log(viz,vizHook);
    if (vizHook === null){
      throw new Error("Something went badly wrong!");
    }
    //append(htype,hookElement,id,classes,value,link)
    append('div',vizHook,"viz-"+viz.goal,"",viz["viz-title"]);
  });
}

Load viz and place on goal


var gulp = require('gulp'),
  sass = require('gulp-sass'),
  neat = require('node-neat'),
  styleguide = require('./lib/styleguide'),
  source = 'lib/app/**/*.scss',
  outputPath = 'demo-output';

gulp.task('styleguide:generate', function() {
  return gulp.src(source)
    .pipe(styleguide.generate({
        title: 'SC5 Styleguide',
        server: true,
        rootPath: outputPath,
        overviewPath: 'README.md',
        styleVariables: 'lib/app/sass/_styleguide_variables.scss'
      }))
    .pipe(gulp.dest(outputPath));
});

gulp.task('styleguide:applystyles', function() {
  return gulp.src('lib/app/sass/app.scss')
    .pipe(sass({
      errLogToConsole: true,
      includePaths: neat.includePaths
    }))
    .pipe(styleguide.applyStyles())
    .pipe(gulp.dest(outputPath));
});

gulp.task('styleguide', ['styleguide:static', 'styleguide:generate', 'styleguide:applystyles']);

gulp.task('styleguide:static', function() {
  gulp.src(['lib/demo/**'])
    .pipe(gulp.dest(outputPath + '/demo'));
});

gulp.task('watch', ['styleguide'], function() {
  // Start watching changes and update styleguide whenever changes are detected
  gulp.watch(source, ['styleguide']);
});

var gulp = require('gulp'),
  sass = require('gulp-sass'),
  neat = require('node-neat'),
  styleguide = require('./lib/styleguide'),
  source = 'lib/app/**/*.scss',
  outputPath = 'demo-output';

gulp.task('styleguide:generate', function() {
  return gulp.src(source)
    .pipe(styleguide.generate({
        title: 'SC5 Styleguide',
        server: true,
        rootPath: outputPath,
        overviewPath: 'README.md',
        styleVariables: 'lib/app/sass/_styleguide_variables.scss'
      }))
    .pipe(gulp.dest(outputPath));
});

gulp.task('styleguide:applystyles', function() {
  return gulp.src('lib/app/sass/styleguide-app.scss')
    .pipe(sass({
      errLogToConsole: true,
      includePaths: neat.includePaths
    }))
    .pipe(styleguide.applyStyles())
    .pipe(gulp.dest(outputPath));
});

gulp.task('styleguide', ['styleguide:static', 'styleguide:generate', 'styleguide:applystyles']);

gulp.task('styleguide:static', function() {
  gulp.src(['lib/demo/**'])
    .pipe(gulp.dest(outputPath + '/demo'));
});

gulp.task('watch', ['styleguide'], function() {
  // Start watching changes and update styleguide whenever changes are detected
  gulp.watch(source, ['styleguide']);
});

Rename the resultant CSS file to fix the empty demo problem


import React, {Component} from 'react'
import {connect} from 'react-redux'
import Nav from './shared/Nav'

class App extends Component {
  render () {
    return (
      <div>
        <Nav loggedIn={this.props.state.reducer.loggedIn}
          currentlySending={this.props.state.reducer.awaitingResponse}
          history={this.props.history}
          dispatch={this.props.dispatch}
          location={this.props.location} />
        {this.props.children}
      </div>
    )
  }
}

App.propTypes = {
  state: React.PropTypes.object,
  history: React.PropTypes.object,
  location: React.PropTypes.object,
  children: React.PropTypes.object,
  dispatch: React.PropTypes.func
}

function select (state) {
  return {
    state: state
  }
}

export default connect(select)(App)

import React, {Component} from 'react'
import {connect} from 'react-redux'
import Nav from './shared/Nav'
import LinearProgress from 'material-ui/LinearProgress';

class App extends Component {
  render () {
    return (
      <div>
        {this.props.state.reducer.awaitingResponse ? <LinearProgress color= 'white' mode="indeterminate" style={{backgroundColor: 'black'}} /> : null }
        <Nav loggedIn={this.props.state.reducer.loggedIn}
          currentlySending={this.props.state.reducer.awaitingResponse}
          history={this.props.history}
          dispatch={this.props.dispatch}
          location={this.props.location} />
        {this.props.children}
      </div>
    )
  }
}

App.propTypes = {
  state: React.PropTypes.object,
  history: React.PropTypes.object,
  location: React.PropTypes.object,
  children: React.PropTypes.object,
  dispatch: React.PropTypes.func
}

function select (state) {
  return {
    state: state
  }
}

export default connect(select)(App)

Add loading bar to overall app


function GameOfLife(params) {
  params = params || {};
}

GameOfLife.prototype.getCols = function() {
}

GameOfLife.prototype.getRows = function() {
}

module.exports = GameOfLife;

function GameOfLife(params) {
  params = params || {};
  if (Object.keys(params).length == 0) {
    params.cols = 5;
    params.rows = 5;
  }
  this.init(params);
}

GameOfLife.prototype.init = function(params) {
  params = params || {};

  if (params.cols && params.rows) {
    this._cols = params.cols;
    this._rows = params.rows;
  } else

  throw new Error('Invalid parameters: ' + JSON.stringify(params));
}

GameOfLife.prototype.getCols = function() {
  return this._cols;
}

GameOfLife.prototype.getRows = function() {
  return this._rows;
}

module.exports = GameOfLife;

Implement getCols(), getRows() and Constructor.


'use strict';

var authHeader = require('auth-header');
var tokenUtils = require('./token');

module.exports = function (options) {
  return function (req, res, next) {
    var authorization = req.get('authorization') || req.query.token;

    req.challenge = authorization;

    var auth = authHeader.parse(authorization);

    if (auth && auth.values && auth.values.length) {
      auth = auth.values[0];

      if (auth.scheme !== 'Bearer') {
        return res.status(400).json('Invalid authorization scheme, expected \'Bearer\'');
      }

      tokenUtils.decode(auth.token, options, function (err, decoded) {
        if (err) {
          console.error(err);
          req.authenticated = false;
          req.authentication = err;
        }
        else {
          req.authenticated = true;
          req.authentication = decoded;
        }

        next();
      });
    }
    else {
      return res.status(400).json('Authorization token is missing');
    }
  };
};

'use strict';

var authHeader = require('auth-header');
var tokenUtils = require('./token');

module.exports = function (options) {
  return function (req, res, next) {
    var authorization = req.get('authorization') || 'Bearer ' + req.query.token;

    req.challenge = authorization;

    var auth = authHeader.parse(authorization);

    if (auth && auth.values && auth.values.length) {
      auth = auth.values[0];

      if (auth.scheme !== 'Bearer') {
        return res.status(400).json('Invalid authorization scheme, expected \'Bearer\'');
      }

      tokenUtils.decode(auth.token, options, function (err, decoded) {
        if (err) {
          console.error(err);
          req.authenticated = false;
          req.authentication = err;
        }
        else {
          req.authenticated = true;
          req.authentication = decoded;
        }

        next();
      });
    }
    else {
      return res.status(400).json('Authorization token is missing');
    }
  };
};

Add bearer to qp token


import {
    WS_INSERT_JOB,
    WS_UPDATE_JOB,
    WS_REMOVE_JOB,
    FIND_JOBS,
    GET_JOB,
    GET_RESOURCES,
    GET_LINKED_JOB
} from "../app/actionTypes";
import { updateDocuments, insert, update, remove } from "../utils/reducers";

export const initialState = {
    documents: null,
    term: "",
    page: 0,
    total_count: 0,
    detail: null,
    filter: "",
    fetched: false,
    refetchPage: false,
    resources: null,
    linkedJobs: {}
};

export default function jobsReducer(state = initialState, action) {
    switch (action.type) {
        case WS_INSERT_JOB:
            return insert(state, action, "created_at");

        case WS_UPDATE_JOB:
            return update(state, action, "created_at");

        case WS_REMOVE_JOB:
            return remove(state, action);

        case GET_LINKED_JOB.SUCCEEDED:
            return { ...state, linkedJobs: { ...state.linkedJobs, [action.data.id]: action.data } };

        case FIND_JOBS.REQUESTED:
            return {
                ...state,
                term: action.term
            };

        case FIND_JOBS.SUCCEEDED:
            return updateDocuments(state, action, "created_at");

        case GET_JOB.REQUESTED:
            return { ...state, detail: null };

        case GET_JOB.SUCCEEDED:
            return { ...state, detail: action.data };

        case GET_RESOURCES.SUCCEEDED:
            return { ...state, resources: action.data };

        default:
            return state;
    }
}

import {
    WS_INSERT_JOB,
    WS_UPDATE_JOB,
    WS_REMOVE_JOB,
    FIND_JOBS,
    GET_JOB,
    GET_RESOURCES,
    GET_LINKED_JOB
} from "../app/actionTypes";
import { updateDocuments, insert, update, remove } from "../utils/reducers";

export const initialState = {
    documents: null,
    term: "",
    page: 0,
    total_count: 0,
    detail: null,
    filter: "",
    fetched: false,
    resources: null,
    linkedJobs: {}
};

export default function jobsReducer(state = initialState, action) {
    switch (action.type) {
        case WS_INSERT_JOB:
            return insert(state, action, "created_at");

        case WS_UPDATE_JOB:
            return update(state, action, "created_at");

        case WS_REMOVE_JOB:
            return remove(state, action);

        case GET_LINKED_JOB.SUCCEEDED:
            return { ...state, linkedJobs: { ...state.linkedJobs, [action.data.id]: action.data } };

        case FIND_JOBS.REQUESTED:
            return {
                ...state,
                term: action.term
            };

        case FIND_JOBS.SUCCEEDED:
            return updateDocuments(state, action, "created_at");

        case GET_JOB.REQUESTED:
            return { ...state, detail: null };

        case GET_JOB.SUCCEEDED:
            return { ...state, detail: action.data };

        case GET_RESOURCES.SUCCEEDED:
            return { ...state, resources: action.data };

        default:
            return state;
    }
}

Remove unused jobs state property refetchPage


define([
  'Class', 'uri', 'bluebird',
  'map/services/DataService'
], function(Class, UriTemplate, Promise, ds) {

  'use strict';

  var GET_REQUEST_ID = 'GeostoreService:get',
      SAVE_REQUEST_ID = 'GeostoreService:save';

  var URL = window.gfw.config.GFW_API_HOST + '/geostore/{id}';

  var GeostoreService = Class.extend({

    get: function(id) {
      return new Promise(function(resolve, reject) {

      var url = new UriTemplate(URL).fillFromObject({id: id});

      ds.define(GET_REQUEST_ID, {
        cache: {type: 'persist', duration: 1, unit: 'days'},
        url: url,
        type: 'GET'
      });

      var requestConfig = {
        resourceId: GET_REQUEST_ID,
        success: resolve
      };

      ds.request(requestConfig);

      });
    },

    save: function(geojson) {
      return new Promise(function(resolve, reject) {

      var url = new UriTemplate(URL).fillFromObject({});

      ds.define(SAVE_REQUEST_ID, {
        url: url,
        type: 'POST'
      });

      var params = { geojson: geojson };
      var requestConfig = {
        resourceId: SAVE_REQUEST_ID,
        data: JSON.stringify(params),
        success: function(response) {
          resolve(response.id);
        },
        error: reject
      };

      ds.request(requestConfig);

      });
    }

  });

  return new GeostoreService();

});

define([
  'Class', 'uri', 'bluebird',
  'map/services/DataService'
], function(Class, UriTemplate, Promise, ds) {

  'use strict';

  var GET_REQUEST_ID = 'GeostoreService:get',
      SAVE_REQUEST_ID = 'GeostoreService:save';

  var URL = window.gfw.config.GFW_API_HOST + '/geostore/{id}';

  var GeostoreService = Class.extend({

    get: function(id) {
      return new Promise(function(resolve, reject) {

      var url = new UriTemplate(URL).fillFromObject({id: id});

      ds.define(GET_REQUEST_ID, {
        cache: {type: 'persist', duration: 1, unit: 'days'},
        url: url,
        type: 'GET'
      });

      var requestConfig = {
        resourceId: GET_REQUEST_ID,
        success: resolve
      };

      ds.request(requestConfig);

      });
    },

    save: function(geojson) {
      return new Promise(function(resolve, reject) {

      var url = new UriTemplate(URL).fillFromObject({});

      ds.define(SAVE_REQUEST_ID, {
        url: url,
        type: 'POST'
      });

      var requestConfig = {
        resourceId: SAVE_REQUEST_ID,
        data: JSON.stringify(geojson),
        success: function(response) {
          resolve(response.id);
        },
        error: reject
      };

      ds.request(requestConfig);

      });
    }

  });

  return new GeostoreService();

});

Update to work with new Geostore API


(function() {
  var OgpParser;

  OgpParser = (typeof exports !== "undefined" && exports !== null) && exports || (this.OgpParser = {});

  OgpParser.execute = function($) {
    return {
      title: $("meta[property='og:title']").first().attr("content"),
      summary: $("meta[property='og:description']").first().attr("content"),
      image: $("meta[property='og:image']").first().attr("content"),
      language: $("meta[property='og:locale']").first().attr("content")
    };
  };

}).call(this);

(function() {
  var OgpParser;

  OgpParser = (typeof exports !== "undefined" && exports !== null) && exports || (this.OgpParser = {});

  OgpParser.execute = function($) {
    return {
      title: $("meta[property='og:title']").first().attr("content"),
      summary: $("meta[property='og:description']").first().attr("content"),
      image: $("meta[property='og:image']").first().attr("content"),
      language: $("meta[property='og:locale']").first().attr("content"),
      site: $("meta[property='og:site_name']").first().attr("content")
    };
  };

}).call(this);

Add og:site_name to opengraph parser


var banks = {};

module.exports = function(infoHash) {

	var newObj = {};

	newObj.create = function(engine) {
		banks[engine.infoHash] = {
			downloaded: 0,
			total: engine.torrent.pieces.length,
			pieceLength: engine.torrent.pieceLength,
			map: Array.apply(null, Array(engine.torrent.pieces.length)).map(function () { return false })
		};
	};

	newObj.clear = function() {
		delete banks[infoHash];
	};

	newObj.update = function(piece) {
		banks[infoHash].downloaded++;
		banks[infoHash].map[piece] = true;
	};

	newObj.filePercent = function(offset, length) {
		var distance = Math.ceil(length / banks[infoHash].pieceLength);
		offset = Math.floor(offset / banks[infoHash].pieceLength) -1;
		var downloaded = 0;
		for (var i = offset; i <= offset + distance && i < banks[infoHash].total; i++) {
			if (banks[infoHash].map[i])
				downloaded++;
		}
		return downloaded / distance;
	};

	newObj.get = function() {
		return banks[infoHash];
	};

	return newObj;
};

var events = require('events')
var banks = {};

module.exports = function(infoHash) {

	var newObj = {};

	newObj.create = function(engine) {
		banks[engine.infoHash] = {
			downloaded: 0,
			total: engine.torrent.pieces.length,
			ev: new events.EventEmitter(),
			pieceLength: engine.torrent.pieceLength,
			map: Array.apply(null, Array(engine.torrent.pieces.length)).map(function () { return false })
		};
	};

	newObj.clear = function() {
		delete banks[infoHash];
	};

	newObj.update = function(piece) {
		banks[infoHash].downloaded++;
		banks[infoHash].map[piece] = true;
		if (banks[infoHash].downloaded == banks[infoHash].total)
			banks[infoHash].ev.emit('completed')
	};

	newObj.filePercent = function(offset, length) {
		var distance = Math.ceil(length / banks[infoHash].pieceLength);
		offset = Math.floor(offset / banks[infoHash].pieceLength) -1;
		var downloaded = 0;
		for (var i = offset; i <= offset + distance && i < banks[infoHash].total; i++) {
			if (banks[infoHash].map[i])
				downloaded++;
		}
		return downloaded / distance;
	};

	newObj.get = function() {
		return banks[infoHash];
	};

	return newObj;
};

Add Completed Event to Piece Bank


var buster = typeof window !== 'undefined' ? window.buster : require('buster');
var unhandledRejection = require('../lib/decorators/unhandledRejection');

buster.testCase('unhandledRejection', {

	'should not fail if JSON.stringify throws': function() {
		var fixture = unhandledRejection({});
		var circle = {};
		circle.self = circle;

		buster.refute.exception(function() {
			fixture.onPotentiallyUnhandledRejection({ handled: false, value: circle });
		});
	}

});

var buster = typeof window !== 'undefined' ? window.buster : require('buster');
var unhandledRejection = require('../lib/decorators/unhandledRejection');

buster.testCase('unhandledRejection', {

	'should not fail if JSON.stringify throws': function() {
		var fixture = unhandledRejection({});
		var circle = { self: void 0 };
		circle.self = circle;

		buster.refute.exception(function() {
			fixture.onPotentiallyUnhandledRejection({
				id: 'JSON.stringify circular ref test',
				handled: false,
				value: circle
			});
		});
	}

});

Make fake error id more recognizable


var match = function(message)
{
    return (message.text.toLowerCase().indexOf("calculate") == 0 );
}

var calculate = function(message)
{
    text = message.text;
    return text;
}

module.exports =
{
    match: match,
    execute: calculate
}

Add newline to end of file.


export const initialState = {
  isOpen: false,
  requiredError: false,
  downloadUrl: '',
  downloadSize: ''
};

const setModalDownloadParams = (state, { payload }) => ({
  ...state,
  isOpen: payload.open,
  downloadUrl: payload.downloadUrl,
  downloadSize: payload.size
});

const setRequiredFieldsError = (state, { payload }) => {
  let isFieldRequiredEmpty = 0;
  ['firstName', 'lastName', 'organization'].forEach(f => {
    if (!payload[f] || payload[f] === '') {
      isFieldRequiredEmpty += 1;
    }
  });
  return {
    ...state,
    requiredError: isFieldRequiredEmpty > 0
  };
};

const toggleModalDownload = (state, { payload }) => ({
  ...state,
  isOpen: payload.open
});

export default {
  setModalDownloadParams,
  setRequiredFieldsError,
  toggleModalDownload
};

export const initialState = {
  isOpen: false,
  requiredError: false,
  downloadUrl: '',
  downloadSize: ''
};

const setModalDownloadParams = (state, { payload }) => ({
  ...state,
  isOpen: payload.open,
  downloadUrl: payload.downloadUrl,
  downloadSize: payload.size
});

const setRequiredFieldsError = (state, { payload }) => {
  const requiredFields = ['firstName', 'lastName', 'organization'];
  const isFieldRequiredEmpty = requiredFields.some(
    f => !payload[f] || payload[f] === ''
  );
  return {
    ...state,
    requiredError: isFieldRequiredEmpty
  };
};

const toggleModalDownload = (state, { payload }) => ({
  ...state,
  isOpen: payload.open
});

export default {
  setModalDownloadParams,
  setRequiredFieldsError,
  toggleModalDownload
};

Change forEach for some loop


function Controller (view){
  var user_questions = [];
  this.view = view;

  $.ajax({
    url: '/questions/index',
    method: 'GET',
    dataType: 'json'
  }).done(function(data){
    user_questions = data['questions'];
    view.updateQuestions(user_questions);
  });

  $(".button").click(function(e){
    e.preventDefault();
    $('#question_form').show();
    $('.block_screen').show();
  });

  $('.block_screen').click(function(e){
    e.preventDefault();
    view.clearModal();
  });

  $('button').click(function(e){
    e.preventDefault();
    var question = view.getQuestion($("form.edit_question input[name='question[question]']").val(), user_questions);
    if ($(this).attr('name') === 'delete'){
      view.deleteQuestion(question);
    } else if (($(this).attr('name') === 'push_edit')){
      view.editQuestion(question);
    } else if (($(this).attr('name') === 'activate')){

    }
  });

  $('form.post_question').submit( function(e){
    e.preventDefault();
    view.postQuestion(this, user_questions);
  });

  $('.container').on('click', '.edit', function(e){
    e.preventDefault();
    var question = view.getQuestion($(this).text(), user_questions);
    var attribute = $(this).closest('div').parent().closest('div').attr('id');
    if (attribute === 'coach') {
      view.showEditQuestion(question, 'answer');
    } else {
      view.showEditQuestion(question, 'edit');
    }
  });

}

Set Up New Event Handlers To Watch Button Clicks


import includes from 'lodash/includes';
import map from 'lodash/map';
import reduce from 'lodash/reduce';
import keys from 'lodash/keys';
import isEmpty from 'lodash/isEmpty';

export const getFieldsMeta = (schema, getFieldMeta) => {
  const fields = keys(schema.fields);

  return reduce(fields, (result, field) => {
    const fieldMeta = getFieldMeta(field);
    result[field] = {
      ...fieldMeta,
      valid: (!isEmpty(fieldMeta.value) || fieldMeta.touched) && !fieldMeta.error,
    };
    return result;
  }, {});
};

export const fieldsAreValid = (fieldNames, fieldsMeta) => !includes(map(fieldNames, fieldName => fieldsMeta[fieldName].valid), false);

import includes from 'lodash/includes';
import map from 'lodash/map';
import reduce from 'lodash/reduce';
import keys from 'lodash/keys';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';

export const getFieldsMeta = (schema, getFieldMeta) => {
  const fieldKeys = keys(schema.fields);

  const reduceFields = (_fieldKeys, prefix = '') => reduce(_fieldKeys, (result, field) => {
    const nestedFields = get(schema.fields, `${field}.fields`);

    if (nestedFields) {
      // Recurse for nested field keys
      result[field] = reduceFields(keys(nestedFields), `${field}.`);
    } else {
      const fieldKey = `${prefix}${field}`;
      const fieldMeta = getFieldMeta(fieldKey);
      result[field] = {
        ...fieldMeta,
        valid: (!isEmpty(fieldMeta.value) || fieldMeta.touched) && !fieldMeta.error,
      };
    }

    return result;
  }, {});

  return reduceFields(fieldKeys)
};

export const fieldsAreValid = (fieldNames, fieldsMeta) => !includes(map(fieldNames, fieldName => get(fieldsMeta, `${fieldName}.valid`)), false);

Fix getFieldsMeta util to recurse into nested fields


const { environment } = require('@rails/webpacker')
const ConcatPlugin = require('webpack-concat-plugin')

environment.plugins.append('ConcatPlugin', new ConcatPlugin({
  uglify: true,
  sourceMap: true,
  outputPath: 'js',
  fileName: 'application.bundle.js',
  filesToConcat: [
    'jquery',
    'jquery-ujs',
    'jquery.iframe-transport',
    'dropzone',
    'govuk_frontend_toolkit/javascripts/govuk/stick-at-top-when-scrolling.js',
    'govuk_frontend_toolkit/javascripts/govuk/stop-scrolling-at-footer.js',
    'jquery-accessible-accordion-aria',
    'jsrender',
    'jquery-highlight',
    'jquery-throttle-debounce',
    'accessible-autocomplete',
    './app/webpack/javascripts/vendor/**/*',
    './app/webpack/javascripts/modules/**/*',
    './app/webpack/javascripts/plugins/**/*',
    './app/webpack/javascripts/application.js'
  ]
}))

module.exports = environment

const { environment } = require('@rails/webpacker')
const ConcatPlugin = require('webpack-concat-plugin')

environment.plugins.append('ConcatPlugin', new ConcatPlugin({
  uglify: true,
  sourceMap: true,
  outputPath: 'js',
  fileName: 'application.bundle.js',
  filesToConcat: [
    'jquery',
    'jquery-ujs',
    'jquery.iframe-transport',
    'dropzone',
    'govuk_frontend_toolkit/javascripts/govuk/stick-at-top-when-scrolling.js',
    'govuk_frontend_toolkit/javascripts/govuk/stop-scrolling-at-footer.js',
    'jquery-accessible-accordion-aria',
    'jsrender',
    'jquery-highlight',
    'jquery-throttle-debounce',
    'accessible-autocomplete',
    './app/webpack/javascripts/vendor/**/*',
    './app/webpack/javascripts/modules/**/*',
    './app/webpack/javascripts/plugins/**/*',
    './app/webpack/javascripts/application.js'
  ]
}))

environment.config.merge({
  output: {
    filename: 'js/[name]-[hash].js'
  }
})

module.exports = environment

Resolve webpack-dev-server output filename error


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

export default 'test-file-stub';

Add license comment for test file mock.


import resolver from './helpers/resolver';
import registerSelectHelper from './helpers/register-select-helper';
registerSelectHelper();
import {
  setResolver
} from 'ember-qunit';
import { start } from 'ember-cli-qunit';

setResolver(resolver);
start();

import resolver from './helpers/resolver';
import {
  setResolver
} from 'ember-qunit';
import { start } from 'ember-cli-qunit';

setResolver(resolver);
start();

Remove function import and call


import { chaynsInfo } from '../../chayns-info';

export default function getGlobalData(req, res) {
    if (chaynsInfo) {
        const data = chaynsInfo.getGlobalData();
        res.answer(data);
    }
}

import { chaynsInfo } from '../../chayns-info';

export default function getGlobalData(req, res) {
    if (chaynsInfo && res && res.answer) {
        const data = chaynsInfo.getGlobalData();
        res.answer(data);
    }
}

Fix t.answer is not a function


var fs = require('fs'),
    path = require('path'),
    _ = require('lodash'),
    glob = require('glob'),
    Q = require('q');

var stagecraftStubDir = path.resolve(__dirname, '..', 'app', 'support', 'stagecraft_stub', 'responses'),
    stagecraftStubGlob = path.resolve(stagecraftStubDir, '*.json'),
    ignoreFileRegexp = /services\.json|unimplemented-page-type\.json/;

var services = [];

function readModule(file) {
  var defer = Q.defer();
  fs.readFile(file, 'utf8', function (err, dashboardData) {
    if (err) {
      if (err.code === 'EISDIR') {
        return;
      } else {
        defer.reject(err);
        throw err;
      }
    }

    dashboardData = JSON.parse(dashboardData);
    services.push(_.pick(dashboardData, 'slug', 'title', 'department', 'agency', 'dashboard-type'));
    defer.resolve();

  });

  return defer.promise;
}

glob(stagecraftStubGlob, function (err, files) {
  if (err) {
    throw err;
  }
  Q.all(_.map(files, function (file) {
    if (!ignoreFileRegexp.test(file)) {
      return readModule(file);
    }
  })).then(function () {
    console.log('Writing ' + services.length + ' services into services.json');
    fs.writeFileSync(stagecraftStubDir + '/services.json', JSON.stringify({ items: services }, null, 2) + '\n');
  });

});

var fs = require('fs'),
    path = require('path'),
    _ = require('lodash'),
    glob = require('glob'),
    Q = require('q');

var stagecraftStubDir = path.resolve(__dirname, '..', 'app', 'support', 'stagecraft_stub', 'responses'),
    stagecraftStubGlob = path.resolve(stagecraftStubDir, '*.json');

var services = [];

function readModule(file) {
  var defer = Q.defer();
  fs.readFile(file, 'utf8', function (err, dashboardData) {
    if (err) {
      if (err.code === 'EISDIR') {
        defer.resolve();
        return;
      } else {
        defer.reject(err);
        throw err;
      }
    }

    dashboardData = JSON.parse(dashboardData);
    if (dashboardData['page-type'] === 'dashboard') {
      services.push(_.pick(dashboardData, 'slug', 'title', 'department', 'agency', 'dashboard-type'));
    }
    defer.resolve();

  });

  return defer.promise;
}

glob(stagecraftStubGlob, function (err, files) {
  if (err) {
    throw err;
  }
  Q.all(_.map(files, function (file) {
    return readModule(file);
  })).then(function () {
    console.log('Writing ' + services.length + ' services into services.json');
    fs.writeFileSync(stagecraftStubDir + '/services.json', JSON.stringify({ items: services }, null, 2) + '\n');
  });

});

Check dashboard stubs based on page-type


import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Iframe from 'react-iframe'

import Typography from '@material-ui/core/Typography';

import PageHead from './page-head';
import PageBody from './page-body';

export default class Notebook extends Component {
   render = () => {
    const {fileId} = this.props;
    const baseUrl =  `${window.location.origin}/api/v1`;
    return (
      <div>
        <PageHead>
          <Typography  color="inherit" gutterBottom variant="display1">
            Notebook
          </Typography>
        </PageHead>
        <PageBody>
          <Iframe id='iframe' url={`${baseUrl}/notebooks/${fileId}/html`}
          width='100%'
          onLoad={function(){
            var frame = document.getElementById('iframe');
            if (frame) {
              frame.height = frame.contentWindow.document.body.scrollHeight + 'px';
            }
          }}/>
        </PageBody>
      </div>
    );
  }
}

Notebook.propTypes = {
  fileId: PropTypes.string
}

Notebook.defaultProps = {

  fileId: null,
}

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Iframe from 'react-iframe'

import { Typography, withStyles } from '@material-ui/core';

import PageHead from './page-head';
import PageBody from './page-body';

const styles = () => ({
  iframe: {
    border: 0,
    left: 0,
    position: 'absolute',
    top: 0,
    width:'100%',
    height:'100%'
  },
  container: {
    overflow: 'hidden',
    position: 'relative',
    paddingTop: '72%'
  }
});

class Notebook extends Component {
   render = () => {
    const { fileId, classes } = this.props;
    const baseUrl =  `${window.location.origin}/api/v1`;
    return (

        <PageHead>
          <Typography  color="inherit" gutterBottom variant="display1">
            Notebook
          </Typography>
        </PageHead>
        <PageBody>

          <Iframe id='iframe' url={`${baseUrl}/notebooks/${fileId}/html`}
          className={classes.iframe}/>

        </PageBody>

    );
  }
}

Notebook.propTypes = {
  fileId: PropTypes.string
}

Notebook.defaultProps = {

  fileId: null,
}

export default withStyles(styles)(Notebook)

Use CSS styling to proportionally resize the iFrame


'use strict';

var _ = require('underscore'),
    Evaporate = require('evaporate'),
    coreSettings = require('../core/settings');

var uploadFiles = function(files) {
    var evap = new Evaporate({
        signerUrl: coreSettings.get('signerUrl'),
        aws_key: coreSettings.get('awsKey'),
        bucket: coreSettings.get('awsBucket'),
        logging: false
    });
    _.each(files, function(file) {
        // TODO Later we'll want to store the id of the file upload. We can use
        // it to cancel the upload if needed.
        // var id = evap.add({ //
        evap.add({
            // TODO - NAMES ARE CURRENTLY JUST FOR TESTING.
            // WE WANT TO USE A UUID FOR FILE NAMES.
            name: Math.floor(Math.random() * 1000000) + '_' + file.name,
            file: file,
            complete: function() {
                console.log('File upload complete');
            },
            progress: function(progress) {
                console.log('PROGRESS: ' + progress);
            }
        });
    });
};

module.exports = {
    uploadFiles: uploadFiles
};

'use strict';

var _ = require('underscore'),
    Evaporate = require('evaporate'),
    uuid = require('node-uuid'),
    settings = require('../settings'),
    coreSettings = require('../core/settings');

var uploadFiles = function(files) {
    var evap = new Evaporate({
        signerUrl: coreSettings.get('signerUrl'),
        aws_key: coreSettings.get('awsKey'),
        bucket: coreSettings.get('awsBucket'),
        logging: false
    });
    _.each(files, function(file) {
        var user = settings.getUser(),
            userId = user.get('id');
        // TODO Later we'll want to store the id of the file upload. We can use
        // it to cancel the upload if needed.
        // var id = evap.add({ //
        evap.add({
            // TODO - NAMES ARE CURRENTLY JUST FOR TESTING.
            // WE WANT TO USE A UUID FOR FILE NAMES.
            name: userId + '-' + uuid.v4(),
            file: file,
            complete: function() {
                console.log('File upload complete');
            },
            progress: function(progress) {
                console.log('PROGRESS: ' + progress);
            }
        });
    });
};

module.exports = {
    uploadFiles: uploadFiles
};

Use uuid for uploaded file name.


import { Comments } from '../../lib/collections/comments'
import { registerMigration, migrateDocuments } from './migrationUtils';

registerMigration({
  name: "testCommentMigration",
  idempotent: true,
  action: async () => {
    await migrateDocuments({
      description: "Checking how long migrating a lot of comments takes",
      collection: Comments,
      batchSize: 1000,
      unmigratedDocumentQuery: {},
      migrate: async (documents) => {
        const updates = documents.map(comment => {
          return {
            updateOne: {
              filter: {_id: comment._id},
              update: {
                $set: {
                  testContents: {
                      originalContents: {
                          data: "htmlReference"
                      },
                      html: "htmlReference",
                      version: "1.0.0",
                      userId: "userIdReference",
                      editedAt: "postedAtReference"
                  }
                }
              }
            }
          }
        })
        await Comments.rawCollection().bulkWrite(
          updates,
          { ordered: false }
        );
      }
    })
  },
});

import { Comments } from '../../lib/collections/comments'
import { registerMigration, migrateDocuments } from './migrationUtils';

registerMigration({
  name: "testCommentMigration",
  idempotent: true,
  action: async () => {
    await migrateDocuments({
      description: "Checking how long migrating a lot of comments takes",
      collection: Comments,
      batchSize: 1000,
      unmigratedDocumentQuery: {
        schemaVersion: {$lt: 1}
      },
      migrate: async (documents) => {
        const updates = documents.map(comment => {
          return {
            updateOne: {
              filter: {_id: comment._id},
              update: {
                $set: {
                  testContents: {
                      originalContents: {
                          data: "htmlReference"
                      },
                      html: "htmlReference",
                      version: "1.0.0",
                      userId: "userIdReference",
                      editedAt: "postedAtReference"
                  }
                },
                schemaVersion: 1
              }
            }
          }
        })
        await Comments.rawCollection().bulkWrite(
          updates,
          { ordered: false }
        );
      }
    })
  },
});

Add unmigratedDocumentQuery to test migration


import Ember from 'ember';
import startApp from '../../helpers/start-app';
import {TRANSPARENT_PIXEL} from 'dummy/utils/img-manager/img-clone-holder';
import '../../helpers/later';

var application;

module('Acceptance: should update the image after the src changed', {
  setup:    function () {
    application = startApp();
  },
  teardown: function () {
    Ember.run(application, 'destroy');
  }
});

test('visiting /img-wrap/delayed-src', function () {
  var $imgContainer;
  visit('/img-wrap/delayed-src');
  andThen(function(){
    fillIn('#img-src', 'assets/images/cartoon-1.jpg');
    later(10);
  });
  andThen(function () {
    equal(currentPath(), 'img-wrap.delayed-src');
    $imgContainer = find('.img-container');
    equal($imgContainer.find('img').attr('src'), 'assets/images/cartoon-1.jpg');
    equal($imgContainer.find('img').attr('alt'), 'Cartoon 1');
  });
});

import Ember from 'ember';
import startApp from '../../helpers/start-app';
import {TRANSPARENT_PIXEL} from 'dummy/utils/img-manager/img-clone-holder';
import '../../helpers/later';

var application;

module('Acceptance: should update the image after the src changed', {
  setup:    function () {
    application = startApp();
  },
  teardown: function () {
    Ember.run(application, 'destroy');
  }
});

test('visiting /img-wrap/delayed-src', function () {
  var $imgContainer;
  visit('/img-wrap/delayed-src');
  andThen(function(){
    fillIn('#img-src', 'assets/images/cartoon-1.jpg');
    later(100);
  });
  andThen(function () {
    equal(currentPath(), 'img-wrap.delayed-src');
    $imgContainer = find('.img-container');
    equal($imgContainer.find('img').attr('src'), 'assets/images/cartoon-1.jpg');
    equal($imgContainer.find('img').attr('alt'), 'Cartoon 1');
  });
});

Test was running too fast, delaying more


import { select } from 'd3-selection'

export function createRunHeaderComponents() {

  function runHeaderComponents(s) {
    s.selectAll('.zambezi-grid-headers .zambezi-grid-header')
        .each(runComponents)
  }

  return runHeaderComponents

  function runComponents(d, i) {
    const components = d.headerComponents
        , target = d3.select(this)

    if (!components) return
    components.forEach(component => target.each(component))
  }
}

import { select } from 'd3-selection'
import { selectionChanged, rebind } from '@zambezi/d3-utils'

export function createRunHeaderComponents() {

  const changed = selectionChanged().key(columnChangeKey)
  const api = rebind().from(changed, 'key')

  function runHeaderComponents(s) {
    s.selectAll('.zambezi-grid-headers .zambezi-grid-header')
      .select(changed)
        .each(runComponents)
  }

  return api(runHeaderComponents)

  function runComponents(d, i) {
    const components = d.headerComponents
        , target = d3.select(this)

    if (!components) return
    components.forEach(component => target.each(component))
  }
}

function columnChangeKey(column) {
  return [
    column.id
  , column.label || '·'
  , column.key || '·'
  , ~~column.offset
  , ~~column.absoluteOffset
  , ~~column.width
  , column.sortAscending || '·'
  , column.sortDescending || '·'
  ]
  .concat(
    column.children ?
    ( '(' + column.children.map(columnChangeKey).join(',') + ')' )
    : []
  )
  .join('|')
}

Implement optionally running header components only on column change


/**
 * @license
 * js-interview-questions <http://github.com/Tyriar/js-interview-questions>
 * Copyright 2015 Daniel Imms <http://www.growingwiththeweb.com>
 * Released under the MIT license <http://github.com/Tyriar/js-interview-questions/blob/master/LICENSE>
 */
'use strict';

/**
 * @constructor
 */
function TwoStackQueue() {
  this.inbox = [];
  this.outbox = [];
}

TwoStackQueue.prototype.push = function (value) {
  this.inbox.push(value);
};

TwoStackQueue.prototype.pop = function () {
  if (!this.outbox.length) {
    if (!this.inbox.length) {
      return undefined;
    }
    while (this.inbox.length) {
      this.outbox.push(this.inbox.pop());
    }
  }
  return this.outbox.pop();
};

module.exports = TwoStackQueue;

/**
 * @license
 * js-interview-questions <http://github.com/Tyriar/js-interview-questions>
 * Copyright 2015 Daniel Imms <http://www.growingwiththeweb.com>
 * Released under the MIT license <http://github.com/Tyriar/js-interview-questions/blob/master/LICENSE>
 */
'use strict';

/**
 * Creates a queue implemented with two stacks.
 * @constructor
 */
function TwoStackQueue() {
  this.inbox = [];
  this.outbox = [];
}

/**
 * Push a value to the queue.
 * @param {Object} value The value to push.
 */
TwoStackQueue.prototype.push = function (value) {
  this.inbox.push(value);
};

/**
 * Pops a value from the queue and returns it.
 * @return {Object} The popped value.
 */
TwoStackQueue.prototype.pop = function () {
  if (!this.outbox.length) {
    if (!this.inbox.length) {
      return undefined;
    }
    while (this.inbox.length) {
      this.outbox.push(this.inbox.pop());
    }
  }
  return this.outbox.pop();
};

module.exports = TwoStackQueue;

Add jsdoc to two stack queue


/**
 * Add shim config for configuring the dependencies and exports for
 * older, traditional "browser globals" scripts that do not use define()
 * to declare the dependencies and set a module value.
 */
(function(seajs, global) {

  // seajs.config({
  // shim: {
  //   "jquery": {
  //     src: "lib/jquery.js",
  //     exports: "jQuery" or function
  //   },
  //   "jquery.easing": {
  //     src: "lib/jquery.easing.js",
  //     deps: ["jquery"],
  //     exports: "jQuery"
  //   }
  // })

  seajs.on("config", onConfig)
  onConfig(seajs.config.data)

  function onConfig(data) {
    if (!data) return
    var shim = data.shim

    for (var id in shim) {
      var item = shim[id]

      // Set dependencies
      item.deps && define(item.src, item.deps)

      // Define the proxy cmd module
      define(id, [item.src], function() {
        var exports = item.exports
        return typeof exports === "function" ? exports() : global[exports]
      })
    }
  }

})(seajs, this);

/**
 * Add shim config for configuring the dependencies and exports for
 * older, traditional "browser globals" scripts that do not use define()
 * to declare the dependencies and set a module value.
 */
(function(seajs, global) {

  // seajs.config({
  // shim: {
  //   "jquery": {
  //     src: "lib/jquery.js",
  //     exports: "jQuery" or function
  //   },
  //   "jquery.easing": {
  //     src: "lib/jquery.easing.js",
  //     deps: ["jquery"]
  //   }
  // })

  seajs.on("config", onConfig)
  onConfig(seajs.config.data)

  function onConfig(data) {
    if (!data) return
    var shim = data.shim

    for (var id in shim) {
      (function(item) {

        // Set dependencies
        item.deps && define(item.src, item.deps)

        // Define the proxy cmd module
        define(id, [item.src], function() {
          var exports = item.exports
          return typeof exports === "function" ? exports() :
              typeof exports === "string" ? global[exports] :
                  exports
        })

      })(shim[id])
    }
  }

})(seajs, this);

Fix a bug of exports


//
// This is only a SKELETON file for the 'Resistor Color' exercise. It's been provided as a
// convenience to get you started writing code faster.
//

export const colorCode = () => {
  throw new Error("Remove this statement and implement this function");
};

// Fill the array with the various colors
export const COLORS = [];

//
// This is only a SKELETON file for the 'Resistor Color' exercise. It's been provided as a
// convenience to get you started writing code faster.
//

export const colorCode = () => {
  throw new Error("Remove this statement and implement this function");
};

export const COLORS = undefined;

Remove empty array to not lead implementation


export class PlayerModelService {
	constructor($log, $http, $localStorage, _, websocket) {
		'ngInject';

		this.$log = $log;
		this.$http = $http;
		this.$localStorage = $localStorage;

		this._ = _;
		this.ws = websocket;

		this.model = {
			player: null
		};
	}

	insert(data) {
		this.$log.info('insert()', data);

		this.model.player = data;
		this.$localStorage.player = this.model.player;
	}

	update(prop, value) {
		this.model.player[prop] = value;
	}
}

export class PlayerModelService {
	constructor($log, $http, $localStorage, _, websocket) {
		'ngInject';

		this.$log = $log;
		this.$http = $http;
		this.$localStorage = $localStorage;

		this._ = _;
		this.ws = websocket;

		this.model = {
			player: null
		};
	}

	insert(data) {
		this.$log.info('insert()', data, this);

		this.model.player = data;
		this.$localStorage.player = this.model.player;
	}

	update(prop, value) {
		this.model.player[prop] = value;
	}
}

Add additional object for debug output


import PropTypes from 'prop-types'
import React from 'react'
import './ColorCycle.css'

const framerate = 33 // ms

class ColorCycle extends React.Component {
  static propTypes = {
    text: PropTypes.string.isRequired,
  }

  shouldComponentUpdate (prevProps) {
    return prevProps.text !== this.props.text
  }

  render () {
    const text = this.props.text.split('').map((char, i) => {
      const delay = (i - this.props.text.length + 1) * framerate

      return (
        <span key={char + i} styleName='char' style={{ animationDelay: `${delay}ms` }}>
          {char}

      )
    })

    return (

        {text}

    )
  }
}

export default ColorCycle

import PropTypes from 'prop-types'
import React from 'react'
import './ColorCycle.css'

const framerate = 33 // ms

class ColorCycle extends React.Component {
  static propTypes = {
    text: PropTypes.string.isRequired,
  }

  shouldComponentUpdate (prevProps) {
    return prevProps.text !== this.props.text
  }

  render () {
    const text = this.props.text.split('').map((char, i) => {
      const delay = (i - this.props.text.length + 1) * framerate

      return (
        <span key={this.props.text + i} styleName='char' style={{ animationDelay: `${delay}ms` }}>
          {char}

      )
    })

    return (

        {text}

    )
  }
}

export default ColorCycle

Fix react key uniqueness issue


import React from "react"
import { shallow } from "enzyme"
import { expect } from "chai"
import sinon from "sinon"

import Room from "../../web/static/js/components/room"

describe("Room component", () => {
  const mockRetroChannel = { push: sinon.spy(), on: () => {} }

  describe(".handleIdeaSubmission", () => {
    it("pushes the idea to the room channel", () => {
      const roomComponent = shallow(<Room retroChannel={mockRetroChannel} users={[]} />)

      roomComponent
        .instance()
        .handleIdeaSubmission({ category: "sad", body: "we don't use our linter" })

      expect(
        mockRetroChannel.push.calledWith("new_idea", { category: "sad", body: "we don't use our linter" }),
      ).to.equal(true)
    })
  })
})

import React from "react"
import { shallow } from "enzyme"
import { expect } from "chai"
import sinon from "sinon"

import Room from "../../web/static/js/components/room"
import CategoryColumn from "../../web/static/js/components/category_column"

describe("Room component", () => {
  const mockRetroChannel = { push: sinon.spy(), on: () => {} }

  describe(".handleIdeaSubmission", () => {
    it("pushes the idea to the room channel", () => {
      const roomComponent = shallow(<Room retroChannel={mockRetroChannel} users={[]} />)

      roomComponent
        .instance()
        .handleIdeaSubmission({ category: "sad", body: "we don't use our linter" })

      expect(
        mockRetroChannel.push.calledWith("new_idea", { category: "sad", body: "we don't use our linter" }),
      ).to.equal(true)
    })
  })

  describe("Action item column", () => {
    it("is not visible on render", () => {
      const roomComponent = shallow(<Room retroChannel={mockRetroChannel} users={[]} />)
      expect(roomComponent.containsMatchingElement(
        <CategoryColumn category="action-item" ideas={[]}/>
      )).to.equal(false)
    })
  })
})

Add test to check that Action Item column is not displayed on render


$(document).ready(function(){

  $(".metronome-parameters").on("submit", function(event){
    event.preventDefault();
    if ($('#metronome-button').val() === "Start"){
      $('#metronome-button').val("Stop");
      var tempo = $("#tempo-field").val();
      var beatsPerMeasure = $("#beats-field").val();
      metronome = new Metronome(tempo, beatsPerMeasure);
      metronome.start();
    } else {
      $('#metronome-button').val("Start");
      metronome.stop();
    }

  });
})

$(document).ready(function(){

  $(".metronome-parameters").on("submit", function(event){
    event.preventDefault();
    if ($('#metronome-button').val() === "Start"){
      $('#metronome-button').val("Stop");
      var tempo = $("#tempo-field").val();
      var beatsPerMeasure = $("#beats-field").val();
      metronome = new Metronome(tempo, beatsPerMeasure);
      metronome.start();
    } else {
      $('#metronome-button').val("Start");
      metronome.stop();
    }

  });

  $("#metronome-stick").animate({ textIndent: 100 }, {
    step: function(degrees) {
        $(this).css('transform',"rotate(" + degrees + "deg)");
    }
  } );
})

Create ability to animate metronome. Needs method and logic


import Ember from 'ember';

const OPTIONS = ['clientName', 'product', 'key', 'env', 'webhook', 'longtail', 'selectAccount'];
const DEFAULT_LABEL = 'Link Bank Account';

export default Ember.Component.extend({
  tagName: 'button',
  type: 'button',
  action: 'processPlaidToken',
  attributeBindings: ['type'],

  label: DEFAULT_LABEL,
  institution: null,
  clientName: null,
  product: null,
  key: null,
  env: null,
  webhook: null,

  _link: null,

  _open: Ember.on('click', function() {
    this._link.open(this.get('institution'));
  }),

  _setup: Ember.on('init', function() {
    const options = Ember.merge(this.getProperties(OPTIONS), {
      onSuccess: this._onSuccess.bind(this)
    });

    this._link = Plaid.create(options);
  }),

  _onSuccess: function(token) {
    this.sendAction('action', token);
  }
});

import Ember from 'ember';

const OPTIONS = ['clientName', 'product', 'key', 'env', 'webhook', 'longtail', 'selectAccount'];
const DEFAULT_LABEL = 'Link Bank Account';

export default Ember.Component.extend({
  tagName: 'button',
  type: 'button',
  action: 'processPlaidToken',
  attributeBindings: ['type'],

  label: DEFAULT_LABEL,
  institution: null,
  clientName: null,
  product: null,
  key: null,
  env: null,
  webhook: null,
  selectAccount: null,

  _link: null,

  _open: Ember.on('click', function() {
    this._link.open(this.get('institution'));
  }),

  _setup: Ember.on('init', function() {
    const options = Ember.merge(this.getProperties(OPTIONS), {
      onSuccess: this._onSuccess.bind(this)
    });

    this._link = Plaid.create(options);
  }),

  _onSuccess: function(token, meta) {
    this.sendAction('action', token, meta);
  }
});

Support for meta and selectAccount properties.


var keystone = require('../../../');
var session = require('../../../lib/session');

module.exports = function (req, res) {
	session.signout(req, res, function () {
		if (typeof keystone.get('signout redirect') === 'string') {
			return res.redirect(keystone.get('signout redirect'));
		} else if (typeof keystone.get('signout redirect') === 'function') {
			return keystone.get('signout redirect')(req, res);
		} else {
			return res.redirect('/' + keystone.get('admin path') + '/signin?signedout');
		}
	});
};

var keystone = require('../../../');
var session = require('../../../lib/session');

module.exports = function (req, res) {
	session.signout(req, res, function () {
		if (typeof keystone.get('signout redirect') === 'string') {
			return res.redirect(keystone.get('signout redirect'));
		} else if (typeof keystone.get('signout redirect') === 'function') {
			return keystone.get('signout redirect')(req, res);
		} else {
			return res.redirect('/' + keystone.get('admin path') + '/signin?signedout');
			// After logging out, the user will be redirected to /signin?signedout
			// It shows a bar on top of the sign in panel saying "You have been signed out".
		}
	});
};

Comment for signin?signedout version added


import React, { Component } from "react";
import {
  ButtonDropdown,
  DropdownToggle,
  DropdownMenu,
  DropdownItem
} from "reactstrap";

class DestinationSelect extends Component {
  state = { dropdownOpen: false };
  toggle = () => {
    this.setState({ dropdownOpen: !this.state.dropdownOpen });
  };
  render() {
    const { systems, destination, select } = this.props;
    const selectedSystem = systems.find(s => s.id === destination);
    return (
      <ButtonDropdown isOpen={this.state.dropdownOpen} toggle={this.toggle}>
        <DropdownToggle caret>
          {selectedSystem ? selectedSystem.displayName : "Select a System"}
        </DropdownToggle>
        <DropdownMenu>
          {systems
            .concat()
            .sort((a, b) => {
              if (a.type > b.type) return 1;
              if (a.type < b.type) return -1;
              return 0;
            })
            .map(s => (
              <DropdownItem key={s.id} onClick={() => select(s.id)}>
                {s.displayName}
              </DropdownItem>
            ))}
        </DropdownMenu>
      </ButtonDropdown>
    );
  }
}

export default DestinationSelect;

import React, { Component } from "react";
import {
  ButtonDropdown,
  DropdownToggle,
  DropdownMenu,
  DropdownItem
} from "reactstrap";

class DestinationSelect extends Component {
  state = { dropdownOpen: false };
  toggle = () => {
    this.setState({ dropdownOpen: !this.state.dropdownOpen });
  };
  render() {
    const { systems, destination, select } = this.props;
    const selectedSystem = systems.find(s => s.id === destination);
    return (
      <ButtonDropdown isOpen={this.state.dropdownOpen} toggle={this.toggle}>
        <DropdownToggle caret>
          {selectedSystem ? selectedSystem.displayName : "Select a System"}
        </DropdownToggle>
        <DropdownMenu>
          {systems
            .concat()
            .sort((a, b) => {
              if (a.displayName > b.displayName) return 1;
              if (a.displayName < b.displayName) return -1;
              return 0;
            })
            .map(s => (
              <DropdownItem key={s.id} onClick={() => select(s.id)}>
                {s.displayName}
              </DropdownItem>
            ))}
        </DropdownMenu>
      </ButtonDropdown>
    );
  }
}

export default DestinationSelect;

Sort the exocomp system list by name.


// This file gets added to the page via /app/views/shared/_head
// That Rails view also adds several JavaScript globals to the page,
// including the available locales and wikis, the Features enabled,
// and other static (from a JavaScript perspective) data objects.

// Polyfill
import '@babel/polyfill';
import Rails from 'rails-ujs';

require('location-origin');

Rails.start(); // Enables rails-ujs, which adds JavaScript enhancement to some Rails views

$(() => {
  window.I18n = require('i18n-js');
  window.List = require('list.js'); // List is used for sorting tables outside of React
  require('./utils/course.js'); // This adds jquery features for some views outside of React
  // This is the main React entry point. It renders the navbar throughout the app, and
  // renders other components depending on the route.
  require('./components/app.jsx');
  require('events').EventEmitter.defaultMaxListeners = 30;
  require('./utils/editable.js');
  require('./utils/users_profile.js');
});

// This file gets added to the page via /app/views/shared/_head
// That Rails view also adds several JavaScript globals to the page,
// including the available locales and wikis, the Features enabled,
// and other static (from a JavaScript perspective) data objects.

// Polyfill
import '@babel/polyfill';
import Rails from 'rails-ujs';

require('location-origin');

Rails.start(); // Enables rails-ujs, which adds JavaScript enhancement to some Rails views

document.addEventListener('DOMContentLoaded', () => {
  window.I18n = require('i18n-js');
  window.List = require('list.js'); // List is used for sorting tables outside of React
  require('./utils/course.js'); // This adds jquery features for some views outside of React
  // This is the main React entry point. It renders the navbar throughout the app, and
  // renders other components depending on the route.
  require('./components/app.jsx');
  require('events').EventEmitter.defaultMaxListeners = 30;
  require('./utils/editable.js');
  require('./utils/users_profile.js');
});

Replace jquery Ready() with vanilla JS


import React, {Component, PropTypes} from 'react';
import DocumentTitle from 'react-document-title';
import ImmutablePropTypes from 'react-immutable-proptypes';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';

import {fetchResource} from 'actions/resourceActions';
import {resourcePageSelectors} from 'selectors/resourcePageSelectors';

export class UnconnectedResourcePage extends Component {
  componentDidMount() {
    const {actions, id} = this.props;
    actions.fetchResource(id);
  }

  render() {
    const {resource} = this.props;
    const name = resource.getIn(['name', 'fi']);
    return (
      <DocumentTitle title={`${name} - Respa`}>

{name}

      </DocumentTitle>
    );
  }
}

UnconnectedResourcePage.propTypes = {
  actions: PropTypes.object.isRequired,
  id: PropTypes.string.isRequired,
  resource: ImmutablePropTypes.map.isRequired,
};

function mapDispatchToProps(dispatch) {
  return {actions: bindActionCreators({fetchResource}, dispatch)};
}

export default connect(resourcePageSelectors, mapDispatchToProps)(UnconnectedResourcePage);

import React, {Component, PropTypes} from 'react';
import DocumentTitle from 'react-document-title';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Loader from 'react-loader';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';

import {fetchResource} from 'actions/resourceActions';
import {resourcePageSelectors} from 'selectors/resourcePageSelectors';

export class UnconnectedResourcePage extends Component {
  componentDidMount() {
    const {actions, id} = this.props;
    actions.fetchResource(id);
  }

  render() {
    const {resource} = this.props;
    const name = resource.getIn(['name', 'fi']);
    return (
      <DocumentTitle title={`${name} - Respa`}>
        <Loader loaded={Boolean(resource.size)}>

{name}

        </Loader>
      </DocumentTitle>
    );
  }
}

UnconnectedResourcePage.propTypes = {
  actions: PropTypes.object.isRequired,
  id: PropTypes.string.isRequired,
  resource: ImmutablePropTypes.map.isRequired,
};

function mapDispatchToProps(dispatch) {
  return {actions: bindActionCreators({fetchResource}, dispatch)};
}

export default connect(resourcePageSelectors, mapDispatchToProps)(UnconnectedResourcePage);

Add loading spinner to resource page