/*jshint node:true*/
module.exports = {
  scenarios: [
    {
      name: 'default',
      bower: {
        dependencies: { }
      }
    },
    {
      name: 'ember-1.13',
      bower: {
        dependencies: {
          'ember': '~1.13.0'
        },
        resolutions: {
          'ember': '~1.13.0'
        }
      }
    },
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      }
    }
  ]
};

/*jshint node:true*/
module.exports = {
  scenarios: [
    {
      name: 'default',
      bower: {
        dependencies: { }
      }
    },
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      }
    }
  ]
};

Remove Ember Try scenario for Ember 1.13


$(function(){
  var $replyMessage = $('[data-reply-to-message]');

  var editor = new MediumEditor($replyMessage, {
    placeholder: $replyMessage.attr('placeholder')
  });

  setTimeout(function() { $replyMessage.focus() }, 0);

  $("form[data-reply-form]").submit(function( event ) {
    var content = $("[data-reply-to-message]").html()
    $(this).find("#message_content").val(content)
  });

});

$(function(){
  var $replyMessage = $('[data-reply-to-message]');

  var editor = new MediumEditor($replyMessage, {
    placeholder: $replyMessage.attr('placeholder'),
    cleanPastedHTML: true
  });

  setTimeout(function() { $replyMessage.focus() }, 0);

  $("form[data-reply-form]").submit(function( event ) {
    var content = $("[data-reply-to-message]").html()
    $(this).find("#message_content").val(content)
  });

});

Clean up the text pasted into the medium editor


export default {
  period: 'week',
  weeks: 13,
  dataset: 'MODIS',
  layerStartDate: null,
  layerEndDate: null
};

export default {
  period: 'week',
  weeks: 13,
  dataset: 'VIIRS',
  layerStartDate: null,
  layerEndDate: null
};

Change default dataset for Fire Alerts widget to VIIRS instead of MODIS


/*jshint node:true*/
function scenario(emberVersion) {
  return {
    name: 'ember-' + emberVersion,
    bower: {
      dependencies: {
        'ember': '~' + emberVersion + '.0'
      },
      resolutions: {
        'ember': '~' + emberVersion + '.0'
      }
    }
  };
}

module.exports = {
  scenarios: [
    scenario('1.10'),
    scenario('1.11'),
    scenario('1.12'),
    scenario('1.13'),
    scenario('2.0'),
    scenario('2.1'),
    scenario('2.2'),
    scenario('2.3'),
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      }
    }
  ]
};

/*jshint node:true*/
function scenario(emberVersion) {
  return {
    name: 'ember-' + emberVersion,
    bower: {
      dependencies: {
        'ember': '~' + emberVersion + '.0'
      },
      resolutions: {
        'ember': '~' + emberVersion + '.0'
      }
    }
  };
}

module.exports = {
  scenarios: [
    scenario('1.11'),
    scenario('1.12'),
    scenario('1.13'),
    scenario('2.0'),
    scenario('2.1'),
    scenario('2.2'),
    scenario('2.3'),
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      }
    }
  ]
};

Remove ember 1.10 testing scenario


/* eslint global-require: 0 */
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../modules/reducer';
import Immutable from 'immutable';

const middlewares = [thunk];

if ('production' !== process.env.NODE_ENV) {
  const createLogger = require('redux-logger');
  const logger = createLogger({
    stateTransformer: (state) => {
      const newState = {};

      for (const i of Object.keys(state)) {
        if (Immutable.Iterable.isIterable(state[i])) {
          newState[i] = state[i].toJS();
        } else {
          newState[i] = state[i];
        }
      }

      return newState;
    },
  });
  middlewares.push(logger);
}

const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);

export default function configureStore(initialState) {
  return createStoreWithMiddleware(rootReducer, initialState);
}

/* eslint global-require: 0 */
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../modules/reducer';
import Immutable from 'immutable';

const middlewares = [thunk];

if ('production' !== process.env.NODE_ENV) {
  const createLogger = require('redux-logger');
  const logger = createLogger({
    stateTransformer: (state) => {
      const newState = {};

      for (const i of Object.keys(state)) {
        if (Immutable.Iterable.isIterable(state[i])) {
          newState[i] = state[i].toJS();
        } else {
          newState[i] = state[i];
        }
      }

      return newState;
    },
  });
  middlewares.push(logger);
}

const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);

export default function configureStore(initialState) {
  return createStoreWithMiddleware(
    rootReducer,
    initialState,
    window.devToolsExtension && window.devToolsExtension()
  );
}

Enable Redux DevTools in dev


(function(){
  'use strict'

  const New = {
    templateUrl: 'pages/new/new.html',
    controllerAs: 'model',
    controller: function(BookmarkFactory, TagFactory, $state){

      const model = this;
      model.saveBookmark = saveBookmark;
      model.loadTags = loadTags;
      model.validationClass = validationClass;
      model.resource_types = ["video", "conversation thread", "book/publication", "blog/post", "other"];

      // NOTE: add & remove class based on form validation
      function validationClass(value){
        if (value.$dirty){
          return value.$invalid ? 'remove icon red' : 'checkmark icon green';
        }
      }

      // NOTE: ngTagsInput module
      function loadTags(query) {
        model.testing = "clicked";
        return TagFactory.getTags()
                         .then(function(response){
                           return response;
                         });
      }//end loadTags

      // NOTE: save new bookmarks
      function saveBookmark(){
        return BookmarkFactory.saveBookmark(model.newBookmark)
          .then(saveBookmarkResponse)
      }//end saveBookmark

      function saveBookmarkResponse(response){
        if(response.status == "success"){
          return $state.go("show", { id: response.success.id});
        } else {
          return model.error = response.errors;
        }
      }//end saveBookmarkResponse

    }//end controller

  }//end New

  angular
    .module("shareMark")
    .component("new", New);

}());

(function(){
  'use strict'

  const New = {
    templateUrl: 'pages/new/new.html',
    controllerAs: 'model',
    controller: function(BookmarkFactory, TagFactory, $state, $q){

      const model = this;
      model.saveBookmark = saveBookmark;
      model.loadTags = loadTags;
      model.validationClass = validationClass;
      model.resource_types = ["video", "conversation thread", "book/publication", "blog/post", "other"];

      // NOTE: add & remove class based on form validation
      function validationClass(value){
        if (value.$dirty){
          return value.$invalid ? 'remove icon red' : 'checkmark icon green';
        }
      }

      // TODO: look into add cache for search
      function loadTags($query) {
        return TagFactory.getTags()
        .then(function(response) {
          return response.filter(function(tag) {
            return tag.name.toLowerCase().indexOf($query.toLowerCase()) != -1;
          });
        });
      };

      // NOTE: save new bookmarks
      function saveBookmark(){
        return BookmarkFactory.saveBookmark(model.newBookmark)
          .then(saveBookmarkResponse)
      }//end saveBookmark

      function saveBookmarkResponse(response){
        if(response.status == "success"){
          return $state.go("show", { id: response.success.id});
        } else {
          return model.error = response.errors;
        }
      }//end saveBookmarkResponse

    }//end controller

  }//end New

  angular
    .module("shareMark")
    .component("new", New);

}());

Add filter for tags autocomplete for creating a new bookmark


// Dependencies
var net = require('net'),
    express = require('express'),
    exec = require('child_process').exec;

// mochad client
var client;

function setupConnection() {
    client = net.connect({port: 1099}, function() {
        console.log('Connection successful');
    });

    client.on('error', function(e) {
        console.log('Connection error: ' + e.code);
        console.log('Restarting mochad; retrying in 3 seconds...');
        exec('mochad');
        setTimeout(setupConnection, 3000);
    });
}

setupConnection();

// Web server
var app = express();

app.get('/', function(req, res) {
    res.send({hello: 'world'});
});

app.listen(3000);

// Dependencies
var net = require('net'),
    express = require('express'),
    exec = require('child_process').exec;

// mochad client
var client;

function setupConnection() {
    client = net.connect({port: 1099}, function() {
        console.log('Connection successful');
    });

    client.setNoDelay();

    client.on('error', function(e) {
        console.log('Connection error: ' + e.code);
        console.log('Restarting mochad; retrying in 3 seconds...');
        exec('mochad');
        setTimeout(setupConnection, 3000);
    });
}

function sendX10Command(addr, value, callback) {
    var cmd = 'pl ' + addr + ' ' + value;
    console.log(cmd);
    client.write(cmd + '\n', null, callback);
}

setupConnection();

// Web server
var app = express();
app.use(express.bodyParser());

app.get('/', function(req, res) {
    res.send({hello: 'world'});
});

app.put('/api/modules/:addr', function(req, res) {
    if (['on', 'off'].indexOf(req.body.value) != -1) {
        sendX10Command(req.params.addr, req.body.value, function(err) {
            if (err) {
                res.send(500);
            }
            else {
                res.send(200);
            }
        });
    }
    else {
        res.send(400);
    }
});

app.listen(3000);

Add PUT route to switch modules on/off.


import config from '-/config';
import configureStore from './configure';
import { loadState, saveState } from './localStorage';
import { invokeAfterUninterruptedDelay } from '~/utils/throttle';

const state = config.env.production ? loadState() : undefined;
const store = configureStore(state);
const onDispatch = () => saveState(store.getState());
const throttle = invokeAfterUninterruptedDelay(250, onDispatch);

store.subscribe( throttle );

export default store;

if (config.env.development) {
    window.getState = store.getState;
    console.group('%c Development Mode', 'color:cyan;');
    console.log('Added window.getState');
    console.log('State:', store.getState());
    console.groupEnd();
}

import config from '-/config';
import configureStore from './configure';
import { loadState, saveState, loadCache } from './localStorage';
import { invokeAfterUninterruptedDelay } from '~/utils/throttle';

const state = config.env.production ? loadState() : undefined;
const store = configureStore(state);
const onDispatch = () => saveState(store.getState());
const throttle = invokeAfterUninterruptedDelay(250, onDispatch);

store.subscribe( throttle );

export default store;

if (config.env.development) {
    window.getState = store.getState;
    window.getCache = loadCache;
    console.group('%c Development Mode', 'color:cyan;');
    console.log('Enabled: %cwindow.getState, window.getCache', 'color: skyblue');
    console.log('State:', store.getState());
    console.log('Cache:', loadCache());
    console.groupEnd();
}

Add cache to store initialisation


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

import currency from 'currency.js';

export const selectPrescriptionSubTotal = ({ payment }) => {
  const { transaction } = payment;
  const { items = [] } = transaction || {};
  const total = items.reduce((acc, { totalPrice }) => acc + totalPrice || 0, 0);
  return currency(total);
};

export const selectPrescriptionTotal = ({ payment }) => {
  const { insurancePolicy } = payment;

  const subtotal = selectPrescriptionSubTotal({ payment });

  return currency(subtotal * (insurancePolicy ? insurancePolicy.discountRate / 100 : 1));
};

export const selectCreditBeingUsed = ({ payment }) => {
  const { paymentAmount } = payment;

  const prescriptionTotal = selectPrescriptionTotal({ payment });

  const creditBeingUsed = Math.max(prescriptionTotal.subtract(paymentAmount).value, 0);

  return currency(creditBeingUsed);
};

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

import currency from 'currency.js';

export const selectPrescriptionSubTotal = ({ payment }) => {
  const { transaction } = payment;
  const { items = [] } = transaction || {};
  const total = items.reduce((acc, { totalPrice }) => currency(totalPrice || 0).add(acc), 0);
  return total;
};

export const selectPrescriptionTotal = ({ payment }) => {
  const { insurancePolicy } = payment;

  const subtotal = selectPrescriptionSubTotal({ payment });

  return currency(subtotal * (insurancePolicy ? insurancePolicy.discountRate / 100 : 1));
};

export const selectCreditBeingUsed = ({ payment }) => {
  const { paymentAmount } = payment;

  const prescriptionTotal = selectPrescriptionTotal({ payment });

  const creditBeingUsed = Math.max(prescriptionTotal.subtract(paymentAmount).value, 0);

  return currency(creditBeingUsed);
};

Add changed use of currency


'use babel'

export class LinterUI {
  static name = 'Linter'

  constructor(editors) {
    this.editors = editors
  }

  dispose() {

  }
}

'use babel'

import {CompositeDisposable} from 'atom'

export class LinterUI {
  static name = 'Linter'

  constructor(editors) {
    this.editors = editors
    this.subscriptions = new CompositeDisposable()
  }

  dispose() {
    this.subscriptions.dispose()
  }
}

Add a proper dispose to UI class


var lilswf = function(){
    var window,
        undefined,
        self = this;
        self.raw = "",
        self.version = [],
        self.installed = self.isCool = false;
    function activeXObjectGetVariable(activeXObj, name){
        try{
            return activeXObj.GetVariable(name);
        }catch(e){
            return "";
        }
    }
    function newActiveObject(name){
        try{
            return new ActiveXObject(name);
        }catch(e){
            return undefined;
        }
    }
    function parseActiveXVersionVariable(raw){
        var parts = raw.split(","),
            version = [],
            i = 0,
            l = parts.length;
        for(; i < l; i++){
            version[i] = parseInt(parts[i], 10) || -1;
        }
        return version;
    }
    self.atLeast = function(){
        return false;
    };
    return self;
}();

/**
 * lil lib to help you detect flash.
 * NOTE: I am not production ready yet.
 */
var lilswf = function(){
    var window,
        undefined,
        self = this;
        self.raw = "",
        self.version = [],
        self.installed = self.isCool = false;
    /**
     * A safe accessor for the native ActiveX GetVariable method.
     *
     * @param {Object} activeXObj A reference to an ActiveXObject.
     * @param {String} name The variable name for lookup.
     * @type {String}
     * @return The value of the AxtiveX if it is found or an empty string.
     */
    function activeXObjectGetVariable(activeXObj, name){
        try{
            return activeXObj.GetVariable(name);
        }catch(e){
            return "";
        }
    }
    /**
     * A safe accessor for creating a native ActiveX object
     *
     * @param {String} name The ActiveX object name lookup.
     * @type Object || undefined
     * @return One of an ActiveX object or undefined.
     */
    function newActiveObject(name){
        try{
            return new ActiveXObject(name);
        }catch(e){
            return undefined;
        }
    }
    /**
     * Parse an ActiveX $version variable into properly casted members:
     *
     * @param {String} raw The ActiveX Object GetVariable($version) string return value.
     * @type Array
     * @return An array of integers casted from the original raw version string. Values that can't be casted are returned as a -1 value.
     */
    function parseActiveXVersionVariable(raw){
        var parts = raw.match(/[0-9]+/g),
            i = 0,
            l = parts.length;
        for(; i < l; i++){
            parts[i] = parts[i], 10);
        }
        return parts;
    }
    /**
     * TBD.
     *
     * @params arguments
     * @type Boolean
     */
    self.atLeast = function(){
        return false;
    };
    return self;
}();

Add jsdocs back. Migrate to rex in string parser.


import Vue from 'vue';
import VueRouter from 'vue-router';
import RestaurantList from '../components/RestaurantList.vue'

Vue.use(VueRouter);

const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/solna',
      component: RestaurantList,
      props: { list_type: 'solna' },
      alias: ['/ki']
    },
    {
      path: '/uppsala',
      component: RestaurantList,
      props: { list_type: 'uppsala' },
      alias: ['/uu', '/bmc']
    },
    {
      path: '/',
      component: RestaurantList,
      props: { list_type: 'both' },
      alias: ['*']
    },
  ]
});

export default router;

import Vue from 'vue';
import VueRouter from 'vue-router';
import RestaurantList from '../components/RestaurantList.vue'

Vue.use(VueRouter);

const router = new VueRouter({
  mode: 'hash',
  base: '/menu/',
  routes: [
    {
      path: '/solna',
      component: RestaurantList,
      props: { list_type: 'solna' },
      alias: ['/ki']
    },
    {
      path: '/uppsala',
      component: RestaurantList,
      props: { list_type: 'uppsala' },
      alias: ['/uu', '/bmc']
    },
    {
      path: '/',
      component: RestaurantList,
      props: { list_type: 'both' },
      alias: ['*']
    },
  ]
});

export default router;

Use hash mode for router as server can't be changed for use with history mode.


(function(win){
    'use strict';

    var paramRe = /:([^\/.\\]+)/g;

    function Router(){
        this.routes = [];
    }

    Router.prototype.route = function(path, fn){
        paramRe.lastIndex = 0;
        var regexp = path + '', match;
        while(match = paramRe.exec(path)){
            regexp = regexp.replace(match[0], '([^/.\\\\]+)');
        }
        this.routes.push({
            regexp: new RegExp('^' + regexp + '$'),
            callback: fn
        });
        return this;
    };

    Router.prototype.dispatch = function(path){
        path = path || win.location.pathname;
        for(var i = 0, len = this.routes.length, route, matches; i < len; i++){
            route = this.routes[i];
            matches = route.regexp.exec(path);
            if(matches && matches[0]){
                route.callback.apply(null, matches.slice(1));
                return this;
            }
        }
        return this;
    };

    win.Router = Router;

})(this);

(function(win){
    'use strict';

    var paramRe = /:([^\/.\\]+)/g;

    function Router(){
        if(!(this instanceof Router)){
            return new Router();
        }
        this.routes = [];
    }

    Router.prototype.route = function(path, fn){
        paramRe.lastIndex = 0;
        var regexp = path + '', match;
        while(match = paramRe.exec(path)){
            regexp = regexp.replace(match[0], '([^/.\\\\]+)');
        }
        this.routes.push({
            regexp: new RegExp('^' + regexp + '$'),
            callback: fn
        });
        return this;
    };

    Router.prototype.dispatch = function(path){
        path = path || win.location.pathname;
        for(var i = 0, len = this.routes.length, route, matches; i < len; i++){
            route = this.routes[i];
            matches = route.regexp.exec(path);
            if(matches && matches[0]){
                route.callback.apply(null, matches.slice(1));
                return this;
            }
        }
        return this;
    };

    win.Router = Router;

})(this);

Support instantiation without new operator


const Factories                 = use('core/factories');
const Errors                    = use('core/errors');

const PermissionsFactory        = Factories('Permissions');

function checkPermissions(body) {

    if (!this.request.handler.permission) {
        return body;
    }

    if (!this.request.session) {
        this.response.status = this.response.statuses._401_Unauthorized;
        throw new Errors.Unauthorized("Session is invalid");
    }

    return PermissionsFactory.getByMember(this.request.session.member)
        .then(permissions => {
            this.request.session.permissions = permissions.map(permission => permission.title.toLowerCase());

            if (!this.request.session.permissions.includes(this.request.handler.permission.toLowerCase())) {
                this.response.status = this.response.statuses._403_Forbidden;
                throw new Errors.Forbidden("You don't have permission to perform this action");
            }

            if (this.request.session.permissions.includes(this.request.handler.annotations["rootownerpermissions"])) {
                this.request.session.rootOwner = true;
            }

            return body;
        });

}

module.exports = checkPermissions;

const Factories                 = use('core/factories');
const Errors                    = use('core/errors');

const PermissionsFactory        = Factories('Permissions');

function checkPermissions(body) {

    if (!this.request.handler.permission) {
        return body;
    }

    if (!this.request.session) {
        throw new Errors.Unauthorized("Session is invalid");
    }

    return PermissionsFactory.getByMember(this.request.session.member)
        .then(permissions => {
            this.request.session.permissions = permissions.map(permission => permission.title.toLowerCase());

            if (!this.request.session.permissions.includes(this.request.handler.permission.toLowerCase())) {
                throw new Errors.Forbidden("You don't have permission to perform this action");
            }

            if (this.request.session.permissions.includes(this.request.handler.annotations["rootownerpermissions"])) {
                this.request.session.rootOwner = true;
            }

            return body;
        });

}

module.exports = checkPermissions;

Remove setting response status code on permission validation


import gulp from 'gulp';
import loadPlugins from 'gulp-load-plugins';
import browserSync from 'browser-sync';

const $ = loadPlugins();
const bs = browserSync.create();

gulp.task('serve:test', ['styles'], done => {
  bs.init({
    logLevel: 'silent',
    notify: false,
    open: false,
    port: 9000,
    server: {
      baseDir: ['test/fixtures', 'dist']
    },
    ui: false
  }, done);
});

gulp.task('integration', 'serve:test', () => {
  return gulp.src('test/spec/**/*.js', {read: false})
    .pipe($.webdriver({
      desiredCapabilities: {
        browserName: 'phantomjs'
      },
      slow: 200 // integration tests are usually slower than unit tests
    }));
});

gulp.task('test', ['integration'], () => {
  bs.exit();
});

import gulp from 'gulp';
import loadPlugins from 'gulp-load-plugins';
import browserSync from 'browser-sync';

const $ = loadPlugins();
const bs = browserSync.create();

gulp.task('serve:test', ['styles'], done => {
  bs.init({
    logLevel: 'silent',
    notify: false,
    open: false,
    port: 9000,
    server: {
      baseDir: ['test/fixtures', 'dist']
    },
    ui: false
  }, done);
});

gulp.task('integration', ['serve:test'], () => {
  return gulp.src('test/spec/**/*.js', {read: false})
    .pipe($.webdriver({
      desiredCapabilities: {
        browserName: 'phantomjs'
      },
      slow: 200 // integration tests are usually slower than unit tests
    }));
});

gulp.task('test', ['integration'], () => {
  bs.exit();
});

Fix gulp task syntax error


import startMirage from 'ember-cli-mirage/start-mirage';

//
// Used to set up mirage for a test. Must be called after one of the
// `ember-qunit` `setup*Test()` methods. It starts the server and sets
// `this.server` to point to it, and shuts the server down when the test
// finishes.
//
// NOTE: the `hooks = self` is for mocha support
//
export default function setupMirage(hooks = self) {
  hooks.beforeEach(function() {
    if (!this.owner) {
      throw new Error('You must call one of the ember-qunit setupTest(),'
        + ' setupRenderingTest() or setupApplicationTest() methods before'
        + ' calling setupMirage()');
    }

    this.server = startMirage(this.owner);
  });

  hooks.afterEach(function() {
    this.server.shutdown();
    delete this.server;
  });
}

import startMirage from 'ember-cli-mirage/start-mirage';
import { settled } from '@ember/test-helpers';

//
// Used to set up mirage for a test. Must be called after one of the
// `ember-qunit` `setup*Test()` methods. It starts the server and sets
// `this.server` to point to it, and shuts the server down when the test
// finishes.
//
// NOTE: the `hooks = self` is for mocha support
//
export default function setupMirage(hooks = self) {
  hooks.beforeEach(function() {
    if (!this.owner) {
      throw new Error('You must call one of the ember-qunit setupTest(),'
        + ' setupRenderingTest() or setupApplicationTest() methods before'
        + ' calling setupMirage()');
    }

    this.server = startMirage(this.owner);
  });

  hooks.afterEach(function() {
    return settled().then(() => {
      this.server.shutdown();
      delete this.server;
    });
  });
}

Fix teardown when pending requests


import isArray from 'lodash/lang/isArray';
import intersection from 'lodash/array/intersection';

export default function matchesType(targetType, draggedItemType) {
  var draggedItemTypeArr = draggedItemType.split('#');
  if (isArray(targetType)) {
  	if (intersection(targetType, draggedItemTypeArr) !== []) {
  		return true;
  	} else {
  		return false;
  	}
  } else {
    return draggedItemTypeArr.some(t => t === targetType);
  }
}

import isArray from 'lodash/lang/isArray';
import intersection from 'lodash/array/intersection';

export default function matchesType(targetType, draggedItemType) {
  var draggedItemTypeArr = draggedItemType.split('#');
  if (isArray(targetType)) {
  	if (intersection(targetType, draggedItemTypeArr).length > 0) {
  		return true;
  	} else {
  		return false;
  	}
  } else {
    return draggedItemTypeArr.some(t => t === targetType);
  }
}

Fix check for empty aray in matchTypes.js


import request from 'supertest';
import express from 'express';
import healthEndpoint from '../../../../server/routes/health';

describe('GET /health', () => {
  let app;
  before(() => {
    app = express();
    app.use('/health', healthEndpoint);
  });

  it('responds with { status: "OK" }', (done) => {
    request(app)
      .get('/health')
      .expect('Content-Type', /json/)
      .expect('Content-Length', '15')
      .expect(200, {
        status: 'OK',
      }, done);
  });
});

import path from 'path';
import fs from 'fs';
import rimraf from 'rimraf';
import request from 'supertest';
import express from 'express';
import healthEndpoint from '../../../../server/routes/health';

describe('GET /health', () => {
  let app;
  before(() => {
    app = express();
    app.use('/health', healthEndpoint);
  });

  it('responds with { status: "OK" }', (done) => {
    request(app)
      .get('/health')
      .expect('Content-Type', /json/)
      .expect(200, {
        status: 'OK',
      }, done);
  });

  context('when the build-info.json file is present', () => {
    const filePath = path.resolve(__dirname, '../../../../build-info.json');

    before(() => {
      const buildInfo = { buildNumber: 'foo', gitRef: 'bar' };
      fs.writeFileSync(filePath, JSON.stringify(buildInfo), { encoding: 'utf-8' });
    });

    after(() => {
      rimraf.sync(filePath);
    });

    it('responds with the build information inside the status ended', (done) => {
      const server = express();
      server.use('/health', healthEndpoint);

      request(app)
      .get('/health')
      .expect('Content-Type', /json/)
      .expect(200, {
        status: 'OK',
        buildNumber: 'foo',
        gitRef: 'bar',
      }, done);
    });
  });
});

Add test coverage on health endpoint for build-info


import React, { Component } from 'react';

class HashSelector extends Component {
  render() {
    return (
      <div className='flex flex-row justify-center mv4'>
        <input className='mh2' type='radio' name='hash-algo' value='sha-1' checked={'sha-1' === this.props.hashType} onChange={this.props.setHashType} />SHA-1
        <input className='mh2' type='radio' name='hash-algo' value='sha-256' checked={'sha-256' === this.props.hashType} onChange={this.props.setHashType} />SHA-256
        <input className='mh2' type='radio' name='hash-algo' value='sha-384' checked={'sha-384' === this.props.hashType} onChange={this.props.setHashType} />SHA-384
        <input className='mh2' type='radio' name='hash-algo' value='sha-512' checked={'sha-512' === this.props.hashType} onChange={this.props.setHashType} />SHA-512
      </div>
    );
  }
}

export default HashSelector;

Split into readable length lines


$(document).ready( function () {
  $(".upvote").click( function() {
    var commentId = $(".upvote").data("id");
    event.preventDefault();
    $.ajax({
      url: '/response/up_vote',
      method: 'POST',
      data: { id: commentId },
      dataType: 'JSON'
    }).done( function (voteCount) {
      if (voteCount == 1) {
        $("span[data-id=" + commentId + "]").html(voteCount + " vote");
      } else {
        $("span[data-id=" + commentId + "]").html(voteCount + " vote");
      }
    }).fail( function (voteCount) {
        console.log("Failed. Here is the voteCount:");
        console.log(voteCount);
    })
  })
})

$(document).ready( function () {
  $(".upvote").click( function() {
    var responseId = $(".upvote").data("id");
    event.preventDefault();
    $.ajax({
      url: '/response/up_vote',
      method: 'POST',
      data: { id: responseId },
      dataType: 'JSON'
    }).done( function (voteCount) {
      if (voteCount == 1) {
        $("span[data-id=" + responseId + "]").html(voteCount + " vote");
      } else {
        $("span[data-id=" + responseId + "]").html(voteCount + " vote");
      }
    }).fail( function (voteCount) {
        console.log("Failed. Here is the voteCount:");
        console.log(voteCount);
    })
  })
})

Change commentId to accurately be responseId.


module.exports = function(grunt) {

    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        concat: {
            options: {
                stripBanners: false, // no comments are stripped
                banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
                    '<%= grunt.template.today("yyyy-mm-dd") %> */\n',
                separator: '\n'
            },
            dist: {
                // Assemble different game objects
                src: [
                    "base/objects/Objects.js",
                    "base/objects/Sprite.js",
                    "base/objects/TexturedPlane.js",
                    "base/objects/DevActor.js",
                    "base/objects/Axis.js",
                    "base/objects/Grid.js",
                    "base/objects/Target.js",
                    "base/objects/TextTexture.js",
                    "base/objects/_init.js",
                ],
                dest: 'base/Objects.js',
            },
        },
        jshint: {
            options: {
                laxbreak: true
            },
            all: ['base/*.js', 'base/objects/*.js']
        }
    });

    // Load the plugin that provides the "uglify" task.
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-jshint');

    // Default task(s).
    grunt.registerTask('default', ['concat', 'jshint']);
};

module.exports = function(grunt) {

    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        concat: {
            options: {
                stripBanners: false, // no comments are stripped
                banner: '/*! <%= pkg.name %> - v<%= pkg.version %> */\n',
                separator: '\n'
            },
            dist: {
                // Assemble different game objects
                src: [
                    "base/objects/Objects.js",
                    "base/objects/Sprite.js",
                    "base/objects/TexturedPlane.js",
                    "base/objects/DevActor.js",
                    "base/objects/Axis.js",
                    "base/objects/Grid.js",
                    "base/objects/Target.js",
                    "base/objects/TextTexture.js",
                    "base/objects/_init.js",
                ],
                dest: 'base/Objects.js',
            },
        },
        jshint: {
            options: {
                laxbreak: true
            },
            all: ['base/*.js', 'base/objects/*.js']
        }
    });

    // Load the plugin that provides the "uglify" task.
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-jshint');

    // Default task(s).
    grunt.registerTask('default', ['concat', 'jshint']);
};

Remove timestamp from the concat task.


module.exports = function(grunt) {
  'use strict';

  grunt.initConfig({
    less: {
      dev: {
        options: {
          paths: ['velvet']
        },
        files: {
          'build/style.css': 'velvet/velvet.less'
        }
      },
      compress: {
        options: {
          paths: ['less'],
          yuicompress: true
        },
        files: {
          'css/style.css': 'less/main.less'
        }
      }
    },
    copy: {
      dist: {
        files: {
          'build/': 'velvet/*.{png,gif}'
        }
      }
    },
    watch: {
      less: {
        files: ['velvet/*.less'],
        tasks: ['less:dev']
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-copy');

  grunt.registerTask('default', 'less:dev copy');
  grunt.registerTask('release', 'less:compress copy');
};

module.exports = function(grunt) {
  'use strict';

  grunt.initConfig({
    less: {
      development: {
        options: {
          paths: ['velvet']
        },
        files: {
          'build/style.css': 'velvet/velvet.less'
        }
      },
      production: {
        options: {
          paths: ['less'],
          yuicompress: true
        },
        files: {
          'css/style.css': 'less/main.less'
        }
      }
    },
    copy: {
      options: {
        processContentExcluded: ['**/*.{png,gif}']
      },
      main: {
        files: [
          { expand: true
          , cwd: 'velvet/'
          , src: ['*.{png,gif}']
          , dest: 'build/'
          }
        ]
      }
    },
    watch: {
      files: 'velvet/*.less',
      tasks: ['less:development']
    }
  });

  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('default', ['less:development', 'copy']);
  grunt.registerTask('release', ['less:production', 'copy']);
};

Change content of grunt file accordingly


module.exports = function (grunt) {
    grunt.initConfig({

        // watch changes to less files
        watch: {
            styles: {
                files: ["less/*"],
                tasks: ["less"]
            },
            options: {
                spawn: false,
            },
        },

        // compile set less files
        less: {
            development: {
                options: {
                    paths: ["less"],
                },
                files: {
                    "css/site.css": ["less/*.less", "!less/_*.less"]
                }
            }
        },

    });

    // Load tasks so we can use them
    grunt.loadNpmTasks("grunt-contrib-watch");
    grunt.loadNpmTasks("grunt-contrib-less");

    // the default task will show the usage
    grunt.registerTask("default", "Prints usage", function () {
        grunt.log.writeln("");
        grunt.log.writeln("Using Base");
        grunt.log.writeln("------------------------");
        grunt.log.writeln("");
        grunt.log.writeln("* run 'grunt --help' to get an overview of all commands.");
        grunt.log.writeln("* run 'grunt dev' to start watching and compiling LESS changes.");
    });

    grunt.registerTask("dev", ["less:development", "watch:styles"]);
};

module.exports = function (grunt) {
    grunt.initConfig({

        styleinjector: {
            files: {
                src : 'css/site.css'
            },
            options: {
                watchTask: true
            }
        },

        // watch changes to less files
        watch: {
            styles: {
                files: ["less/*"],
                tasks: ["less"]
            },
            options: {
                spawn: false,
            },
        },

        // compile set less files
        less: {
            development: {
                options: {
                    paths: ["less"],
                },
                files: {
                    "css/site.css": ["less/*.less", "!less/_*.less"]
                }
            }
        },

    });

    // Load tasks so we can use them
    grunt.loadNpmTasks("grunt-contrib-watch");
    grunt.loadNpmTasks("grunt-contrib-less");
    grunt.loadNpmTasks('grunt-style-injector');

    // the default task will show the usage
    grunt.registerTask("default", "Prints usage", function () {
        grunt.log.writeln("");
        grunt.log.writeln("Using Base");
        grunt.log.writeln("------------------------");
        grunt.log.writeln("");
        grunt.log.writeln("* run 'grunt --help' to get an overview of all commands.");
        grunt.log.writeln("* run 'grunt dev' to start watching and compiling LESS changes.");
    });

    grunt.registerTask("dev", ["less:development", "styleinjector", "watch:styles"]);
};

Add styleinjector task for live css injection


'use strict';

module.exports = function(grunt) {
    var browsers = [
        'Chrome',
        'PhantomJS',
        'Firefox'
    ];

    if (process.env.TRAVIS){
        browsers = ['PhantomJS'];
    }

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        karma: {
            unit: {
                options: {
                    files: [
                        'components/angular/angular.js',
                        'components/angular-mocks/angular-mocks.js',
                        'components/chai/chai.js',
                        'ngStorage.js',
                        'test/spec.js'
                    ]
                },

                frameworks: ['mocha'],

                browsers: [
                    'Chrome',
                    'PhantomJS',
                    'Firefox'
                ],

                singleRun: true
            }
        },

        uglify: {
            options: {
                banner: '/*! <%= pkg.name %> <%= pkg.version %> | Copyright (c) <%= grunt.template.today("yyyy") %> Gias Kay Lee | MIT License */'
            },

            build: {
                src: '<%= pkg.name %>.js',
                dest: '<%= pkg.name %>.min.js'
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-karma');

    grunt.registerTask('test', ['karma']);

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

'use strict';

module.exports = function(grunt) {
    var browsers = [
        'Chrome',
        'PhantomJS',
        'Firefox'
    ];

    if (process.env.TRAVIS){
        browsers = ['PhantomJS'];
    }

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        karma: {
            unit: {
                options: {
                    files: [
                        'components/angular/angular.js',
                        'components/angular-mocks/angular-mocks.js',
                        'components/chai/chai.js',
                        'ngStorage.js',
                        'test/spec.js'
                    ]
                },

                frameworks: ['mocha'],

                browsers: browsers,

                singleRun: true
            }
        },

        uglify: {
            options: {
                banner: '/*! <%= pkg.name %> <%= pkg.version %> | Copyright (c) <%= grunt.template.today("yyyy") %> Gias Kay Lee | MIT License */'
            },

            build: {
                src: '<%= pkg.name %>.js',
                dest: '<%= pkg.name %>.min.js'
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-karma');

    grunt.registerTask('test', ['karma']);

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

USe browsers variable for Grunt.


module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'), // the package file to use

        qunit: { // internal task or name of a plugin (like "qunit")
            all: ['js/tests/*.html']
        },
        watch: {
            files: [
                'js/tests/*.js',
                'js/tests/*.html',
                'tmpl/*.html',
                'js/*.js',
                'src/*.go'
            ],
            tasks: ['qunit', 'shell:buildGo', 'shell:testGo']
        },
        shell: {
            buildGo: {
                command: 'go build -o rtfblog src/*.go',
                options: {
                    stdout: true,
                    stderr: true
                }
            },
            testGo: {
                command: 'go test ./...',
                options: {
                    stdout: true,
                    stderr: true
                }
            }
        }
    });
    // load up your plugins
    grunt.loadNpmTasks('grunt-contrib-qunit');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-shell');
    // register one or more task lists (you should ALWAYS have a "default" task list)
    grunt.registerTask('default', ['qunit', 'shell:buildGo', 'shell:testGo']);
};

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'), // the package file to use

        qunit: { // internal task or name of a plugin (like "qunit")
            all: ['js/tests/*.html']
        },
        watch: {
            files: [
                'js/tests/*.js',
                'js/tests/*.html',
                'tmpl/*.html',
                'js/*.js',
                'src/*.go'
            ],
            tasks: ['qunit', 'shell:buildGo', 'shell:testGo']
        },
        shell: {
            buildGo: {
                command: 'go build -o rtfblog src/*.go',
                options: {
                    stdout: true,
                    stderr: true
                }
            },
            testGo: {
                command: 'go test ./src/...',
                options: {
                    stdout: true,
                    stderr: true
                }
            }
        }
    });
    // load up your plugins
    grunt.loadNpmTasks('grunt-contrib-qunit');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-shell');
    // register one or more task lists (you should ALWAYS have a "default" task list)
    grunt.registerTask('default', ['qunit', 'shell:buildGo', 'shell:testGo']);
};

Call go test for src/ only


import {describe, it} from 'mocha';

describe('module - docker', function () {
  it('TODO write tests');

  describe('dump', function () {
    it('TODO write tests');
  });

  describe('help', function () {
    it('TODO write tests');
  });

  describe('logs', function () {
    it('TODO write tests');
  });

  describe('setup', function () {
    it('TODO write tests');
  });

  describe('start', function () {
    it('TODO write tests');
  });

  describe('stop', function () {
    it('TODO write tests');
  });
});

import {describe, it} from 'mocha';
import assert from 'assert';
import path from 'path';
import sh from 'shelljs';
import {countOccurences, runSSHCommand} from '../../utils';

// require server ssh auth information from tests directory
const servers = require(path.resolve(__rootdir, 'tests/servers'));

describe('module - docker', function () {
  it('TODO write tests');

  describe('help', function () {
    it('TODO write tests');
  });

  describe('setup', function () {
    // reuse this function for 3 tests below
    // TODO break this into multiple functions
    // so parts can be used for other tests
    function checkDocker(name) {
      // TODO get server name form mup.js file
      const serverInfo = servers['my' + name];

      return async function (done) {
        this.timeout(60000);

        sh.cd(path.resolve(__rootdir, 'tests/project-1'));

        const out = sh.exec('mup docker setup', {silent: true});
        assert.equal(out.code, 0);

        const num = countOccurences('setup docker: SUCCESS', out.output);
        assert.equal(num, 3);

        const sshOut = await runSSHCommand(serverInfo, 'which docker');
        assert.equal(sshOut.code, 0);

        done();
      };
    }

    it('should install docker on "meteor" vm', checkDocker('meteor'));
    it('should install docker on "mongo" vm', checkDocker('mongo'));
    it('should install docker on "proxy" vm', checkDocker('proxy'));
  });
});

Add tests for 'mup docker setup'


// Collection of all business processes which where made online

'use strict';

module.exports = require('./resolve')(require('../db').BusinessProcess);

// Collection of all business processes which where made online

'use strict';

var  isFalsy = require('../utils/is-falsy');

module.exports = require('../db').BusinessProcess.instances
	.filter(function (obj) {
		if (obj.master !== obj) return false;
		return (obj.constructor.prototype !== obj);
	})
	.filterByKey('isFromEregistrations', true)
	.filterByKey('isDemo', isFalsy);

Fix general business processes collection


var	oauthAuth = new GitHub(logged ? { token: logged } : {}),
	repo = oauthAuth.getRepo(OWNER_NAME, PROJECT_NAME),
	auth = logged ? oauthAuth.getUser() : false;

// READ REPO
repo.getDetails(function(e,r){
	// CHECK ADMIN
	if (r.permissions && r.permissions.admin) {
		// Show admin link
		document.querySelector('a[href*="admin"][hidden]').removeAttribute('hidden');
		// CHECK FORK
		if (r.owner.type === "User" && r.fork) {
			// Check synch
			// var parentNwo = [r.parent.owner.login, r.parent.name].join('/');
			var parentRepo = oauthAuth.getRepo(r.parent.owner.login, r.parent.name);
			// Get parent ref
			parentRepo.getRef('heads/master', function(e,r) {
				console.log('parent ref', r.object.sha);
				if (r.object.sha != BUILD_REVISION) {
					// Update ref
					repo.createRef({ ref: 'refs/heads/master', sha: r.object.sha }, function (e,r) {
						console.log('new ref', r);
						flash('updated: new ref');
					});
				}
			});
		}
	}
});

var	oauthAuth = new GitHub(logged ? { token: logged } : {}),
	repo = oauthAuth.getRepo(OWNER_NAME, PROJECT_NAME),
	auth = logged ? oauthAuth.getUser() : false;

// READ REPO
repo.getDetails(function(e,r){
	// CHECK ADMIN
	if (r.permissions && r.permissions.admin) {
		// Show admin link
		document.querySelector('a[href*="admin"][hidden]').removeAttribute('hidden');
		// CHECK FORK
		if (r.owner.type === "User" && r.fork) {
			// Check synch
			// var parentNwo = [r.parent.owner.login, r.parent.name].join('/');
			var parentRepo = oauthAuth.getRepo(r.parent.owner.login, r.parent.name);
			// Get parent ref
			parentRepo.getRef('heads/master', function(e,r) {
				console.log('parent ref', r.object.sha);
				if (r.object.sha != BUILD_REVISION) {
					// Update ref
					repo.updateHead('refs/heads/master', r.object.sha, true, function (e,r) {
						console.log('new ref', r);
						dialog('updated: new ref');
					});
				}
			});
		}
	}
});

Use updateHead instead of createRef


/* globals module */
module.exports = {
  afterInstall: function() {
    var _this = this;
    return this.addBowerPackagesToProject([
      { name: 'semantic-ui-daterangepicker', target: '5d46ed2e6e5a0bf398bb6a5df82e06036dfc46be' },
      { name: 'blueimp-file-upload', target: '9.11.2' },
      { name: 'devicejs', target: '0.2.7' },
      { name: 'localforage', target: '1.3.3' }
    ]).then(function() {
      return _this.addAddonsToProject({
        packages: [
          { name: 'semantic-ui-ember', target: '0.9.3' },
          { name: 'ember-moment', target: '6.0.0' },
          { name: 'ember-link-action', target: '0.0.34' },
          { name: 'broccoli-jscs', target: '1.2.2' },
          'https://github.com/Flexberry/ember-localforage-adapter.git'
        ]
      });
    });
  },

  normalizeEntityName: function() {}
};

/* globals module */
module.exports = {
  afterInstall: function() {
    var _this = this;
    return this.addBowerPackagesToProject([
      { name: 'semantic-ui-daterangepicker', target: '5d46ed2e6e5a0bf398bb6a5df82e06036dfc46be' },
      { name: 'blueimp-file-upload', target: '9.11.2' },
      { name: 'devicejs', target: '0.2.7' },
      { name: 'localforage', target: '1.3.3' }
    ]).then(function() {
      return _this.addAddonsToProject({
        packages: [
          { name: 'semantic-ui-ember', target: '0.9.3' },
          { name: 'ember-moment', target: '6.0.0' },
          { name: 'ember-link-action', target: '0.0.34' },
          { name: 'broccoli-jscs', target: '1.2.2' },
          { name: 'ember-browserify', target: '1.1.9' },
          { name: 'dexie', target: '1.3.6' },
          'https://github.com/Flexberry/ember-localforage-adapter.git'
        ]
      });
    });
  },

  normalizeEntityName: function() {}
};

Fix ember-flexberry blueprint add dependencies


import { injectLocale } from 'common/i18n/helpers'

export default (store, i18n) => ({
  path: 'events',

  async getComponent(nextState, cb) {
    const EventsContainer = await import(/* webpackChunkName: 'events' */ './containers/EventsContainer')

    i18n.languages.forEach(async lang => {
      injectLocale(i18n, {
        locale: lang,
        namespace: 'Events',
        resources: await import(/* webpackMode: 'lazy-once', webpackChunkName: 'events' */ `./locales/${lang}.json`)
      })
    })

    cb(null, EventsContainer.default)
  }

})

import { injectLocale } from 'common/i18n/helpers'

export default (store, i18n) => ({
  path: 'events',

  async getComponent(nextState, cb) {
    const EventsContainer = await import(/* webpackChunkName: 'events' */ './containers/EventsContainer')

    i18n.availableLanguages.forEach(async lang => {
      injectLocale(i18n, {
        locale: lang,
        namespace: 'Events',
        resources: await import(/* webpackMode: 'lazy-once', webpackChunkName: 'events' */ `./locales/${lang}.json`)
      })
    })

    cb(null, EventsContainer.default)
  }

})

Fix Events routes i18n languages


$(function () {
	SC.initialize({client_id: '28b05a26f8f8bec9d27bea7c938337ff'});
	getTracks();
	var timer = setInterval(getTracks, 10000);
})

function getTracks() {
	$('ul').html('');
	SC.get('/tracks')
	.then(function (response) {
		response.forEach(function (track) {
			$('ul').append('
' + track.created_at + ' - ' + track.title + '
');
		});
		$('li a').on('click', function () {
			loadTrack($(this).attr('data-id'));
		});
	});
}

function loadTrack(track) {
	console.log('Track loaded:' + track);
	var stream;
	SC.stream('/tracks/' + track).then(function(player){
		stream = player;
		stream.play();
	});
	return stream;
}

$(function () {
	SC.initialize({client_id: '28b05a26f8f8bec9d27bea7c938337ff'});
	getTracks();
	var timer = setInterval(getTracks, 100000);
})

function getTracks() {
	$('ul').html('');
	SC.get('/tracks')
	.then(function (response) {
		console.log(response);
		response.forEach(function (track) {
			$('ul').append('
' + track.created_at + ' - ' + track.title + '
');
		});
		$('li a').on('click', function () {
			loadTrack($(this).attr('data-id'));
		});
		console.log(response[0].artwork_url);
		// $('.art-display').css('background-image', 'url(' + response[0].artwork_url + ')');
	});
}

function loadTrack(trackId) {
	var stream;
	SC.get('/tracks/' + trackId).then(function (resource) {
		if (resource.artwork_url) {
			$('.art-display').css('background-image', 'url(' + resource.artwork_url + ')');
		}
	});
	// console.log(SC.get('/tracks/' + trackId + '/artwork_url'));
	SC.stream('/tracks/' + trackId).then(function(player){
		stream = player;
		stream.play();
	});
	console.log('Track loaded:' + trackId);
	return stream;
}

Add ability to load album art


'use strict';

import test from 'blue-tape';
import hello from '../source/index';

test('Tests run', (assert) => {
  assert.pass('Tests run');
  assert.end();
});

test('Greet World', (assert) => Promise.resolve(
  assert.equal(hello('World'), 'Hello, World!')
));

'use strict';

import test from 'blue-tape';
import hello from '../source/index';

test('Tests run', (assert) => {
  assert.pass('Tests run');
  assert.end();
});

test('Greet World', (assert) => new Promise((resolve) => {
  assert.equal(hello('World'), 'Hello, World!');

  resolve();
}));

Return Promise and resolve explicitly


goog.module('legacy.namespace.goog.define');
goog.module.declareLegacyNamespace();

/** @define {string} */
goog.define('GOOG_DEFINE', 'goog.define');

//!!TODO actually emit the legacy namespace alias for the export
exports = GOOG_DEFINE;

goog.module('legacy.namespace.goog.define');
goog.module.declareLegacyNamespace();

/** @define {string} */
const GOOG_DEFINE = goog.define('GOOG_DEFINE', 'goog.define');

//!!TODO actually emit the legacy namespace alias for the export
exports = GOOG_DEFINE;

Replace call to goog.define with assignment to the result of goog.define


import md5 from "md5";
import I18n from "i18n-js";

function onMissingTranslation(md5, message) {
  return `translation missing: ${I18n.locale}.${message}`;
}

const interpolate = (message, data) =>
  message.replace(/%{([^{}]*)}/g, (a, b) => data[b]);

export default function configure(I18n) {
  I18n.screw = function screw(message, data) {
    const hash = md5(message);
    let translation = I18n.translations[I18n.locale]?.[hash];
    if (typeof translation === "undefined") {
      translation = onMissingTranslation(hash, message);
    }
    return interpolate(translation, data);
  };
}

import md5 from "md5";
import I18n from "i18n-js";

function onMissingTranslation(md5, message) {
  return `translation missing: ${I18n.locale}.${message}`;
}

const interpolate = (message, data) =>
  message.replace(/%{([^{}]*)}/g, (a, b) => data[b]);

I18n.screw = function screw(message, data) {
  const hash = md5(message);
  let translation = I18n.translations[I18n.locale]?.[hash];
  if (typeof translation === "undefined") {
    translation = onMissingTranslation(hash, message);
  }
  return interpolate(translation, data);
};

Define screw method directly on I18n


import gulp from 'gulp';
import nugetRestore from 'gulp-nuget-restore';

export default {
  /**
   * Task name
   * @type {String}
   */
  name: 'sitecore:nuget-restore',

  /**
   * Task description
   * @type {String}
   */
  description: 'Restore all nuget packages for solution.',

  /**
   * Task default configuration
   * @type {Object}
   */
  config: {
    deps: [],
  },

  /**
   * Task help options
   * @type {Object}
   */
  help: {
    'solution, -s': 'Solution filepath',
  },

  /**
   * Task function
   * @param {object} config
   * @param {Function} end
   * @param {Function} error
   */
  fn(config, end) {
    gulp.src(config.solution)
      .pipe(nugetRestore())
      .on('end', end);
  },
};

import gulp from 'gulp';
import nugetRestore from 'gulp-nuget-restore';

export default {
  /**
   * Task name
   * @type {String}
   */
  name: 'sitecore:nuget-restore',

  /**
   * Task description
   * @type {String}
   */
  description: 'Restore all nuget packages for solution.',

  /**
   * Task default configuration
   * @type {Object}
   */
  config: {
    deps: [],
  },

  /**
   * Task help options
   * @type {Object}
   */
  help: {
    'solution, -s': 'Solution file path',
  },

  /**
   * Task function
   * @param {object} config
   * @param {Function} end
   * @param {Function} error
   */
  fn(config, end, error) {
    if (!config.solution) {
      error('A solution file path was not set.');

      return;
    }

    gulp.src(config.solution)
      .pipe(nugetRestore())
      .on('end', end);
  },
};

Add error handling to nuget restore task


import {black} from './colors.js'

export function getIcon(
  unscaledSize,
  {monitored, metrics = []},
  {asImageData = false} = {},
) {
  const scale = devicePixelRatio
  const size = unscaledSize * scale
  const canvas = document.createElement('canvas')
  canvas.width = canvas.height = size
  const c = canvas.getContext('2d')

  metrics.map(m => {
    const tau = 2 * Math.PI
    const mid = size / 2
    const angle = m.ratio
    const fin = 1 / 30
    c.beginPath()
    c.moveTo(mid, mid)
    c.arc(mid, mid, mid, (angle - fin - 0.25) * tau, (angle + fin - 0.25) * tau)
    c.closePath()
    c.fillStyle = m.color
    c.fill()
  })

  if (!monitored) {
    const rect = (...args) => c.fillRect(...args.map(i => (i / 16) * size))
    c.fillStyle = black
    rect(4, 4, 3, 8)
    rect(9, 4, 3, 8)
  }

  let result = canvas
  if (asImageData) {
    result = c.getImageData(0, 0, size, size)
  }
  return result
}

import {black} from './colors.js'

export function getIcon(
  unscaledSize,
  {monitored, metrics = []},
  {asImageData = false} = {},
) {
  const scale = devicePixelRatio
  const size = unscaledSize * scale
  const mid = size / 2
  const canvas = document.createElement('canvas')
  canvas.width = canvas.height = size
  const c = canvas.getContext('2d')

  const cone = (radius, start, end, color) => {
    const tau = 2 * Math.PI
    c.beginPath()
    c.moveTo(mid, mid)
    c.arc(mid, mid, radius, start * tau, end * tau)
    c.closePath()
    c.fillStyle = color
    c.fill()
  }

  metrics.map(m => {
    const angle = m.ratio
    const fin = 1 / 30
    cone(mid, angle - fin - 0.25, angle + fin - 0.25, m.color)
  })

  if (!monitored) {
    cone(mid / 4, 0, 1, black)
  }

  let result = canvas
  if (asImageData) {
    result = c.getImageData(0, 0, size, size)
  }
  return result
}

Make the pause indicator radially symmetric


'use strict'

module.exports = {
  extends: './base.js',
  plugins: [
    'node'
  ],
  rules: {
    'node/no-unsupported-features': 'error',

    'template-curly-spacing': ['error', 'always'],
    'prefer-arrow-callback': 'error',
    'no-dupe-class-members': 'error',
    'no-this-before-super': 'error',
    'constructor-super': 'error',
    'object-shorthand': 'error',
    'no-const-assign': 'error',
    'no-new-symbol': 'error',
    'require-yield': 'error',
    'arrow-spacing': 'error',
    'arrow-parens': ['error', 'as-needed'],
    'prefer-const': 'error',
    'strict': ['error', 'global'],
    'no-var': 'error'
  },
  env: {
    es6: true
  },
  parserOptions: {
    sourceType: 'script'
  }
}

'use strict'

module.exports = {
  extends: './base.js',
  plugins: [
    'node'
  ],
  rules: {
    'node/no-unsupported-features': 'error',

    'template-curly-spacing': ['error', 'always'],
    'prefer-arrow-callback': 'error',
    'no-dupe-class-members': 'error',
    'no-this-before-super': 'error',
    'prefer-rest-params': 'error',
    'constructor-super': 'error',
    'object-shorthand': 'error',
    'no-const-assign': 'error',
    'no-new-symbol': 'error',
    'require-yield': 'error',
    'arrow-spacing': 'error',
    'arrow-parens': ['error', 'as-needed'],
    'prefer-const': 'error',
    'strict': ['error', 'global'],
    'no-var': 'error'
  },
  env: {
    es6: true
  },
  parserOptions: {
    sourceType: 'script'
  }
}

Add prefer-rest-params to Node preset


// Handlebars Helper {{gh-path}}
// Usage: Assume 'http://www.myghostblog.org/myblog/'
// {{gh-path}} or {{gh-path ‘blog’}} for Ghost’s root (/myblog/)
// {{gh-path ‘admin’}} for Ghost’s admin root (/myblog/ghost/)
// {{gh-path ‘api’}} for Ghost’s api root (/myblog/ghost/api/v0.1/)
// {{gh-path 'admin' '/assets/hi.png'}} for resolved url (/myblog/ghost/assets/hi.png)
import ghostPaths from 'ghost/utils/ghost-paths';

function ghostPathsHelper(path, url) {
    var base;

    switch (path.toString()) {
        case 'blog':
            base = ghostPaths().blogRoot;
            break;
        case 'admin':
            base = ghostPaths().adminRoot;
            break;
        case 'api':
            base = ghostPaths().apiRoot;
            break;
        default:
            base = ghostPaths().blogRoot;
            break;
    }

    if (url && url.length > 0) {
        base = base + url;
    }

    return new Ember.Handlebars.SafeString(base);
}

export default ghostPathsHelper;

// Handlebars Helper {{gh-path}}
// Usage: Assume 'http://www.myghostblog.org/myblog/'
// {{gh-path}} or {{gh-path ‘blog’}} for Ghost’s root (/myblog/)
// {{gh-path ‘admin’}} for Ghost’s admin root (/myblog/ghost/)
// {{gh-path ‘api’}} for Ghost’s api root (/myblog/ghost/api/v0.1/)
// {{gh-path 'admin' '/assets/hi.png'}} for resolved url (/myblog/ghost/assets/hi.png)
import ghostPaths from 'ghost/utils/ghost-paths';

function ghostPathsHelper(path, url) {
    var base,
        argsLength = arguments.length,
        paths = ghostPaths();

    // function is always invoked with at least one parameter, so if
    // arguments.length is 1 there were 0 arguments passed in explicitly
    if (argsLength === 1) {
        path = 'blog';
    } else if (argsLength === 2 && !/^(blog|admin|api)$/.test(path)) {
        url = path;
        path = 'blog';
    }

    switch (path.toString()) {
        case 'blog':
            base = paths.blogRoot;
            break;
        case 'admin':
            base = paths.adminRoot;
            break;
        case 'api':
            base = paths.apiRoot;
            break;
        default:
            base = paths.blogRoot;
            break;
    }

    // handle leading and trailing slashes

    base = base[base.length - 1] !== '/' ? base + '/' : base;

    if (url && url.length > 0) {
        if (url[0] === '/') {
            url = url.substr(1);
        }

        base = base + url;
    }

    return new Ember.Handlebars.SafeString(base);
}

export default ghostPathsHelper;

Fix argument handling in gh-path helper.


import debugModule from "debug";
const debug = debugModule("debugger:store:common");
const reduxDebug = debugModule("debugger:redux");

import { compose, createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import createLogger from "redux-cli-logger";

export function abbreviateValues(value, options = {}, depth = 0) {
  options.stringLimit = options.stringLimit || 66;
  options.arrayLimit = options.arrayLimit || 8;
  options.recurseLimit = options.recurseLimit || 4;

  if (depth > options.recurseLimit) {
    return "...";
  }

  const recurse = child => abbreviateValues(child, options, depth + 1);

  if (value instanceof Array) {
    if (value.length > options.arrayLimit) {
      value = [
        ...value.slice(0, options.arrayLimit / 2),
        "...",
        ...value.slice(value.length - options.arrayLimit / 2 + 1)
      ];
    }

    return value.map(recurse);
  } else if (value instanceof Object) {
    return Object.assign(
      {},
      ...Object.entries(value).map(([k, v]) => ({ [recurse(k)]: recurse(v) }))
    );
  } else if (typeof value === "string" && value.length > options.stringLimit) {
    let inner = "...";
    let extractAmount = (options.stringLimit - inner.length) / 2;
    let leading = value.slice(0, Math.ceil(extractAmount));
    let trailing = value.slice(value.length - Math.floor(extractAmount));
    return `${leading}${inner}${trailing}`;
  } else {
    return value;
  }
}

export default function configureStore(
  reducer,
  saga,
  initialState,
  composeEnhancers
) {
  const sagaMiddleware = createSagaMiddleware();

  if (!composeEnhancers) {
    composeEnhancers = compose;
  }

  const loggerMiddleware = createLogger({
    log: reduxDebug,
    stateTransformer: state =>
      abbreviateValues(state, {
        arrayLimit: 4,
        recurseLimit: 3
      }),
    actionTransformer: abbreviateValues
  });

  let store = createStore(
    reducer,
    initialState,

    composeEnhancers(applyMiddleware(sagaMiddleware, loggerMiddleware))
  );

  sagaMiddleware.run(saga);

  return { store, sagaMiddleware };
}

import debugModule from "debug";
const debug = debugModule("debugger:store:common");

import { compose, createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";

export default function configureStore(
  reducer,
  saga,
  initialState,
  composeEnhancers
) {
  const sagaMiddleware = createSagaMiddleware();

  if (!composeEnhancers) {
    composeEnhancers = compose;
  }

  let store = createStore(
    reducer,
    initialState,

    composeEnhancers(applyMiddleware(sagaMiddleware))
  );

  sagaMiddleware.run(saga);

  return { store, sagaMiddleware };
}

Remove logger middleware from debugger


// We have to remove node_modules/react to avoid having multiple copies loaded.
// eslint-disable-next-line import/no-unresolved
import React, { PropTypes } from 'react';
import { modules } from 'stripes-loader'; // eslint-disable-line

const Pluggable = props => {
  const plugins = modules.plugin || [];
  let best;

  for (name of Object.keys(plugins)) {
    const m = plugins[name];
    if (m.pluginType === props.type) {
      best = m;
      if (m.module === props.stripes.plugins[props.type]) break;
    }
  }

  if (best) {
    const Child = props.stripes.connect(best.getModule());
    return <Child {...props} />
  }

  return props.children;
};

Pluggable.propTypes = {
  type: PropTypes.string.isRequired,
};

export default Pluggable;

// We have to remove node_modules/react to avoid having multiple copies loaded.
// eslint-disable-next-line import/no-unresolved
import React, { PropTypes } from 'react';
import { modules } from 'stripes-loader'; // eslint-disable-line

const Pluggable = (props, context) => {
  const plugins = modules.plugin || [];
  let best;

  for (name of Object.keys(plugins)) {
    const m = plugins[name];
    if (m.pluginType === props.type) {
      best = m;
      if (m.module === context.stripes.plugins[props.type]) break;
    }
  }

  if (best) {
    const Child = context.stripes.connect(best.getModule());
    return <Child {...props} />
  }

  return props.children;
};

Pluggable.contextTypes = {
  stripes: PropTypes.shape({
    hasPerm: PropTypes.func.isRequired,
  }).isRequired,
};

Pluggable.propTypes = {
  type: PropTypes.string.isRequired,
};

export default Pluggable;

Use Stripes object from context, not from prop


export default {
  "prime-directive": {
    alert: null,
    confirmationMessage: "Are you sure want to proceed to the Idea Generation stage? This will commence the retro in earnest.",
    nextStage: "idea-generation",
    progressionButton: {
      copy: "Proceed to Idea Generation",
      iconClass: "arrow right",
    },
  },
  "idea-generation": {
    alert: null,
    confirmationMessage: "Are you sure you would like to proceed to the action items stage?",
    nextStage: "action-items",
    progressionButton: {
      copy: "Proceed to Action Items",
      iconClass: "arrow right",
    },
  },
  "action-items": {
    alert: null,
    confirmationMessage: null,
    nextStage: "action-item-distribution",
    progressionButton: {
      copy: "Send Action Items",
      iconClass: "send",
    },
  },
  "action-item-distribution": {
    alert: {
      headerText: "Action Items Distributed",
      bodyText: "The facilitator has distributed this retro's action items. You will receive an email breakdown shortly!",
    },
    confirmationMessage: null,
    nextStage: null,
    progressionButton: null,
  },
}

export default {
  "prime-directive": {
    alert: null,
    confirmationMessage: "Are you sure want to proceed to the Idea Generation stage?",
    nextStage: "idea-generation",
    progressionButton: {
      copy: "Proceed to Idea Generation",
      iconClass: "arrow right",
    },
  },
  "idea-generation": {
    alert: null,
    confirmationMessage: "Are you sure you would like to proceed to the action items stage?",
    nextStage: "action-items",
    progressionButton: {
      copy: "Proceed to Action Items",
      iconClass: "arrow right",
    },
  },
  "action-items": {
    alert: null,
    confirmationMessage: null,
    nextStage: "action-item-distribution",
    progressionButton: {
      copy: "Send Action Items",
      iconClass: "send",
    },
  },
  "action-item-distribution": {
    alert: {
      headerText: "Action Items Distributed",
      bodyText: "The facilitator has distributed this retro's action items. You will receive an email breakdown shortly!",
    },
    confirmationMessage: null,
    nextStage: null,
    progressionButton: null,
  },
}

Remove qualification that idea-generation starts the retro in earnest


// All JSON schemas related to ballot objects

exports.candidates = {
    "title": "Ballot Candidates Schema",
    "type": "array",
    "minItems": 5,
    "items": {
        "type": "object",
        "properties": {
            "id": {
                "type": "string",
                "required":true
            },
            "thumbnail": {
                "type": "string",
                "pattern": "^http:\/\/cf\.geekdo-images\.com\/images\/[a-z0-9]*_t\.jpg$",
                "required":true
            },
            "name": {
                "type": "string",
                "required":true
            }
        },
        "additionalProperties": false
    }
};

// All JSON schemas related to ballot objects

exports.candidates = {
    "title": "Ballot Candidates Schema",
    "type": "array",
    "minItems": 5,
    "items": {
        "type": "object",
        "properties": {
            "id": {
                "type": "string",
                "required":true
            },
            "thumbnail": {
                "type": "string",
                "pattern": "^http:\/\/cf\.geekdo-images\.com\/images\/[a-z0-9]*_t\.(?:jpg|png)$",
                "required":true
            },
            "name": {
                "type": "string",
                "required":true
            }
        },
        "additionalProperties": false
    }
};

Allow pngs to be the thumbnail.


import React from 'react';
import { storiesOf } from '@kadira/storybook';

import SetStateExample from './SetStateExample';
import '../../styles/index.css';

storiesOf('<DropNCrop />', module).add('setState Example (default)', () => (
  <SetStateExample />
));

import React from 'react';
import { storiesOf } from '@kadira/storybook';

import SetStateExample from './SetStateExample';
import '../../styles/index.css';

storiesOf('<DropNCrop />', module).add('setState Example (default)', () => (
  <div>
    <style>
      {
        `html {
        box-sizing: border-box;
      }
      *, *:before, *:after {
        box-sizing: inherit;
      }`
      }
    </style>
    <SetStateExample />
  </div>
));

Add box-sizing to storybook demo


var WebSocketServer = require("ws").Server,
    wss = new WebSocketServer({ port: 8080 });

wss.broadcast = function broadcast (data) {
	wss.clients.forEach(function sendTo (client) {
		// Send the data + silently drop all errors
		client.send(data, function ack (error) {});
	});
};

wss.on("connection", function connection (ws) {
	ws.on("message", wss.broadcast);
});

var WebSocketServer = require("ws").Server,
    wss = new WebSocketServer({ port: 8079 });

wss.broadcast = function broadcast (sender, data) {
	wss.clients.forEach(function sendTo (client) {
		if (client == sender) return;
		// Send the data + silently drop all errors
		client.send(data, function ack (error) {});
	});
};

wss.on("connection", function connection (ws) {
	ws.on("message", wss.broadcast.bind(this, ws));
});

Fix broadcast to only broadcast to everyone else


var express = require('express');
var app = express();
var bodyParser = require('body-parser');

//Parse setup
GLOBAL.Parse = require('parse').Parse;
Parse.initialize("W42PEkh5M824lpvx1FZsJxWBP6Eq7o9rvNHyJFP5", "5OA1f5vv9VbcCCT1reXAckJYPylUszIcRhDWUfT7");

// configure app to use bodyParser()
// this will let us get the data from a POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// ROUTES FOR OUR API
// =============================================================================
var router = express.Router();              // get an instance of the express Router

function savetoparse(str) {
	var TestObject = Parse.Object.extend("TestObject");
    var testObject = new TestObject();
      testObject.save({query: str}, {
      success: function(object) {
      	console.log("Saved: " + str);
      },
      error: function(model, error) {
      	console.log("Failed!");
      }
    });
}

// test route to make sure everything is working (accessed at GET http://localhost:3000/api)
router.get('/', function(req, res) {
    res.json({ message: 'Slingshot API' });
});

router.get('/save/:str', function(req, res) {
	res.json({ str: req.params.str});
    savetoparse(req.params.str);
});

app.use('/api', router);

module.exports = app;

var express = require('express');
var app = express();
var bodyParser = require('body-parser');

//Parse setup
GLOBAL.Parse = require('parse').Parse;
Parse.initialize("W42PEkh5M824lpvx1FZsJxWBP6Eq7o9rvNHyJFP5", "5OA1f5vv9VbcCCT1reXAckJYPylUszIcRhDWUfT7");

// configure app to use bodyParser()
// this will let us get the data from a POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// ROUTES FOR OUR API
// get an instance of the express Router
var router = express.Router();

router.get('/', function(req, res) {
    res.json({ message: 'Slingshot API' });
});

router.route('/bags')
	.get(function(req, res) {
		res.send('GET request for: bags');
	})
	.post(function(req, res) {
		res.send('POST request for: bags');
	});

app.use('/api', router);

module.exports = app;

Add template for bags get and post verbs.


(function (cmds) {
	'use strict';
	if (typeof window.di === 'undefined') { window.di = {}; }; di.con = {}; var noop = function () { }; di.usecon = typeof console !== 'undefined';
	var cl = cmds.length; for (var i = 0; i < cl; i++) { var c = cmds[i];
		(function (c) { di.con[c] = di.usecon ? function () { if (di.usecon && typeof console !== 'undefined' && console[c]) { return console[c].apply(console, arguments); } } : noop })(c);
	};
})('log debug info warn error assert clear dir dirxml trace group groupCollapsed groupEnd time timeEnd profile profileEnd count exception table'.split(' '));

// [di]base 0.2
(function (cmds) {
	'use strict';
	if (typeof window.di === 'undefined') { window.di = {}; }; di.con = {}; var noop = function () { }; di.usecon = typeof console !== 'undefined';
	var cl = cmds.length; for (var i = 0; i < cl; i++) { var c = cmds[i];
		(function (c) { di.con[c] = di.usecon ? function () { if (di.usecon && typeof console !== 'undefined' && typeof console[c] == 'function') { return console[c].apply(console, arguments); } } : noop })(c);
	};
})('log debug info warn error assert clear dir dirxml trace group groupCollapsed groupEnd time timeEnd profile profileEnd count exception table'.split(' '));

Check whether console[c] is actually a function


const React = require('react');
const h = require('react-hyperscript');

class NetworkInfoMenu extends React.Component {
  render() {
    const { infoList } = this.props;

    const noInfoMessage = [
      h('p', 'No additional information was found for this network!'),
      h('p', 'Additional information about the network is normally found here, but we couldn\'t find any for this one.')
    ];

    const comments = infoList.map(comment => h('p', comment.replace(/<p>/g, ' ')));

    return (
      h('div.info-menu', [
        comments.length > 0 ? comments : noInfoMessage
      ])
    );
  }
}

module.exports = NetworkInfoMenu;

const React = require('react');
const h = require('react-hyperscript');

class NetworkInfoMenu extends React.Component {
  render() {
    const { infoList } = this.props;

    const noInfoMessage = [
      h('p', 'No additional information was found for this network!'),
      h('p', 'Additional information about the network is normally found here, but we couldn\'t find any for this one.')
    ];

    const comments = infoList.map( ( comment, key ) => h('p', { key }, comment.replace(/<p>/g, ' ')));

    return (
      h('div.info-menu', [
        comments.length > 0 ? comments : noInfoMessage
      ])
    );
  }
}

module.exports = NetworkInfoMenu;

Add key to pathway comment elements


module.exports = (db, Sequelize) => {
  const Message = db.define('message', {
    id: {
      type: Sequelize.INTEGER,
      autoIncrement: true,
      primaryKey: true,
    },
    title: Sequelize.STRING,
    body: Sequelize.TEXT,
  });

  return Message;
};

module.exports = (db, Sequelize) => {
  const Message = db.define('message', {
    id: {
      type: Sequelize.INTEGER,
      autoIncrement: true,
      primaryKey: true,
    },
    title: Sequelize.STRING,
    body: Sequelize.TEXT,
    isDeleted: Sequelize.BOOLEAN,
  });

  return Message;
};

Add 'isDeleted' field to Message schema


/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule forEachAccumulated
 * @flow
 */

'use strict';

/**
 * @param {array} arr an "accumulation" of items which is either an Array or
 * a single item. Useful when paired with the `accumulate` module. This is a
 * simple utility that allows us to reason about a collection of items, but
 * handling the case when there is exactly one item (and we do not need to
 * allocate an array).
 * @param {function} cb Callback invoked with each element or a collection.
 * @param {?*} scope Scope used as `this` in a callback.
 */
function forEachAccumulated<T>(
  arr: ?(T | Array<T>),
  cb: ((elem: T) => void),
  scope: ?any,
) {
  if (Array.isArray(arr)) {
    arr.forEach(cb, scope);
  } else if (arr) {
    cb.call(scope, arr);
  }
}

module.exports = forEachAccumulated;

/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule forEachAccumulated
 * @flow
 */

'use strict';

/**
 * @param {array} arr an "accumulation" of items which is either an Array or
 * a single item. Useful when paired with the `accumulate` module. This is a
 * simple utility that allows us to reason about a collection of items, but
 * handling the case when there is exactly one item (and we do not need to
 * allocate an array).
 * @param {function} cb Callback invoked with each element or a collection.
 * @param {?} [scope] Scope used as `this` in a callback.
 */
function forEachAccumulated<T>(
  arr: ?(T | Array<T>),
  cb: ((elem: T) => void),
  scope: ?any,
) {
  if (Array.isArray(arr)) {
    arr.forEach(cb, scope);
  } else if (arr) {
    cb.call(scope, arr);
  }
}

module.exports = forEachAccumulated;

Use correct optional paramater JSDoc syntax


const Path = require('path')

const action = process.argv[2]
var args = process.argv.slice(3)
var env = process.env.NODE_ENV
var webpack

switch (action) {
  case 'build':
    webpack = 'webpack'
    env = env || 'production'
    break

  case 'dist':
    webpack = 'webpack'
    env = env || 'production'
    args.push('-p')
    break

  case 'dev':
  case 'start':
    webpack = 'webpack-dev-server'
    env = env || 'development'
    args.push('--hot')
    break

  default:
    console.log('missing command: build, dev or dist')
    process.exit(1)
}

var cmd = []
cmd.push(require.resolve(`.bin/${webpack}`))
cmd.push('--config')
cmd.push(Path.resolve(__dirname, '../webpackfile.js'))
cmd.push('--progress')
cmd.push(...args)

console.log(cmd.join(' '))

const Path = require('path')

const action = process.argv[2]
var args = process.argv.slice(3)
var env = process.env.NODE_ENV
var webpack

switch (action) {
  case 'build':
    webpack = 'webpack'
    env = env || 'production'
    break

  case 'dist':
    webpack = 'webpack'
    env = env || 'production'
    args.push('-p')
    break

  case 'dev':
  case 'start':
    webpack = 'webpack-dev-server'
    env = env || 'development'
    args.push('--hot')
    break

  case '--version':
  case '-v':
    console.log('echo', 'VWP version: ', require('../package.json').version)
    return

  default:
    console.log('missing command: build, dev or dist')
    process.exit(1)
}

var cmd = []
cmd.push(require.resolve(`.bin/${webpack}`))
cmd.push('--config')
cmd.push(Path.resolve(__dirname, '../webpackfile.js'))
cmd.push('--progress')
cmd.push(...args)

console.log(cmd.join(' '))

Add option to display the VWP version


Add a level to Field


/* eslint-disable no-sync */
'use strict';

// MODULES //

var fs = require( 'fs' );

// MAIN //

/**
* Synchronously reads the entire contents of a file.
*
* @param {(string|Buffer|integer)} file - file path or file descriptor
* @param {(Object|string)} [options] - options
* @returns {(Buffer|string|Error)} file contents or an error
*
* @example
* var out = readFileSync( __filename );
* if ( out instanceof Error ) {
*     throw out;
* }
* console.log( out );
*/
function readFileSync( file, options ) {
	var f;
	try {
		if ( arguments.length > 1 ) {
			f = fs.readFileSync( file, options );
		} else {
			f = fs.readFileSync( file );
		}
	} catch ( err ) {
		return err;
	}
	return f;
} // end FUNCTION readFileSync()

// EXPORTS //

module.exports = readFileSync;

'use strict';

// MODULES //

var fs = require( 'fs' );

// MAIN //

/**
* Synchronously reads the entire contents of a file.
*
* @param {(string|Buffer|integer)} file - file path or file descriptor
* @param {(Object|string)} [options] - options
* @returns {(Buffer|string|Error)} file contents or an error
*
* @example
* var out = readFileSync( __filename );
* if ( out instanceof Error ) {
*     throw out;
* }
* console.log( out );
*/
function readFileSync( file, options ) {
	var f;
	try {
		if ( arguments.length > 1 ) {
			f = fs.readFileSync( file, options ); // eslint-disable-line no-sync
		} else {
			f = fs.readFileSync( file ); // eslint-disable-line no-sync
		}
	} catch ( err ) {
		return err;
	}
	return f;
} // end FUNCTION readFileSync()

// EXPORTS //

module.exports = readFileSync;

Disable lint rule for specific lines


/*
 * FFT plugin for dance.js
 *
 * Usage of frequencies being 2px with 1px spacing:
 *
 * var dance = new Dance('song.ogg'),
 *     canvas = document.getElementById('fftcanvas');
 * dance.fft( canvas, 2, 1 );
 */

(function() {
  Dance.addPlugin( 'fft', function( canvasEl, freqWidth, spacing ) {
    var
      ctx = canvasEl.getContext( '2d' ),
      h = canvasEl.height,
      w = canvasEl.width;

    freqWidth = freqWidth || 1;
    spacing = spacing || 0;
    ctx.fillStyle = "white";

    this.bind( 'update', function() {
      var spectrum = this.spectrum();
      ctx.clearRect( 0, 0, w, h );
      for ( var i = 0, l = spectrum.length; i < l; i++ ) {
        ctx.fillRect( i * ( spacing + freqWidth ), h, freqWidth, -spectrum[ i ] / 2 );
      }
    });

    return this;
  });
})();

/*
 * FFT plugin for dance.js
 *
 * Usage of frequencies being 2px with 1px spacing:
 *
 * var dance = new Dance('song.ogg'),
 *     canvas = document.getElementById('fftcanvas');
 * dance.fft( canvas, 2, 1 );
 */

(function() {
  Dance.addPlugin( 'fft', function( canvasEl, freqWidth, spacing ) {
    var
      ctx    = canvasEl.getContext( '2d' ),
      h      = canvasEl.height,
      w      = canvasEl.width,
      hRatio = h / 255;

    freqWidth = freqWidth || 1;
    spacing = spacing || 0;
    ctx.fillStyle = "white";

    this.bind( 'update', function() {
      var spectrum = this.spectrum();
      ctx.clearRect( 0, 0, w, h );
      for ( var i = 0, l = spectrum.length; i < l; i++ ) {
        ctx.fillRect( i * ( spacing + freqWidth ), h, freqWidth, -spectrum[ i ] * hRatio );
      }
    });

    return this;
  });
})();

Generalize Dance.FFT plugin to scale to any height canvas element


export const SHOW_ERROR = '§_SHOW_ERROR'
export const HIDE_ERROR = '§_HIDE_ERROR'

export const showError = ({ message, fatal, icon = 'alert' }) => ({
  type: SHOW_ERROR,
  payload: { message, fatal, icon }
})

export const hideError = () => ({
  type: HIDE_ERROR,
  payload: {}
})

export const SHOW_ERROR = '§_SHOW_ERROR'
export const HIDE_ERROR = '§_HIDE_ERROR'

export const showError = ({ message, fatal, icon = 'alert', timeout = 0 }) => (dispatch) => {
  dispatch({
    type: SHOW_ERROR,
    payload: { message, fatal, icon, timeout }
  })

  if (timeout) {
    setTimeout(() => dispatch(hideError()), timeout)
  }
}

export const hideError = () => ({
  type: HIDE_ERROR,
  payload: {}
})

Add timeout for auto-closing errors


/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */
/* global stream$Writable */
'use strict';

module.exports = (stream: stream$Writable) => {
  if (process.stdout.isTTY) {
    stream.write('\r\x1B[K');
  }
};

/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */
/* global stream$Writable */
'use strict';

module.exports = (stream: stream$Writable) => {
  if (process.stdout.isTTY) {
    stream.write('\x1b[999D\x1b[K');
  }
};

Clear line more aggressively for Windows


'use strict';

// Declare app level module which depends on views, and components
var app = angular.module('Stocks', ['ngRoute', 'ngResource']);

app.config(function($routeProvider) {
  $routeProvider.when('/', {
  	controller: 'HomeController',
  	templateUrl: 'views/home/home.html'
  })
  .when('/stocks/:id', {
  	controller: 'StocksController',
  	templateUrl: 'views/stocks/stock.html'
  })
  .otherwise({redirectTo: '/'});
});

'use strict';

// Declare app level module which depends on views, and components
var app = angular.module('Stocks', ['ngRoute', 'ngResource']);

app.config(function($routeProvider) {
  $routeProvider.when('/', {
  	controller: 'HomeController',
  	templateUrl: 'views/home/home.html',
    resolve: {
      postPromise: ['stocks', function(stocks) {
        return stocks.getAll();
      }]
    }
  })
  .when('/stocks/:id', {
  	controller: 'StocksController',
  	templateUrl: 'views/stocks/stock.html'
  })
  .otherwise({
  	redirectTo: '/'
  });
});

Add resolve property to home route to auto get stocks from express


var fs = require('fs');

var gitane = require('gitane');
var winston = require('winston');

function cloneInto(repoUrl, destPath, keyPath, callback) {
  var gitClone = function(keyData) {
    // clone with . to avoid the extra containing folder
    var gitCmd = 'git clone ' + repoUrl + ' .';
    winston.log('info', 'Cloning', repoUrl);
    winston.log('debug', 'Running git command:', gitCmd);
    gitane.run(destPath, keyData, gitCmd, function(err, stdout, stderr, exitCode) {
      callback(err, stdout, stderr, exitCode);
    });
  };

  // If there's a key, clone with the key; otherwise, clone with no key
  if (keyPath) {
    winston.log('debug', 'Using key:', keyPath);
    fs.readFile(keyPath, function(err, keyData) {
      if (err) { callback(err); return; }
      gitClone(keyData);
    });
  } else {
    winston.log('debug', 'No key provided');
    gitClone(null);
  }
}

module.exports = {
  cloneInto: cloneInto
};

var fs = require('fs');

var gitane = require('gitane');
var winston = require('winston');

function cloneInto(repoUrl, destPath, keyPath, callback) {
  var gitClone = function(keyData) {
    // clone with . to avoid the extra containing folder
    var gitCmd = 'git clone ' + repoUrl + ' .';
    // If keyData is truthy, we're using a key to clone a private repo;
    // otherwise, we're cloning a public repo and don't need a key
    if (keyData) {
      winston.log('info', 'Cloning', repoUrl, 'with private key');
    } else {
      winston.log('info', 'Cloning', repoUrl);
    }
    winston.log('debug', 'Running git command:', gitCmd);
    gitane.run(destPath, keyData, gitCmd, function(err, stdout, stderr, exitCode) {
      callback(err, stdout, stderr, exitCode);
    });
  };

  // If there's a key, clone with the key; otherwise, clone with no key
  if (keyPath) {
    winston.log('debug', 'Using key:', keyPath);
    fs.readFile(keyPath, function(err, keyData) {
      if (err) { callback(err); return; }
      gitClone(keyData);
    });
  } else {
    winston.log('debug', 'No key provided');
    gitClone(null);
  }
}

module.exports = {
  cloneInto: cloneInto
};

Improve log lines for private/public clones


(function() {
    var Handler = function() {
        var options = {
            urlPatterns: [],
            shortcutKey: null,
            linkSelector: null
        };

        this.init = function() {
            var keys = Object.keys(options);

            chrome.storage.sync.get(keys, function(saved) {
                for (var key in keys) {
                    if (saved[key]) {
                        options[key] = saved[key];
                    }
                }
            });

            console.log("options", options);
        };

        this.enabled = function() {
            var enabled = false;

            if (options.urlPatterns.length > 0) {
                var r = new RegExp("^(" + options.urlPatterns.join("|") + ")", "i");
                if (r.test(window.location.href)) {
                    enabled = true;
                }
            }

            return enabled;
        }

        this.handleKeypress = function(e) {
            var tag = e.target.tagName.toLowerCase();

            if (tag === 'input' || tag === 'textarea') {
                return;
            }

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

            if (e.keyCode === options.shortcutKey) {
                var a = document.querySelector(options.linkSelector);

                if (a) {
                    chrome.extension.sendMessage({ url: a.href });
                }
            }
        };
    }

    if (window === top) {
        var handler = new Handler();
        handler.init();

        if (handler.enabled()) {
            window.addEventListener("keypress", handler.handleKeypress, false);
        }
    }
})();

(function() {
    var Handler = function() {
        var options = {
            urlPatterns: [],
            shortcutKey: null,
            linkSelector: null
        };

        var window;
        var enabled = false;

        this.init = function(obj) {
            window = obj;

            var keys = Object.keys(options);

            chrome.storage.sync.get(keys, function(saved) {
                for (var i = 0; i < keys.length; i++) {
                    var key = keys[i];
                    if (saved[key]) {
                        options[key] = saved[key];
                    }
                }

                optionsLoaded();
            });
        };

        this.enabled = function() {
            return enabled;
        }

        var optionsLoaded = function() {
            if (options.urlPatterns.length > 0) {
                var r = new RegExp("^(" + options.urlPatterns.join("|") + ")", "i");
                if (r.test(window.location.href)) {
                    enabled = true;
                }
            }

            if (enabled) {
                window.addEventListener("keypress", handleKeypress, false);
            }
        };

        var handleKeypress = function(e) {
            var tag = e.target.tagName.toLowerCase();

            if (tag === 'input' || tag === 'textarea') {
                return;
            }

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

            if (e.keyCode === options.shortcutKey) {
                var a = document.querySelector(options.linkSelector);

                if (a) {
                    chrome.extension.sendMessage({ url: a.href });
                }
            }
        };
    }

    if (window === top) {
        var handler = new Handler();
        handler.init(window);
    }
})();

Clean up how we enable the extension after loading options


'use strict';
var mongoose = require('mongoose'),
  User = mongoose.model('User'),
  _ = require('lodash');

var findUser = exports.findUser = function(id, cb) {
  User.findOne({
        _id: id
    }, function(err, user) {
        if (err || !user) return cb(null);
        cb(user);
    });
};

/**
 * Generic require login routing middleware
 */
exports.requiresLogin = function(req, res, next) {
  if (!req.isAuthenticated()) {
    return res.status(401).send('User is not authorized');
  }
  findUser(req.user._id, function(user) {
      if (!user) return res.status(401).send('User is not authorized');
      req.user = user;
      next();
  });
};

/**
 * Generic validates if the first parameter is a mongo ObjectId
 */
exports.isMongoId = function(req, res, next) {
  if ((_.size(req.params) === 1) && (!mongoose.Types.ObjectId.isValid(_.values(req.params)[0]))) {
      return res.status(500).send('Parameter passed is not a valid Mongo ObjectId');
  }
  next();
};

'use strict';
var mongoose = require('mongoose'),
  User = mongoose.model('User'),
  _ = require('lodash');

/**
 * Generic validates if the first parameter is a mongo ObjectId
 */
exports.isMongoId = function(req, res, next) {
  if ((_.size(req.params) === 1) && (!mongoose.Types.ObjectId.isValid(_.values(req.params)[0]))) {
      return res.status(500).send('Parameter passed is not a valid Mongo ObjectId');
  }
  next();
};

Remove requiresLogin and helper function from auth


/* global Thing */

(function (document, window) {
    'use strict';

    var Main = function () {
        // console.log('Main()');

        this.field = document.getElementById('field');

        this.things = [];

        for (var i = 0; i < Thing.MAX_NUMBER; i++) {
            var thing = new Thing(this.field);

            this.things.push(thing);
        }

        Main.tick(0);
    };

    Main.tick = function (time) {
        window.requestAnimationFrame(Main.tick);
    };

    window.Main = Main;

})(document, window);

/* global Thing */

(function (document, window) {
    'use strict';

    var Main = function () {
        // console.log('Main()');

        this.field = document.getElementById('field');

        for (var i = 0; i < Thing.MAX_NUMBER; i++) {
            new Thing(this.field);
        }

        Main.tick(0);
    };

    Main.DURATION = 1000;
    Main.durationIndex = null;

    Main.tick = function (time) {
        var t = Math.floor(time / Main.DURATION);
        if (t !== Main.durationIndex) {
            Main.durationIndex = t;

            var thing = Thing.getInactiveThing();
            if (thing) {
                thing.setPosition(t * 10, t * 10);
                thing.activate(true);
            }
        }

        window.requestAnimationFrame(Main.tick);
    };

    window.Main = Main;

})(document, window);

Add dummy animation for testing


var moment = require('moment');
var geolib = require('geolib');

// Use moment.js to calculate how much time has elapsed since some prior time
exports.getTimeElapsedSince = function(time) {
  return moment(time).fromNow();
};

// Use geolib.js to calculate distance from the mark to the user
exports.getDistanceFrom = function(mark, user) {
  var dist = geolib.getDistance(
    {latitude: user.x, longitude: user.y},
    {latitude: mark.x, longitude: mark.y}
  );
  return dist + 'm';
};

// The object we pull from the database has specific data (time/location)
// Here we create a new object that has data that is relevant to the user
exports.createResponseObjects = function(marks, user) {
  var responseObjects = [];
  var responseObject;

  marks.forEach(function(mark) {
    responseObject = {
      messageId: mark.messageId,
      timestamp: exports.getTimeElapsedSince(mark.timestamp),
      distance: exports.getDistanceFrom(mark, user),
      messageString: mark.messageString
    };
    responseObjects.push(responseObject);
  });

  return responseObjects;
};

exports.log = function(message, content) {
  console.log('\n--------------------------------------------------------');
  console.log(moment().format("dddd, MMMM Do YYYY, h:mm:ss a") + message);
  console.log(content);
  console.log('--------------------------------------------------------');
};

Add server-side logging utility function