/**
 * CreateAccountField component.
 *
 * 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.
 */

/**
 * Internal dependencies
 */
import {
	Input,
	TextField,
} from '../../../../material-components';
import classnames from 'classnames';

export default function CreateAccountField( {
	hasError,
	value,
	setValue,
	name,
	label,
} ) {
	if ( 'undefined' === typeof value ) {
		return null;
	}

	return (
		<TextField
			className={ classnames(
				'mdc-text-field',
				{ 'mdc-text-field--error': hasError }
			) }
			label={ label }
			name={ name }
			onChange={ ( event ) => {
				setValue( event.target.value, name );
			} }
			outlined
			required
		>
			<Input
				name={ name }
				value={ value }
			/>
		</TextField>
	);
}

/**
 * CreateAccountField component.
 *
 * 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.
 */

/**
 * Internal dependencies
 */
import {
	Input,
	TextField,
} from '../../../../material-components';
import classnames from 'classnames';

export default function CreateAccountField( {
	hasError,
	value,
	setValue,
	name,
	label,
} ) {
	// Ensure field doesn't render until default value is available, fixing a potential render bug.
	if ( value === undefined ) {
		return null;
	}

	return (
		<TextField
			className={ classnames(
				'mdc-text-field',
				{ 'mdc-text-field--error': hasError }
			) }
			label={ label }
			name={ name }
			onChange={ ( event ) => {
				setValue( event.target.value, name );
			} }
			outlined
			required
		>
			<Input
				name={ name }
				value={ value }
			/>
		</TextField>
	);
}

Improve and document field value check.


import Ember from 'ember';

import config from '../config/environment';

export default Ember.Service.extend({
    host: config.HOST,
    namespace: 'solr',
    core: 'dina',

    parseResponse (data) {
        return data.response.docs.mapBy('primary_id');
    },

    select (query, {entityType, fq, rows}) {
        const service = this;
        let url = `${this.host}/${this.namespace}/${this.core}/select?q=${query}&fl=primary_id&wt=json&indent=true`;

        if (entityType) {
            fq['entity_type'] = entityType;
        }

        let fqString = '';
        for(let key in fq) {
            fqString += `&fq=${key}%3D${fq[key]}`;
        }

        url += fqString;

        if (rows) {
            url += `&rows=${rows}`;
        }

        return new Ember.RSVP.Promise(function (resolve, reject) {
            Ember.$.getJSON(url).success((data)=>{
                resolve(service.parseResponse(data));
            }).fail((error)=>{
                reject(error);
            });
        });
    },
    updateIndex () {
        const url = `${this.host}/${this.namespace}/${this.core}/dataimport?command=delta-import&commit=1&wt=json`;
        Ember.$.getJSON(url);
    }
});

import Ember from 'ember';

import config from '../config/environment';

export default Ember.Service.extend({
    host: config.HOST,
    namespace: 'solr',
    core: 'dina',

    parseResponse (data) {
        return data.response.docs.mapBy('primary_id');
    },

    select (query, {entityType, fq, rows}) {
        const service = this;
        let url = `${this.host}/${this.namespace}/${this.core}/select?q=${query}&fl=primary_id&wt=json&indent=true`;

        if (entityType) {
            fq['entity_type'] = entityType;
        }

        let fqString = '';
        for(let key in fq) {
            fqString += `&fq=${key}%3A${fq[key]}`;
        }

        url += fqString;

        if (rows) {
            url += `&rows=${rows}`;
        }

        return new Ember.RSVP.Promise(function (resolve, reject) {
            Ember.$.getJSON(url).success((data)=>{
                resolve(service.parseResponse(data));
            }).fail((error)=>{
                reject(error);
            });
        });
    },
    updateIndex () {
        const url = `${this.host}/${this.namespace}/${this.core}/dataimport?command=delta-import&commit=1&wt=json`;
        Ember.$.getJSON(url);
    }
});

Update how filter query is applied


const express = require('express')
const guildFeedSubscriberAPI = express.Router({ mergeParams: true })
const controllers = require('../../../../../controllers/index.js')
const feedHasSubscriber = require('../../../../../middleware/feedHasSubscriber.js')
const Joi = require('@hapi/joi')
const validator = require('express-joi-validation').createValidator({
  passError: true
});
const filterSchema = require('../../../../../util/validation/filterSchema.js')
const subscriberSchema = require('../../../../../util/validation/subscriberSchema.js')
const subscriberIDSchema = Joi.object({
  subscriberID: Joi.string()
})

// Create a subscriber
guildFeedSubscriberAPI.post(
  '/',
  validator.body(subscriberSchema),
  controllers.api.guilds.feeds.subscribers.createSubscriber
)

// Validate the subscriber exists for the feed in params
guildFeedSubscriberAPI.use(
  '/:subscriberID',
  validator.params(subscriberIDSchema),
  feedHasSubscriber
)

// Delete a subscriber
guildFeedSubscriberAPI.delete(
  '/:subscriberID',
  controllers.api.guilds.feeds.subscribers.deleteSubscriber
)

// Edit a subscriber
guildFeedSubscriberAPI.patch(
  '/:subscriberID',
  validator.body(filterSchema),
  controllers.api.guilds.feeds.subscribers.editSubscriber
)

module.exports = guildFeedSubscriberAPI

const express = require('express')
const guildFeedSubscriberAPI = express.Router({ mergeParams: true })
const controllers = require('../../../../../controllers/index.js')
const feedHasSubscriber = require('../../../../../middleware/feedHasSubscriber.js')
const Joi = require('@hapi/joi')
const validator = require('express-joi-validation').createValidator({
  passError: true
});
const filterSchema = require('../../../../../util/validation/filterSchema.js')
const subscriberSchema = require('../../../../../util/validation/subscriberSchema.js')
const subscriberIDSchema = Joi.object({
  guildID: Joi.string(),
  feedID: Joi.string(),
  subscriberID: Joi.string()
})

// Create a subscriber
guildFeedSubscriberAPI.post(
  '/',
  validator.body(subscriberSchema),
  controllers.api.guilds.feeds.subscribers.createSubscriber
)

// Validate the subscriber exists for the feed in params
guildFeedSubscriberAPI.use(
  '/:subscriberID',
  validator.params(subscriberIDSchema),
  feedHasSubscriber
)

// Delete a subscriber
guildFeedSubscriberAPI.delete(
  '/:subscriberID',
  controllers.api.guilds.feeds.subscribers.deleteSubscriber
)

// Edit a subscriber
guildFeedSubscriberAPI.patch(
  '/:subscriberID',
  validator.body(filterSchema),
  controllers.api.guilds.feeds.subscribers.editSubscriber
)

module.exports = guildFeedSubscriberAPI

Fix URL schema for subscribers


import fetch from 'isomorphic-fetch';
import url from 'url';

const baseUrlObj = {
  protocol: 'https:',
  host: 'api.github.com',
  pathname: '/gists',
};

const baseUrlStr = url.format(baseUrlObj);

const FILENAME = 'playground.rs';

export function load(id) {
  return fetch(`${baseUrlStr}/${id}`)
    .then(response => response.json())
    .then(gist => ({
      id: id,
      url: gist.html_url,
      code: gist.files[FILENAME].content,
    }));
}

const gistBody = code => ({
  description: "Rust code shared from the playground",
  public: true,
  files: {
    [ FILENAME ]: {
      content: code,
    },
  },
});

export function save(code) {
  return fetch(baseUrlStr, {
    method: 'post',
    body: JSON.stringify(gistBody(code)),
  })
    .then(response => response.json())
    .then(response => {
      let { id, html_url: url } = response;
      return { id, url };
    });
}

import fetch from 'isomorphic-fetch';
import url from 'url';

const baseUrlObj = {
  protocol: 'https:',
  host: 'api.github.com',
  pathname: '/gists',
};

const baseUrlStr = url.format(baseUrlObj);

const FILENAME = 'playground.rs';

export function load(id) {
  return fetch(`${baseUrlStr}/${id}`)
    .then(response => response.json())
    .then(gist => ({
      id: id,
      url: gist.html_url,
      code: gist.files[FILENAME].content,
    }));
}

const gistBody = code => ({
  description: "Rust code shared from the playground",
  public: true,
  files: {
    [FILENAME]: {
      content: code,
    },
  },
});

export function save(code) {
  return fetch(baseUrlStr, {
    method: 'post',
    body: JSON.stringify(gistBody(code)),
  })
    .then(response => response.json())
    .then(response => {
      let { id, html_url: url } = response;
      return { id, url };
    });
}

Remove spaces in computed properties


/*eslint-env browser*/
/*eslint no-use-before-define:0 */
/*global $*/

(function(){
  "use strict";
  $(document).ready(function(){
    setInterval(updatePage, 100);
  });

  function updatePage(){
    $.getJSON("./state.json", updateImage);
  }

  function updateLights(data){
    $.each(data, function(index, value){
      document.getElementById(value.location + "2" + value.direction).style.fill = value.state;
      document.getElementById(value.location + "2" + value.direction + "-load").innerHTML = value.load;
    });
  }

  function updateImage(data){
    updateLights(data);
    colorHighest(data);
    createStatistics(data);
  }

  function createStatistics(data){
    var totalCars = Array.reduce(data, function(total, current){
    return current.load + total;}, 0);
    document.getElementById("totalCars").innerHTML = totalCars;

  }

  function colorHighest(data){
    var carCounts = $.map(data, function(d){return d.load});
    var highestCarCount = Array.max(carCounts);
    $.each(data, function(index, value){
      var color = value.load === highestCarCount ? "yellow": "black";
      document.getElementById(value.location + "2" + value.direction + "-load").style.fill = color;
    });
  }

  Array.max = function( array ){
    return Math.max.apply( Math, array );
  };
})();

/*eslint-env browser*/
/*eslint no-use-before-define:0 */
/*global $*/

(function(){
  "use strict";
  $(document).ready(function(){
    setInterval(updatePage, 100);
  });

  function updatePage(){
    $.getJSON("./state.json", updateImage);
  }

  function updateLights(data){
    $.each(data, function(index, value){
      document.getElementById(value.location + "2" + value.direction).style.fill = value.state;
      document.getElementById(value.location + "2" + value.direction + "-load").innerHTML = value.load;
    });
  }

  function updateImage(data){
    updateLights(data);
    colorHighest(data);
    createStatistics(data);
  }

  function createStatistics(data){
    var totalCars = Array.reduce(data, function(total, current){
    return current.load + total;}, 0);
    document.getElementById("totalCars").innerHTML = totalCars;

  }

  function colorHighest(data){
    var carCounts = $.map(data, function(d){return d.load});
    var highestCarCount = Array.max(carCounts);
    $.each(data, function(index, value){
      var color = value.load === highestCarCount ? "orange": "black";
      document.getElementById(value.location + "2" + value.direction + "-load").style.fill = color;
    });
  }

  Array.max = function( array ){
    return Math.max.apply( Math, array );
  };
})();

Change highest color to orange


module.exports = (function(){

	return function unixToDate(unixTime) {
		var date = new Date(Number(unixTime));
		var year = date.getFullYear();
		var month;
		var day;

		if (date.getMonth().toString().length === 1) {
			month = "0" + date.getMonth().toString();
		} else {
			month = date.getMonth().toString();
		}

		if (date.getDate().toString().length === 1) {
			day = "0" + date.getDay().toString();
		} else {
			day = date.getDate().toString();
		}

		return year + '-' + month + '-' + day;
	}
})();

module.exports = (function(){

	return function unixToDate(unixTime) {
		console.log(unixTime)
		var date = new Date(Number(unixTime));
		var year = date.getFullYear();
		var month;
		var day;

		if (date.getMonth().toString().length === 1) {
			month = "0" + (date.getMonth() + 1).toString();
		} else {
			month = (date.getMonth() + 1).toString();
		}

		if (date.getDate().toString().length === 1) {
			day = "0" + date.getDay().toString();
		} else {
			day = date.getDate().toString();
		}

		return year + '-' + month + '-' + day;
	}
})();

Fix previous month bug due to 0-11 months scale.


var functions = require('firebase-functions');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
//  response.send("Hello from Firebase!");
// });

const functions = require('firebase-functions');

const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

class HTTPError extends Error {
  constructor(code, message) {
    super(message);
    this.code = code;
  }
}

// TODO: Implement the real matching algorithm.
// This version always creates a single 'Firebase' topic and assigns everyone to it.
exports.closeSlot = functions.https.onRequest((req, res) => {
  const slotId = req.body.id;
  if (!slotId) {
    res.sendStatus(400);
    return;
  }

  // Get a few refs for convenience
  const db = admin.database();
  const rootRef = db.ref();
  const slotRef = db.ref(`/slots/${slotId}`);
  const reqsRef = db.ref(`/requests/${slotId}`);

  return slotRef.once('value')
    .then(snapshot => {
      // Does the slot exist?
      const slot = snapshot.val();
      if (!slot) {
        throw new HTTPError(404, 'slot not found');
      }

      // Mark the slot as closed
      return slotRef.update({state: 'closed'});
    })
    .then(() => {
      // Fetch all of the requests
      return reqsRef.once('value');
    })
    .then(snapshot => {
      // Build a multi-key database update
      const updates = {};

      // Create a new topic
      const topicId = 'firebase-001';
      const topic = {
        name: 'Firebase',
        members: {},
      };
      updates[`/topics/${slotId}/${topicId}`] = topic;

      // Add every user to the topic
      const uids = Object.keys(snapshot.val())
      uids.forEach(uid => {
        topic.members[uid] = true;
        updates[`/account/${uid}/slots/${slotId}/topicId`] = topicId;
      });

      // Apply all updates atomically
      return rootRef.update(updates);
    })
    .then(() => {
      res.sendStatus(200);
    })
    .catch(err => {
      console.error(err);
      if (err instanceof HTTPError) {
        res.sendStatus(err.code);
      } else {
        res.sendStatus(500);
      }
    });
});

Implement temporary version of `closeSlot` HTTP function


module.exports = function (db) {
	db.set = set;
	return db;
}

function set(path, obj, cb) {
	var objs = [];
  putObjBatch(path, obj, objs);

  this.batch(objs, function (err) {
		cb(err);
	});
}

function putObjBatch(path, obj, objs) {
	var keyPath = '';
	if (path !== '/' && path.length > 1) {
		keyPath = path.substr(1) + '/';
	}

	for (var objKey in obj) {
		var objVal = obj[objKey];

		if (typeof objVal === 'object') {
			if (keyPath !== '' && keyPath.length > 1) {
				putObjBatch('/' + keyPath + objKey, objVal, objs);
			} else {
				putObjBatch('/' + objKey, objVal, objs);
			}

		} else {
			objs.push({ type: 'put', key: keyPath + objKey, value: objVal });
		}
  }
}

module.exports = function (db) {
	db.set = set;
	return db;
};

function set(path, obj, cb) {
	var objs = [];
  putObjBatch(path, obj, objs);

  this.batch(objs, function (err) {
		cb(err);
	});
}

function putObjBatch(path, obj, objs) {
	var keyPath = '';
	if (path !== '/' && path.length > 1) {
		keyPath = path.substr(1) + '/';
	}

	for (var objKey in obj) {
		var objVal = obj[objKey];

		if (typeof objVal === 'object') {
			if (keyPath !== '' && keyPath.length > 1) {
				putObjBatch('/' + keyPath + objKey, objVal, objs);
			} else {
				putObjBatch('/' + objKey, objVal, objs);
			}

		} else {
			objs.push({ type: 'put', key: keyPath + objKey, value: objVal });
		}
  }
}

Put the missing semicolon at the end of exports declaration


var Storage = require('FuseJS/Storage');

var data = 'favorites';

/* ...
-----------------------------------------------------------------------------*/
var addFavorite
, deleteFavorite
, getFavorites;

/* Functions
-----------------------------------------------------------------------------*/
addFavorite = function(id) {
  var favorite = getFavorites();

  if (favorite === null) {
    favorite = {};
  }

  if (favorite[id]) {
    return;
  }

  favorite[id] = { name: 'woop' };
  Storage.writeSync(data, JSON.stringify(favorite));
}

deleteFavorite = function(id) {
  var favorite = getFavorites();

  if (favorite[id]) {
    delete favorite[id];
    Storage.writeSync(data, JSON.stringify(favorite));
  }
}

getFavorites = function() {
  var favorites = JSON.parse(Storage.readSync(data));
  if (favorites === null) {
    favorites = {};

    Storage.write(data, JSON.stringify(favorites));
  }

  return favorites;
}

/* Exports
-----------------------------------------------------------------------------*/
module.exports = {
  addFavorite: addFavorite,
  deleteFavorite: deleteFavorite,
  getFavorites: getFavorites
};

var Storage = require('FuseJS/Storage');

var data = 'favorites';

/* ...
-----------------------------------------------------------------------------*/
var addFavorite
, deleteFavorite
, getFavorites;

/* Functions
-----------------------------------------------------------------------------*/
addFavorite = function(id) {
  var favorite = getFavorites();

  if (favorite[id]) {
    return;
  }

  favorite[id] = { name: 'woop' };
  Storage.writeSync(data, JSON.stringify(favorite));
}

deleteFavorite = function(id) {
  var favorite = getFavorites();

  if (favorite[id]) {
    delete favorite[id];
    Storage.writeSync(data, JSON.stringify(favorite));
  }
}

getFavorites = function() {
  var favorites = JSON.parse(Storage.readSync(data));
  if (favorites === null) {
    favorites = {};

    Storage.write(data, JSON.stringify(favorites));
  }

  return favorites;
}

/* Exports
-----------------------------------------------------------------------------*/
module.exports = {
  addFavorite: addFavorite,
  deleteFavorite: deleteFavorite,
  getFavorites: getFavorites
};

Remove old code in addFavorite


/**
 * Module dependencies.
 */
var passport = require('passport-strategy')
  , util = require('util');

function Strategy() {
  passport.Strategy.call(this);
  this.name = 'authenticated';
}

/**
 * Inherit from `passport.Strategy`.
 */
util.inherits(Strategy, passport.Strategy);

/**
 * Authenticate request if it has already been authenticated.
 *
 * @param {Object} req
 * @api protected
 */
Strategy.prototype.authenticate = function(req) {
  var property = 'user';
  if (this._passport && this._passport.instance) {
    property = this._passport.instance._userProperty || 'user';
  }

  if (this[property]) {
    // Already authenticated.  Note that we call `pass()` here, rather than
    // `success()`, as the request has already been authenticated and no further
    // session handling is neccessary.
    return this.pass();
  }

  // Not already authenticated.
  return this.fail();
};

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;

/**
 * Module dependencies.
 */
var passport = require('passport-strategy')
  , util = require('util');

function Strategy() {
  passport.Strategy.call(this);
  this.name = 'authenticated';
}

/**
 * Inherit from `passport.Strategy`.
 */
util.inherits(Strategy, passport.Strategy);

/**
 * Authenticate request if it has already been authenticated.
 *
 * @param {Object} req
 * @api protected
 */
Strategy.prototype.authenticate = function(req) {
  var property = 'user';
  if (this._passport && this._passport.instance) {
    property = this._passport.instance._userProperty || 'user';
  }

  if (req[property]) {
    // Already authenticated.  Note that we call `pass()` here, rather than
    // `success()`, as the request has already been authenticated and no further
    // session handling is neccessary.
    return this.pass();
  }

  // Not already authenticated.
  return this.fail();
};

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;

Check req for user property.


var https       = require('https');
var inspect     = require('eyes').inspector({ stream: null });
var querystring = require('querystring');
var xml2js      = require('xml2js');

module.export.access = function(data, callback){
  var parser     = new xml2js.Parser();
  var stringData = querystring.stringify(data);

  var options = {
    host: 'www.pivotaltracker.com',
    port: 443,
    path: '/services/v3/tokens/active',
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Content-Length': stringData.length
    }
  };

  var req = https.request(options, function(res) {
    res.on('data', function(d) {
      parser.parseString(d, function (err, result) {
        return callback.call(this, err, result);
      });
    });

  });

  req.write(stringData);
  req.end();

  req.on('error', function(e) {
    return callback.call(this, e);
  });

};

var https       = require('https');
var inspect     = require('eyes').inspector({ stream: null });
var querystring = require('querystring');
var xml2js      = require('xml2js');

module.exports.access = function(data, callback){
  var parser     = new xml2js.Parser();
  var stringData = querystring.stringify(data);
  var noAccess   = new RegExp(/Access denied/);

  var options = {
    host: 'www.pivotaltracker.com',
    port: 443,
    path: '/services/v3/tokens/active',
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Content-Length': stringData.length
    }
  };

  var req = https.request(options, function(res) {
    res.on('data', function(d) {

      if(noAccess.test(d.toString())){
        return callback.call(this, null, {message: d.toString()});
      }

      parser.parseString(d, function (err, result) {
        return callback.call(this, err, result);
      });

    });

  });

  req.write(stringData);
  req.end();

  req.on('error', function(e) {
    return callback.call(this, e);
  });

};

Handle responce if no access


'use strict';

const express = require('express');
const db = require('./db.js');

const app = express();

const PORT = process.env.PORT || 3020;

app.get('/tickets', (req, res) => {
  const task = req.query.task;
  const num = req.query.num;

  db.getTickets(task, num)
  .then(data => {
    console.log('GOT DATA!');
    console.log(data);
    res.status(200).send(data);
  })
  .catch(err => {
    console.log('error getting tickets', err);
    res.status(503).send(err);
  });
});

app.listen(PORT, 'localhost', (err) => {
  if (err) { console.log(err); };
  console.log(`Listening at http://localhost:${PORT}`);
});

'use strict';

const express = require('express');
const db = require('./db.js');

const app = express();

const PORT = process.env.PORT || 3020;

app.get('/tickets', (req, res) => {
  const task = req.query.task;
  const num = req.query.num;

  db.getOpenTickets(task, num)
  .then(data => {
    console.log('GOT DATA!');
    console.log(data.length, data);
    res.status(200).send(data);
  })
  .catch(err => {
    console.log('error getting tickets', err);
    res.status(503).send(err);
  });
});

app.listen(PORT, 'localhost', (err) => {
  if (err) { console.log(err); };
  console.log(`Listening at http://localhost:${PORT}`);
});

Modify name db helper function


var password_field = document.getElementById("textfield");
password_field.removeAttribute("onkeyup");

var virtual_keyboard = document.getElementById("a_0").parentElement;
virtual_keyboard.remove();

var password_field = document.getElementById("textfield");
password_field.removeAttribute("onkeyup");
password_field.removeAttribute("readonly");

var virtual_keyboard = document.getElementById("a_0").parentElement;
virtual_keyboard.remove();

Fix entering password in Vision Banco interface


// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
//= require turbolinks
//= require jquery3
//= require jquery_ujs
//= require foundation
//= require_tree .

$(document).on('turbolinks:load', function() {
  $(document).foundation();
  $('body').on('click', '[data-open="ajax-reveal"]', function(e) {
    e.preventDefault();
    $.ajax({
      url: $(this).data('url'),
      success: function(result) {
        $(".reveal-content").html(result);
      },
      error: function(result) {
        $(".reveal-content").html('Error loading content. Please close popup and retry.');
      }
    });
  });
});

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
//= require turbolinks
//= require jquery3
//= require jquery_ujs
//= require foundation
//= require_tree .

$(document).on('turbolinks:load', function() {
  $(document).foundation();
  $('body').on('click', '[data-open="ajax-reveal"]', function(e) {
    e.preventDefault();
    $.ajax({
      url: $(this).data('url'),
      success: function(result) {
        $('.reveal-content').html(result);
      },
      error: function(result) {
        $('.reveal-content').html('Error loading content. Please close popup and retry.');
      }
    });
  });
});

Use single quotes instead of double quotes


/**
 * The root component that wraps the main app component with all
 * data-related components such as `ApolloProvider`.
 * This component works on all platforms.
 * @flow
 */

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

import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import ApolloProvider from 'react-apollo/ApolloProvider';

import reducers from './reducers';
// App container that provides navigation props and viewer data.
import Spoors from './containers/Spoors';

const networkInterface = createNetworkInterface('/api');

const client = new ApolloClient({
  networkInterface
});

// We use our own Redux store, so we combine Apollo with it.
const store = createStore(
  combineReducers({
    apollo: client.reducer(),
    ...reducers
  }),
  {}, // initial state
  compose(
    applyMiddleware(client.middleware()),
    // If you are using the devToolsExtension, you can add it here also
    window.devToolsExtension ? window.devToolsExtension() : f => f,
  )
);

class App extends Component {
  render() {
    return (
      <ApolloProvider store={store} client={client}>
        <Spoors />
      </ApolloProvider>
    );
  }
}

export default App;

/**
 * The root component that wraps the main app component with all
 * data-related components such as `ApolloProvider`.
 * This component works on all platforms.
 * @flow
 */

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

import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import ApolloClient, { createNetworkInterface, addTypename } from 'apollo-client';
import ApolloProvider from 'react-apollo/ApolloProvider';

import reducers from './reducers';
// App container that provides navigation props and viewer data.
import Spoors from './containers/Spoors';

const networkInterface = createNetworkInterface('/api');

const client = new ApolloClient({
  networkInterface,
  // Apollo transformer to automatically add `__typename` to all queries.
  queryTransformer: addTypename,
  // Normalize ID for different object types for Apollo caching.
  dataIdFromObject: (result) => {
    if (result.id && result.__typename) {
      return result.__typename + result.id;
    }
    return null;
  }
});

// We use our own Redux store, so we combine Apollo with it.
const store = createStore(
  combineReducers({
    apollo: client.reducer(),
    ...reducers
  }),
  {}, // initial state
  compose(
    applyMiddleware(client.middleware()),
    // If you are using the devToolsExtension, you can add it here also
    window.devToolsExtension ? window.devToolsExtension() : f => f,
  )
);

class App extends Component {
  render() {
    return (
      <ApolloProvider store={store} client={client}>
        <Spoors />
      </ApolloProvider>
    );
  }
}

export default App;

Add sample configuration for normalizing ID


var ds_model = require('./datasource/model');

function resolve (name, callback) {

    // TODO check name to be valid (and avoid SQL injection)
    if (!name) {
        return callback(M.error(M.error.API_DS_INVALID_NAME, name));
    }

    var appId = M.config.app.id;

    ds_model.get(appId, name, function(err, ds) {

        if (err) { return callback(); }

        // TODO improve this:
        //      move db to data
        var datasource = {
            type: ds.type,
            db: ds.db
        };

        for (var i in ds.data) {
            datasource[i] = ds.data[i];
        }

        callback(null, datasource);
    });
}

exports.resolve = resolve;

var ds_model = require('./datasource/model');

function resolve (name, callback) {

    // TODO check name to be valid (and avoid SQL injection)
    if (!name) {
        return callback(M.error(M.error.API_DS_INVALID_NAME, name));
    }

    var appId = M.config.app.id;

    ds_model.get(appId, name, function(err, ds) {

        if (err) { return callback(err); }

        // TODO improve this:
        //      move db to data
        var datasource = {
            type: ds.type,
            db: ds.db
        };

        for (var i in ds.data) {
            datasource[i] = ds.data[i];
        }

        callback(null, datasource);
    });
}

exports.resolve = resolve;

Send error in callback, too.


module.exports = {
    Tile: require('./tile'),
    Map: require('./map'),
    TileBatch: require('./batch'),
    MBTiles: require('./mbtiles'),
    SphericalMercator: require('./sphericalmercator')
};

module.exports = {
    Tile: require('./tile'),
    Map: require('./map'),
    TileBatch: require('./batch'),
    MBTiles: require('./mbtiles'),
    SphericalMercator: require('./sphericalmercator'),
    Pool: require('./mappool')
};

Add Pool to exported classes.


var tape = require('../');
var tap = require('tap');
var concat = require('concat-stream');

var stripFullStack = require('./common').stripFullStack;
var testWrapper = require('./anonymous-fn/test-wrapper');

tap.test('inside anonymous functions', function (tt) {
    tt.plan(1);

    var test = tape.createHarness();
    var tc = function (rows) {
        tt.same(stripFullStack(rows.toString('utf8')), [
            'TAP version 13',
            '# wrapped test failure',
            'not ok 1 fail',
            '  ---',
            '    operator: fail',
            '    at: <anonymous> ($TEST/anonymous-fn.js:$LINE:$COL)',
            '    stack: |-',
            '      Error: fail',
            '          [... stack stripped ...]',
            '          at $TEST/anonymous-fn.js:$LINE:$COL',
            '          at Test.<anonymous> ($TEST/anonymous-fn/test-wrapper.js:$LINE:$COL)',
            '          [... stack stripped ...]',
            '  ...',
            '',
            '1..1',
            '# tests 1',
            '# pass  0',
            '# fail  1'
        ].join('\n') + '\n');
    };

    test.createStream().pipe(concat(tc));

    test('wrapped test failure', testWrapper(function (t) {
        t.fail('fail');
        t.end();
    }));
});

var tape = require('../');
var tap = require('tap');
var concat = require('concat-stream');

var stripFullStack = require('./common').stripFullStack;
var testWrapper = require('./anonymous-fn/test-wrapper');

tap.test('inside anonymous functions', function (tt) {
    tt.plan(1);

    var test = tape.createHarness();
    var tc = function (rows) {
        var body = stripFullStack(rows.toString('utf8'));

        // Handle stack trace variation in Node v0.8
        body = body.replace(
            'at Test.module.exports',
            'at Test.<anonymous>'
        );

        tt.same(body, [
            'TAP version 13',
            '# wrapped test failure',
            'not ok 1 fail',
            '  ---',
            '    operator: fail',
            '    at: <anonymous> ($TEST/anonymous-fn.js:$LINE:$COL)',
            '    stack: |-',
            '      Error: fail',
            '          [... stack stripped ...]',
            '          at $TEST/anonymous-fn.js:$LINE:$COL',
            '          at Test.<anonymous> ($TEST/anonymous-fn/test-wrapper.js:$LINE:$COL)',
            '          [... stack stripped ...]',
            '  ...',
            '',
            '1..1',
            '# tests 1',
            '# pass  0',
            '# fail  1'
        ].join('\n') + '\n');
    };

    test.createStream().pipe(concat(tc));

    test('wrapped test failure', testWrapper(function (t) {
        t.fail('fail');
        t.end();
    }));
});

Handle stack variation in Node v0.8


var request = require('request');
var async = require('async');

function SuperMaster(masters){
    this.cache = {};
    this.masters = masters;

    // Initialize cache with empty list for each master
    masters.forEach((master) => { this.cache[master.name] = [] });
}

SuperMaster.prototype.getJobs = function(callback){
  var self = this;

  async.map(self.masters, (master, callback) => {
    // Issue request against each master
    request.get(master.endpoint + "/api/json", (err, res, body) => {
      var jobs;
      if(err || res.statusCode != 200 ){
        if(res && res.statusCode != 200)
          err = new Error(res.statusMessage);
        console.warn("Request to", master.name, "failed.\nMessage:", err.message);
        jobs = self.cache[master.name]; // Set jobs from cache

        jobs.forEach((job) => {
          job.cached = true;
        });
      }else{
        jobs = JSON.parse(body).jobs;

        jobs.forEach((job) => {
          job.master = master.name;
          job.icon = master.icon || "";
        });

        // Update cached job list for master
        self.cache[master.name] = jobs;
      }

      callback(null, jobs);
    });

  }, (err, jobs) => {
    callback(err, [].concat.apply([], jobs) || []);
  });
}

module.exports = SuperMaster;

var request = require('request').defaults({ timeout: 1000 });
var async = require('async');

function SuperMaster(masters){
    this.cache = {};
    this.masters = masters;

    // Initialize cache with empty list for each master
    masters.forEach((master) => { this.cache[master.name] = [] });
}

SuperMaster.prototype.getJobs = function(callback){
  var self = this;

  async.map(self.masters, (master, callback) => {
    // Issue request against each master
    request.get(master.endpoint + "/api/json", (err, res, body) => {
      var jobs;
      if(err || res.statusCode != 200 ){
        if(res && res.statusCode != 200)
          err = new Error(res.statusMessage);

        console.warn("Request to", master.name, "failed.\nMessage:", err.message);
        jobs = self.cache[master.name]; // Set jobs from cache

        jobs.forEach((job) => {
          job.cached = true;
        });
      }else{
        jobs = JSON.parse(body).jobs;

        jobs.forEach((job) => {
          job.master = master.name;
          job.icon = master.icon || "";
        });

        // Update cached job list for master
        self.cache[master.name] = jobs;
      }

      callback(null, jobs);
    });

  }, (err, jobs) => {
    callback(err, [].concat.apply([], jobs) || []);
  });
}

module.exports = SuperMaster;

Add timeout for requests to jenkins masters


// @flow

import RaisedButton from "material-ui/RaisedButton";
import TextField from "material-ui/TextField";
import * as React from "react";

type Props = {
  term: string,
  onSearch: (string) => void,
  onClear: () => void,
};

type State = {
  term: string,
};

class PieceSearch extends React.Component<Props, State> {
  state = {
    term: this.props.term,
  };

  onSearchChange = (event: SyntheticEvent<HTMLButtonElement>, term: string) => {
    this.setState({
      term,
    });
  };

  onSearch = (event: SyntheticEvent<HTMLButtonElement>) => {
    event.preventDefault();
    this.props.onSearch(this.state.term);
  };

  render() {
    return (
      <form onSubmit={this.onSearch}>
        <TextField
          id="term"
          floatingLabelText="Tittel"
          value={this.state.term}
          onChange={this.onSearchChange}
        />
        <RaisedButton label="Søk" type="submit" primary />
        <RaisedButton
          label="Tøm"
          type="reset"
          onTouchTap={this.props.onClear}
        />
      </form>
    );
  }
}

export default PieceSearch;

// @flow

import RaisedButton from "material-ui/RaisedButton";
import TextField from "material-ui/TextField";
import * as React from "react";

type Props = {
  term: string,
  onSearch: (string) => void,
  onClear: () => void,
};

type State = {
  term: string,
};

class PieceSearch extends React.Component<Props, State> {
  state = {
    term: this.props.term,
  };

  onSearchChange = (event: SyntheticEvent<HTMLButtonElement>, term: string) => {
    this.setState({
      term,
    });
  };

  onSearch = (event: SyntheticEvent<HTMLButtonElement>) => {
    event.preventDefault();
    this.props.onSearch(this.state.term);
  };

  render() {
    return (
      <form onSubmit={this.onSearch}>
        <TextField
          id="term"
          floatingLabelText="Tittel, komponist eller arrangør"
          value={this.state.term}
          onChange={this.onSearchChange}
        />
        <RaisedButton label="Søk" type="submit" primary />
        <RaisedButton
          label="Tøm"
          type="reset"
          onTouchTap={this.props.onClear}
        />
      </form>
    );
  }
}

export default PieceSearch;

Update music search label text


import React, { Component } from 'react';

export default class AddProject extends Component {
  constructor() {
    super();

    this.state = {
      projectId: '',
    };

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

  submit(e) {
    e.preventDefault();
    this.props.onAdd(this.state.projectId);
  }

  render() {
    return (
      <form onSubmit={this.submit}>

Project ID

        <input
          id="projectId"
          onChange={e => this.setState({ projectId: e.target.value })}
          type="number"
          value={this.state.projectId}
        />
        <button type="submit">Add</button>
      </form>
    );
  }
}

import React, { Component } from 'react';

export default class AddProject extends Component {
  constructor() {
    super();

    this.state = {
      projectId: '',
    };

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

  submit(e) {
    e.preventDefault();
    this.props.onAdd(this.state.projectId);
  }

  render() {
    return (
      <form onSubmit={this.submit}>

Project ID

          <input
            id="projectId"
            onChange={e => this.setState({ projectId: e.target.value })}
            type="number"
            value={this.state.projectId}
            className="form-control"
          />

            <button type="submit" className="btn btn-primary">Add</button>

      </form>
    );
  }
}

Move manual project ID dropdown to Bootstrap styles


describe('rte directive', function () {
  var scope, element;

  beforeEach(module('templates'));
  beforeEach(module('rte-angular'));

  beforeEach(inject(function ($rootScope, $compile, $sce) {
    element = angular.element('<rte ng-model="testContent"></rte>');

    scope = $rootScope;
    scope.testContent = $sce.trustAsHtml('
Hello, world
');

    $compile(element)(scope);
    scope.$digest();
  }));

  it('should have a contenteditable', function () {
    expect(element.html()).toContain('contenteditable');
  });
});

describe('rte directive', function () {
  var scope, element;

  beforeEach(module('templates'));
  beforeEach(module('rte-angular'));

  beforeEach(inject(function ($rootScope, $compile, $sce) {
    element = angular.element('<rte ng-model="testContent"></rte>');

    scope = $rootScope;
    scope.testContent = $sce.trustAsHtml('
Hello, world
');

    $compile(element)(scope);
    scope.$digest();
  }));

  describe('2-way data binding', function () {
    it('should render the model', function () {
      expect(element.find('.rte-content').html()).toBe(scope.testContent.toString());
    });

    it('should update the model on model change', inject(function ($sce) {
      var html = '
Hello, world
';

      scope.testContent = $sce.trustAsHtml(html);
      scope.$digest();

      expect(element.find('.rte-content').html()).toBe(html);
    }));
  });
});

Add 2-way data binding unit tests


var chai = require('chai');
var expect = chai.expect;

describe('Client', function() {
  //Add subsections
});

var chai = require('chai');
var expect = chai.expect;

describe('Client', function() {

  describe('handlers.js', function() {
    var handlers = require('../server/handlers.js');

    describe('getRoutes', function() {

      it('should be a function', function() {
        expect(handlers.getRoutes).to.be.a('function');
      });

      /**
       * Add these tests:
       * it should take two strings
       * it should return a promise that contains path data
       * it should query a string properly
       * it should provide all the required props to a ajax req
       * it should throw an error upon ajax failure
       */
    });

    describe('submit', function() {
      it('should be a function', function() {
        expect(handlers.getRoutes).to.be.a('function');
      })
    });
  });

  //Add more subsections
});

Add several tests for handlers.js, getRoutes()


"use strict";

const baseConfig = require("./rollup.config");
const plugin = require("./plugin");
const path = require("path");

module.exports = baseConfig.map(config => {
  const templateType = getTemplateType(config);
  config.plugins.push(
    plugin({
      open: true,
      filename: `stats.${templateType}.html`,
      template: templateType
    })
  );
  return config;
});

function getTemplateType({ input }) {
  const filename = path.basename(input, path.extname(input));
  const [, templateType] = filename.split("-");
  return templateType;
}

"use strict";

const baseConfig = require("./rollup.config");
const plugin = require("./plugin");
const path = require("path");

module.exports = baseConfig.map(config => {
  const templateType = getTemplateType(config);
  config.plugins.push(
    plugin({
      open: true,
      title: `test ${templateType}`,
      filename: `stats.${templateType}.html`,
      template: templateType
    })
  );
  return config;
});

function getTemplateType({ input }) {
  const filename = path.basename(input, path.extname(input));
  const [, templateType] = filename.split("-");
  return templateType;
}

Use title with graph type


'use strict';

import gulp from 'gulp';

gulp.task('watch', [
  'framework:watch',
  'bower:watch',
  'assets:watch',
  'copy:watch',
  'locales:watch',
  'markup:watch',
  'scripts:watch',
  'styles:watch',
  'templates:watch',
  'vulcanize:watch',
  'cache-config:watch',
  'browser-sync'
], () => {
  console.info('Watching');
});

'use strict';

import gulp from 'gulp';

gulp.task('watch', [
  'framework:watch',
  'server:watch',
  'bower:watch',
  'assets:watch',
  'copy:watch',
  'locales:watch',
  'markup:watch',
  'scripts:watch',
  'styles:watch',
  'templates:watch',
  'vulcanize:watch',
  'cache-config:watch',
  'browser-sync'
], () => {
  console.info('Watching');
});

Add server task to gulp watch task


var southWest = L.latLng(34.9816, -85.4719);
var northEast = L.latLng(35.217, -85.0462);
var center = L.latLng(35.0657, -85.241);
var bounds = L.latLngBounds(southWest, northEast);

var map = L.map('map', {
  maxZoom: 18,
  minZoom: 11,
  maxBounds: bounds,
  center: center,
  zoom: 12
});

L.tileLayer('static/tiles/{z}/{x}/{y}.png', {
    attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Zone data © City of Chattanooga, Imagery © Mapbox'}).addTo(map);

r

var southWest = L.latLng(34.9816, -85.4719);
var northEast = L.latLng(35.217, -85.0462);
var center = L.latLng(35.0657, -85.241);
var bounds = L.latLngBounds(southWest, northEast);

var map = L.map('map', {
  maxZoom: 18,
  minZoom: 11,
  maxBounds: bounds,
  center: center,
  zoom: 12
});

L.tileLayer('http://chattanoogapubliclibrary.github.io/cpd-zones-tiles/tiles/{z}/{x}/{y}.png', {
    attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Zone data © City of Chattanooga, Imagery © Mapbox'}).addTo(map);

r

Replace local tile URL with Chattanooga Public Library map tiles on GitHub.


// @flow
import { NARROW_SPACE, WIDE_SPACE, NARROW_BAR, WIDE_BAR } from "../Core/Characters"

const NARROW_WIDTH = 1
const WIDE_WIDTH = 3

export function rect(x: number, wide: boolean, filled: boolean): string {
	return `<rect x="${x}" y="0" width="${wide ? WIDE_WIDTH : NARROW_WIDTH}" height="1" fill="${filled ? "black" : "white"}"/>`
}

export function renderBarcodeToSVG(input: string): string {
	let svg = ""
	let x = 0

	for (const char of input) {
		switch (char) {
			case NARROW_SPACE:
				svg += rect(x, false, false)
				x += NARROW_WIDTH
				break
			case WIDE_SPACE:
				svg += rect(x, true, false)
				x += WIDE_WIDTH
				break
			case NARROW_BAR:
				svg += rect(x, false, true)
				x += NARROW_WIDTH
				break
			case WIDE_BAR:
				svg += rect(x, true, true)
				x += WIDE_WIDTH
				break
		}
	}

	return `<svg viewBox="0 0 ${x} 1" preserveAspectRatio="none">${svg}</svg>`
}

// @flow
import { NARROW_SPACE, WIDE_SPACE, NARROW_BAR, WIDE_BAR } from "../Core/Characters"

const NARROW_WIDTH = 1
const WIDE_WIDTH = 3

export function rect(x: number, wide: boolean, filled: boolean): string {
	return `<rect x="${x}" y="0" width="${wide ? WIDE_WIDTH : NARROW_WIDTH}" height="1" fill="${filled ? "black" : "white"}"/>`
}

export function renderBarcodeToSVG(input: string, {width, height}: {width?: number, height?: number} = {}): string {
	let svg = ""
	let x = 0

	for (const char of input) {
		switch (char) {
			case NARROW_SPACE:
				svg += rect(x, false, false)
				x += NARROW_WIDTH
				break
			case WIDE_SPACE:
				svg += rect(x, true, false)
				x += WIDE_WIDTH
				break
			case NARROW_BAR:
				svg += rect(x, false, true)
				x += NARROW_WIDTH
				break
			case WIDE_BAR:
				svg += rect(x, true, true)
				x += WIDE_WIDTH
				break
		}
	}

	let options = [
		`viewBox="0 0 ${x} 1"`,
		`preserveAspectRatio="none"`,
	]
	if (width) {
		options.push(`width="${width}"`)
	}
	if (height) {
		options.push(`height="${height}"`)
	}

	return `<svg ${options.join(" ")}>${svg}</svg>`
}

Implement width and height options


import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchPost } from '../actions';

class PostsShow extends Component {
  componentDidMount() {
    const { id } = this.props.match.params;
    this.props.fetchPost(id);
  }

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

    if (!post) {
      return (
Loading...
);
    }

    return(

        <Link to="/">Back to Index</Link>

{post.title}

Categories: {post.categories}

{post.content}

    );
  }
}

function mapStateToProps({ posts }, ownProps) {
  return { post: posts[ownProps.match.params.id] };
}

export default connect(mapStateToProps, { fetchPost })(PostsShow);

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchPost, deletePost } from '../actions';

class PostsShow extends Component {
  componentDidMount() {
    const { id } = this.props.match.params;
    this.props.fetchPost(id);
  }

  onDeleteClick() {
    const { id } = this.props.match.params;
    this.props.deletePost(id);
  }

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

    if (!post) {
      return (
Loading...
);
    }

    return(

        <Link to="/">Back to Index</Link>
        <button
          className="btn btn-danger pull-xs-right"
          onClick={this.onDeleteClick.bind(this)}
          >
          Delete Post
        </button>

{post.title}

Categories: {post.categories}

{post.content}

    );
  }
}

function mapStateToProps({ posts }, ownProps) {
  return { post: posts[ownProps.match.params.id] };
}

export default connect(mapStateToProps, { fetchPost, deletePost })(PostsShow);

Create deletePost function and import/wire up deletePost action creator


(function(){
  'use strict';

  var controls = document.querySelector('#controls');
  controls.showDisplay = function() {
    chrome.app.window.create('build/display.html',
      {
        id: 'displayWindow',
        alwaysOnTop: true,
        frame: 'none',
        focused: false,
        hidden: true,
        resizable: false,
        state: 'maximized',
        visibleOnAllWorkspaces: true
      },
      function(displayWindow) {
        displayWindow.contentWindow.addEventListener('load', function(){
          displayWindow.outerBounds.left = 1930;
          displayWindow.maximize();
          displayWindow.show(false);
        });
    });
  };
})();

(function(){
  'use strict';

  var controller = {

    DISPLAY_WINDOW_ID: 'displayWindow',

    showDisplay: function() {
      if (chrome.app.window.get(this.DISPLAY_WINDOW_ID)) {
        return;
      }

      var externalDisplayInfo, queueMoveToExternalDisplay = false;
      chrome.system.display.getInfo(function(displayInfos) {
        for (var i = displayInfos.length - 1; i >= 0; i--) {
          if (!displayInfos[i].isPrimary && displayInfos[i].isEnabled) {
            externalDisplayInfo = displayInfos[i];
            break;
          }
        }
      });

      chrome.app.window.create('build/display.html',
        {
          id: this.DISPLAY_WINDOW_ID,
          alwaysOnTop: true,
          frame: 'none',
          focused: false,
          hidden: true,
          resizable: false,
          state: 'maximized',
          visibleOnAllWorkspaces: true
        },
        (function(displayWindow) {
          if (externalDisplayInfo)  {
            this.moveToExternalDisplay(displayWindow, externalDisplayInfo);
          } else {
            queueMoveToExternalDisplay = true;
          }
      }).bind(this));

    },

    moveToExternalDisplay: function(displayWindow, externalDisplayInfo) {
      displayWindow.contentWindow.addEventListener('load', function(){
        displayWindow.outerBounds.left = externalDisplayInfo.bounds.left;
        displayWindow.maximize();
        displayWindow.show(false);
      });
    }
  };

  var controls = document.querySelector('#controls');
  controls.showDisplay = controller.showDisplay.bind(controller);
})();

Move display window to external display based on external display info.


import Vue from 'vue'
import Router from 'vue-router'

// Import Components
import Landing from '@/components/landing/Landing'
import Editor from '@/components/editor/Editor'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Landing',
      component: Landing
    },
    {
      path: '/example',
      name: 'Example',
      component: Editor
    }
  ]
})

import Vue from 'vue'
import Router from 'vue-router'

// Import Components
import Landing from '@/components/landing/Landing'
import Editor from '@/components/editor/Editor'

Vue.use(Router)

export default new Router({
  routes: [{
    path: '/',
    name: 'Landing',
    component: Landing
  },
  {
    path: '/example/dzi',
    name: 'DZI Example',
    component: Editor,
    props: {
      default: true,
      type: 'dzi'
    }
  },
  {
    path: '/example/simple',
    name: 'Simple Example',
    component: Editor,
    props: {
      default: true,
      type: 'simple'
    }
  },
  {
    path: '/example',
    name: 'Examples',
    component: Editor,
    props: {
      default: true,
      type: 'examples'
    }
  }

  ]
})

Add routes for DZI and simple example


var CONTEXT_URI = process.env.CONTEXT_URI;

exports.requiresSession = function (req, res, next) {
    if (req.session.encodedUsername) {
        res.cookie('_eun', req.session.encodedUsername);
        next();
    } else {
        req.session.redirectUrl = CONTEXT_URI + req.originalUrl;
        if (req.url.split('?')[1] !== undefined) {
            var intent = "session.redirect";
            var redirectUrl = CONTEXT_URI + req.url;
            res.redirect(CONTEXT_URI + "/signup" + "?intent=" + intent + "&redirectUrl=" + encodeURIComponent(redirectUrl));
        } else {
            res.redirect(CONTEXT_URI + "/signup");
        }
    }
};

exports.setSession = function (req, res, user) {
    req.session.username = user.username;
    req.session.encodedUsername = user.encodedUsername;
    req.session.githubUsername = user.githubUser.username;
    req.session.avatarUrl = user.githubUser._json.avatar_url;
    res.cookie('_eun', req.session.encodedUsername);
};

exports.resetSession = function (req) {
    req.session.encodedUsername = null;
    req.session.auth = null;
    req.session.intent = null;
    req.session.oneselfUsername = null;
    req.session.username = null;
    req.session.githubUsername = null;
    req.session.avatarUrl = null;
};

var CONTEXT_URI = process.env.CONTEXT_URI;

exports.requiresSession = function (req, res, next) {
    if (req.session.encodedUsername) {
        res.cookie('_eun', req.session.encodedUsername);
        next();
    } else {
        req.session.redirectUrl = CONTEXT_URI + req.originalUrl;
        if (req.url.split('?')[1] !== undefined) {
            var intent = "session.redirect";
            var redirectUrl = CONTEXT_URI + req.url;
            res.redirect(CONTEXT_URI + "/signup" + "?intent=" + intent + "&redirectUrl=" + encodeURIComponent(redirectUrl));
        } else {
            res.redirect(CONTEXT_URI + "/signup");
        }
    }
};

exports.setSession = function (req, res, user) {
    req.session.username = user.username;
    req.session.encodedUsername = user.encodedUsername;
    req.session.githubUsername = user.githubUser.username;
    req.session.avatarUrl = user.githubUser._json.avatar_url;
    res.cookie('_eun', req.session.encodedUsername);
};

exports.resetSession = function (req) {
    req.session.destory;
    req.session.encodedUsername = null;
    req.session.auth = null;
    req.session.intent = null;
    req.session.oneselfUsername = null;
    req.session.username = null;
    req.session.githubUsername = null;
    req.session.avatarUrl = null;
};

Destroy session redis way too.


import assertString from './util/assertString';

/* eslint-disable max-len */
const creditCard = /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})|62[0-9]{5,}$/;
/* eslint-enable max-len */

export default function isCreditCard(str) {
  assertString(str);
  const sanitized = str.replace(/[^0-9]+/g, '');
  if (!creditCard.test(sanitized)) {
    return false;
  }
  let sum = 0;
  let digit;
  let tmpNum;
  let shouldDouble;
  for (let i = sanitized.length - 1; i >= 0; i--) {
    digit = sanitized.substring(i, (i + 1));
    tmpNum = parseInt(digit, 10);
    if (shouldDouble) {
      tmpNum *= 2;
      if (tmpNum >= 10) {
        sum += ((tmpNum % 10) + 1);
      } else {
        sum += tmpNum;
      }
    } else {
      sum += tmpNum;
    }
    shouldDouble = !shouldDouble;
  }
  return !!((sum % 10) === 0 ? sanitized : false);
}

import assertString from './util/assertString';

/* eslint-disable max-len */
const creditCard = /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})|62[0-9]{14}$/;
/* eslint-enable max-len */

export default function isCreditCard(str) {
  assertString(str);
  const sanitized = str.replace(/[^0-9]+/g, '');
  if (!creditCard.test(sanitized)) {
    return false;
  }
  let sum = 0;
  let digit;
  let tmpNum;
  let shouldDouble;
  for (let i = sanitized.length - 1; i >= 0; i--) {
    digit = sanitized.substring(i, (i + 1));
    tmpNum = parseInt(digit, 10);
    if (shouldDouble) {
      tmpNum *= 2;
      if (tmpNum >= 10) {
        sum += ((tmpNum % 10) + 1);
      } else {
        sum += tmpNum;
      }
    } else {
      sum += tmpNum;
    }
    shouldDouble = !shouldDouble;
  }
  return !!((sum % 10) === 0 ? sanitized : false);
}

Fix Union Pay credit card regex


'use strict'

module.exports = toHashOfStrings;

function toHashOfStrings (obj) {
  var out = {};
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      out[key] = obj[key].toString();
    }
  }
  return out;
}

'use strict'

module.exports = toHashOfStrings;

function toHashOfStrings (obj) {
  var out = {};
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      out[key] = new String(obj[key]).toString();
    }
  }
  return out;
}

Fix null/undefined error in hash of strings


var chakram = require('chakram'),
    expect = chakram.expect;

describe("mPayroll API", function() {

    it("should have POST employees endpoint", function () {
        this.timeout(4000);
        expect(chakram.post("http://localhost:3000/api_mpayroll/employees")).to.have.status(201);
        return chakram.wait();
    });

});

var chakram = require('chakram'),
    expect = chakram.expect;

describe("mPayroll API", function() {
    const empRec = {
        "name": 'Harry Hourly',
        "type": 'H',
        "rate": '10.00'
    };

    it("should have POST employees endpoint", function () {
        this.timeout(4000);
        expect(chakram.post("http://localhost:3000/api_mpayroll/employees", empRec)).to.have.status(201);
        return chakram.wait();
    });

    it("POST employees endpoint without employee data is Bad Request", function () {
        this.timeout(4000);

        expect(chakram.post("http://localhost:3000/api_mpayroll/employees")).to.have.status(400);
        return chakram.wait();
    });
});

ADD - TDD for POST employees endpoint without employee data


// The Karma plugin we're using installs itself on window.__karma__, so disable the dangling underscore ESLint rule
/* eslint-disable no-underscore-dangle */

/**
 * This is where your tests go.  It should happen automatically when you
 * add files to the karma configuration.
 */
(function() {
    'use strict';

    var specs = [],
        config = {};

    // you can automatically get the test files using karma's configs
    Object.keys(window.__karma__.files).forEach(function(file) {
        if (/spec\.js$/.test(file)) {
            specs.push(file);
        }
    });

    // This is where karma puts the files
    config.baseUrl = '/base';

    // Karma lets you list the test files here
    config.deps = specs;
    config.callback = window.__karma__.start;

    require.config(config);
}());

Fix (ignore) no-underscore-dangle rule violations


(function() {
  'use strict';

  /* global exports, require */

  var path = require('path');

  exports.config = {
    seleniumAddress: 'http://localhost:4444/wd/hub',
    capabilities: {
      browserName: 'chrome',
      chromeOptions: {
        args: ['--load-extension=' + path.resolve('e2e-app')]
      }
    },
    specs: ['e2e/**/*.js']
  };
})();

(function() {
  'use strict';

  /* global exports, require */

  var path = require('path');

  exports.config = {
    seleniumAddress: 'http://localhost:4444/wd/hub',
    capabilities: {
      browserName: 'chrome',
      chromeOptions: {
        args: ['--no-sandbox', '--load-extension=' + path.resolve('e2e-app')]
      }
    },
    specs: ['e2e/**/*.js']
  };
})();

Add --no-sandbox arg to protractor chrome


/* eslint no-bitwise: 0 */
function swap16 (val) {
  return ((val & 0xFF) << 8) |
    ((val >> 8) & 0xFF);
}

function decodeBigEndian (imageFrame, pixelData) {
  if (imageFrame.bitsAllocated === 16) {
    let arrayBuffer = pixelData.buffer;
    let offset = pixelData.byteOffset;
    const length = pixelData.length;
    // if pixel data is not aligned on even boundary, shift it so we can create the 16 bit array
    // buffers on it

    if (offset % 2) {
      arrayBuffer = arrayBuffer.slice(offset);
      offset = 0;
    }

    if (imageFrame.pixelRepresentation === 0) {
      imageFrame.pixelData = new Uint16Array(arrayBuffer, offset, length / 2);
    } else {
      imageFrame.pixelData = new Int16Array(arrayBuffer, offset, length / 2);
    }
    // Do the byte swap
    for (let i = 0; i < imageFrame.pixelData.length; i++) {
      imageFrame[i] = swap16(imageFrame.pixelData[i]);
    }

  } else if (imageFrame.bitsAllocated === 8) {
    imageFrame.pixelData = pixelData;
  }

  return imageFrame;
}

export default decodeBigEndian;

/* eslint no-bitwise: 0 */
function swap16 (val) {
  return ((val & 0xFF) << 8) |
    ((val >> 8) & 0xFF);
}

function decodeBigEndian (imageFrame, pixelData) {
  if (imageFrame.bitsAllocated === 16) {
    let arrayBuffer = pixelData.buffer;
    let offset = pixelData.byteOffset;
    const length = pixelData.length;
    // if pixel data is not aligned on even boundary, shift it so we can create the 16 bit array
    // buffers on it

    if (offset % 2) {
      arrayBuffer = arrayBuffer.slice(offset);
      offset = 0;
    }

    if (imageFrame.pixelRepresentation === 0) {
      imageFrame.pixelData = new Uint16Array(arrayBuffer, offset, length / 2);
    } else {
      imageFrame.pixelData = new Int16Array(arrayBuffer, offset, length / 2);
    }
    // Do the byte swap
    for (let i = 0; i < imageFrame.pixelData.length; i++) {
      imageFrame.pixelData[i] = swap16(imageFrame.pixelData[i]);
    }

  } else if (imageFrame.bitsAllocated === 8) {
    imageFrame.pixelData = pixelData;
  }

  return imageFrame;
}

export default decodeBigEndian;

Fix byte swap for Explicit VR Big Endian (retired)


(function() {
    var config = {
        topics: [
            "sensor/temperature/#",
            "control/radiator/+/active",
            "control/centralheating/active",
        ],
        name_overrides: {
            'livingroom': "Living Room",
            'spareroom': "Spare Room"
        },
        fixed_rooms: ["office"],
        mqtt_broker: "ws://localhost:9001",
    };

    window.homesense = {config: config};

})();

(function() {
    var protocol = location.protocol === "https:" ? "wss://" : "ws://";
    var mqtt_broker = protocol + location.host + "/";
    var config = {
        topics: [
            "sensor/temperature/#",
            "control/radiator/+/active",
            "control/centralheating/active",
        ],
        name_overrides: {
            'livingroom': "Living Room",
            'spareroom': "Spare Room"
        },
        fixed_rooms: ["office"],
        // mqtt_broker: "ws://127.0.0.1:8080/",
        mqtt_broker: mqtt_broker,
    };

    window.homesense = {config: config};

})();

Build websocket URL from existing location


/* Promo page specific JS fucntions */

function onReferralURLClick() {
    var ref_url = document.getElementById('referral_display').value;
    window.prompt("Copy to clipboard: Ctrl+C, Enter", ref_url);
}

function showEmailList() {
    var email_list = document.getElementById('emailList')
    var show_email_button = document.getElementById('showEmailButton')

    // Toggle the visibility of the list and the text of the button
    if ( email_list.style.display == 'block') {
        show_email_button.value = 'Show Email List';
        email_list.style.display = 'none';
    } else {
        show_email_button.value = 'Hide Email List';
        email_list.style.display = 'block';
    }
}

Remove show email JS function


#!/usr/bin/env node
const test = require('tap').test
    , spawn = require('child_process').spawn
    ;

test('build test add-ons', function(t) {
  t.plan(1);
  spawn('node-gyp', ['rebuild'], { 'cwd': __dirname, 'stdio': 'inherit'})
    . on('close', function(code) { t.strictEqual(code, 0); t.end();})
    ;
});

#!/usr/bin/env node
const test  = require('tap').test
    , spawn = require('child_process').spawn
    , path  = require('path')
    , fs    = require('fs')
    ;

test('build test add-ons', function(t) {
  t.plan(1);

  fs.exists(path.resolve(__dirname, 'build', 'Makefile'), function(exists) {
    var command = exists ? 'build' : 'rebuild';
    console.log(command);
    spawn('node-gyp', [command], { 'cwd': __dirname, 'stdio': 'inherit'})
      . on('close', function(code) { t.strictEqual(code, 0); t.end();})
      ;
  });
});

Rebuild test add-ons more gently.


// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from "vue";
import App from "./App";
import momentPlugin from "./moment";
import router from "./router";

Vue.use(momentPlugin, "$moment");
Vue.config.productionTip = false;

/* eslint-disable no-new */
new Vue({
  el: "#app",
  router,
  components: { App },
  template: "<App/>",
});

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from "vue";
import App from "./App";
import momentPlugin from "./moment";
import router from "./router";

Vue.use(momentPlugin, "$moment");
Vue.config.productionTip = false;
Vue.config.devtools = true;
Vue.config.performance = true;

/* eslint-disable no-new */
new Vue({
  el: "#app",
  router,
  components: { App },
  template: "<App/>",
});

Enable performance inspection in dev tools


export default {
  name: 'folder',

  lookup(options) {
    let found;
    if (typeof window !== 'undefined') {
      const language = window.location.pathname.match(/\/([a-zA-Z-]*)/g);
      if (typeof options.lookupFromUrlIndex === 'number') {
        found = language[options.lookupFromFolderIndex].replace('/', '');
      } else {
        found = language[0].replace('/', '');
      }
    }
    return found;
  }
};

export default {
  name: 'folder',

  lookup(options) {
    let found;
    if (typeof window !== 'undefined') {
      const language = window.location.pathname.match(/\/([a-zA-Z-]*)/g);
      if (language instanceof Array) {
        if (typeof options.lookupFromUrlIndex === 'number') {
          found = language[options.lookupFromFolderIndex].replace('/', '');
        } else {
          found = language[0].replace('/', '');
        }
      }
    }
    return found;
  }
};

Check if language const contains array


module.exports = {
  env: {
    mocha: true
  },

  rules: {
    // specify the maximum depth callbacks can be nested
    'max-nested-callbacks': 0
  }
}

module.exports = {
  env: {
    mocha: true
  },

  rules: {
    // specify the maximum depth callbacks can be nested
    'max-nested-callbacks': 0,
    // specify the maximum number of statement allowed in a function
    'max-statements': 0
  }
}

Disable max-statements in Mocha tests


import {connect} from 'react-redux';
import {Component} from 'react';
import PropTypes from 'prop-types';

class DocumentTitle extends Component {
    constructor(props) {
        super(props);
        this.state = {
            initialTitle: document.title,
        };
    }

    UNSAFE_componentWillReceiveProps(props) {
        if (props.pendingCallbacks.length) {
            document.title = 'Updating...';
        } else {
            document.title = this.state.initialTitle;
        }
    }

    shouldComponentUpdate() {
        return false;
    }

    render() {
        return null;
    }
}

DocumentTitle.propTypes = {
    pendingCallbacks: PropTypes.array.isRequired,
};

export default connect(state => ({
    pendingCallbacks: state.pendingCallbacks,
}))(DocumentTitle);

import {connect} from 'react-redux';
import {Component} from 'react';
import PropTypes from 'prop-types';

class DocumentTitle extends Component {
    constructor(props) {
        super(props);
        const {update_title} = props.config;
        this.state = {
            initialTitle: document.title,
            update_title: update_title,
        };
    }

    UNSAFE_componentWillReceiveProps(props) {
        if (props.pendingCallbacks.length) {
            document.title = this.state.update_title;
        } else {
            document.title = this.state.initialTitle;
        }
    }

    shouldComponentUpdate() {
        return false;
    }

    render() {
        return null;
    }
}

DocumentTitle.propTypes = {
    pendingCallbacks: PropTypes.array.isRequired,
    update_title: PropTypes.string,
};

export default connect(state => ({
    config: state.config,
    pendingCallbacks: state.pendingCallbacks,
}))(DocumentTitle);

Use update_title in DocumentTitle component


require.config({
    baseUrl: "/static/scripts/src",
    paths: {
        // Dependencies:
        "underscore": "lib/underscore-min",
        "backbone": "lib/backbone-min",
        "jquery": "lib/jquery-3.1.1.min",
        "chartjs": "lib/Chart.min"
    },

    moduleDefaults: {
        "build/templates": {}
    },

    shim: {
        chartjs: {exports: "Chart"},
        leaflet: {exports: "L"},
        underscore: {exports: "_"},
        backbone: {deps: ["underscore", "jquery"],
                   exports: "Backbone"}
    }
});

require(["setup"], function(setup) {
    window.app = setup.start();
});

require.config({
    baseUrl: "/static/scripts/src",
    paths: {
        // Dependencies:
        "underscore": "lib/underscore-min",
        "backbone": "lib/backbone",
        "jquery": "lib/jquery-3.1.1.min",
        "chartjs": "lib/Chart.min"
    },

    moduleDefaults: {
        "build/templates": {}
    },

    shim: {
        chartjs: {exports: "Chart"},
        leaflet: {exports: "L"},
        underscore: {exports: "_"},
        backbone: {deps: ["underscore", "jquery"],
                   exports: "Backbone"}
    }
});

require(["setup"], function(setup) {
    window.app = setup.start();
});

Revert to old version of Backbone


import React from 'react';
import './ToolBar.css';

export default ({ tools }) => {
  return (
    <div className="toolbar">
      {tools.map((tool, index) => (
        <button
          key={index}
          onClick={() => tool.onClick()}
          disabled={tool.disabled ? tool.disabled : false}
        >
          {tool.label}
        </button>
      ))}
    </div>
  );
};

import React from 'react';
import './ToolBar.css';

export default ({ tools }) => {
  return (
    <div className="toolbar">
      {tools.map((tool, index) => (
        <button
          key={index}
          onClick={() => tool.onClick()}
          disabled={!!tool.disabled}
        >
          {tool.label}
        </button>
      ))}
    </div>
  );
};

Use simpler toolbar disabled logic


// @flow
// robot status panel with connect button
import * as React from 'react'

import type {Robot} from '../../robot'

import StatusCard from './StatusCard'
import AttachedInstrumentsCard from './AttachedInstrumentsCard'
import InformationCard from './InformationCard'
import ConnectivityCard from './ConnectivityCard'
import ConnectAlertModal from './ConnectAlertModal'
import styles from './styles.css'

type Props = Robot

export default function RobotSettings (props: Props) {
  return (

        <StatusCard {...props} />

        <AttachedInstrumentsCard {...props} />

        <InformationCard {...props} />

          <ConnectivityCard {...props} />

  )
}

export {ConnectAlertModal}

// @flow
// robot status panel with connect button
import * as React from 'react'

import type {Robot} from '../../robot'

import StatusCard from './StatusCard'
// import AttachedInstrumentsCard from './AttachedInstrumentsCard'
import InformationCard from './InformationCard'
import ConnectivityCard from './ConnectivityCard'
import ConnectAlertModal from './ConnectAlertModal'
import styles from './styles.css'

type Props = Robot

export default function RobotSettings (props: Props) {
  return (

        <StatusCard {...props} />

      {/*

      <AttachedInstrumentsCard {...props} />

 */}

        <InformationCard {...props} />

          <ConnectivityCard {...props} />

  )
}

export {ConnectAlertModal}

Hide AttachedInstrumentsCard until wired up


'use strict';

Connector.playerSelector = '#page_sidebar';

Connector.getArtist = () => {
	let artists = $('.player-track-artist').children().toArray();
	return Util.joinArtists(artists);
};

Connector.trackSelector = '.player-track-title';

Connector.currentTimeSelector = '.player-progress .progress-time';

Connector.durationSelector = '.player-progress .progress-length';

Connector.trackArtSelector = '.player-cover img';

Connector.filter = MetadataFilter.getRemasteredFilter();

Connector.isPlaying = () => $('.svg-icon-pause').length > 0;

'use strict';

Connector.playerSelector = '#page_sidebar';

Connector.getArtist = () => {
	let artists = $('.player-track-artist').children().toArray();
	return Util.joinArtists(artists);
};

Connector.trackSelector = '.player-track-title';

Connector.currentTimeSelector = '.player-progress .progress-time';

Connector.durationSelector = '.player-progress .progress-length';

Connector.trackArtSelector = '.player-cover img';

Connector.filter = MetadataFilter.getRemasteredFilter();

Connector.isPlaying = () => $('#player .svg-icon-pause').length > 0;

Fix Deezer is isPlaying() detection


var installAjax = function () {
	if (!stubAjax) return;
	jasmine.Ajax.install();
};

var uninstallAjax = function () {
	if (!stubAjax) return;
	jasmine.Ajax.uninstall();
};

var expectResponse = function (response) {
	if (!stubAjax) return;
	jasmine.Ajax.requests.mostRecent().respondWith(response);
};

window.responses = {};

var installAjax = function () {
	if (!stubAjax) return;
	jasmine.Ajax.install();
};

var uninstallAjax = function () {
	if (!stubAjax) return;
	jasmine.Ajax.uninstall();
};

var expectResponse = function (response) {
	if (!stubAjax) return;
	jasmine.Ajax.requests.mostRecent().respondWith(response);
};

var urlRegex = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;

var parseUrl = function (url) {
	var result = urlRegex.exec(url);
	var names = ['url', 'scheme', 'slash', 'host', 'port', 'path', 'query', 'hash'];
	var output = {};
	names.forEach(function (name, i) {
		if (name === "query") {
			output[name] = parseQueryString(result[i]);
		} else {
			output[name] = result[i];
		}
	});
	return output;
};

var parseQueryString = function (queryString) {
	var output = {
		_originalQueryString: queryString
	};
	if (!queryString) return output;
	queryString
		.split("&")
		.map(function (queryPortion) {
			return queryPortion.split("=");
		})
		.forEach(function (queryPortion) {
			var attr = unescape(queryPortion[0]);
			var val = unescape(queryPortion[1]);
			if (!attr || !val) return;
			output[attr] = val;
		});
	return output;
};

window.responses = {};

Add xhr parsing helper methods


/* @flow */
import { isObject, arrayEach, objectEach } from 'fela-utils'

import type { StyleType } from '../../../flowtypes/StyleType'
import type { DOMRenderer } from '../../../flowtypes/DOMRenderer'
import type { NativeRenderer } from '../../../flowtypes/NativeRenderer'

function extendStyle(
  style: Object,
  extension: Object,
  extendPlugin: Function,
  renderer: DOMRenderer | NativeRenderer
): void {
  // extend conditional style objects
  if (extension.hasOwnProperty('condition')) {
    if (extension.condition) {
      renderer._mergeStyle(style, extendPlugin(extension.style))
    }
  } else {
    // extend basic style objects
    renderer._mergeStyle(style, extension)
  }
}

function extend(
  style: Object,
  type: StyleType,
  renderer: DOMRenderer | NativeRenderer
): Object {
  objectEach(style, (value, property) => {
    if (property === 'extend') {
      const extensions = [].concat(value)

      arrayEach(extensions, extension =>
        extendStyle(style, extension, extend, renderer)
      )
      delete style[property]
    } else if (isObject(value)) {
      // support nested extend as well
      style[property] = extend(value, type, renderer)
    }
  })

  return style
}

export default () => extend

/* @flow */
import { isObject, arrayEach, objectEach } from 'fela-utils'

import type { StyleType } from '../../../flowtypes/StyleType'
import type { DOMRenderer } from '../../../flowtypes/DOMRenderer'
import type { NativeRenderer } from '../../../flowtypes/NativeRenderer'

function extendStyle(
  style: Object,
  extension: Object,
  extendPlugin: Function,
  type: StyleType,
  renderer: DOMRenderer | NativeRenderer
): void {
  // extend conditional style objects
  if (extension.hasOwnProperty('condition')) {
    if (extension.condition) {
      renderer._mergeStyle(style, extendPlugin(extension.style, type, renderer))
    }
  } else {
    // extend basic style objects
    renderer._mergeStyle(style, extension)
  }
}

function extend(
  style: Object,
  type: StyleType,
  renderer: DOMRenderer | NativeRenderer
): Object {
  objectEach(style, (value, property) => {
    if (property === 'extend') {
      const extensions = [].concat(value)

      arrayEach(extensions, extension =>
        extendStyle(style, extension, extend, type, renderer)
      )
      delete style[property]
    } else if (isObject(value)) {
      // support nested extend as well
      style[property] = extend(value, type, renderer)
    }
  })

  return style
}

export default () => extend

Fix missing renderer issue when deep merging


var log = require('./helpers/log.js')
var bin = require('./helpers/bin.js')
var root = require('./helpers/root.js')
var exec = require('./helpers/exec.js')

module.exports = function () {
  var nyc = bin('nyc') + ' --include="' + root + 'src/" --sourceMap=false --instrument=false --all=true --reporter=lcov --reporter=text-summary --report-dir=' + root + 'coverage/'
  var mocha = bin('mocha') + ' --compilers js:babel-core/register --require babel-polyfill ' + root + 'tests/*'
  var command = 'NODE_ENV=test ' + nyc + ' ' + mocha

  log.info('Running tests in `tests/`')
  exec(command, function (err) {
    log.info('Tests finished. Check your coverage via coverage/lcov-report/index.html')
    if (err) process.exit(1)
  })
}

var log = require('./helpers/log.js')
var bin = require('./helpers/bin.js')
var root = require('./helpers/root.js')
var exec = require('./helpers/exec.js')

module.exports = function () {
  var nyc = bin('nyc') + ' --all --only="' + root + 'src/" --require babel-core/register --require babel-polyfill --sourceMap=false --instrument=false --reporter=lcov --reporter=text-summary --report-dir=' + root + 'coverage/'
  var mocha = bin('mocha') + ' ' + root + 'tests/*'
  var command = 'NODE_ENV=test ' + nyc + ' ' + mocha

  log.info('Running tests in `tests/`')
  exec(command, function (err) {
    log.info('Tests finished. Check your coverage via coverage/lcov-report/index.html')
    if (err) process.exit(1)
  })
}

Include all source files into coverage


var storyListener = function (modal) {

    var observer = new MutationObserver(function (mutations) {
        mutations.forEach(handleMutationEvents);
    });

    var handleMutationEvents = function handleMutationEvents(mutation) {
        Array.prototype.forEach.call(mutation.addedNodes, addListenersInNode);
        addListenersInNode(mutation.target);
    }

    var addListenersInNode = function styleLabelsInNode(node) {
        if (nodeIsElement(node)) {
            console.log(findFinishButtons(node));
            addListenerToButtons(findFinishButtons(node));
        }
    }

    var nodeIsElement = function nodeIsElement(node) {
        return (typeof node.querySelectorAll !== 'undefined');
    }

    var findFinishButtons = function findLabelsInNode(node) {
        return node.querySelectorAll('.button.finish.autosaves');
    }

    var addListenerToButtons = function addListenerToButtons(buttons) {
        Array.prototype.forEach.call(buttons, function(button) {
            button.addEventListener('click', promptListener, false);
            function promptListener() {
                modal.modal('show');
                button.removeEventListener('click', promptListener, false);
            }
        });
    }

    var readyStateCheckInterval = setInterval(function() {
        if (document.readyState === "complete") {
            clearInterval(readyStateCheckInterval);

            var config = {
                attributes: true,
                characterData: true,
                childList: true,
                subtree: true
            };

            observer.observe(document, config);
        }
    }, 10);

    $('body').on('click', '.finish.button', function (e) {
        modal.modal('show')
    })
};

var storyListener = function (modal) {
    $(document).on('click', '.finish.button', function () {
        modal.modal('show')
    })
};

Attach event listeners to all finish buttons


define(['backbone'], function(Backbone) {

    return Backbone.Model.extend({
        idAttribute: 'SCANPARAMETERSMODELID',
        urlRoot: '/exp/parameters/models',

        defaults: {
            SEQUENCENUMBER: null,
            START: null,
            STOP: null,
            STEP: null,
            ARRAY: null,
        },

        validation: {
            DATACOLLECTIONPLANID: {
                required: true,
                pattern: 'digits',
            },
            SCANPARAMETERSSERVICEID: {
                required: true,
                pattern: 'digits',
            },
            SEQUENCENUMBER: {
                required: true,
                pattern: 'number'
            },
            START: {
                required: function() {
                    return !this.get('ARRAY')
                },
                pattern: 'number'
            },
            STOP: {
                required: function() {
                    return !this.get('ARRAY')
                },
                pattern: 'number'
            },
            STEP: {
                required: function() {
                    return !this.get('ARRAY')
                },
                pattern: 'number'
            },
            ARRAY: {
                required: function() {
                    return (this.get('START') || this.get('STOP') || this.get('STEP'))
                },
                pattern: 'array'
            },
        },

    })

})

define(['backbone'], function(Backbone) {

    return Backbone.Model.extend({
        idAttribute: 'SCANPARAMETERSMODELID',
        urlRoot: '/exp/parameters/models',

        defaults: {
            SEQUENCENUMBER: null,
            START: null,
            STOP: null,
            STEP: null,
            ARRAY: null,
        },

        validation: {
            DATACOLLECTIONPLANID: {
                required: true,
                pattern: 'digits',
            },
            SCANPARAMETERSSERVICEID: {
                required: true,
                pattern: 'digits',
            },
            SEQUENCENUMBER: {
                required: true,
                pattern: 'number'
            },
            START: {
                required: function() {
                    return !this.get('ARRAY')
                },
                pattern: 'number'
            },
            STOP: {
                required: function() {
                    return !this.get('ARRAY')
                },
                pattern: 'number'
            },
            STEP: {
                required: function() {
                    return !this.get('ARRAY')
                },
                pattern: 'number'
            },
            ARRAY: {
                required: function() {
                    return !(this.get('START') || this.get('STOP') || this.get('STEP'))
                },
                pattern: 'array'
            },
        },

    })

})

Add the missing logical not in the scan model validation


Template.betItem.helpers({
  openStatus: function() {
    return ( this.status === "open" ) ? true : false;
  },

  pendingStatus: function() {
    return ( this.status === "pending" ) ? true : false;
  },

  completedStatus: function() {
    return ( this.status === "completed" ) ? true : false;
  }
});

Template.betItem.events({
  'click .remove_bet_button' : function(){
    Bets.remove( this._id );
  },

  'click .accept_button' : function() {
    Bets.update({ _id: this._id },
      { $set: { status: "pending" }
    });
  },

  'click .complete_bet_button' : function() {
    Bets.update({ _id: this._id },
      { $set: { status: "completed" }
    });
  }
});

Template.betItem.helpers({
  openStatus: function() {
    return ( this.status === "open" ) ? true : false;
  },

  pendingStatus: function() {
    return ( this.status === "pending" ) ? true : false;
  },

  completedStatus: function() {
    return ( this.status === "completed" ) ? true : false;
  },

  showEditForm: function() {
    if(Session.get("edit")) {
      return true;
    }
    else {
      Session.set("edit", false)
      return false;
    }
  },
});

Template.betItem.events({
  'click .remove_bet_button' : function(){
    Bets.remove( this._id );
  },

  'click .accept_button' : function() {
    Bets.update({ _id: this._id },
      { $set: { status: "pending" }
    });
  },

  'click .complete_bet_button' : function() {
    Bets.update({ _id: this._id },
      { $set: { status: "completed" }
    });
  }
});

Add show edit form logic


// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

module.exports = function (grunt) {
    var config = require("./config.js");

    // Helper function to load the config file
    function loadConfig(path) {
      var glob = require('glob');
      var object = {};
      var key;

      glob.sync('*', {cwd: path}).forEach(function(option) {
        key = option.replace(/\.js$/,'');
        object[key] = require(path + option);
      });

      return object;
    }

    // Load task options
    var gruntConfig = loadConfig('./tasks/options/');

    // Package data
    gruntConfig.pkg = grunt.file.readJSON("package.json");

    // Project config
    grunt.initConfig(gruntConfig);

    // Load all grunt-tasks in package.json
    require("load-grunt-tasks")(grunt);

    // Register external tasks
    grunt.loadTasks("tasks/");

    // Task alias's
    grunt.registerTask("default", ["clean", "less", "concat", "copy", "replace", "bom"]);
    grunt.registerTask("css", ["less", "bom"]);
    grunt.registerTask("base", ["clean:base", "concat:baseDesktop", "concat:basePhone", "concat:baseStringsDesktop", "concat:baseStringsPhone", "replace", "bom"]);
    grunt.registerTask("ui", ["clean:ui", "concat:uiDesktop", "concat:uiPhone", "concat:uiStringsDesktop", "concat:uiStringsPhone", "replace", "less", "bom"]);
}

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

module.exports = function (grunt) {
    var config = require("./config.js");

    grunt.file.preserveBOM = true;

    // Helper function to load the config file
    function loadConfig(path) {
      var glob = require('glob');
      var object = {};
      var key;

      glob.sync('*', {cwd: path}).forEach(function(option) {
        key = option.replace(/\.js$/,'');
        object[key] = require(path + option);
      });

      return object;
    }

    // Load task options
    var gruntConfig = loadConfig('./tasks/options/');

    // Package data
    gruntConfig.pkg = grunt.file.readJSON("package.json");

    // Project config
    grunt.initConfig(gruntConfig);

    // Load all grunt-tasks in package.json
    require("load-grunt-tasks")(grunt);

    // Register external tasks
    grunt.loadTasks("tasks/");

    // Task alias's
    grunt.registerTask("default", ["clean", "less", "concat", "copy", "replace"]);
    grunt.registerTask("css", ["less"]);
    grunt.registerTask("base", ["clean:base", "concat:baseDesktop", "concat:basePhone", "concat:baseStringsDesktop", "concat:baseStringsPhone", "replace"]);
    grunt.registerTask("ui", ["clean:ui", "concat:uiDesktop", "concat:uiPhone", "concat:uiStringsDesktop", "concat:uiStringsPhone", "replace", "less"]);
}

Test change to see if BOM task is really necessary


"use strict";
module.exports = function (grunt) {
	// Start w/ a clean slate
	grunt.initConfig({});
	// JSHint
	grunt.loadNpmTasks("grunt-contrib-jshint");
	grunt.config("jshint", {
		"options": {"jshintrc": true},
		"all": ["*.js"]
	});
	// Default task
	grunt.registerTask("default", "Does nothing", function () { /* Does nothing */ });
};

"use strict";
module.exports = function (grunt) {
	// Start w/ a clean slate
	grunt.initConfig({});

	// JSHint
	grunt.loadNpmTasks("grunt-contrib-jshint");
	grunt.config("jshint", {
		"options": {"jshintrc": true},
		"all": ["*.js"]
	});

	// Default task
	grunt.registerTask("default", "Does nothing", function () { /* Does nothing */ });
	// Aliases
	grunt.registerTask("hint", ["jshint"]);
	grunt.registerTask("lint", ["jshint"]);
};

Add Grunt JSHint task aliases


/**
 * Feature flags.
 *
 * Site Kit by Google, Copyright 2021 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 const enabledFeatures = new Set( global?._googlesitekitBaseData?.enabledFeatures || [] );

/**
 * Returns true if a feature is enabled; false otherwise.
 *
 * @since 1.25.0
 *
 * @param {string} feature            The name of the feature to check.
 * @param {Set}    [_enabledFeatures] Optional. The set of enabled features. Uses `enabledFeatures` set by the server in a global JS variable, by default.
 * @return {boolean} `true` if a feature is enabled; `false` otherwise.
 */
export const isFeatureEnabled = ( feature, _enabledFeatures = enabledFeatures ) => {
	return _enabledFeatures.has( feature );
};

/**
 * Feature flags.
 *
 * Site Kit by Google, Copyright 2021 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 const enabledFeatures = new Set( global?._googlesitekitBaseData?.enabledFeatures || [] );

/**
 * Returns true if a feature is enabled; false otherwise.
 *
 * @since 1.25.0
 * @since n.e.x.t Changed _enabledFeatures argument to be a `Set` instead of `Array`.
 *
 * @param {string} feature            The name of the feature to check.
 * @param {Set}    [_enabledFeatures] Optional. The set of enabled features. Uses `enabledFeatures` set by the server in a global JS variable, by default.
 * @return {boolean} `true` if a feature is enabled; `false` otherwise.
 */
export const isFeatureEnabled = ( feature, _enabledFeatures = enabledFeatures ) => {
	if ( ! ( _enabledFeatures instanceof Set ) ) {
		return false;
	}
	return _enabledFeatures.has( feature );
};

Update isFeatureEnabled to require a Set and add guard.


/*global QUnit:false, module:false, test:false, asyncTest:false, expect:false*/
/*global start:false, stop:false ok:false, equal:false, notEqual:false, deepEqual:false*/
/*global notDeepEqual:false, strictEqual:false, notStrictEqual:false, raises:false*/
(function(el) {

  test("creating an element with attributes", function() {
    var e = el('p', {id: 'my-id', 'class': 'my-class', 'data-alice': 'bob'});

    var dataAttribute = e.attributes.getNamedItem('data-alice');
    ok(dataAttribute);
    equal(dataAttribute.value, 'bob');

    equal(e.id, 'my-id');
    equal(e.className, 'my-class');
  });

  test("setting an attribute to null removes it from the element", function() {
    var e = el('a', {href: null});
    ok( !e.href );
  });
}(el));

/*global QUnit:false, module:false, test:false, asyncTest:false, expect:false*/
/*global start:false, stop:false ok:false, equal:false, notEqual:false, deepEqual:false*/
/*global notDeepEqual:false, strictEqual:false, notStrictEqual:false, raises:false*/
(function(el) {

  test("creating an element with attributes", function() {
    var e = el('p', {id: 'my-id', 'class': 'my-class', 'data-alice': 'bob'});

    var dataAttribute = e.attributes.getNamedItem('data-alice');
    ok(dataAttribute);
    equal(dataAttribute.value, 'bob');

    equal(e.id, 'my-id');
    equal(e.className, 'my-class');
  });

  test("attributes named differently by the DOM are not an issue as long as you use the HTML naming", function() {
    var e;
    e = el('p', {'class': 'my-class1 my-class2'});
    equal(e.className, 'my-class1 my-class2');

    e = el('label', {'for': 'my-input'});
    equal(e.htmlFor, 'my-input');
  });

  test("setting an attribute to null removes it from the element", function() {
    var e = el('a', {href: null});
    ok( !e.href );
  });
}(el));

Create a test specifically for naming inconsistencies beween DOM and HTML.


var assert = require('chai').assert;
var StringUtilities = require('../build/StringUtilities').StringUtilities;

describe('StringUtilities', function () {
    describe('format', function () {
        it('should format a string with no replacements', function () {
            assert.equal(StringUtilities.format("Test"), "Test");
        });

        it('should format a string with one replacement', function () {
            assert.equal(StringUtilities.format("Test %s", "one"), "Test one");
        });

        it('should format a string with multiple replacements', function () {
            assert.equal(StringUtilities.format("Test %s %s %s", "one", "two", "three"), "Test one two three");
        });
    });

    describe('toProperCase', function () {
        it('should properly case a sentence', function () {
            assert.equal(StringUtilities.toProperCase("this is a test, only a test.  the case should be proper."), "This Is A Test, Only A Test.  The Case Should Be Proper.");
        });
    });
});

var assert = require('chai').assert;
var StringUtilities = require('../build/stringUtilities').StringUtilities;

describe('StringUtilities', function () {
    describe('format', function () {
        it('should format a string with no replacements', function () {
            assert.equal(StringUtilities.format("Test"), "Test");
        });

        it('should format a string with one replacement', function () {
            assert.equal(StringUtilities.format("Test %s", "one"), "Test one");
        });

        it('should format a string with multiple replacements', function () {
            assert.equal(StringUtilities.format("Test %s %s %s", "one", "two", "three"), "Test one two three");
        });
    });

    describe('toProperCase', function () {
        it('should properly case a sentence', function () {
            assert.equal(StringUtilities.toProperCase("this is a test, only a test.  the case should be proper."), "This Is A Test, Only A Test.  The Case Should Be Proper.");
        });
    });
});

Fix casing of require StringUtilities


module.exports = {
  siteMetadata: {
    title: 'Gatsby Default Starter',
  },
  plugins: [
    'gatsby-plugin-react-helmet',
    {
      resolve: `gatsby-plugin-google-analytics`,
      options: {
        trackingId: 'UA-93011585-1',
        // Puts tracking script in the head instead of the body
        head: false,
        // Setting this parameter is optional
        anonymize: true,
        // Setting this parameter is also optional
        respectDNT: true,
        // Avoids sending pageview hits from custom paths
        exclude: ['/preview/**', '/do-not-track/me/too/'],
      },
    },
    {
      resolve: 'gatsby-plugin-google-fonts',
      options: {
        fonts: ['Lora', 'Raleway:400,700'],
      },
    },
  ],
};

module.exports = {
  siteMetadata: {
    title: 'Gatsby Default Starter',
  },
  plugins: [
    'gatsby-plugin-react-helmet',
    {
      resolve: `gatsby-plugin-google-analytics`,
      options: {
        trackingId: 'UA-93011585-1',
        // Puts tracking script in the head instead of the body
        head: false,
        // Setting this parameter is optional
        anonymize: true,
        // Setting this parameter is also optional
        respectDNT: true,
        // Avoids sending pageview hits from custom paths
        exclude: ['/preview/**', '/do-not-track/me/too/'],
      },
    },
    {
      resolve: 'gatsby-plugin-google-fonts',
      options: {
        fonts: ['Lora', 'Raleway:400,700', 'Roboto'],
      },
    },
  ],
};

Add roboto to google fonts