(function (module) {
    module.controller('projectListProcess', projectListProcess);
    projectListProcess.$inject = ["processes", "project", "$state", "mcmodal", "$filter"];

    function projectListProcess(processes, project, $state, mcmodal, $filter) {
        console.log('projectListProcess');
        var ctrl = this;

        ctrl.viewProcess = viewProcess;
        ctrl.chooseTemplate = chooseTemplate;

        ctrl.processes = processes;
        ctrl.project = project;
        if (ctrl.processes.length !== 0) {
            console.log('  ctrl.processes.length !== 0');
            ctrl.processes = $filter('orderBy')(ctrl.processes, 'name');
            ctrl.current = ctrl.processes[0];
            //$state.go('projects.project.processes.list.view', {process_id: ctrl.current.id});
        }

        ///////////////////////////////////

        function viewProcess(process) {
            ctrl.current = process;
            $state.go('projects.project.processes.list.view', {process_id: ctrl.current.id});
        }

        function chooseTemplate() {
            mcmodal.chooseTemplate(ctrl.project);
        }
    }
}(angular.module('materialscommons')));

(function (module) {
    module.controller('projectListProcess', projectListProcess);
    projectListProcess.$inject = ["processes", "project", "$state", "mcmodal", "$filter"];

    function projectListProcess(processes, project, $state, mcmodal, $filter) {
        var ctrl = this;

        ctrl.viewProcess = viewProcess;
        ctrl.chooseTemplate = chooseTemplate;

        ctrl.processes = processes;
        ctrl.project = project;
        if (ctrl.processes.length !== 0) {
            ctrl.processes = $filter('orderBy')(ctrl.processes, 'name');
            ctrl.current = ctrl.processes[0];
            $state.go('projects.project.processes.list.view', {process_id: ctrl.current.id});
        }

        ///////////////////////////////////

        function viewProcess(process) {
            ctrl.current = process;
            $state.go('projects.project.processes.list.view', {process_id: ctrl.current.id});
        }

        function chooseTemplate() {
            mcmodal.chooseTemplate(ctrl.project);
        }
    }
}(angular.module('materialscommons')));

Remove debug of ui router state changes.


import React, { PropTypes } from 'react';

import './index.css';

const Pit = React.createClass({
  propTypes: {
    pit: PropTypes.object.isRequired,
  },

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

    return (

{pit.name}

{pit.type}

{pit.dataset}

{pit.id}

        {Object.keys(pit.data).map((key) =>

            {key}
            {pit.data[key]}

        )}

    );
  },
});

export default Pit;

Add keys to table loop


import {
    SOCKET_SET_OPENING, SOCKET_SET_CLOSING, SOCKET_SEND_MESSAGE
} from "../constants/ActionTypes";

export default (socketClient) => ({
    open: url => {
        const action = { type: SOCKET_SET_OPENING };
        try {
            socketClient.open(url);
            action.payload = url;
        } catch (err) {
            action.error = true;
            action.payload = err;
        }
        return action;
    },

    close: () => {
        const action = { type: SOCKET_SET_CLOSING };
        try {
            socketClient.close();
        } catch (err) {
            action.error = true;
            action.payload = err;
        }
        return action;
    },

    send: message => {
        const action = { type: SOCKET_SEND_MESSAGE };
        try {
            socketClient.send(JSON.stringify(message));
        } catch (err) {
            action.error = true;
            action.payload = err;
        }
        return action;
    }
});

import {
    SOCKET_SET_OPENING, SOCKET_SET_CLOSING, SOCKET_SEND_MESSAGE
} from "../constants/ActionTypes";

export default (socketClient) => ({
    open: url => {
        const action = { type: SOCKET_SET_OPENING };
        try {
            socketClient.open(url);
            action.payload = url;
        } catch (err) {
            action.error = true;
            action.payload = err;
        }
        return action;
    },

    close: () => {
        const action = { type: SOCKET_SET_CLOSING };
        try {
            socketClient.close();
        } catch (err) {
            action.error = true;
            action.payload = err;
        }
        return action;
    },

    send: message => {
        const action = { type: SOCKET_SEND_MESSAGE };
        try {
            socketClient.send(JSON.stringify(message));
            action.payload = message;
        } catch (err) {
            action.error = true;
            action.payload = err;
        }
        return action;
    }
});

Add message to SEND_MESSAGE action for debugging.


/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */
import moment from 'moment';

import { UIDatabase } from '../database';
import { FRIDGE_ACTIONS } from '../actions/FridgeActions';

const initialState = () => {
  const fridges = UIDatabase.objects('Location');

  return {
    fridges,
    selectedFridge: fridges[0],
    fromDate: moment(new Date())
      .subtract(30, 'd')
      .toDate(),
    toDate: new Date(),
  };
};

export const FridgeReducer = (state = initialState(), action) => {
  const { type } = action;

  switch (type) {
    case FRIDGE_ACTIONS.SELECT: {
      const { payload } = action;
      const { fridge } = payload;

      return { ...state, selectedFridge: fridge };
    }

    case FRIDGE_ACTIONS.CHANGE_FROM_DATE: {
      const { payload } = action;
      const { date } = payload;

      return { ...state, fromDate: date };
    }

    case FRIDGE_ACTIONS.CHANGE_TO_DATE: {
      const { payload } = action;
      const { date } = payload;

      return { ...state, toDate: date };
    }

    default:
      return state;
  }
};

/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */
import moment from 'moment';

import { UIDatabase } from '../database';
import { FRIDGE_ACTIONS } from '../actions/FridgeActions';

const initialState = () => {
  const fridges = UIDatabase.objects('Location');

  return {
    fridges,
    selectedFridge: fridges[0],
    fromDate: moment(new Date())
      .subtract(30, 'd')
      .toDate(),
    toDate: new Date(),
  };
};

export const FridgeReducer = (state = initialState(), action) => {
  const { type } = action;

  switch (type) {
    case FRIDGE_ACTIONS.SELECT: {
      const { payload } = action;
      const { fridge } = payload;

      return { ...state, selectedFridge: fridge };
    }

    case FRIDGE_ACTIONS.CHANGE_FROM_DATE: {
      const { payload } = action;
      const { date } = payload;

      const fromDate = new Date(date);
      fromDate.setUTCHours(0, 0, 0, 0);

      return { ...state, fromDate };
    }

    case FRIDGE_ACTIONS.CHANGE_TO_DATE: {
      const { payload } = action;
      const { date } = payload;

      const toDate = new Date(date);
      toDate.setUTCHours(23, 59, 59, 999);

      return { ...state, toDate };
    }

    default:
      return state;
  }
};

Add UTC manipulation of dates for to and froms


'use strict';

const moment = require('moment')
const _ = require('lodash')
const lastBusinessDay = require('../../businessDays').last

module.exports = ({ getChatUsers, getWorkEntries }) => {
  return async function warnAboutMissingTimesheet () {

    const day = lastBusinessDay(moment()).format('YYYY-MM-DD');
    const users = await getChatUsers()

    const workEntries = users.map(slackId => {
      return (async function () {
        const workEntries = await getWorkEntries(slackId, day, day)
        const hours = _.reduce(workEntries, function (sum, entry) {
          return sum + entry[ 'hours' ]
        }, 0);

        if (hours < 8) {
          return {
            text: `Ieri ha segnato solo ${hours} ore\n, non è che ti sei dimenticato di qualcosa?`,
            channel: slackId
          }
        }
      }())
    })

    return _.filter(await Promise.all(workEntries))
  }
}

'use strict';

const moment = require('moment')
const _ = require('lodash')
const lastBusinessDay = require('../../businessDays').last

module.exports = ({ getChatUsers, getWorkEntries }) => {
  return async function warnAboutMissingTimesheet () {

    const day = lastBusinessDay(moment()).format('YYYY-MM-DD');
    const users = await getChatUsers()

    const workEntries = users.map(slackId => {
      return (async function () {
        const workEntries = await getWorkEntries(slackId, day, day)
        const hours = _.reduce(workEntries, function (sum, entry) {
          return sum + entry[ 'hours' ]
        }, 0);

        if (hours < 8) {
          return {
            text: `Ieri ha segnato solo ${hours} ore, non è che ti sei dimenticato di qualcosa?\n Puoi <https://report.ideato.it/|aggiornare il timesheet>`,
            channel: slackId
          }
        }
      }())
    })

    return _.filter(await Promise.all(workEntries))
  }
}

Add link to report from hell


'use strict';

if (!waitlist) {
	var waitlist = {};
}

waitlist.base = (function(){
	function getMetaData (name) {
		return $('meta[name="'+name+'"]').attr('content');
	}

	function displayMessage(message, type, html=false) {
		var alertHTML = $($.parseHTML(`

				<button type="button" class="close" data-dismiss="alert" aria-label="Close">
				×
				</button>

`));
		var textContainer = $('.text-xs-center', alertHTML);
		if (html === true) {
			textContainer.html(message);
		} else {
			textContainer.text(message);
		}
		var alertArea = $('#alert-area-base');
		alertArea.append(alertHTML);
	}

	return {
		getMetaData: getMetaData,
		displayMessage: displayMessage
	};
})();

'use strict';

if (!waitlist) {
	var waitlist = {};
}

waitlist.base = (function(){
	function getMetaData (name) {
		return $('meta[name="'+name+'"]').attr('content');
	}

	function displayMessage(message, type, html=false, id=false) {
		var alertHTML = $($.parseHTML(`

				<button type="button" class="close" data-dismiss="alert" aria-label="Close">
				×
				</button>

`));
		var textContainer = $('.text-xs-center', alertHTML);
		if (id !== false) {
			alertHTML.attr("id", id);
		}
		if (html === true) {
			textContainer.html(message);
		} else {
			textContainer.text(message);
		}
		var alertArea = $('#alert-area-base');
		alertArea.append(alertHTML);
	}

	return {
		getMetaData: getMetaData,
		displayMessage: displayMessage
	};
})();

Update alert to allow for setting an id.


var shell = require('shelljs');

if (exec('git status --porcelain').output != '') {
  console.error('Git working directory not clean.');
  process.exit(2);
}

var versionIncrement = process.argv[process.argv.length -1];

if (versionIncrement != 'major' && versionIncrement != 'minor' && versionIncrement != 'patch') {
  console.error('Usage: node release.js major|minor|patch');
  process.exit(1);
}

exec('npm version ' + versionIncrement);

exec('npm test');
exec('git push');
exec('git push --tags');
exec('npm publish');

function exec(cmd) {
  var ret = shell.exec(cmd, { silent : true });

  if (ret.code != 0) {
    console.error(ret.output);
    process.exit(1);
  }

  return ret;
}

var shell = require('shelljs');

if (exec('git status --porcelain').stdout != '') {
  console.error('Git working directory not clean.');
  process.exit(2);
}

var versionIncrement = process.argv[process.argv.length -1];

if (versionIncrement != 'major' && versionIncrement != 'minor' && versionIncrement != 'patch') {
  console.error('Usage: node release.js major|minor|patch');
  process.exit(1);
}

exec('npm version ' + versionIncrement);

exec('npm test');
exec('git push');
exec('git push --tags');
exec('npm publish');

function exec(cmd) {
  var ret = shell.exec(cmd, { silent : true });

  if (ret.code != 0) {
    console.error(ret.output);
    process.exit(1);
  }

  return ret;
}

Fix checking of exec output


import { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { scrollIt } from 'utils/scroll';

class ScrollToHighlightIndex extends PureComponent {
  // eslint-disable-line react/prefer-stateless-function
  componentDidMount() {
    setTimeout(this.handleScroll, 150);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.content.html !== this.props.content.html) {
      setTimeout(this.handleScroll, 150);
    }
  }

  handleScroll = () => {
    const { idx, targetElementsSelector } = this.props;
    const e = idx
      ? document.querySelectorAll(targetElementsSelector)[idx]
      : document.querySelectorAll(targetElementsSelector)[0];

    if (e) {
      scrollIt(document.querySelector(targetElementsSelector), 300, 'smooth');
    }
  };

  render() {
    return null;
  }
}

ScrollToHighlightIndex.propTypes = {
  idx: PropTypes.string,
  targetElementsSelector: PropTypes.string,
  content: PropTypes.object
};

export default ScrollToHighlightIndex;

import { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { scrollIt } from 'utils/scroll';

class ScrollToHighlightIndex extends PureComponent {
  // eslint-disable-line react/prefer-stateless-function
  componentDidMount() {
    setTimeout(this.handleScroll, 150);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.content.html !== this.props.content.html) {
      setTimeout(this.handleScroll, 150);
    }
  }

  handleScroll = () => {
    const { idx, targetElementsSelector } = this.props;
    const target = idx
      ? document.querySelectorAll(targetElementsSelector)[idx]
      : document.querySelectorAll(targetElementsSelector)[0];
    if (target) {
      scrollIt(target, 300, 'smooth');
    }
  };

  render() {
    return null;
  }
}

ScrollToHighlightIndex.propTypes = {
  idx: PropTypes.string,
  targetElementsSelector: PropTypes.string,
  content: PropTypes.object
};

export default ScrollToHighlightIndex;

Use index to scroll to desired target


function localizeHtmlPage() {
	//Localize by replacing __MSG_***__ meta tags
	var objects = document.querySelectorAll('.message');
	for (var j = 0; j < objects.length; j++) {
		var obj = objects[j];

		var valStrH = obj.innerHTML.toString();
		var valNewH = valStrH.replace(/__MSG_(\w+)__/g, function (match, v1) {
			return v1 ? browser.i18n.getMessage(v1) : "";
		});

		if (valNewH != valStrH) {
			obj.innerHTML = valNewH;
		}
	}
}

localizeHtmlPage();

function localizeHtmlPage() {
	//Localize by replacing __MSG_***__ meta tags
	var objects = document.querySelectorAll('.message');
	for (var j = 0; j < objects.length; j++) {
		var obj = objects[j];

		var valStrH = obj.innerHTML.toString();
		var valNewH = valStrH.replace(/__MSG_(\w+)__/g, function (match, v1) {
			return v1 ? browser.i18n.getMessage(v1) : "";
		});

		if (valNewH != valStrH) {
			obj.textContent = valNewH;
		}
	}
}

localizeHtmlPage();

Change to using textContent instead of innerHTML


import Ember from "ember";

export default Ember.Component.extend({
  didInsertElement: function() {
    // show the dialog
    this.$('.modal').modal('show');

    // send the according action after it has been hidden again
    var _this = this;
    this.$('.modal').one('hidden.bs.modal', function() {
      Ember.run(function() {
        // send the according action after it has been hidden
        _this.sendAction("dismiss");
      });
    });
  },
  showDoNotAccept: Ember.computed.notEmpty('doNotAcceptText'),
  actions: {
    accept: function() {
      this.sendAction("accept");
    },
    doNotAccept: function() {
      this.sendAction("doNotAccept");
    },
    cancel: function() {
      this.sendAction("cancel");
    }
  }
});

import Ember from "ember";

export default Ember.Component.extend({
  didInsertElement: function() {
    // show the dialog
    this.$('.modal').modal('show');

    // send the according action after it has been hidden again
    var _this = this;
    this.$('.modal').one('hidden.bs.modal', function() {
      Ember.run(function() {
        // send the according action after it has been hidden
        _this.sendAction("dismiss");
      });
    });
  },
  willDestroyElement: function() {
    this.$('.modal').modal('hide');
  },
  showDoNotAccept: Ember.computed.notEmpty('doNotAcceptText'),
  actions: {
    accept: function() {
      this.sendAction("accept");
    },
    doNotAccept: function() {
      this.sendAction("doNotAccept");
    },
    cancel: function() {
      this.sendAction("cancel");
    }
  }
});

Hide dialog when leaving route


//
// Copyright (c) 2016-2017 DrSmugleaf
//

"use strict"
require("dotenv").config({ path: __dirname + "/../.env" })
const constants = require("../libs/util/constants")
const client = require("../banterbot").client

before(function(done) {
  global.constants = constants
  global.client = client
  client.on("dbReady", () => {
    global.guild = client.guilds.get("260158980343463937")
    global.guild.createChannel("mocha-tests", "text").then((channel) => {
      global.channel = channel
      done()
    })
  })
})

beforeEach(function(done) {
  setTimeout(done, 2000)
})

after(function() {
  return global.channel.delete()
})

//
// Copyright (c) 2016-2017 DrSmugleaf
//

"use strict"
require("dotenv").config({ path: __dirname + "/../.env" })
const constants = require("../libs/util/constants")
const client = require("../banterbot").client

before(function(done) {
  global.constants = constants
  global.client = client
  client.on("dbReady", () => {
    global.guild = client.guilds.get("260158980343463937")
    global.guild.createChannel("test", "text").then((channel) => {
      global.channel = channel
      done()
    })
  })
})

beforeEach(function(done) {
  setTimeout(done, 2000)
})

after(function() {
  return global.channel.delete()
})

Change mocha tests discord channel name to test


function isDevOrSandpit () {
    var hostname = window.location.hostname;
    var env = {};

    if(hostname.indexOf('develop') > -1) {
        env.name = 'develop'
    }

    if(hostname.indexOf('sandpit') > -1) {
        env.name = 'sandpit'
    }

    // if((hostname.indexOf('127') > -1) || (hostname.indexOf('localhost'))) {
    //     env.name = 'localhost'
    // }

    return env;
}

function isDevOrSandpit () {
    var hostname = window.location.hostname;
    var env = {};

    if(hostname.indexOf('develop') > -1) {
        env.name = 'develop'
    }

    if(hostname.indexOf('sandpit') > -1) {
        env.name = 'sandpit'
    }

    // if((hostname.indexOf('127') > -1) || (hostname.indexOf('localhost')) > -1) {
    //     env.name = 'localhost'
    // }

    return env;
}

Check localhost domain to set env notification


import { throttle } from "underscore";

export const requestGarbageCollection =
  // For this global function to be defined, the --expose-gc flag must
  // have been passed to node at the bottom of the ../../meteor script,
  // probably via the TOOL_NODE_FLAGS environment variable.
  typeof global.gc === "function"
    // Restrict actual garbage collections to once per 500ms.
    ? throttle(global.gc, 500)
    : function () {};

import { throttle } from "underscore";

export const requestGarbageCollection =
  // For this global function to be defined, the --expose-gc flag must
  // have been passed to node at the bottom of the ../../meteor script,
  // probably via the TOOL_NODE_FLAGS environment variable.
  typeof global.gc === "function"
    // Restrict actual garbage collections to once per second.
    ? throttle(global.gc, 1000)
    : function () {};

Increase garbage collection throttling delay.


/* NewsFeedContainer.js
 * Container for our NewsFeed as a view
 * Dependencies: ActionTypes
 * Modules: NewsActions, and NewsFeed
 * Author: Tiffany Tse
 * Created: July 29, 2017
 */
//import dependencie
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
//import modules
import { loadNews } from '../actions/NewsActions.js';
import NewsFeed from '../component/NewsFeed.js';
import { reshapeNewsData } from '../util/DataTransformations.js';
import { allNewsSelector } from '../selectors/NewsSelectors.js';

//create state for mapStateToProps which exposes  state tree's news property as a prop
//to NewsFeed called news
const mapStateToProps = state ({
   news: reshapeNewsData(state.news);
});

//create the dispatcher for actions as a prop
const mapDispatchToProps = dispatch => (
    bindActionCreators({ loadNews}, dispatch)
);

//export mapStateToProps and mapDispatchToProps as props to to newsFeed
export default connect(mapStateToProps, mapDispatchToProps)(NewsFeed);

/* NewsFeedContainer.js
 * Container for our NewsFeed as a view
 * Dependencies: ActionTypes
 * Modules: NewsActions, and NewsFeed
 * Author: Tiffany Tse
 * Created: July 29, 2017
 */
//import dependencie
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
//import modules
import { loadNews } from '../actions/NewsActions.js';
import NewsFeed from '../component/NewsFeed.js';
import { reshapeNewsData } from '../util/DataTransformations.js';
import { allNewsSelector } from '../selectors/NewsSelectors.js';

//create state for mapStateToProps which exposes  state tree's news property as a prop
//to NewsFeed called news
const mapStateToProps = state ({
   news: allNewsSelector(state);
});

//create the dispatcher for actions as a prop
const mapDispatchToProps = dispatch => (
    bindActionCreators({ loadNews}, dispatch)
);

//export mapStateToProps and mapDispatchToProps as props to to newsFeed
export default connect(mapStateToProps, mapDispatchToProps)(NewsFeed);

Update reshapeNewsData to use allNewsSelector and pass state to it


/*jshint node:true */
"use strict";

var request = require('request');

var reddit = {
  top: function (subreddit, callback) {
    var url = "http://www.reddit.com/r/" + subreddit + "/top.json";
    var currentTime = (new Date()).getTime();

    request
      .get({url: url + "?bust=" + currentTime, json: true}, function (error, response, body) {
        if (!error && response.statusCode == 200) {
          body.url = url;
          body.timestamp = currentTime;

          if (callback) {
            callback(body);
          }
        }
      });
  }
};

module.exports = reddit;

/*jshint node:true */
"use strict";

var request = require('request');

var reddit = {
  top: function (subreddit, callback) {
    var url = "http://www.reddit.com/r/" + subreddit + "/top.json";
    var currentTime = new Date();

    request
      .get({url: url + "?bust=" + currentTime, json: true}, function (error, response, body) {
        if (!error && response.statusCode == 200) {
          body.url = url;
          body.date = {
            "timestamp": currentTime.getTime(),
            "year": currentTime.getUTCFullYear(),
            "month": currentTime.getUTCMonth() + 1,  // Date.getUTCMonth is 0 indexed... wtf?
            "day": currentTime.getUTCDate(),
            "hour": currentTime.getUTCHours(),
            "minute": currentTime.getUTCMinutes()
          };

          if (callback) {
            callback(body);
          }
        }
      });
  }
};

module.exports = reddit;

Expand timestamp json for db indicies


Ember.Route.reopen({
  beforeModel: function(transition) {

    if (transition) {

      transition.then(function() {

        Ember.run.scheduleOnce('afterRender', this, function() {

          $(document).foundation();
        });
      });
    }
  }
});

App = Ember.Application.create();

// Add routes here
App.Router.map(function() {

  // Atoms (Components)
  this.resource('atoms', function() {

    // Sub-routes here
    this.route('hs-button');
  });

  // Molecules (Modules)
  this.resource('molecules', function() {

    // Sub-routes here
  });

  // Organisms (Layouts)
  this.resource('organisms', function() {

    // Sub-routes here
  });

  // Templates
  this.resource('templates', function() {

    // Sub-routes here
  });
});

// Use this mixin for high-level atomic routes
App.AtomicRouteMixin = Ember.Mixin.create({
  renderTemplate: function() {

    this.render('atomic');
  }
});

// Modify high-level routes to use the same template
App.AtomsRoute = Ember.Route.extend(App.AtomicRouteMixin);
App.MoleculesRoute = Ember.Route.extend(App.AtomicRouteMixin);
App.OrganismsRoute = Ember.Route.extend(App.AtomicRouteMixin);
App.TemplatesRoute = Ember.Route.extend(App.AtomicRouteMixin);

Initialize Foundation after every route transition


angular.module('myApp')
.factory('ctrlPanelService', ['$rootScope', 'userService', 'locationService','transactionService',
function($rootScope, $userService, locationService, transactionService) {

	var settings = { users: null, userId: null };

	settings.updateUsers = function(users) {
		settings.users = users;
		$rootScope.$broadcast('updateUsers');
	}

	settings.setUserId = function(userId) {
		settings.userId = userId;
		$rootScope.$broadcast('updateUserId');
	}

	// define generic submit event for other controllers to use
	settings.submit = function() { $rootScope.$broadcast('submit') };

	return settings;

}]);

angular.module('myApp')
.factory('ctrlPanelService', ['$rootScope', 'userService', 'locationService','transactionService',
function($rootScope, $userService, locationService, transactionService) {
	// this service is responsible for storing and broadcasting changes
	// to any of the control panel's settings. These settings are intended
	// to be available to the entire app to be synced with whatever portion
	// uses them. For example, transactions tab in charts will react to a
	// selected userId via 'updateUserId' event on the $rootScope.

	var settings = { users: null, userId: null };

	settings.updateUsers = function(users) {
		settings.users = users;
		$rootScope.$broadcast('updateUsers');
	}

	settings.setUserId = function(userId) {
		settings.userId = userId;
		$rootScope.$broadcast('updateUserId');
	}

	// define generic submit event for other controllers to use
	settings.submit = function() { $rootScope.$broadcast('submit') };

	return settings;

}]);

Add comments for ctrlPanelService and its role


module.exports = {
  // Autoprefixer
  autoprefixer: {
    // https://github.com/postcss/autoprefixer#browsers
    browsers: [
      'Explorer >= 10',
      'ExplorerMobile >= 10',
      'Firefox >= 30',
      'Chrome >= 34',
      'Safari >= 7',
      'Opera >= 23',
      'iOS >= 7',
      'Android >= 4.4',
      'BlackBerry >= 10'
    ]
  },
  // BrowserSync
  browserSync: {
    browser: 'default', // or ["google chrome", "firefox"]
    https: false, // Enable https for localhost development.
    notify: false, // The small pop-over notifications in the browser.
    port: 9000
  },
  // GitHub Pages
  ghPages: {
    branch: 'gh-pages',
    domain: 'polymer-starter-kit.startpolymer.org', // change it!
    origin: 'origin'
  },
  // PageSpeed Insights
  // Please feel free to use the `nokey` option to try out PageSpeed
  // Insights as part of your build process. For more frequent use,
  // we recommend registering for your own API key. For more info:
  // https://developers.google.com/speed/docs/insights/v1/getting_started
  pageSpeed: {
    key: '', // need uncomment in task
    nokey: true,
    site: 'http://polymer-starter-kit.startpolymer.org', // change it!
    strategy: 'mobile' // or desktop
  }
};

module.exports = {
  // Autoprefixer
  autoprefixer: {
    // https://github.com/postcss/autoprefixer#browsers
    browsers: [
      'Explorer >= 10',
      'ExplorerMobile >= 10',
      'Firefox >= 30',
      'Chrome >= 34',
      'Safari >= 7',
      'Opera >= 23',
      'iOS >= 7',
      'Android >= 4.4',
      'BlackBerry >= 10'
    ]
  },

  // BrowserSync
  browserSync: {
    browser: 'default', // or ["google chrome", "firefox"]
    https: false, // Enable https for localhost development.
    notify: false, // The small pop-over notifications in the browser.
    port: 9000
  },

  // GitHub Pages
  ghPages: {
    branch: 'gh-pages',
    domain: 'polymer-starter-kit.startpolymer.org', // change it!
    origin: 'origin'
  },

  // Metalsmith
  metalsmith: {
    configFile: './psa-config.yaml'
  },

  // PageSpeed Insights
  // Please feel free to use the `nokey` option to try out PageSpeed
  // Insights as part of your build process. For more frequent use,
  // we recommend registering for your own API key. For more info:
  // https://developers.google.com/speed/docs/insights/v1/getting_started
  pageSpeed: {
    key: '', // need uncomment in task
    nokey: true,
    site: 'http://polymer-starter-kit.startpolymer.org', // change it!
    strategy: 'mobile' // or desktop
  }
};

Add metalsmith config to gulp config


$(document).ready(function() {
  $(document).on('change', '.question-answered-box', function(){
    the_id = $(this).data('the-id')
    text_answer_field = $(this).closest('.text-answer').find('.text-answer-field')
    matrix_answer_field = $(this).closest('.answer_fields_wrapper').find('.submission-matrix')
    if($(this).prop('checked')) {
      $(text_answer_field).attr("readonly", true)
      $(text_answer_field).addClass("disabled")
      $("input[name='answers["+the_id+"]']:radio").attr("disabled", true)
      $("li.answer-option-"+the_id+" textarea").attr("disabled", true)
      $("select#answers_"+the_id).attr("disabled", true)
      $(matrix_answer_field).find('select').attr("disabled", true)
    }
    else {
      $(text_answer_field).attr("disabled", false)
      $(text_answer_field).attr("readonly", false)
      $(text_answer_field).removeClass("disabled")
      $("input[name='answers["+the_id+"]']:radio").attr("disabled", false)
      $("li.answer-option-"+the_id+" textarea").attr("disabled", false)
      $("select#answers_"+the_id).attr("disabled", false)
      $(matrix_answer_field).find('select').attr("disabled", false)
      $('.sticky_save_all').click();
    }
  });
});

$(document).ready(function() {
  $(document).on('change', '.question-answered-box', function(){
    the_id = $(this).data('the-id')
    text_answer_field = $(this).closest('.text-answer').find('.text-answer-field')
    matrix_answer_field = $(this).closest('.answer_fields_wrapper').find('.submission-matrix')
    if($(this).prop('checked')) {
      $(text_answer_field).attr("readonly", true)
      $(text_answer_field).addClass("disabled")
      $("input[name='answers["+the_id+"]']:radio").attr("disabled", true)
      $("li.answer-option-"+the_id+" textarea").attr("disabled", true)
      $("select#answers_"+the_id).attr("disabled", true)
      $(matrix_answer_field).find('select').attr("disabled", true)
      $(matrix_answer_field).find('input').attr("disabled", true)
    }
    else {
      $(text_answer_field).attr("disabled", false)
      $(text_answer_field).attr("readonly", false)
      $(text_answer_field).removeClass("disabled")
      $("input[name='answers["+the_id+"]']:radio").attr("disabled", false)
      $("li.answer-option-"+the_id+" textarea").attr("disabled", false)
      $("select#answers_"+the_id).attr("disabled", false)
      $(matrix_answer_field).find('select').attr("disabled", false)
      $(matrix_answer_field).find('input').attr("disabled", false)
      $('.sticky_save_all').click();
    }
  });
});

Mark as answered to disable all matrix input types


/*
 * Copyright (c) 2010 Kevin Decker (http://www.incaseofstairs.com/)
 * See LICENSE for license information
 */
var HistoryHandler = (function() {
	var currentHash, callbackFn;

	function loadHash() {
		return decodeURIComponent(/#(.*)$/.exec(location.href)[1]);
	}
	function checkHistory(){
		var hashValue = loadHash();
		if(hashValue !== currentHash) {
			currentHash = hashValue;
			callbackFn(currentHash);
		}
	}

	return {
		init: function(callback) {
			callbackFn = callback;

			currentHash = loadHash();
			callbackFn(currentHash);
			setInterval(checkHistory, 500);
		},
		store: function(state) {
			currentHash = state;
			location = "#" + encodeURIComponent(state);
		}
	}
})();

/*
 * Copyright (c) 2010 Kevin Decker (http://www.incaseofstairs.com/)
 * See LICENSE for license information
 */
var HistoryHandler = (function() {
	var currentHash, callbackFn;

	function loadHash() {
		return decodeURIComponent(/#(.*)$/.exec((location.href || []))[1] || "");
	}
	function checkHistory(){
		var hashValue = loadHash();
		if(hashValue !== currentHash) {
			currentHash = hashValue;
			callbackFn(currentHash);
		}
	}

	return {
		init: function(callback) {
			callbackFn = callback;

			currentHash = loadHash();
			callbackFn(currentHash);
			setInterval(checkHistory, 500);
		},
		store: function(state) {
			currentHash = state;
			location = "#" + encodeURIComponent(state);
		}
	}
})();

Fix no hash case in history handler


/* ************************************************************************

  widgets

  Copyright:
    2009 Deutsche Telekom AG, Germany, http://telekom.com

 ************************************************************************ */

/**
 * Start View
 */
qx.Class.define("widgets.view.Start", {
  extend : unify.view.StaticView,
  type : "singleton",

  members :
  {
    // overridden
    getTitle : function(type, param) {
      return "Start";
    },

    // overridden
    _createView : function()
    {
      var layer = new unify.ui.Layer(this);

      //var titlebar = new unify.ui.ToolBar(this);
      //layer.add(titlebar);

      /*var content = new unify.ui.Content;
      content.add("Hello World");
      layer.add(content);*/

      var layerWidget = new unify.ui.widget.core.Layer(layer);
      var cont = new unify.ui.widget.basic.Label("Das ist ein Test");
      layerWidget.add(cont, {
        left: 50,
        top: 10
      });
      cont.set({
        width: 100,
        height: 50
      });

      return layer;
    }
  }
});

/* ************************************************************************

  widgets

  Copyright:
    2009 Deutsche Telekom AG, Germany, http://telekom.com

 ************************************************************************ */

/**
 * Start View
 */
qx.Class.define("widgets.view.Start", {
  extend : unify.view.StaticView,
  type : "singleton",

  members :
  {
    // overridden
    getTitle : function(type, param) {
      return "Start";
    },

    // overridden
    _createView : function()
    {
      var layer = new unify.ui.Layer(this);

      //var titlebar = new unify.ui.ToolBar(this);
      //layer.add(titlebar);

      /*var content = new unify.ui.Content;
      content.add("Hello World");
      layer.add(content);*/

      var layerWidget = new unify.ui.widget.core.Layer(layer);
      /*var cont = new unify.ui.widget.basic.Label("Das ist ein Test");
      layerWidget.add(cont, {
        left: 50,
        top: 10
      });
      cont.set({
        width: 100,
        height: 50
      });*/

      var scroller = new unify.ui.widget.container.Scroll();
      layerWidget.add(scroller, {
        left: 50,
        top: 50
      });
      scroller.set({
        width: 300,
        height: 300
      });

      var label = new unify.ui.widget.basic.Label("Das ist ein Test");
      scroller.add(label, {
        left: 50,
        top: 10
      });
      label.set({
        width: 100,
        height: 50
      });

      return layer;
    }
  }
});

Add scroll area to test application


import Mirage, {faker} from 'ember-cli-mirage';

export default Mirage.Factory.extend({
});

import Mirage/*, {faker} */ from 'ember-cli-mirage';

export default Mirage.Factory.extend({
});

Update factory blueprint to pass JSHint


/* eslint no-console:0 */
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
//
// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate
// layout file, like app/views/layouts/application.html.erb

console.log('Hello World from Webpacker')

Remove default message from webpack


// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * This view displays information on the HTTP cache.
 *  @constructor
 */
function HttpCacheView() {
  const mainBoxId = 'http-cache-view-tab-content';
  const statsDivId = 'http-cache-view-cache-stats';

  DivView.call(this, mainBoxId);

  this.statsDiv_ = $(statsDivId);

  // Register to receive http cache info.
  g_browser.addHttpCacheInfoObserver(this);
}

inherits(HttpCacheView, DivView);

HttpCacheView.prototype.onLoadLogFinish = function(data) {
  return this.onHttpCacheInfoChanged(data.httpCacheInfo);
};

HttpCacheView.prototype.onHttpCacheInfoChanged = function(info) {
  this.statsDiv_.innerHTML = '';

  if (!info)
    return false;

  // Print the statistics.
  var statsUl = addNode(this.statsDiv_, 'ul');
  for (var statName in info.stats) {
    var li = addNode(statsUl, 'li');
    addTextNode(li, statName + ': ' + info.stats[statName]);
  }
  return true;
};

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * This view displays information on the HTTP cache.
 */

var HttpCacheView = (function() {
  // IDs for special HTML elements in http_cache_view.html
  const MAIN_BOX_ID = 'http-cache-view-tab-content';
  const STATS_DIV_ID = 'http-cache-view-cache-stats';

  // We inherit from DivView.
  var superClass = DivView;

  /**
   *  @constructor
   */
  function HttpCacheView() {
    // Call superclass's constructor.
    superClass.call(this, MAIN_BOX_ID);

    this.statsDiv_ = $(STATS_DIV_ID);

    // Register to receive http cache info.
    g_browser.addHttpCacheInfoObserver(this);
  }

  cr.addSingletonGetter(HttpCacheView);

  HttpCacheView.prototype = {
    // Inherit the superclass's methods.
    __proto__: superClass.prototype,

    onLoadLogFinish: function(data) {
      return this.onHttpCacheInfoChanged(data.httpCacheInfo);
    },

    onHttpCacheInfoChanged: function(info) {
      this.statsDiv_.innerHTML = '';

      if (!info)
        return false;

      // Print the statistics.
      var statsUl = addNode(this.statsDiv_, 'ul');
      for (var statName in info.stats) {
        var li = addNode(statsUl, 'li');
        addTextNode(li, statName + ': ' + info.stats[statName]);
      }
      return true;
    }
  };

  return HttpCacheView;
})();

Refactor HttpCacheView to be defined inside an anonymous namespace.


'use strict';

const common = require('./../server/common');

export class DungeonBluePrints {
  constructor (number, worldSize, locationSize) {
    this.number = number;
    this.worldSize = worldSize;
    this.locationSize = locationSize;

    this.blueprints = {};
  }

  generate () {
    for (let i = 0; i < this.worldSize; i++) {
      let dungeonId = `dungeon_${i}`;

      let lx = common.getRandomInt(0, this.worldSize - 1);
      let ly = common.getRandomInt(0, this.worldSize - 1);

      let locationId = `location_${ly}_${lx}`;

      let levels = common.getRandomInt(1, this.worldSize);

      let entrances = [];
      for (let l = 0; l < levels; l++) {
        let x = common.getRandomInt(0, this.locationSize - 1);
        let y = common.getRandomInt(0, this.locationSize - 1);

        entrances.push([x, y]);
      }

      this.blueprints[dungeonId] = {
        'locationId': locationId,
        'levels': levels,
        'entrances': entrances
      };
    }
  }
}

'use strict';

const common = require('./../server/common');

export class DungeonBluePrints {
  constructor (number, worldSize, locationSize) {
    this.number = number;
    this.worldSize = worldSize;
    this.locationSize = locationSize;

    this.blueprints = {};
  }

  generate () {
    const idList = [];

    while (this.number) {
      let lx = common.getRandomInt(0, this.worldSize - 1);
      let ly = common.getRandomInt(0, this.worldSize - 1);

      let locationId = `location_${ly}_${lx}`;

      if (locationId in idList) continue;

      idList.push(locationId);
      this.number--;

      let dungeonId = `dungeon_${this.number}`;

      let levels = common.getRandomInt(1, this.worldSize);

      let entrances = [];
      for (let l = 0; l < levels; l++) {
        let x = common.getRandomInt(0, this.locationSize - 1);
        let y = common.getRandomInt(0, this.locationSize - 1);

        entrances.push([x, y]);
      }

      this.blueprints[dungeonId] = {
        'locationId': locationId,
        'levels': levels,
        'entrances': entrances
      };
    }
  }
}

Improve generate method for uniq locations


const path = require('path');

module.exports = {
    entry: "./src/index.tsx",

    output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "public/")
    },

    resolve: {
        // Add '.ts' and '.tsx' as resolvable extensions.
        extensions: [".ts", ".tsx", ".js", ".json"]
    },

    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: "awesome-typescript-loader"
            },
            {
                test: /\.js$/,
                use: "source-map-loader",
                enforce: "pre"
            },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.(otf|eot|svg|ttf|woff|woff2|jpg|png)(\?.+)?$/,
                use: 'url-loader'
            }
        ]
    }
};

const path = require('path');

module.exports = {
    entry: "./src/index.tsx",

    output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "public/")
    },

    resolve: {
        // Add '.ts' and '.tsx' as resolvable extensions.
        extensions: [".ts", ".tsx", ".js", ".json"]
    },

    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: "awesome-typescript-loader"
            },
            {
                test: /\.js$/,
                use: "source-map-loader",
                exclude: /node_modules/,
                enforce: "pre"
            },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.(otf|eot|svg|ttf|woff|woff2|jpg|png)(\?.+)?$/,
                use: 'url-loader'
            }
        ]
    }
};

Fix for a source-map warning


window.onload = function() {

    var tabs = function() {
        var args = models.application.arguments;
        var current = $("#"+args[0]);
        var sections = $(".section");
        sections.hide();
        current.show();
    }

    sp = getSpotifyApi(1);
    var models = sp.require("sp://import/scripts/api/models");
    models.application.observe(models.EVENT.ARGUMENTSCHANGED, tabs);
}

window.onload = function() {

    var tabs = function() {
        var args = models.application.arguments;
        var current = $("#"+args[0]);
        var sections = $(".section");
        sections.hide();
        current.show();
    }

    sp = getSpotifyApi(1);
    var models = sp.require("sp://import/scripts/api/models");
    models.application.observe(models.EVENT.ARGUMENTSCHANGED, tabs);

    $("#judgeMeButton").click(function() {
        console.info("judging...");
    });
}

Add handler for Judging button


'use strict';

const td            = require('testdouble');

const MakeDir       = require('../../../src/utils/make-dir');

const fs            = require('fs');
const path          = require('path');

describe('MakeDir', () => {
  context('when base and destPath', () => {
    afterEach(() => {
      td.reset();
    });

    it('makes the directory', () => {
      var mkdirSync = td.replace(fs, 'mkdirSync');

      let base = './';
      const destPath = 'foo/bar';

      MakeDir(base, destPath);

      // Verify replaced property was invoked.
      destPath.split('/').forEach((segment) => {
        base = path.join(base, segment);

        td.verify(mkdirSync(base));
      });
    });
  });
});

'use strict';

const td            = require('testdouble');

const MakeDir       = require('../../../src/utils/make-dir');

const fs            = require('fs');
const path          = require('path');

describe('MakeDir', () => {
  context('when base and destPath', () => {
    let mkdirSync;

    beforeEach(() => {
      mkdirSync = td.replace(fs, 'mkdirSync');
    });

    afterEach(() => {
      td.reset();
    });

    it('makes the directory', () => {
      let base = './';
      const destPath = 'foo/bar';

      MakeDir(base, destPath);

      // Verify replaced property was invoked.
      destPath.split('/').forEach((segment) => {
        base = path.join(base, segment);

        td.verify(mkdirSync(base));
      });
    });
  });
});

Update test to move test double replacement to before block


require('../../index.js');
var scope = require('./Tadpoles');
scope.view.exportFrames({
	amount: 200,
	directory: __dirname,
	onComplete: function() {
		console.log('Done exporting.');
	},
	onProgress: function(event) {
		console.log(event.percentage + '% complete, frame took: ' + event.delta);
	}
});

require('../../node.js/');
var paper = require('./Tadpoles');
paper.view.exportFrames({
	amount: 400,
	directory: __dirname,
	onComplete: function() {
		console.log('Done exporting.');
	},
	onProgress: function(event) {
		console.log(event.percentage + '% complete, frame took: ' + event.delta);
	}
});

Clean up Node.js tadpoles example.


/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @emails oncall+react_native
 * @format
 */

'use strict';

const {transform: babelTransform} = require('@babel/core');
const fixtures = require('../__test_fixtures__/fixtures.js');
const failures = require('../__test_fixtures__/failures.js');

function transform(fixture, filename) {
  return babelTransform(fixture, {
    babelrc: false,
    cwd: '/',
    filename: filename,
    highlightCode: false,
    plugins: [require('@babel/plugin-syntax-flow'), require('../index')],
  }).code;
}

describe('Babel plugin inline view configs', () => {
  Object.keys(fixtures)
    .sort()
    .forEach(fixtureName => {
      it(`can inline config for ${fixtureName}`, () => {
        expect(transform(fixtures[fixtureName], fixtureName)).toMatchSnapshot();
      });
    });

  Object.keys(failures)
    .sort()
    .forEach(fixtureName => {
      it(`fails on inline config for ${fixtureName}`, () => {
        expect(() => {
          transform(failures[fixtureName], fixtureName);
        }).toThrowErrorMatchingSnapshot();
      });
    });
});

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @emails oncall+react_native
 * @format
 */

'use strict';

const {transform: babelTransform} = require('@babel/core');
const fixtures = require('../__test_fixtures__/fixtures.js');
const failures = require('../__test_fixtures__/failures.js');

const transform = (fixture, filename) =>
  babelTransform(fixture, {
    babelrc: false,
    cwd: '/',
    filename: filename,
    highlightCode: false,
    plugins: [require('@babel/plugin-syntax-flow'), require('../index')],
  }).code.replace(/^[A-Z]:\\/g, '/'); // Ensure platform consistent snapshots.

describe('Babel plugin inline view configs', () => {
  Object.keys(fixtures)
    .sort()
    .forEach(fixtureName => {
      it(`can inline config for ${fixtureName}`, () => {
        expect(transform(fixtures[fixtureName], fixtureName)).toMatchSnapshot();
      });
    });

  Object.keys(failures)
    .sort()
    .forEach(fixtureName => {
      it(`fails on inline config for ${fixtureName}`, () => {
        expect(() => {
          transform(failures[fixtureName], fixtureName);
        }).toThrowErrorMatchingSnapshot();
      });
    });
});

Fix inline-view-configs test on Windows.


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

module.exports = {
  context: __dirname + '/app',
  entry: './index.js',

  output: {
    path: __dirname + '/bin',
    publicPath: '/',
    filename: 'bundle.js',
  },

  stats: {
    colors: true,
    progress: true,
  },

  resolve: {
    extensions: ['', '.webpack.js', '.js'],
  },

  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        query: {
          presets: ['es2015'],
        },
        loader: 'babel',
      },
    ],
  },
};

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

module.exports = {
  context: __dirname + '/app',
  entry: './index.js',

  output: {
    path: __dirname + '/bin',
    publicPath: '/',
    filename: 'bundle.js',
  },

  stats: {
    colors: true,
    progress: true,
  },

  resolve: {
    extensions: ['', '.webpack.js', '.js'],
    modulesDirectories: [
      'app',
      'node_modules',
    ],
  },

  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        query: {
          presets: ['es2015'],
        },
        loader: 'babel',
      },
    ],
  },
};

Resolve directories in the app folder


const path = require('path');

module.exports = {
  devtool: 'inline-source-map',
  entry: './src/index.js',
  module: {
    rules: [
      {
        test: /\.(js|vue)$/,
        loader: 'eslint-loader',
        enforce: 'pre',
        exclude: /node_modules/,
        options: {
          formatter: require('eslint-friendly-formatter')
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },

  output: {
    library: 'Muxy',
    libraryTarget: 'umd',
    umdNamedDefine: true,
    path: path.resolve(__dirname, 'dist'),
    filename: 'muxy-extensions-sdk.js'
  }
};

const path = require('path');

const port = process.env.PORT || 9000;

module.exports = {
  devtool: 'inline-source-map',
  entry: './src/index.js',
  module: {
    rules: [
      {
        test: /\.(js|vue)$/,
        loader: 'eslint-loader',
        enforce: 'pre',
        exclude: /node_modules/,
        options: {
          formatter: require('eslint-friendly-formatter')
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },

  output: {
    library: 'Muxy',
    libraryTarget: 'umd',
    umdNamedDefine: true,
    path: path.resolve(__dirname, 'dist'),
    filename: 'muxy-extensions-sdk.js'
  },

  devServer: { port }
};

Use PORT env for dev server


const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const project = require('./project.config.json');

const plugins = [
  new CleanWebpackPlugin(['public']),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'commons',
    filename: 'js/common.js'
  }),
  new HtmlWebpackPlugin({
    title: project.title,
    filename: '../index.html',
    template: 'index_template.ejs',
    inject: true
  })
];

module.exports = {
  entry: {
    'app': './src'
  },
  output: {
    path: path.join(__dirname, './public/assets'),
    publicPath: project.repoName + '/assets/',
    filename: 'js/[name].js'
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel',
        include: path.join(__dirname, 'src')
      },
      {
        test: /\.css$/,
        loader: 'style!css'
      }
    ]
  },
  resolve: {
    root: [ path.resolve(__dirname, 'src') ],
    extensions: ['', '.js', '.jsx'],
  },
  plugins: plugins
};

const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const project = require('./project.config.json');

const plugins = [
  new CleanWebpackPlugin(['public']),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'commons',
    filename: 'js/common.js'
  }),
  new HtmlWebpackPlugin({
    title: project.title,
    filename: '../index.html',
    template: 'index_template.ejs',
    inject: true,
    hash: true
  })
];

module.exports = {
  entry: {
    'app': './src'
  },
  output: {
    path: path.join(__dirname, './public/assets'),
    publicPath: project.repoName + '/assets/',
    filename: 'js/[name].js'
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel',
        include: path.join(__dirname, 'src')
      },
      {
        test: /\.css$/,
        loader: 'style!css'
      }
    ]
  },
  resolve: {
    root: [ path.resolve(__dirname, 'src') ],
    extensions: ['', '.js', '.jsx'],
  },
  plugins: plugins
};

Add hashing in build HMTL


var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');
var path = require("path");

module.exports = {
    context: __dirname,

    entry: [
        './ditto/static/chat/js/base.js',
    ],

    output: {
        path: path.resolve('./ditto/static/dist'),
        publicPath: '/static/dist/', // This will override the url generated by django's staticfiles
        filename: "[name]-[hash].js",
    },

    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loaders: ["babel-loader?optional[]=es7.comprehensions&optional[]=es7.classProperties&optional[]=es7.objectRestSpread&optional[]=es7.decorators"],
            }
        ]
    },
    plugins: [
        new BundleTracker({filename: './webpack-stats-prod.json'}),

        // removes a lot of debugging code in React
        new webpack.DefinePlugin({
            'process.env': {
                'NODE_ENV': JSON.stringify('production')
            }}),

        // keeps hashes consistent between compilations
        new webpack.optimize.OccurenceOrderPlugin(),

        // minifies your code
        new webpack.optimize.UglifyJsPlugin({
            compressor: {
                warnings: false
            }
        })
    ],
  resolve: {
    extensions: ['', '.js', '.jsx']
  }
};

var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');
var path = require("path");

module.exports = {
    context: __dirname,

    entry: [
        './ditto/static/chat/js/base.js',
    ],

    output: {
        path: path.resolve('./ditto/static/dist'),
        publicPath: '/static/dist/', // This will override the url generated by django's staticfiles
        filename: "[name]-[hash].js",
    },

    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loaders: ["babel-loader?optional[]=es7.comprehensions&optional[]=es7.classProperties&optional[]=es7.objectRestSpread&optional[]=es7.decorators"],
            }
        ]
    },
    plugins: [
        new BundleTracker({filename: './webpack-stats-prod.json'}),

        // removes a lot of debugging code in React
        new webpack.DefinePlugin({
            'process.env': {
                'NODE_ENV': JSON.stringify('production')
            }}),

        // keeps hashes consistent between compilations
        new webpack.optimize.OccurenceOrderPlugin(),

        // minifies your code
        new webpack.optimize.UglifyJsPlugin({
            compressor: {
                warnings: false
            }
        })
    ],
  resolve: {
    fallback: path.join(__dirname, 'node_modules'),
    extensions: ['', '.js', '.jsx']
  }
};

Fix prod'n webpack conf for linked deps


goog.provide('app_scaleline_directive');

goog.require('app');
goog.require('ngeo_control_directive');
goog.require('ol.control.ScaleLine');

(function() {
  var module = angular.module('app');

  module.directive('appScaleline', [
    /**
     * @return {angular.Directive} The Directive Object Definition.
     */
    function() {
      return {
        restrict: 'E',
        scope: {
          map: '=appScalelineMap'
        },
        controller: function() {
          this['createControl'] = function(target) {
            return new ol.control.ScaleLine({
              target: target
            });
          };
        },
        controllerAs: 'ctrl',
        bindToController: true,
        template: '<div ngeo-control="ctrl.createControl"' +
            'ngeo-control-map="ctrl.map">'
      };
    }
  ]);
})();

goog.provide('app_scaleline_directive');

goog.require('app');
goog.require('ngeo_control_directive');
goog.require('ol.control.ScaleLine');

(function() {
  var module = angular.module('app');

  module.directive('appScaleline', [
    /**
     * @return {angular.Directive} The Directive Object Definition.
     */
    function() {
      return {
        restrict: 'E',
        scope: {
          'map': '=appScalelineMap'
        },
        controller: function() {
          this['createControl'] = function(target) {
            return new ol.control.ScaleLine({
              target: target
            });
          };
        },
        controllerAs: 'ctrl',
        bindToController: true,
        template: '<div ngeo-control="ctrl.createControl"' +
            'ngeo-control-map="ctrl.map">'
      };
    }
  ]);
})();

Use quotes for scope properties


var $ = require('jquery-detached').getJQuery();
var jsModules = require('@jenkins-cd/js-modules');

$(document).ready(function () {
    var tryBlueOcean = $('
Try Blue Ocean UI ...
');

    tryBlueOcean.click(function () {
        // We could enhance this further by looking at the current
        // URL and going to different places in the BO UI depending
        // on where the user is in classic jenkins UI e.g. if they
        // are currently in a job on classic UI, bring them to the
        // same job in BO UI Vs just brining them to the root of
        // BO UI i.e. make the button context sensitive.
        window.location.replace(jsModules.getRootURL() + '/blue');
    });

    $('#page-head #header').append(tryBlueOcean);
});

var $ = require('jquery-detached').getJQuery();
var jsModules = require('@jenkins-cd/js-modules');

$(document).ready(() => {
    var tryBlueOcean = $('
Try Blue Ocean UI ...
');

    tryBlueOcean.click(() => {
        // We could enhance this further by looking at the current
        // URL and going to different places in the BO UI depending
        // on where the user is in classic jenkins UI e.g. if they
        // are currently in a job on classic UI, bring them to the
        // same job in BO UI Vs just brining them to the root of
        // BO UI i.e. make the button context sensitive.
        window.location.replace(`${jsModules.getRootURL()}/blue`);
    });

    $('#page-head #header').append(tryBlueOcean);
});

Add a "Try Blue Ocean UI" button in classic Jenkins - fix lint errors


/**
 * Copyright 2015, Government of Canada.
 * All rights reserved.
 *
 * This source code is licensed under the MIT license.
 *
 * @providesModule User
 */

var Cookie = require('react-cookie');
var Events = require('./Events');

/**
 * An object that manages all user authentication and the user profile.
 *
 * @constructor
 */
var User = function () {

    this.name = null;

    this.events = new Events(['change', 'logout'], this);

    this.load = function (data) {
        // Update the username, token, and reset properties with the authorized values.
         _.assign(this, _.omit(data, '_id'));

        this.name = data._id;
        Cookie.save('token', data.token);

        this.emit('change');
    };

    this.authorize = function (data) {
        this.load(data);
        dispatcher.sync();
    };

    this.deauthorize = function (data) {

        dispatcher.storage.deleteDatabase(function () {
            location.hash = 'home/welcome';

            this.name = null;

             _.forIn(dispatcher.db, function (collection) {
                collection.documents = [];
                collection.synced = false;
            });

            this.emit('logout', data);

        }.bind(this));

    }.bind(this);

    this.logout = function () {
        dispatcher.send({
            collectionName: 'users',
            methodName: 'logout',
            data: {
                token: this.token
            }
        }, this.deauthorize);
    };
};

module.exports = User;

/**
 * Copyright 2015, Government of Canada.
 * All rights reserved.
 *
 * This source code is licensed under the MIT license.
 *
 * @providesModule User
 */

var Cookie = require('react-cookie');
var Events = require('./Events');

/**
 * An object that manages all user authentication and the user profile.
 *
 * @constructor
 */
var User = function () {

    this.name = null;

    this.events = new Events(['change', 'logout'], this);

    this.load = function (data) {
        // Update the username, token, and reset properties with the authorized values.
         _.assign(this, _.omit(data, '_id'));

        this.name = data._id;
        Cookie.save('token', data.token);

        this.emit('change');
    };

    this.authorize = function (data) {
        this.load(data);
        dispatcher.sync();
    };

    this.deauthorize = function (data) {

        dispatcher.db.loki.deleteDatabase({}, function () {
            location.hash = 'home/welcome';

            this.name = null;

             _.forIn(dispatcher.db, function (collection) {
                collection.documents = [];
                collection.synced = false;
            });

            this.emit('logout', data);

        }.bind(this));

    }.bind(this);

    this.logout = function () {
        dispatcher.send({
            collectionName: 'users',
            methodName: 'logout',
            data: {
                token: this.token
            }
        }, this.deauthorize);
    };
};

module.exports = User;

Delete Loki database and persistent storage on logout.


window.onload = function() {
  sectionShow();
  blogController();
};

function sectionShow() {
  var sectionControls = {'home_btn': 'home', 'about_btn': 'about', 'projects_btn': 'projects', 'blog_btn': 'blog'}
  document.getElementById('menu').addEventListener('click', function(event) {
    event.preventDefault();
    hideSections(sectionControls);
    document.getElementById(sectionControls[event.target.id]).style.display = 'inline';
  });
};

function hideSections(sections) {
  for (i in sections) {
    document.getElementById(sections[i]).style.display = 'none';
  };
};

function blogController() {
  document.getElementById('blog_control').addEventListener('click', function(event) {
    event.preventDefault();
      ajaxWrapper(blogList[i]);
    };
  });
};

function ajaxWrapper(blog_page) {
  var xml = new XMLHttpRequest();
  xml.onreadystatechange = function() {
    if (xml.readyState == 4 && xml.status == 200) {
      blog_div = document.getElementById('blog_show');
      blog_div.style.display = 'inline';
      blog_div.innerHTML = xml.responseText;
    };
  };
  xml.open('GET', blog_page, true);
  xml.send();
};

window.onload = function() {
  sectionShow();
};

function sectionShow() {
  var sectionControls = {'home_btn': 'home', 'about_btn': 'about', 'projects_btn': 'projects', 'blog_btn': 'blog'}
  document.getElementById('menu').addEventListener('click', function(event) {
    event.preventDefault();
    hideSections(sectionControls);
    document.getElementById(sectionControls[event.target.id]).style.display = 'inline';
    if (event.target.id == 'blog_btn') { blogController() };
  });
};

function hideSections(sections) {
  for (i in sections) {
    document.getElementById(sections[i]).style.display = 'none';
  };
};

function blogController() {
  document.getElementById('blog_control').addEventListener('click', function(event) {
    event.preventDefault();
      ajaxWrapper(blogList[i]);
    };
  });
};

function ajaxWrapper(blog_page) {
  var xml = new XMLHttpRequest();
  xml.onreadystatechange = function() {
    if (xml.readyState == 4 && xml.status == 200) {
      blog_div = document.getElementById('blog_show');
      blog_div.style.display = 'inline';
      blog_div.innerHTML = xml.responseText;
    };
  };
  xml.open('GET', blog_page, true);
  xml.send();
};

Move blogController to run only if blog section is loaded


/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */

var msal = require('@azure/msal-node');

const msalConfig = {
    auth: {
       clientId: "6c04f413-f6e7-4690-b372-dbdd083e7e5a",
       authority: "https://login.microsoftonline.com/sgonz.onmicrosoft.com",
    }
};

const pca = new msal.PublicClientApplication(msalConfig);

const deviceCodeRequest = {
    deviceCodeCallback: (response) => (console.log(response.message)),
    scopes: ["user.read"],
    timeout: 5,
};

pca.acquireTokenByDeviceCode(deviceCodeRequest).then((response) => {
    console.log(JSON.stringify(response));
}).catch((error) => {
    console.log(JSON.stringify(error));
});

// Uncomment to test cancellation
// setTimeout(function() {
//     deviceCodeRequest.cancel = true;
// }, 12000);

Fix formating in the device-code sample


/* jshint node: true */
'use strict';

module.exports = {
  name: 'ember-cli-docker-config',

  contentFor: function(type, config) {
    if (type === 'head') {
      return '<script type="text/javascript">window.DynamicENV = ' +
        this.dynamicConfig(config.DynamicConfig) +
        ';</script>';
    }
  },

  dynamicConfig: function(config) {
    var param;

    if (!config) {
      return '';
    }

    var configParams = [];
    for (param in config) {
      if (typeof config[param] === 'object') {
        configParams.push('"' + param + '": ' + this.dynamicConfig(config[param]));
      } else {
        if (process.env[config[param]]) {
          configParams.push('"' + param + '": "' + process.env[config[param]] + '"');
        }
      }
    }

    return '{' + configParams.join(',') + '}';
  }
};

/* jshint node: true */
'use strict';

module.exports = {
  name: 'ember-cli-docker-config',

  contentFor: function(type, config) {
    if (type === 'head') {
      return '<script type="text/javascript">window.DynamicENV = ' +
        this.dynamicConfig(config.DynamicConfig) +
        ';</script>';
    }
  },

  dynamicConfig: function(config) {
    var param;

    if (!config) {
      return '';
    }

    var configParams = [];
    for (param in config) {
      switch (typeof config[param]) {
        case 'object':
          configParams.push('"' + param + '": ' + this.dynamicConfig(config[param]));
          break;
        case 'string':
          if (process.env[config[param]]) {
            configParams.push('"' + param + '": "' + process.env[config[param]] + '"');
          }
          break;
        case 'function':
          configParams.push('"' + param + '": "' + config[param](process.env) + '"');
          break;
      }
    }

    return '{' + configParams.join(',') + '}';
  }
};

Add ability to specify a function instead of a string for dynamic configs


'use strict';

var NS = {};

// MODULES //

var getKeys = require( 'object-keys' ).shim();
NS.RE_EOL = require( '@stdlib/regexp/eol' );
NS.RE_EXTNAME = require( '@stdlib/regexp/extname' );
NS.RE_EXTNAME_POSIX = require( '@stdlib/regexp/extname-posix' );
NS.RE_EXTNAME_WINDOWS = require( '@stdlib/regexp/extname-windows' );
NS.RE_FUNCTION_NAME = require( '@stdlib/regexp/function-name' );

// VARIABLES //

var KEYS = getKeys( NS );

// BIND //

/**
* Binds functions to a REPL namespace.
*
* @private
* @param {Object} ns - namespace
* @returns {Object} input namespace
*
* @example
* var ns = {};
* bind( ns );
* // returns <input_namespace>
*/
function bind( ns ) {
	var key;
	var i;
	for ( i = 0; i < KEYS.length; i++ ) {
		key = KEYS[ i ];
		ns[ key ] = NS[ key ];
	}
	return ns;
} // end FUNCTION bind()

// EXPORTS //

module.exports = bind;

'use strict';

var NS = {};

// MODULES //

var getKeys = require( 'object-keys' ).shim();
NS.RE_EOL = require( '@stdlib/regexp/eol' );
NS.RE_DIRNAME = require( '@stdlib/regexp/dirname' );
NS.RE_DIRNAME_POSIX = require( '@stdlib/regexp/dirname-posix' );
NS.RE_DIRNAME_WINDOWS = require( '@stdlib/regexp/dirname-windows' );
NS.RE_EXTNAME = require( '@stdlib/regexp/extname' );
NS.RE_EXTNAME_POSIX = require( '@stdlib/regexp/extname-posix' );
NS.RE_EXTNAME_WINDOWS = require( '@stdlib/regexp/extname-windows' );
NS.RE_FUNCTION_NAME = require( '@stdlib/regexp/function-name' );

// VARIABLES //

var KEYS = getKeys( NS );

// BIND //

/**
* Binds functions to a REPL namespace.
*
* @private
* @param {Object} ns - namespace
* @returns {Object} input namespace
*
* @example
* var ns = {};
* bind( ns );
* // returns <input_namespace>
*/
function bind( ns ) {
	var key;
	var i;
	for ( i = 0; i < KEYS.length; i++ ) {
		key = KEYS[ i ];
		ns[ key ] = NS[ key ];
	}
	return ns;
} // end FUNCTION bind()

// EXPORTS //

module.exports = bind;

Add dirname RegExps to REPL context


const Eris = require("eris");
const config = require("./cfg");

const bot = new Eris.Client(config.token, {
  getAllUsers: true,
  restMode: true,
});

module.exports = bot;

const Eris = require("eris");
const config = require("./cfg");

const bot = new Eris.Client(config.token, {
  restMode: true,
});

module.exports = bot;

Disable getAllUsers from the client


(function($) {
  $(document).ready(function() {

    $('#template-entry-hidden').load("entry.template.html");

    $('#btn-load-project').click(function(){
      var project = $('#project-name').val();
      $.getJSON(project, renderData);
    });
  });

  function renderData(data) {
    $.each(data, function(index, entry) {
      var entryDiv = $('#template-entry-hidden').clone().children();
      entryDiv.find(".tabula-command").html(entry.entry.command);
      entryDiv.appendTo('div#project-data');
    });

  }

})(jQuery);

(function($) {
  $(document).ready(function() {

    $('#template-entry-hidden').load("entry.template.html");

    $('#btn-load-project').click(function(){
      var project = $('#project-name').val();
      $.getJSON(project, renderData);
    });
  });

  function renderData(data) {
    // For each entry
    $.each(data, function(index, entry) {
      // Clone the template
      var entryDiv = $('#template-entry-hidden').clone().children();
      // Set background colour based on exit status
      entryDiv.css("background-color", pickExitColour(entry.entry.exitStatus))
      entryDiv.find(".tabula-command").html(entry.entry.command);
      // Append to the main div
      entryDiv.appendTo('div#project-data');
    });

  }

  function pickExitColour(exitStatus) {
    if (exitStatus === 0) {
      // Successful
      return "#00FF00";
    } else if (exitStatus > 127) {
      // Killed by user
      return "#FFFF00";
    } else {
      // Failed for some reason.
      return "#FF0000";
    }
  }

})(jQuery);

Set the background colour based on exit status.


/**
 * Cell used for showing an AmountEntry
 *
 * Copyright 2015 Ethan Smith
 */

var Marionette = require('backbone.marionette');

var AmountEntryCell = Marionette.ItemView.extend({
   tagName: 'td',
   render: function() {
      this.$el.html(this.model.entry.readable());
   }
});

module.exports = AmountEntryCell;

/**
 * Cell used for showing an AmountEntry
 *
 * Copyright 2015 Ethan Smith
 */

var Marionette = require('backbone.marionette');

var AmountEntryCell = Marionette.ItemView.extend({
   tagName: 'td',
   className: function() {
      return this.model.entry.get() === 0 ? 'zero' : '';
   },
   render: function() {
      this.$el.html(this.model.entry.readable());
   }
});

module.exports = AmountEntryCell;

Add zero class to new AmountEntry cell


var dbm = require('db-migrate');
var type = dbm.dataType;

exports.up = function(db, callback) {
  db.createTable('ripple_addresses', {
    id: { type: 'int', primaryKey: true, autoIncrement: true },
    user_id: { type: 'int', notNull: true },
    managed: { type: 'boolean', default: false, notNull: true},
    address: { type: 'string', notNull: true, unique: true },
    type: { type: 'string', notNull: true },
    tag: { type: 'string' },
    secret: { type: 'string' },
    previous_transaction_hash: { type: 'string' }
	}, callback);
};

exports.down = function(db, callback) {
	db.dropTable('ripple_addresses', callback);
};

var dbm = require('db-migrate');
var type = dbm.dataType;

exports.up = function(db, callback) {
  db.createTable('ripple_addresses', {
    id: { type: 'int', primaryKey: true, autoIncrement: true },
    user_id: { type: 'int', notNull: true },
    managed: { type: 'boolean', default: false, notNull: true},
    address: { type: 'string', notNull: true },
    type: { type: 'string', notNull: true },
    tag: { type: 'string' },
    secret: { type: 'string' },
    previous_transaction_hash: { type: 'string' },
    createdAt: { type: 'datetime' },
    updatedAt: { type: 'datetime' }
	}, callback);
};

exports.down = function(db, callback) {
	db.dropTable('ripple_addresses', callback);
};

Make ripple_address address column not required. Add timestamps.


const renderWay = require('./renderWay');
const predict = require('./predict');

module.exports = (model, dispatch) => {
  const prediction = predict(model);
  return renderWay(prediction, {
    label: 'prediction',
    numbers: true,
    numbersBegin: model.history[0].length
  });
};

const renderWay = require('./renderWay');
const predict = require('./predict');

module.exports = (model, dispatch) => {
  const prediction = predict(model);
  return renderWay(prediction, {
    label: 'future',
    numbers: true,
    numbersBegin: model.history[0].length
  });
};

Rename prediction label to future


// ==UserScript==
// @name        Remove concourse elements
// @namespace   cloudpipeline.digital
// @description Strips away some of the cruft from the concourse pipeline view when showing it on monitoring screens.
// @include     https://deployer.*.cloudpipeline.digital/*
// @include     https://deployer.cloud.service.gov.uk/*
// @version     1
// @grant       none
// ==/UserScript==
var readyStateCheckInterval = setInterval(function() {
    if (document.readyState === "complete") {
        clearInterval(readyStateCheckInterval);

        console.log("Monitor mode is go");
        var element = document.getElementsByClassName("legend")[0];
        element.parentNode.removeChild(element);

        var element = document.getElementsByClassName("lower-right-info")[0];
        element.parentNode.removeChild(element);

        var hostname = location.hostname.replace("deployer.", "").replace(".cloudpipeline.digital","")

        var element = document.getElementsByTagName("nav")[0];
        element.innerHTML = " " + hostname + "";
    }
}, 10);

// ==UserScript==
// @name        Remove concourse elements
// @namespace   cloudpipeline.digital
// @description Strips away some of the cruft from the concourse pipeline view when showing it on monitoring screens.
// @include     https://deployer.*.cloudpipeline.digital/*
// @include     https://deployer.cloud.service.gov.uk/*
// @version     1
// @grant       none
// ==/UserScript==
var readyStateCheckInterval = setInterval(function() {
    if (document.readyState === "complete") {
        clearInterval(readyStateCheckInterval);

        console.log("Monitor mode is go");
        var element = document.getElementsByClassName("legend")[0];
        element.parentNode.removeChild(element);

        var element = document.getElementsByClassName("lower-right-info")[0];
        element.parentNode.removeChild(element);

        var hostname = location.hostname.replace("deployer.", "").replace(".cloudpipeline.digital","")

        var element = document.getElementById("top-bar-app");
        element.innerHTML = " " + hostname + "";
    }
}, 10);

Update chrome plugin for concourse 2.1.0


describe('current-user module', function() {

  var currentUser;
  beforeEach(module('current-user'));

  beforeEach(inject(function(_currentUser_) {
    currentUser = _currentUser_;
  }));

  describe('currentUser', function() {
    it("should be a resource", function() {
      expect(currentUser).toBeDefined();
    });

    it('should default as empty', function() {
      expect(currentUser.getCurrentUser()).toEqual({});
    });

    it('should update user with setter', function() {
      expect(currentUser.getCurrentUser()).toEqual({});
      currentUser.setCurrentUser({
        'name': 'tester',
      });
      expect(currentUser.getCurrentUser()).toEqual({
        'name': 'tester',
      });
    });
  });
});

describe('current-user module', function() {

  var currentUser;
  beforeEach(module('current-user'));

  beforeEach(inject(function(_currentUser_) {
    currentUser = _currentUser_;
  }));

  describe('currentUser', function() {
    it("should be a resource", function() {
      expect(currentUser).toBeDefined();
    });

    it('should default as empty', function() {
      expect(currentUser.getCurrentUser()).toEqual({});
    });

    it('should update user with setter', function() {
      expect(currentUser.getCurrentUser()).toEqual({});
      currentUser.setCurrentUser({
        'name': 'tester',
      });
      expect(currentUser.getCurrentUser()).toEqual({
        'name': 'tester',
      });
    });

    it('should delete user cache', function() {
      currentUser.setCurrentUser({
        $id: 123,
        name: '123',
      });
      expect(currentUser.getCurrentUser()).toEqual({
        $id: 123,
        name: '123',
      });
      currentUser.deleteCurrentUser();
      expect(currentUser.getCurrentUser()).toEqual({});
    });

    it('should check if user logged in correctly', function() {
      currentUser.setCurrentUser({
        $id: 123,
        name: '123',
      });
      expect(currentUser.isLoggedIn()).toEqual(true);
      currentUser.deleteCurrentUser();
      expect(currentUser.isLoggedIn()).toEqual(false);
    });
  });
});

Update currentUser module test cases


/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2015 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';

const Spinner = React.createClass({
  displayName: 'Spinner',

  render: function() {
    return (

Loading...

    );
  }
});

YUI.add('loading-spinner', function() {
  juju.components.Spinner = Spinner;
}, '0.1.0', {
  requires: []
});

/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2015 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';

class Spinner extends React.Component {
  render() {
    return (

Loading...

    );
  }
};

YUI.add('loading-spinner', function() {
  juju.components.Spinner = Spinner;
}, '0.1.0', {
  requires: []
});

Update Spinner to use es6 class.


/**
 * Creates a new 2 dimensional Vector.
 *
 * @constructor
 * @this {Circle}
 * @param {number} x The x value of the new vector.
 * @param {number} y The y value of the new vector.
 */
function Vector2(x, y) {
    if (typeof x === 'undefined') {
        x = 0;
    }
    if (typeof y === 'undefined') {
        y = 0;
    }

    this.x = x;
    this.y = y;
}

Vector2.prototype.add = function(v) {
    if ( !(v instanceof Vector2) ) {
        throw new InvalidArgumentError("v", "You can only add a vector to another vector! The object passed was: " + v);
    }

    this.x += v.x;
    this.y += v.y;
}

Vector2.prototype.toString = function() {
    return "[" + this.x +  ", " + this.y + "]";
}

/**
 * Creates a new 2 dimensional Vector.
 *
 * @constructor
 * @this {Circle}
 * @param {number} x The x value of the new vector.
 * @param {number} y The y value of the new vector.
 */
function Vector2(x, y) {
    if (typeof x === 'undefined') {
        x = 0;
    }
    if (typeof y === 'undefined') {
        y = 0;
    }

    this.x = x;
    this.y = y;
}

Vector2.prototype.add = function(v) {
    if ( !(v instanceof Vector2) ) {
        throw new InvalidArgumentError("v", "You can only add a vector to another vector! The object passed was: " + v);
    }

    this.x += v.x;
    this.y += v.y;

    return this;
}

Vector2.prototype.toString = function() {
    return "[" + this.x +  ", " + this.y + "]";
}

Change add to return the vector itself, for method chaining


$("#hive-intro-menu .hive-list li").click(function(event) {
  var placeName = $(this).find(".the-place").text();
  var twitterHandle = $(this).find(".the-details .twitter-handle").text();
  var websiteURL = $(this).find(".the-details .website-url").text();
  // show content
  $("#hive-intro-box h2").html(placeName);
  if ( twitterHandle ) {
    $("#hive-intro-box .twitter")
                        .attr("href", "http://twitter.com/" + twitterHandle)
                        .text(twitterHandle)
                        .removeClass("hide");
  }
  $("#hive-intro-box .hive-btn")
                      .attr("href", websiteURL)
                      .text("Visit Website");
  // hide the general info
  $("#hive-intro-box .general-cta").hide();
});

/* ****************************************
*  "Locations" Page
*/

$("#locations-menu div:not(#hive-coming-menu) a").click(function(){
  var locationSelected = $(this).data("profile");
    console.log( locationSelected );
  // highlight selected item
  $("#locations-menu li.active").removeClass("active");
  $(this).parent("li").addClass("active");
  // show corresponding sections, hide the rest
  $(".hive-profile").parent(".container").addClass("hide");
  // profile section of the selected item
  var profile = $(".hive-profile[data-profile="+ locationSelected +"]").parent(".container");
  profile.removeClass("hide");
  $("html, body").animate({ scrollTop: profile.offset().top }, "slow");
});

$("#hive-intro-menu .hive-list li").click(function(event) {
  var placeName = $(this).find(".the-place").text();
  var twitterHandle = $(this).find(".the-details .twitter-handle").text();
  var websiteURL = $(this).find(".the-details .website-url").text();
  // show content
  $("#hive-intro-box h2").html(placeName);
  if ( twitterHandle ) {
    $("#hive-intro-box .twitter")
                        .attr("href", "http://twitter.com/" + twitterHandle)
                        .text(twitterHandle)
                        .removeClass("hide");
  } else {
    $("#hive-intro-box .twitter").addClass("hide");
  }
  $("#hive-intro-box .hive-btn")
                      .attr("href", websiteURL)
                      .text("Visit Website");
  // hide the general info
  $("#hive-intro-box .general-cta").hide();
});

/* ****************************************
*  "Locations" Page
*/

$("#locations-menu div:not(#hive-coming-menu) a").click(function(){
  var locationSelected = $(this).data("profile");
    console.log( locationSelected );
  // highlight selected item
  $("#locations-menu li.active").removeClass("active");
  $(this).parent("li").addClass("active");
  // show corresponding sections, hide the rest
  $(".hive-profile").parent(".container").addClass("hide");
  // profile section of the selected item
  var profile = $(".hive-profile[data-profile="+ locationSelected +"]").parent(".container");
  profile.removeClass("hide");
  $("html, body").animate({ scrollTop: profile.offset().top }, "slow");
});

Hide twitter field if value is empty


class ElectronInterface extends SiteInterface {
    constructor() {
        super();
    }
}

class ElectronInterface extends SiteInterface {
    constructor() {
        super();
        this.style = new Style(ElectronInterface.STYLE_PATH);
    }
}
ElectronInterface.STYLE_PATH = "../interface/SiteInterface/siteInterface.css";

Add style to Electron Interface


'use strict';

var mocha = require('../lib/mocha');

module.exports = function (grunt) {
    grunt.registerMultiTask('mochacli', 'Run Mocha server-side tests.', function () {
        var options = this.options();
        if (!options.files) {
            options.files = this.file.srcRaw;
        }

        mocha(options, this.async());
    });
};

'use strict';

var mocha = require('../lib/mocha');

module.exports = function (grunt) {
    grunt.registerMultiTask('mochacli', 'Run Mocha server-side tests.', function () {
        var options = this.options();
        var globs = [];
        if (!options.files) {
            this.files.forEach(function (glob) {
                globs = globs.concat(glob.orig.src);
            });
            options.files = globs;
        }

        mocha(options, this.async());
    });
};

Fix using the Grunt files format


class ElementHolder {
  element;

  getElement() { return this.element; }

  makeFilterCapturedThis() {
    var capturedThis = this;
    return function (x) {
      return x == capturedThis.element;
    }
  }

  makeFilterLostThis() {
    return function (x) { return x == this.element; }
  }

  makeFilterHidden(element) {
    return function (x) { return x == element; }
  }
}

// ----------------------------------------------------------------------------

var obj = new ElementHolder();

obj.element = 40;
assertEquals(40, obj.getElement());
assertTrue(obj.makeFilterCapturedThis()(40));
assertFalse(obj.makeFilterLostThis()(40));

obj.element = 39;
assertFalse(obj.makeFilterCapturedThis()(40));
assertTrue(obj.makeFilterCapturedThis()(39));

assertFalse(obj.makeFilterHidden(41)(40));
assertTrue(obj.makeFilterHidden(41)(41));

class ElementHolder {
  element;

  getElement() { return this.element; }

  makeFilterCapturedThis() {
    var capturedThis = this;
    return function (x) {
      return x == capturedThis.element;
    }
  }

  makeFilterLostThis() {
    return function () { return this; }
  }

  makeFilterHidden(element) {
    return function (x) { return x == element; }
  }
}

// ----------------------------------------------------------------------------

var obj = new ElementHolder();

obj.element = 40;
assertEquals(40, obj.getElement());
assertTrue(obj.makeFilterCapturedThis()(40));

// http://code.google.com/p/v8/issues/detail?id=1381
// assertUndefined(obj.makeFilterLostThis()());

obj.element = 39;
assertFalse(obj.makeFilterCapturedThis()(40));
assertTrue(obj.makeFilterCapturedThis()(39));

assertFalse(obj.makeFilterHidden(41)(40));
assertTrue(obj.makeFilterHidden(41)(41));

Fix invalid test and disable it due to V8 bug


import { cancelTimers, end, getCurrentRunLoop, hasScheduledTimers } from '@ember/runloop';

function RunLoopAssertion(env) {
  this.env = env;
}

RunLoopAssertion.prototype = {
  reset: function() {},
  inject: function() {},
  assert: function() {
    let { assert } = QUnit.config.current;

    if (getCurrentRunLoop()) {
      assert.ok(false, 'Should not be in a run loop at end of test');
      while (getCurrentRunLoop()) {
        end();
      }
    }

    if (hasScheduledTimers()) {
      assert.ok(false, 'Ember run should not have scheduled timers at end of test');
      cancelTimers();
    }
  },
  restore: function() {},
};

export default RunLoopAssertion;

import { cancelTimers, end, getCurrentRunLoop, hasScheduledTimers } from '@ember/runloop';

export default class RunLoopAssertion {
  constructor(env) {
    this.env = env;
  }

  reset() {}

  inject() {}

  assert() {
    let { assert } = QUnit.config.current;

    if (getCurrentRunLoop()) {
      assert.ok(false, 'Should not be in a run loop at end of test');
      while (getCurrentRunLoop()) {
        end();
      }
    }

    if (hasScheduledTimers()) {
      assert.ok(false, 'Ember run should not have scheduled timers at end of test');
      cancelTimers();
    }
  }

  restore() {}
}

Convert `RunLoopAssert` to ES6 class


// TODO docs, see data/breakpoint.js
define(function(require, exports, module) {

    var Data = require("./data");

    function File(options) {
        this.data = options || {};
        if (!this.data.items)
            this.data.items = [];
        if (!this.data.status)
            this.data.status = "pending";
        this.type = "file";
        this.keepChildren = true;
    }

    File.prototype = new Data(
        ["path", "type", "coverage", "passed", "fullOutput", "output", "ownPassed"],
        ["items"]
    );

    File.prototype.__defineGetter__("passed", function(){
        return typeof this.data.ownPassed == "number"
            ? this.data.ownPassed
            : this.data.passed;
    });

    File.prototype.equals = function(file) {
        return this.data.label == file.label;
    };

    File.prototype.addTest = function(def) {
        var test = Data.fromJSON([def])[0];
        this.data.items.push(test);
        return test;
    };

    module.exports = File;
});

// TODO docs, see data/breakpoint.js
define(function(require, exports, module) {

    var Data = require("./data");

    function File(options) {
        this.data = options || {};
        if (!this.data.items)
            this.data.items = [];
        if (!this.data.status)
            this.data.status = "pending";
        this.type = "file";
        this.keepChildren = true;
    }

    File.prototype = new Data(
        ["path", "type", "coverage", "passed", "fullOutput", "output", "ownPassed"],
        ["items"]
    );

    File.prototype.__defineGetter__("passed", function(){
        return typeof this.data.ownPassed == "number"
            ? this.data.ownPassed
            : this.data.passed;
    });

    File.prototype.equals = function(file) {
        return this.data.label == file.label;
    };

    File.prototype.addTest = function(def, parent) {
        var test = Data.fromJSON([def])[0];
        (parent || this).data.items.push(test);
        return test;
    };

    module.exports = File;
});

Create Tests Dynamically When Needed


/* global describe, it, beforeEach */

import React from 'react'
import { expect } from 'chai'
import { sizeClassNames } from '../src/js/util'

describe('Util', () => {
  describe('sizeClassNames', () => {
    it('should return one classname given props', () => {
      const props = { xs: 12 }
      const actual = sizeClassNames(props)
      expect(actual).to.equal('col-xs-12')
    })

    it('should not return classnames given undefined props', () => {
      const props = {
        xs: 12,
        sm: undefined,
        md: undefined,
        lg: undefined,
      }
      const actual = sizeClassNames(props)
      expect(actual).to.equal('col-xs-12')
    })

    it('should return all classnames given props', () => {
      const props = {
        xs: 12,
        sm: 6,
        md: 1,
        lg: 23,
      }
      const actual = sizeClassNames(props)
      expect(actual).to.equal('col-xs-12 col-sm-6 col-md-1 col-lg-23')
    })

  })
})

/* global describe, it, beforeEach */

import React from 'react'
import { expect } from 'chai'
import { sizeClassNames } from '../src/js/util'

describe('Util', () => {
  describe('sizeClassNames', () => {
    describe('using props', () => {
      it('should return one classname given props', () => {
        const props = { xs: 12 }
        const actual = sizeClassNames(props)
        expect(actual).to.equal('col-xs-12')
      })

      it('should not return classnames given undefined props', () => {
        const props = {
          xs: 12,
          sm: undefined,
          md: undefined,
          lg: undefined,
        }
        const actual = sizeClassNames(props)
        expect(actual).to.equal('col-xs-12')
      })

      it('should return all classnames given props', () => {
        const props = {
          xs: 12,
          sm: 6,
          md: 1,
          lg: 23,
        }
        const actual = sizeClassNames(props)
        expect(actual).to.equal('col-xs-12 col-sm-6 col-md-1 col-lg-23')
      })
    })

    describe('using props and offset', () => {
      it('should return one offset classname given props', () => {
        const props = { xsOffset: 20 }
        const actual = sizeClassNames(props)
        expect(actual).to.equal('col-xs-offset-20')
      })

      it('should not return one offset classname given undefined props', () => {
        const props = {
          xsOffset: 2,
          smOffset: undefined,
          mdOffset: undefined,
          lgOffset: undefined,
        }
        const actual = sizeClassNames(props)
        expect(actual).to.equal('col-xs-offset-2')
      })

      it('should return empty when offset is set to false', () => {
        const actual = sizeClassNames({}, { Offsets: false })
        expect(actual).to.equal('')
      })
    })
  })
})

Add sizeClassNames Given Props & Offset Tests


Template.homeResidentActivityLevelTrend.rendered = function () {
  // Get reference to template instance
  var instance = this;

  instance.autorun(function () {
    // Get reference to Route
    var router = Router.current();

    // Get current Home ID
    var homeId = router.params.homeId;

    // Get data for trend line chart
    var data = ReactiveMethod.call("getHomeActivityCountTrend", homeId);

    if (data) {
      MG.data_graphic({
          title: "Count of residents per activity level for each of last seven days",
          description: "Daily count of residents with inactive, semi-active, and active status.",
          data: [data[0], data[1], data[2]],
          x_axis: false,
          interpolate: 'basic',
          full_width: true,
          height: 200,
          right: 40,
          target: '#trend-chart',
          legend: ['Inactive','Semi-active','Active'],
          legend_target: '.legend',
          colors: ['red', 'gold', 'green'],
          aggregate_rollover: true
      });
    }
  });
}

Template.homeResidentActivityLevelTrend.rendered = function () {
  // Get reference to template instance
  var instance = this;

  instance.autorun(function () {
    // Get reference to Route
    var router = Router.current();

    // Get current Home ID
    var homeId = router.params.homeId;

    // Get data for trend line chart
    var data = ReactiveMethod.call("getHomeActivityCountTrend", homeId);

    if (data) {
      MG.data_graphic({
          title: "Count of residents per activity level for each of last seven days",
          description: "Daily count of residents with inactive, semi-active, and active status.",
          data: data,
          x_axis: true,
          y_accessor: ['inactive', 'semiActive', 'active'],
          interpolate: 'basic',
          full_width: true,
          height: 333,
          right: 49,
          target: '#trend-chart',
          legend: ['Inactive','Semi-active','Active'],

          colors: ['red', 'gold', 'green'],
          aggregate_rollover: true
      });
    }
  });
}

Use object attributes; adjust appearance


(function(){
  // todo: remove depsu to elementBottomIsAlmostVisible
  var nextPage = function(elem) {
    var e = ($.isFunction(elem)) ? elem() : elem
    return Rx.Observable.interval(200).where(function() { return elementBottomIsAlmostVisible(e, 100) })
  }

  var pager = function(ajaxSearch, page, next) {
    return ajaxSearch(page).selectMany(function(res) {
      if (res.length === 0) {
        _.map(["#content-spinner-content", "#content-spinner-latest"], function(elem) { $(elem).html("Ei enempää suorituksia") })
        return rx.empty()
      } else {
        return rx.returnValue(res).concat(next.take(1).selectMany(function() { return pager(ajaxSearch, page+1, next) }))
      }
    })
  }

  var Pager = function() {}
  Pager.prototype.create = function(ajaxQuery, elem) { return pager(ajaxQuery, 1, nextPage(elem)) }
  OnTrail.pager = new Pager()

  Rx.Observable.prototype.scrollWith = function(action, element, visibleElem) {
    return this.distinctUntilChanged().doAction(function() { element.html("") })
      .selectArgs(function() {
        var partialAppliedArgs = [action].concat(_.argsToArray(arguments))
        return OnTrail.pager.create(_.partial.apply(_.partial, partialAppliedArgs), visibleElem || element)
      }).switchLatest()
  }
})()

(function(){
  var timer = Rx.Observable.interval(100).publish()
  timer.connect()

  // todo: remove depsu to elementBottomIsAlmostVisible
  var nextPage = function(elem) {
    var e = ($.isFunction(elem)) ? elem() : elem
    return timer.where(function() { return elementBottomIsAlmostVisible(e, 100) })
  }

  var pager = function(ajaxSearch, page, next) {
    return ajaxSearch(page).selectMany(function(res) {
      if (res.length === 0) {
        _.map(["#content-spinner-content", "#content-spinner-latest"], function(elem) { $(elem).html("Ei enempää suorituksia") })
        return rx.empty()
      } else {
        return rx.returnValue(res).concat(next.take(1).selectMany(function() { return pager(ajaxSearch, page+1, next) }))
      }
    })
  }

  var Pager = function() {}
  Pager.prototype.create = function(ajaxQuery, elem) { return pager(ajaxQuery, 1, nextPage(elem)) }
  OnTrail.pager = new Pager()

  Rx.Observable.prototype.scrollWith = function(action, element, visibleElem) {
    return this.distinctUntilChanged().doAction(function() { element.html("") })
      .selectArgs(function() {
        var partialAppliedArgs = [action].concat(_.argsToArray(arguments))
        return OnTrail.pager.create(_.partial.apply(_.partial, partialAppliedArgs), visibleElem || element)
      }).switchLatest()
  }
})()

Use single hot observable as timed event stream.