import chalk from 'chalk'
import WDIOReporter from 'wdio-reporter'

/**
 * Initialize a new `Dot` matrix test reporter.
 */
export default class DotReporter extends WDIOReporter {
    constructor (options) {
        /**
         * make dot reporter to write to output stream by default
         */
        options = Object.assign(options, { stdout: true })
        super(options)
    }

    /**
     * pending tests
     */
    onTestSkip () {
        this.write(chalk.cyanBright('.'))
    }

    /**
     * passing tests
     */
    onTestPass () {
        this.write(chalk.greenBright('.'))
    }

    /**
     * failing tests
     */
    onTestFail () {
        this.write(chalk.redBright('F'))
    }
}

import chalk from 'chalk'
import WDIOReporter from 'wdio-reporter'

/**
 * Initialize a new `Dot` matrix test reporter.
 */
export default class DotReporter extends WDIOReporter {
    constructor (options) {
        /**
         * make dot reporter to write to output stream by default
         */
        options = Object.assign({ stdout: true }, options)
        super(options)
    }

    /**
     * pending tests
     */
    onTestSkip () {
        this.write(chalk.cyanBright('.'))
    }

    /**
     * passing tests
     */
    onTestPass () {
        this.write(chalk.greenBright('.'))
    }

    /**
     * failing tests
     */
    onTestFail () {
        this.write(chalk.redBright('F'))
    }
}

Allow stdout to be overwritten from config


const assert = require('assert')
const { Before, Given, When, Then } = require('@cucumber/cucumber')
const Shouty = require('../../lib/shouty')
const Coordinate = require('../../lib/coordinate')

const ARBITARY_MESSAGE = 'Hello, world'
let shouty

Before(function() {
  shouty = new Shouty()
})

Given('Lucy is at {int}, {int}', function (x, y) {
  shouty.setLocation('Lucy', new Coordinate(x, y))
})

Given('Sean is at {int}, {int}', function (x, y) {
  shouty.setLocation('Sean', new Coordinate(x, y))
})

When('Sean shouts', function () {
  shouty.shout('Sean', ARBITARY_MESSAGE)
})

Then('Lucy should hear Sean', function () {
  assert.strictEqual(shouty.getShoutsHeardBy('Lucy').size, 1)
})

Then('Lucy should hear nothing', function () {
  assert.strictEqual(shouty.getShoutsHeardBy('Lucy').size, 0)
})

const assert = require('assert')
const { Given, When, Then } = require('@cucumber/cucumber')
const Shouty = require('../../lib/shouty')
const Coordinate = require('../../lib/coordinate')

const ARBITARY_MESSAGE = 'Hello, world'
let shouty = new Shouty()

Given('Lucy is at {int}, {int}', function (x, y) {
  shouty.setLocation('Lucy', new Coordinate(x, y))
})

Given('Sean is at {int}, {int}', function (x, y) {
  shouty.setLocation('Sean', new Coordinate(x, y))
})

When('Sean shouts', function () {
  shouty.shout('Sean', ARBITARY_MESSAGE)
})

Then('Lucy should hear Sean', function () {
  assert.strictEqual(shouty.getShoutsHeardBy('Lucy').size, 1)
})

Then('Lucy should hear nothing', function () {
  assert.strictEqual(shouty.getShoutsHeardBy('Lucy').size, 0)
})

Remove Before hook from startpoint


import { MeetingSeries } from '/imports/meetingseries'

Router.configure({
    // set default application template for all routes
    layoutTemplate: 'appLayout'
});

Router.route('/', {name: 'home'});

Router.route('/meetingseries/:_id', function () {
    var meetingSeriesID = this.params._id;
    this.render('meetingSeriesDetails', {data: meetingSeriesID});
});

Router.route('/minutesadd/:_id', function () {
    let meetingSeriesID = this.params._id;
    ms = new MeetingSeries(meetingSeriesID);

    let id = '';
    ms.addNewMinutes(newMinutesID => {
        id = newMinutesID;
    });

    this.redirect('/minutesedit/' + id);
});

Router.route('/minutesedit/:_id', function () {
    var minutesID = this.params._id;
    this.render('minutesedit', {data: minutesID});
});

import { MeetingSeries } from '/imports/meetingseries'

Router.configure({
    // set default application template for all routes
    layoutTemplate: 'appLayout'
});

Router.route('/', {name: 'home'});

Router.route('/meetingseries/:_id', function () {
    var meetingSeriesID = this.params._id;
    this.render('meetingSeriesDetails', {data: meetingSeriesID});
});

Router.route('/minutesadd/:_id', function () {
    let meetingSeriesID = this.params._id;
    ms = new MeetingSeries(meetingSeriesID);

    let id;
    ms.addNewMinutes(newMinutesID => {
        id = newMinutesID;
    });

    // callback should have been called by now
    if (id) {
        this.redirect('/minutesedit/' + id);
    } else {
        // todo: use error page
        this.redirect('/meetingseries/' + meetingSeriesID);
    }
});

Router.route('/minutesedit/:_id', function () {
    var minutesID = this.params._id;
    this.render('minutesedit', {data: minutesID});
});

Add a fallback redirect in case an error occurs


$(function() {
    $('#applicant_character_server').change(populateArmory);
    $('#applicant_character_name').change(populateArmory);
});

function populateArmory() {
    // if ($('#applicant_armory_link').val() != '') {
    //     return false;
    // }

    cserver = $('#applicant_character_server').val();
    cname   = $('#applicant_character_name').val();

    $('#applicant_armory_link').val("http://www.wowarmory.com/character-sheet.xml?r=" + cserver + "&n=" + cname);
    $('#applicant_armory_link').effect('highlight', {}, 1500);
}

$(function() {
    $('#applicant_character_server').change(populateArmory);
    $('#applicant_character_name').change(populateArmory);
});

function populateArmory() {
    // if ($('#applicant_armory_link').val() != '') {
    //     return false;
    // }

    cserver = escape($('#applicant_character_server').val());
    cname   = escape($('#applicant_character_name').val());

    $('#applicant_armory_link').val("http://www.wowarmory.com/character-sheet.xml?r=" + cserver + "&n=" + cname);
    $('#applicant_armory_link').effect('highlight', {}, 1500);
}

Use escape() on the values before we add them to the Armory link field.


import {
  YAHOO_SEARCH_EXISTING_USERS,
  YAHOO_SEARCH_NEW_USERS,
} from './experimentConstants'

const features = {
  'money-raised-exclamation-point': {
    defaultValue: false,
    rules: [
      {
        variations: [false, true],
        coverage: 0.4,
        condition: {
          v4BetaEnabled: true,
        },
      },
    ],
  },
}

features[YAHOO_SEARCH_EXISTING_USERS] = {
  defaultValue: false,
  // Enable when SFAC search engine is enabled.
  // rules: [
  //   {
  //     condition: {
  //       isTabTeamMember: true,
  //       env: 'local',
  //     },
  //     force: true,
  //   },
  // ],
}

features[YAHOO_SEARCH_NEW_USERS] = {
  defaultValue: 'Google',
  // Enable when SFAC search engine is enabled.
  // rules: [
  //   {
  //     condition: {
  //       isTabTeamMember: true,
  //       env: 'local',
  //     },
  //     force: 'SearchForACause',
  //   },
  // ],
}

export default features

import {
  YAHOO_SEARCH_EXISTING_USERS,
  YAHOO_SEARCH_NEW_USERS,
} from './experimentConstants'

const features = {
  'money-raised-exclamation-point': {
    defaultValue: false,
    rules: [
      {
        condition: {
          isTabTeamMember: true,
          env: 'local',
        },
        // Modify this for local testing.
        force: true,
      },
      {
        variations: [false, true],
        coverage: 0.4,
        condition: {
          v4BetaEnabled: true,
        },
      },
    ],
  },
}

features[YAHOO_SEARCH_EXISTING_USERS] = {
  defaultValue: false,
  // Enable when SFAC search engine is enabled.
  // rules: [
  //   {
  //     condition: {
  //       isTabTeamMember: true,
  //       env: 'local',
  //     },
  //     force: true,
  //   },
  // ],
}

features[YAHOO_SEARCH_NEW_USERS] = {
  defaultValue: 'Google',
  // Enable when SFAC search engine is enabled.
  // rules: [
  //   {
  //     condition: {
  //       isTabTeamMember: true,
  //       env: 'local',
  //     },
  //     force: 'SearchForACause',
  //   },
  // ],
}

export default features

Create local rule for experiment


angular.module('ng.lodash', []).
    factory('_', function($window){
        $window._.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
        return $window._;
    });

angular.module('ng.lodash', []).
  factory('_', function($window){
    $window._.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
    /**
     * Fast UUID generator, RFC4122 version 4 compliant.
     * @author Jeff Ward (jcward.com).
     * @license MIT license
     * @link http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
     **/

    var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); }
    _.uniqueId = function() {
      var d0 = Math.random()*0xffffffff|0;
      var d1 = Math.random()*0xffffffff|0;
      var d2 = Math.random()*0xffffffff|0;
      var d3 = Math.random()*0xffffffff|0;
      return lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'-'+
        lut[d1&0xff]+lut[d1>>8&0xff]+'-'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'-'+
        lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'-'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+
        lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff];
    };
    return $window._;
  });

Change lodash to use guid generator (MIT licensed, Jeff Ward @ stackoverflow)


/**
 * Created by ar on 7/16/15.
 */

var dbuser = "root";
var dbpass = "root";
var dbName="idservice";
var host="localhost";

process.argv.forEach(function (val, index, array) {
    var parts = val.split("=");
    if (parts[0] == "dbuser") {
        dbuser = parts[1];
    } else if (parts[0] == "dbpass") {
        dbpass = parts[1];
    }
});

var database={
    host:host,
    user:dbuser,
    pass:dbpass,
    dbname:dbName,
    connectionURL:"mysql://" + dbuser + ":" + dbpass + "@" + host + "/" + dbName
};
module.exports.database=database;

/**
 * Created by ar on 7/16/15.
 */

var dbuser = "root";
var dbpass = "root";
var dbName="idservice";
var host="localhost";

process.argv.forEach(function (val, index, array) {
    var parts = val.split("=");
    if (parts[0] == "dbuser") {
        dbuser = parts[1];
    } else if (parts[0] == "dbpass") {
        dbpass = parts[1];
    } else if (parts[0] == "host") {
        host = parts[1];
    } else if (parts[0] == "dbName") {
        dbName = parts[1];
    }
});

var database={
    host:host,
    user:dbuser,
    pass:dbpass,
    dbname:dbName,
    connectionURL:"mysql://" + dbuser + ":" + dbpass + "@" + host + "/" + dbName
};
module.exports.database=database;

Allow mysql host and database name to be configurable


//= require jquery.prevent_scroll_propagation
//= require bubble
//= require_tree ./frontend
//= require factlink.backbone
//= require tour

Remove useless require, as bubble also requires this lib


function format_money(currency, amount) {
    var symbol = format_money.map[currency];
    if (symbol === undefined) {
        return currency + ' ' + amount;
    }
    return symbol + amount;
}

// Taken with modifications from:
//    https://github.com/bengourley/currency-symbol-map/blob/master/map.js
// When we need to handle more currencies please look there.
format_money.map = {
    "USD": "$",
    "GBP": "£",
    "AUD": "A$",
    "EUR": "€",
    "JPY": "¥",
};

if (typeof module !== 'undefined') {
    module.exports = {
        format_money: format_money,
    };
}

function format_money(currency, amount) {
    if(currency === 'JPY') { // remove decimal points for JPY
        amount = amount.replace(/\.\d+$/, '');
    }

    var symbol = format_money.map[currency];
    if (symbol === undefined) {
        return currency + ' ' + amount;
    }
    return symbol + amount;
}

// Taken with modifications from:
//    https://github.com/bengourley/currency-symbol-map/blob/master/map.js
// When we need to handle more currencies please look there.
format_money.map = {
    "USD": "$",
    "GBP": "£",
    "AUD": "A$",
    "EUR": "€",
    "JPY": "¥",
};

if (typeof module !== 'undefined') {
    module.exports = {
        format_money: format_money,
    };
}

Add if for JPY amount


module.exports = {
  'palette is generated when random-palette button is clicked': function test(browser) {
    const devServer = browser.globals.devServerURL;

    browser
      .url(devServer)
      .waitForElementVisible('#random-generate', 10000)
      .assert.elementCount('.palette--main .swatch', 0)
      .click('#random-generate')
      .waitForElementVisible('.swatch', 10000)
      .assert.elementCount('.palette--main .swatch', 4)
      .end();
  },
  'palette is generated when valid hex is typed in input': function test(browser) {
    const devServer = browser.globals.devServerURL;

    browser
      .url(devServer)
      .waitForElementVisible('#app', 10000)
      .assert.elementPresent('#generate-palette')
      .assert.elementCount('.palette--main .swatch', 0)
      .setValue('#generate-palette', '#fff')
      .click('#generate-palette-submit')
      .waitForElementVisible('.swatch', 10000)
      .assert.elementCount('.palette--main .swatch', 4)
      .end();
  },

};

module.exports = {
  'palette is generated when random-palette button is clicked': function test(browser) {
    const devServer = browser.globals.devServerURL;

    browser
      .url(devServer)
      .waitForElementVisible('#random-generate', 10000)
      .assert.elementCount('.palette--main .swatch', 0)
      .click('#random-generate')
      .waitForElementVisible('.swatch', 10000)
      .assert.elementCount('.palette--main .swatch', 4)
      .end();
  },

  'palette is generated when valid hex is typed in input': function test(browser) {
    const devServer = browser.globals.devServerURL;

    browser
      .url(devServer)
      .waitForElementVisible('#app', 10000)
      .assert.elementPresent('#generate-palette')
      .assert.elementCount('.palette--main .swatch', 0)
      .setValue('#generate-palette', '#fff')
      .click('#generate-palette-submit')
      .waitForElementVisible('.swatch', 10000)
      .assert.elementCount('.palette--main .swatch', 4)
      .end();
  },

  'history list is created when new palette added that can be clicked on to regenerate palette': function test(browser) {
    const devServer = `${browser.globals.devServerURL}/555444`;

    browser
      .url(devServer)
      .waitForElementVisible('.swatch', 10000)
      .assert.elementCount('.palette--main .swatch', 4)
      .assert.elementCount('.palette--history-list .swatch', 4)
      .click('#random-generate')
      .assert.elementCount('.palette--history-list .swatch', 8)
      .click('.palette--history-list__container:nth-of-type(2) .palette--history-list')
      .assert.cssProperty('.palette--main .swatch:first-of-type', 'background-color', 'rgba(85, 84, 68, 1)')
      .end();
  },
};

Add e2e test for history-list


import React, { Component, PropTypes } from 'react'
import { COLS } from '../constants/Board'

const TILE_SIZE = 60
const basicStyles = {
    position: 'absolute',
    //flexGrow: 1,
    border: '1px solid',
    width: TILE_SIZE,
    height: TILE_SIZE
}

export default class Tile extends Component {

    static propTypes = {
        idx: PropTypes.number.isRequired,
        type: PropTypes.number.isRequired
    }

    getStyles(idx) {
        return {
            ...basicStyles,
            left: (idx % COLS) * TILE_SIZE,
            top: (Math.ceil((idx + 1) / COLS) - 1) * TILE_SIZE
        }
    }

    render() {
        const {type, idx} = this.props
        const styles = this.getStyles(idx)
        return (

{type}

        )
    }
}

import React, { Component, PropTypes } from 'react'
import {Spring} from 'react-motion'
import { COLS } from '../constants/Board'

const TILE_SIZE = 60
const basicStyles = {
    position: 'absolute',
    //flexGrow: 1,
    border: '1px solid',
    width: TILE_SIZE,
    height: TILE_SIZE
}

export default class Tile extends Component {

    static propTypes = {
        position: PropTypes.number.isRequired,
        type: PropTypes.number.isRequired
    }

    getLeftValue(position) {
        return (position % COLS) * TILE_SIZE
    }

    getTopValue(position) {
        return (Math.ceil((position + 1) / COLS) - 1) * TILE_SIZE
    }

    render() {
        const {type, position} = this.props
        return (
            <Spring
                endValue={{val: {left: this.getLeftValue(position), top: this.getTopValue(position)}}}
            >
                {interpolated =>

{type}

                }
            </Spring>
        )
    }
}

Use springs to animate tiles


import { assert } from 'chai';
import cloneDeep from 'lodash/cloneDeep';

import Attributes from '../Attributes';
import defaultTheme from '../../theme';

describe('Attributes', () => {
  describe('#processProps', () => {
    describe('It doesn\'t mutate the default theme', () => {
      let expectedDefaultTheme;

      before(() => {
        expectedDefaultTheme = cloneDeep(defaultTheme);

        Attributes.prototype.processProps.apply(
          Attributes.prototype,
          [
            {
              theme: {
                KEY_COLOR: 'red',
              },
              element: {
                element: 'object',
                meta: {},
                content: [],
              },
            },
          ]
        );
      });

      it('Default theme has not been mutated', () => {
        assert.deepEqual(defaultTheme, expectedDefaultTheme);
      });
    });
  });
});

import { assert } from 'chai';
import cloneDeep from 'lodash/cloneDeep';

import Attributes from '../Attributes';
import defaultTheme from '../../theme';

describe('Attributes', () => {
  describe('#processProps', () => {
    describe('It doesn\'t mutate the default theme', () => {
      let expectedDefaultTheme;

      before(() => {
        expectedDefaultTheme = cloneDeep(defaultTheme);

        Attributes.prototype.transformPropsIntoState.apply(
          Attributes.prototype,
          [
            {
              theme: {
                KEY_COLOR: 'red',
              },
              element: {
                element: 'object',
                meta: {},
                content: [],
              },
            },
          ]
        );
      });

      it('Default theme has not been mutated', () => {
        assert.deepEqual(defaultTheme, expectedDefaultTheme);
      });
    });
  });
});

Use ‘transformPropsIntoState’, the method was renamed.


module.exports = (slackapp) => {
  slackapp.webserver.post('/incoming-webhooks/:teamId', (req, res) => {
    const events = req.body.events;
    if (!events) {
      slackapp.log('Bad incoming webhook request', req);
      res.sendStatus(400);
    } else {
      events.filter((e) => {
        return e.eventName === 'service-review-created';
      }).forEach((e) => {
        e.eventData.consumer.displayName = e.eventData.consumer.name; // Massaging into expected format
        slackapp.log('Posting new review for team', req.params.teamId);
        slackapp.trigger('trustpilot_review_received', [e.eventData, req.params.teamId]);
      });
      res.sendStatus(200);
    }
  });
};

module.exports = (slackapp) => {
  slackapp.webserver.post('/incoming-webhooks/:teamId', (req, res) => {
    const events = req.body.events;
    if (!events) {
      slackapp.log('Bad incoming webhook request', req);
      res.sendStatus(400);
    } else {
      const { params: { teamId }, query: { businessUnitId } } = req;
      events.filter((e) => {
        return e.eventName === 'service-review-created';
      }).forEach((e) => {
        e.eventData.consumer.displayName = e.eventData.consumer.name; // Massaging into expected format
        slackapp.log(`Posting new review for team ${teamId}, business unit ${businessUnitId}`);
        slackapp.trigger('trustpilot_review_received', [e.eventData, teamId, businessUnitId]);
      });
      res.sendStatus(200);
    }
  });
};

Handle businessUnitId parameter in webhook


import Area from './templete'
import {
  getRangeAncestorElem,
  getRange,
  isSelectionInArea,
  createSelectionBaseNode,
  createRange
} from 'utils/selection'

const sciprt = ({ options, widget, el, __S_ }) => {
  $(document).on('selectionchange', () => {
    const isInArea = isSelectionInArea(el.$area)

    if (isInArea && el.$area.html() === '') {
      const p = '

'
      el.$area.append(p)
      createRange(p, 0, p, 0)
    }
  })

  const insertEmptyP = $elem => {
    const $p = $('

')
    $p.insertBefore($elem)
    $elem.remove()
    createSelectionBaseNode($p.get(0), true)
  }

  // 将回车之后生成的非
 的顶级标签，改为

  const pHandle = e => {
    const range = getRange()
    const elem = getRangeAncestorElem(range)
    const $elem = $(elem)
    const $parentElem = $elem.parent()
    const nodeName = elem.nodeName

    if (!$parentElem.is(el.$area)) return
    if (nodeName === 'P') return
    if ($elem.text()) return

    insertEmptyP($elem)
  }

  el.$area.on('keyup', e => {
    if (e.keyCode !== 13) return
    pHandle(e)
  })
}

const area = {
  Tpl: Area,
  run: sciprt
}

export default area

import Area from './templete'
import {
  getRangeAncestorElem,
  getRange,
  isSelectionInArea,
  createSelectionBaseNode,
  createRange
} from 'utils/selection'

const sciprt = ({ options, widget, el, __S_ }) => {
  $(document).on('selectionchange', () => {
    const isInArea = isSelectionInArea(el.$area)

    if (isInArea && el.$area.html() === '') {
      const $p = $('

')
      el.$area.append($p)
      createRange($p.get(0), 0, $p.get(0), 0)
    }
  })

  const insertEmptyP = $elem => {
    const $p = $('

')
    $p.insertBefore($elem)
    $elem.remove()
    createSelectionBaseNode($p.get(0), true)
  }

  // 将回车之后生成的非
 的顶级标签，改为

  const pHandle = e => {
    const range = getRange()
    const elem = getRangeAncestorElem(range)
    const $elem = $(elem)
    const $parentElem = $elem.parent()
    const nodeName = elem.nodeName

    if (!$parentElem.is(el.$area)) return
    if (nodeName === 'P') return
    if ($elem.text()) return

    insertEmptyP($elem)
  }

  el.$area.on('keyup', e => {
    if (e.keyCode !== 13) return
    pHandle(e)
  })
}

const area = {
  Tpl: Area,
  run: sciprt
}

export default area

Fix bug - not node


import pipe from '../pipe';

export const transformIn =
  transformer =>
    pipeline =>
      (stream, close) =>
        pipeline(transformer(stream), close);

export const transformOut =
  transformer =>
    pipeline =>
      (stream, close) =>
        pipe(stream, pipeline).then(transformer);

export const transformError =
  transformer =>
    pipeline =>
      stream =>
      new Pipeline()
        .main(pipeline)
        .catch(errorStream => transformer(errorStream, stream))
        .pipe(stream);

export const disconnect =
  pipeline =>
    stream =>
      noop(pipeline({ ...stream }, () => {}));

/**
 * Change given stream prop to the transformer return value.
 * @param prop
 * @param transformer
 */
export const transformProp =
  (prop, transformer) =>
    pipeline =>
      stream =>
        noop(_.set(stream, prop, transformer(stream)));

import pipe from '../pipe';
import _ from 'lodash';
import { noop } from '../helpers';

export const transformIn =
  transformer =>
    pipeline =>
      (stream, close) =>
        pipeline(transformer(stream), close);

export const transformOut =
  transformer =>
    pipeline =>
      (stream, close) =>
        pipe(stream, pipeline).then(transformer);

export const transformError =
  transformer =>
    pipeline =>
      stream =>
      new Pipeline()
        .main(pipeline)
        .catch(errorStream => transformer(errorStream, stream))
        .pipe(stream);

export const disconnect =
  pipeline =>
    stream =>
      noop(pipeline({ ...stream }, () => {}));

/**
 * Change given stream prop to the transformer return value.
 * @param prop
 * @param transformer
 */
export const transformProp =
  (prop, transformer) =>
    pipeline =>
      stream =>
        noop(_.set(stream, prop, transformer(stream)));

Add missing imports for enhancers


/* eslint-disable import/prefer-default-export */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

export const ACTIONS = {
  // tableAction constants
  SORT_DATA: 'sortData',
  FILTER_DATA: 'filterData',
  REFRESH_DATA: 'refreshData',
  ADD_ITEM: 'addItem',
  ADD_RECORD: 'addRecord',
  HIDE_OVER_STOCKED: 'hideOverStocked',
  HIDE_STOCK_OUT: 'hideStockOut',

  // pageAction constants
  OPEN_MODAL: 'openModal',
  CLOSE_MODAL: 'closeModal',

  // rowAction constants
  REFRESH_ROW: 'refreshRow',
  EDIT_TOTAL_QUANTITY: 'editTotalQuantity',
  EDIT_COUNTED_QUANTITY: 'editCountedTotalQuantity',
  EDIT_REQUIRED_QUANTITY: 'editRequiredQuantity',
  EDIT_EXPIRY_DATE: 'editExpiryDate',
  ENFORCE_REASON: 'enforceReasonChoice',

  // cellAction constants
  FOCUS_CELL: 'focusCell',
  FOCUS_NEXT: 'focusNext',
  SELECT_ROW: 'selectRow',
  SELECT_ALL: 'selectAll',
  SELECT_ITEMS: 'selectItems',
  DESELECT_ROW: 'deselectRow',
  DESELECT_ALL: 'deselectAll',
  DELETE_RECORDS: 'deleteSelectedItems',
};

/* eslint-disable import/prefer-default-export */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

export const ACTIONS = {
  // tableAction constants
  SORT_DATA: 'sortData',
  FILTER_DATA: 'filterData',
  REFRESH_DATA: 'refreshData',
  ADD_ITEM: 'addItem',
  ADD_RECORD: 'addRecord',
  HIDE_OVER_STOCKED: 'hideOverStocked',
  HIDE_STOCK_OUT: 'hideStockOut',

  // pageAction constants
  OPEN_MODAL: 'openModal',
  CLOSE_MODAL: 'closeModal',

  // rowAction constants
  REFRESH_ROW: 'refreshRow',
  EDIT_TOTAL_QUANTITY: 'editTotalQuantity',
  EDIT_COUNTED_QUANTITY: 'editCountedQuantity',
  EDIT_REQUIRED_QUANTITY: 'editRequiredQuantity',
  EDIT_EXPIRY_DATE: 'editExpiryDate',
  ENFORCE_REASON: 'enforceReasonChoice',

  // cellAction constants
  FOCUS_CELL: 'focusCell',
  FOCUS_NEXT: 'focusNext',
  SELECT_ROW: 'selectRow',
  SELECT_ALL: 'selectAll',
  SELECT_ITEMS: 'selectItems',
  DESELECT_ROW: 'deselectRow',
  DESELECT_ALL: 'deselectAll',
  DELETE_RECORDS: 'deleteSelectedItems',
};

Add correct edit counted quantity key value


import PropTypes from 'prop-types';
import React from 'react';
import List from '../List/List';
import ScaledComponent from './ScaledComponent';
import DelayedItems from './DelayedItems';

const propTypes = {
  children: PropTypes.node,
};

const MenuList = ({ children, ...otherProps }) =>
  <List
    className="mdc-simple-menu__items"
    role="menu"
    {...otherProps}
  >
    {children}
  </List>
;
MenuList.propTypes = propTypes;
export default ScaledComponent(DelayedItems(MenuList));

import PropTypes from 'prop-types';
import React from 'react';
import List from '../List/List';
import ScaledComponent from './ScaledComponent';
import DelayedItems from './DelayedItems';

const propTypes = {
  children: PropTypes.node,
};

const MenuList = ({ children, ...otherProps }) =>
  <List
    className="mdc-menu__items"
    role="menu"
    {...otherProps}
  >
    {children}
  </List>
;
MenuList.propTypes = propTypes;
export default ScaledComponent(DelayedItems(MenuList));

Remove simple menu from menu list


$(function () {

    var hc_scoreboard_series = {
        name: 'Points',
        id: ':scores',
        data: [],
        dataLabels: {
            enabled: true,
            color: '#FFFFFF',
            align: 'center',
            y: 10, // 10 pixels down from the top
        }
    };

    // Get initial chart data, set up columns for teams
    $.get( '/team/scores' )
    .done(function(data) {
        $.each(data, function(index, result) {
            hc_scoreboard_series.data.push([
                result.teamname, result.points
            ]);
        });

        $('#hc_scoreboard').highcharts({
            chart: {
                type: 'column'
            },
            title: {
                text: 'Team Scores'
            },
            subtitle: {
                text: '(Updates automatically)'
            },
            xAxis: {
                type: 'category',
                labels: {
                    rotation: -45,
                },
                crosshair: false
            },
            yAxis: {
                min: 0,
                title: {
                    text: 'Points'
                }
            },
            legend: {
                enabled: false
            },
            plotOptions: {
                column: {
                    pointPadding: 0,
                    groupPadding: 0.1,
                    borderWidth: 0,
                    colorByPoint: true,
                    shadow: true
                }
            },
            series: [hc_scoreboard_series]
        });
    });

});

$(function () {

    var hc_scoreboard_series = {
        name: 'Points',
        id: ':scores',
        data: [],
        dataLabels: {
            enabled: true,
            color: '#FFFFFF',
            align: 'center',
            y: 25, // 25 pixels down from the top
            style: {
                fontSize: '3.2vw'
            }
        }
    };

    // Get initial chart data, set up columns for teams
    $.get( '/team/scores' )
    .done(function(data) {
        $.each(data, function(index, result) {
            hc_scoreboard_series.data.push([
                result.teamname, result.points
            ]);
        });

        $('#hc_scoreboard').highcharts({
            chart: {
                type: 'column'
            },
            title: {
                text: 'Team Scores'
            },
            subtitle: {
                text: '(Updates automatically)'
            },
            xAxis: {
                type: 'category',
                labels: {
                    rotation: -45,
                },
                crosshair: false
            },
            yAxis: {
                min: 0,
                title: {
                    text: 'Points'
                }
            },
            legend: {
                enabled: false
            },
            plotOptions: {
                column: {
                    pointPadding: 0,
                    groupPadding: 0.1,
                    borderWidth: 0,
                    colorByPoint: true,
                    shadow: true
                }
            },
            series: [hc_scoreboard_series]
        });
    });

});

Increase font size on scoreboard point labels


/* global angular: true, FileReader: true */

angular.module('webPGQ.directives')
    .directive('openFile', function()
    {
        function link(scope, element)//, attrs)
        {
            var input = element.find('input');

            input.change(function()
            {
                var file = this.files[0];
                console.log("Loading file:", file);

                scope.$apply(function() { scope.loading = true; });

                var reader = new FileReader();

                reader.onload = function(ev)
                {
                    scope.$apply(function()
                    {
                        console.log("Loaded file " + file.name + ".");
                        scope.onOpen({file: file, content: ev.target.result});
                        scope.loading = false;
                    });
                }; // end reader.onload

                reader.readAsText(file);
            });

            element.click(function() { input.click(); });
        } // end link

        return {
            restrict: 'E',
            scope: {
                onOpen: '&',
                class: '@'
            },
            replace: true,
            link: link,
            templateUrl: '/js/directives/open-file.html'
        };
    });

/* global angular: true, FileReader: true */

angular.module('webPGQ.directives')
    .directive('openFile', function()
    {
        function link(scope, element)//, attrs)
        {
            var input = element.find('input');

            input.click(function(event)
            {
                event.stopPropagation();
            });

            input.change(function()
            {
                var file = this.files[0];
                console.log("Loading file:", file);

                scope.$apply(function() { scope.loading = true; });

                var reader = new FileReader();

                reader.onload = function(ev)
                {
                    scope.$apply(function()
                    {
                        console.log("Loaded file " + file.name + ".");
                        scope.onOpen({file: file, content: ev.target.result});
                        scope.loading = false;
                    });
                }; // end reader.onload

                reader.readAsText(file);
            });

            element.click(function() { input.click(); });
        } // end link

        return {
            restrict: 'E',
            scope: {
                onOpen: '&',
                class: '@'
            },
            replace: true,
            link: link,
            templateUrl: '/js/directives/open-file.html'
        };
    });

Stop propagation on file input click events, so we don't infinitely loop.


/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

pref("extensions.customizable-ldap-autocomplete@clear-code.com.directoryServers", "*");

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// you can specify two styles:
//  1. use all LDAP servers: "*"
//  2. use specific LDAP servers: "ldap_2.servers.key1,ldap_2.servers.key2,..."
pref("extensions.customizable-ldap-autocomplete@clear-code.com.directoryServers", "*");

Add examples of preference value


import {remote} from 'electron'

const Notifier = {
  send(options) {
    const {title, body} = options
    const icon = 'resource/img/icon.png'
    const notification = new Notification(title, {body, icon})
    notification.onclick = () => {
      const frame = remote.getCurrentWindow()
      if (frame.isMinimized()) {
        frame.restore()
      }
      frame.focus()
    }
    return notification
  }
}

export default {
  install(Vue, options) {
    Vue.prototype.$notifier = Notifier
  }
}

import {remote} from 'electron'

const Notifier = {
  send(options) {
    const {title, body} = options
    const icon = 'resource/img/icon.png'
    const frame = remote.getCurrentWindow()
    frame.flashFrame(true)
    const notification = new Notification(title, {body, icon})
    notification.onclick = () => {
      if (frame.isMinimized()) {
        frame.restore()
      }
      frame.focus()
    }
    return notification
  }
}

export default {
  install(Vue, options) {
    Vue.prototype.$notifier = Notifier
  }
}

Add frame flash on notification


/*
 This file is a part of libertysoil.org website
 Copyright (C) 2015  Loki Education (Social Enterprise)

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

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

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
export function defaultSelector(state) {
  let data = state.toJS();

  data.is_logged_in = !!data.current_user.id;

  if (data.is_logged_in) {
    let current_user_id = data.current_user.id;

    data.current_user_tags = data.current_user.tags;

    data.current_user = data.users[current_user_id];
    data.current_user.likes = data.likes[current_user_id];
    data.current_user.favourites = data.favourites[current_user_id];

    data.i_am_following = data.following[current_user_id];
  } else {
    data.current_user = null;
  }

  return data;
}

/*
 This file is a part of libertysoil.org website
 Copyright (C) 2015  Loki Education (Social Enterprise)

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

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

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
export function defaultSelector(state) {
  let data = state.toJS();

  data.is_logged_in = !!data.current_user.id;

  if (data.is_logged_in) {
    let current_user_id = data.current_user.id;

    data.current_user_tags = data.current_user.tags;

    data.current_user = data.users[current_user_id];
    data.current_user.likes = data.likes[current_user_id] || [];
    data.current_user.favourites = data.favourites[current_user_id] || [];

    data.i_am_following = data.following[current_user_id];
  } else {
    data.current_user = null;
  }

  return data;
}

Change selector to ensure likes and favourites are not null


import { SET_SEARCH_TERM } from './actions'

export function setSearchTerm (searchTerm) {
  return { type: SET_SEARCH_TERM, searchTerm }
}

import { SET_SEARCH_TERM, ADD_OMDB_DATA } from './actions'
import axios from 'axios'

export function setSearchTerm (searchTerm) {
  return { type: SET_SEARCH_TERM, searchTerm }
}

export function addOMDBData (imdbID, omdbData) {
  return { type: ADD_OMDB_DATA, imdbID, omdbData }
}
// thunk creator - thunks are different from actions because they return functions
export function getOMDBDetails (imdbID) {
  return function (dispatch, getState) {
    axios.get(`http://www.omdbapi.com/?i=${imdbID}`)
    .then((response) => {
      console.log('response', response)
      dispatch(addOMDBData(imdbID, response.data))
    })
    .catch((error) => {
      console.error('axios error', error)
    })
  }
}

Add omdb action creator and thunk async action


// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

frappe.ui.form.on('Stock Settings', {
	refresh: function(frm) {

	}
});

// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

frappe.ui.form.on('Stock Settings', {
	refresh: function(frm) {
		let filters = function() {
			return {
				 filters : {
					is_group : 0
				 }
			};
		}

		frm.set_query("default_warehouse", filters);
		frm.set_query("sample_retention_warehouse", filters);
	}
});

Set Query on warehouse fields in Stock Settings


var Game = function (options) {
  this.stage = options.stage;
  this.grid = options.grid;
  this.player = 0;
}

Game.prototype = {
  start: function(){
    this.addCurrentPlayerCoin();
  },
  coinDropped: function(e){
    var column = this.stage.getColumnFromX(e.target.x());
    var row = 5 - this.grid.update(column, this.player)
    this.stage.animateFall(e, row);
    if( this.winner(6 - row, column) )
      alert("player "+(this.player+1)+" won!");
    else
      this.changeTurns();
  },
  changeTurns: function(){
    this.player ^= 1
    this.addCurrentPlayerCoin();
  },
  addCurrentPlayerCoin: function(){
    var coin = this.stage.addCoin(this.player);
    coin.on("click", this.coinDropped.bind(this));
  },
  winner: function(row, column){
    regex = /([1]{4}|[2]{4})/
    return this.grid.getRow(row).match(regex) ||
           this.grid.getColumn(column).match(regex) ||
           this.diagonalCheck(row, column);
  },
  diagonalCheck: function(row, column){
    return this.grid.getDiagonal(row, column, "slash").match(regex) ||
            this.grid.getDiagonal(row, column, "backslash").match(regex);
  }
}

socket4 = new Game({stage: new Stage(), grid: new Grid()});
socket4.start();

var Game = function (options) {
  this.stage = options.stage;
  this.grid = options.grid;
  this.player = 0;
}

Game.prototype = {
  start: function(){
    this.addCurrentPlayerCoin();
  },
  coinDropped: function(e){
    var column = this.stage.getColumnFromX(e.target.x());
    if (column) {;
      var row = 5 - this.grid.update(column, this.player)
      this.stage.animateFall(e, row);
      if( this.winner(6 - row, column) )
        alert("player "+(this.player+1)+" won!");
      else
        this.changeTurns();
    } else{
      this.stage.animateFall(e, 0);
    }
  },
  changeTurns: function(){
    this.player ^= 1
    this.addCurrentPlayerCoin();
  },
  addCurrentPlayerCoin: function(){
    var coin = this.stage.addCoin(this.player);
    coin.on("click", this.coinDropped.bind(this));
  },
  winner: function(row, column){
    regex = /([1]{4}|[2]{4})/
    return this.grid.getRow(row).match(regex) ||
           this.grid.getColumn(column).match(regex) ||
           this.diagonalCheck(row, column);
  },
  diagonalCheck: function(row, column){
    return this.grid.getDiagonal(row, column, "slash").match(regex) ||
            this.grid.getDiagonal(row, column, "backslash").match(regex);
  }
}

socket4 = new Game({stage: new Stage(), grid: new Grid()});
socket4.start();

Fix turn change when coin doesn't fall on grid


import BenchmarkRun from 'models/BenchmarkRun.js';

import DoingWorkSpinner from 'components/DoingWorkSpinner.jsx';

//A non-ui class which cares about upload file, converting them to json and passing them on to the AppState
export default class FileUploader {

    constructor(uploadBenchmarksFunction) {
        this.uploadBenchmarksFunction = uploadBenchmarksFunction;
    }

    upload(files) {
        if (files.length > 2) {
            alert(`Maximum 2 files allowed to upload, but not ${files.length}!`)
            return
        }
        DoingWorkSpinner.show();

        const benchmarkRuns = [];
        const uploadFunction = this.uploadBenchmarksFunction;
        files.forEach((file, i) => {
            const reader = new FileReader();
            reader.onload = function(evt) {
                try {
                    var parsedBenchmarks = JSON.parse(evt.target.result);
                    const benchmarkRun = new BenchmarkRun({
                        name: file.name,
                        benchmarks: parsedBenchmarks
                    });
                    benchmarkRuns.push(benchmarkRun);
                    if (i == files.length - 1) {
                        benchmarkRuns.sort((a, b) => a.name.localeCompare(b.name));
                        uploadFunction(benchmarkRuns);
                    }
                } catch ( e ) {
                    alert(e); //error in the above string(in this case,yes)!
                    DoingWorkSpinner.hide();
                }
            };
            reader.readAsText(file);
        });
    }

}

import BenchmarkRun from 'models/BenchmarkRun.js';

import DoingWorkSpinner from 'components/DoingWorkSpinner.jsx';

//A non-ui class which cares about upload file, converting them to json and passing them on to the AppState
export default class FileUploader {

    constructor(uploadBenchmarksFunction) {
        this.uploadBenchmarksFunction = uploadBenchmarksFunction;
    }

    upload(files) {
        if (files.length > 2) {
            alert(`Maximum 2 files allowed to upload, but not ${files.length}!`)
            return
        }
        DoingWorkSpinner.show();

        const benchmarkRuns = [];
        const uploadFunction = this.uploadBenchmarksFunction;
        files.forEach((file, i) => {
            const reader = new FileReader();
            const runName = file.name.replace('.json', '');
            reader.onload = function(evt) {
                try {
                    var parsedBenchmarks = JSON.parse(evt.target.result);
                    const benchmarkRun = new BenchmarkRun({
                        name: runName,
                        benchmarks: parsedBenchmarks
                    });
                    benchmarkRuns.push(benchmarkRun);
                    if (i == files.length - 1) {
                        benchmarkRuns.sort((a, b) => a.name.localeCompare(b.name));
                        uploadFunction(benchmarkRuns);
                    }
                } catch ( e ) {
                    alert(e); //error in the above string(in this case,yes)!
                    DoingWorkSpinner.hide();
                }
            };
            reader.readAsText(file);
        });
    }

}

Remove .json suffix from run names


import * as React from 'react';

/**
 * Main List Element to be displayed on the Navigation.
 *
 * @param {Object} props
 * @param {string} props.href hyperlink for the href attribute.
 * @param {string} props.innerText rendered text content of node.
 */
const NavigationItem = (props) => {
  return (

      {props.innerText}

  );
}

/**
  * Main Navigation List.
  * Instance will be embedded in ./Header component.
  *
  * @param {Object} props
  * @param {Object} props.options an array of navigation options.
  * @param {string} props.options[].link an external URL attached to option.
  * @param {string} props.options[].text the displayed text for the option.
  */
export const Navigation = (props) => {
  return (

      {props.options.map (option =>
          <NavigationItem href={option.link} innerText={option.text}/>
      )}

  );
}

import * as React from 'react';

/**
 * Main List Element to be displayed on the Navigation.
 *
 * @param {Object} props
 * @param {string} props.href hyperlink for the href attribute.
 * @param {string} props.innerText rendered text content of node.
 */
const NavigationItem = (props) => {
  return (

      {props.innerText}

  );
}

/**
  * Main Navigation List.
  * Instance will be embedded in ./Header component.
  *
  * @param {Object} props
  * @param {Object} props.options an array of navigation options.
  * @param {string} props.options[].link an external URL attached to option.
  * @param {string} props.options[].text the displayed text for the option.
  */
export const Navigation = (props) => {
  const options = getField(props, 'options', []);

  return (

      {props.options.map (option =>
          <NavigationItem href={option.link} innerText={option.text}/>
      )}

  );
}

Use nav options with getField


'use strict';

const globalHooks = require('../../../hooks');
const hooks = require('feathers-hooks');
const auth = require('feathers-authentication').hooks;

exports.before = {
  all: [
    globalHooks.isAdmin()
  ],
  find: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated()
  ],
  get: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated()
  ],
  create: [
    auth.hashPassword()
  ],
  update: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' })
  ],
  patch: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' })
  ],
  remove: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' })
  ]
};

exports.after = {
  all: [hooks.remove('password')],
  find: [],
  get: [],
  create: [],
  update: [],
  patch: [],
  remove: []
};

'use strict';

const globalHooks = require('../../../hooks');
const hooks = require('feathers-hooks');
const auth = require('feathers-authentication').hooks;

exports.before = {
  all: [],
  find: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated()
  ],
  get: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated()
  ],
  create: [
    auth.hashPassword()
  ],
  update: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' })
  ],
  patch: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' })
  ],
  remove: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' })
  ]
};

exports.after = {
  all: [hooks.remove('password')],
  find: [],
  get: [],
  create: [],
  update: [],
  patch: [],
  remove: []
};

Remove auth hook from user


async registerElement('$variable_id', function () {
  try {
    await refreshString('$element_id', 'textContent', '$data_uri');
  } catch {
  }
});

registerElement('$variable_id', async function () {
  try {
    await refreshString('$element_id', 'textContent', '$data_uri');
  } catch {
  }
});

Put async in right place


import Component from 'react-pure-render/component';
import R from 'ramda';
import React, { PropTypes } from 'react';
import { defineMessages, injectIntl, intlShape } from 'react-intl';
import { IconButton, Menu } from 'react-mdl/lib';
import { MenuItem } from 'react-mdl/lib/Menu';

class FermenterCommander extends Component {

  static propTypes = {
    fermenterRts: PropTypes.object.isRequired,
    fermenterStart: PropTypes.func.isRequired,
    fermenterStop: PropTypes.func.isRequired,
    intl: intlShape.isRequired,
  };

  render() {
    const { fermenterRts: rts, fermenterStart, fermenterStop } = this.props;

    if (R.empty(status)) {
      return (
No status yet...
);
    }

    return (

        <IconButton name="more_vert" id="fermenter_menu_upper_left" />
        <Menu target="fermenter_menu_upper_left" >
          <MenuItem onClick={fermenterStart}>Switch fermenter on</MenuItem>
          <MenuItem onClick={fermenterStop}>Switch fermenter off</MenuItem>
          <MenuItem disabled>Emergency off</MenuItem>
        </Menu>

Current status: [{rts.status}]

    );
  }
}

export default injectIntl(FermenterCommander);

import Component from 'react-pure-render/component';
import R from 'ramda';
import React, { PropTypes } from 'react';
import { defineMessages, injectIntl, intlShape } from 'react-intl';
import { IconButton, Menu } from 'react-mdl/lib';
import { MenuItem } from 'react-mdl/lib/Menu';

class FermenterCommander extends Component {

  static propTypes = {
    fermenterRts: PropTypes.object.isRequired,
    fermenterStart: PropTypes.func.isRequired,
    fermenterStop: PropTypes.func.isRequired,
    intl: intlShape.isRequired,
  };

  render() {
    const { fermenterRts: rts, fermenterStart, fermenterStop } = this.props;
    const maybeShowCurrentCmd = R.defaultTo(' -- ');

    if (R.isEmpty(rts.status)) {
      return (
No status yet...
);
    }

    return (

        <IconButton name="more_vert" id="fermenter_menu_upper_left" />
        <Menu target="fermenter_menu_upper_left" >
          <MenuItem onClick={fermenterStart}>Switch fermenter on</MenuItem>
          <MenuItem onClick={fermenterStop}>Switch fermenter off</MenuItem>
          <MenuItem disabled>Emergency off</MenuItem>
        </Menu>

Current status: [{rts.status}]

| Last command: [{maybeShowCurrentCmd(rts.currentCmd)}]

    );
  }
}

export default injectIntl(FermenterCommander);

Add last fermenter-command in status-display.


var λ = require('./lambada');

describe('lambada', function () {
    it('should return a function', function () {
        expect(typeof λ).toBe('function');
    });
});

var λ = require('./lambada');

describe('lambada', function () {
    it('should return a function', function () {
        expect(typeof λ).toBe('function');
    });

    it('creates a unary function for a simple expression', function () {
        var f = λ('3 + x');
        expect(typeof f).toBe('function');
        expect(f(-10)).toBe(-7);
        expect(f(0)).toBe(3);
        expect(f(10)).toBe(13);
    });

    it('creates a binary function for a simple expression', function () {
        var f = λ('x + 2 * y');
        expect(typeof f).toBe('function');
        expect(f(3, 4)).toBe(11);
        expect(f(4, 3)).toBe(10);
        expect(f(2, 7)).toBe(16);
    });

    it('creates a function for a binary operator', function () {
        var f = λ('+');
        expect(typeof f).toBe('function');
        expect(f(3, 2)).toBe(5);
        expect(f(-3, 4)).toBe(1);
        expect(f(8, 8)).toBe(16);
    });

    it('creates a function for a partially applied binary operator', function () {
        var f = λ('+3');
        expect(typeof f).toBe('function');
        expect(f(-10)).toBe(-7);
        expect(f(0)).toBe(3);
        expect(f(10)).toBe(13);
    });
});

Test for simple arithmetic functions


// ==========================================================================
// Project:   MySystem Unit Test
// Copyright: ©2010 Concord Consortium
// ==========================================================================
/*globals MySystem module test ok equals same stop start */

module("MySystem.AddButtonView");

var addButton = MySystem.AddButtonView.create();

test("add button was created", function() {
  expect(5);
  ok(addButton !== null, "addButton should not be null");
  ok(addButton.kindOf(MySystem.AddButtonView), "addButton should be an AddButtonView.");
  equals(addButton.get('childViews').length, 2, "addButton should have two children.");
  ok(addButton.get('icon').kindOf(SC.ImageView), "icon should be a SproutCore ImageView");
  ok(addButton.get('label').kindOf(SC.LabelView), "label should be a SproutCore LabelView");
});

// ==========================================================================
// Project:   MySystem Unit Test
// Copyright: ©2010 Concord Consortium
// ==========================================================================
/*globals MySystem module test ok equals same stop start */

module("MySystem.AddButtonView");

var addButton = MySystem.AddButtonView.create();

test("add button was created", function() {
  expect(5);
  ok(addButton !== null, "addButton should not be null");
  ok(addButton.kindOf(MySystem.AddButtonView), "addButton should be an AddButtonView.");
  equals(addButton.get('childViews').length, 3, "addButton should have three children.");
  ok(addButton.get('icon').kindOf(SC.ImageView), "icon should be a SproutCore ImageView");
  ok(addButton.get('label').kindOf(SC.LabelView), "label should be a SproutCore LabelView");
});

Update view unit test to reflect new view


// Based on http://www.html5rocks.com/en/tutorials/workers/basics/
define(function() {
    var URL = window.URL || window.webkitURL
    if (URL && Worker && Blob) {
        var blobUrl = URL.createObjectURL(new Blob(
            ['self.onmessage = function(event) { setInterval(function() { self.postMessage({}) }, event.data.interval) }'],
            {type: 'text/javascript'}
        ))
        return function(callback, interval) {
            var worker = new Worker(blobUrl)
            worker.addEventListener('message', callback)
            worker.postMessage({interval: interval})
        }
    }

    console.log("Worker or Blob not available, falling back to setInterval()")
    return setInterval
})

// Based on http://www.html5rocks.com/en/tutorials/workers/basics/
define(function() {
    var URL = window.URL || window.webkitURL
    // BlobBuilder is deprecated but Chrome for Android fails with an "Illegal constructor"
    // instantiating the Blob directly
    var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder
    if (URL && Worker && Blob && BlobBuilder) {
        var blobBuilder = new BlobBuilder
        blobBuilder.append('self.onmessage = function(event) { setInterval(function() { self.postMessage({}) }, event.data.interval) }')
        var blob = blobBuilder.getBlob('text/javascript')
        var blobUrl = URL.createObjectURL(blob)
        return function(callback, interval) {
            var worker = new Worker(blobUrl)
            worker.addEventListener('message', callback)
            worker.postMessage({interval: interval})
        }
    }

    console.log("Worker or Blob not available, falling back to setInterval()")
    return setInterval
})

Use BlobBuilder (supported by Chrome for Android)


"use strict";

var utils = require("../utils");
var log = require("npmlog");

module.exports = function(defaultFuncs, api, ctx) {
  return function changeThreadEmoji(emoji, threadID, callback) {
    var form = {
      'emoji_choice' : emoji,
      'thread_or_other_fbid' : threadID
    };

    defaultFuncs
      .post("https://www.messenger.com/messaging/save_thread_emoji/?source=thread_settings&__pc=EXP1%3Amessengerdotcom_pkg", ctx.jar, form)
      .then(utils.parseAndCheckLogin(ctx.jar, defaultFuncs))
      .then(function(resData) {
        if (resData.error === 1357031) {
          throw {error: "Trying to change emoji of a chat that doesn't exist. Have at least one message in the thread before trying to change the emoji."};
        }
        if (resData.error) {
          throw resData;
        }

        return callback();
      })
      .catch(function(err) {
        log.error("Error in changeThreadEmoji", err);
        return callback(err);
      });
  };
};

"use strict";

var utils = require("../utils");
var log = require("npmlog");

module.exports = function(defaultFuncs, api, ctx) {
  return function changeThreadEmoji(emoji, threadID, callback) {
    if(!callback) {
      callback = function() {};
    }
    var form = {
      'emoji_choice' : emoji,
      'thread_or_other_fbid' : threadID
    };

    defaultFuncs
      .post("https://www.messenger.com/messaging/save_thread_emoji/?source=thread_settings&__pc=EXP1%3Amessengerdotcom_pkg", ctx.jar, form)
      .then(utils.parseAndCheckLogin(ctx.jar, defaultFuncs))
      .then(function(resData) {
        if (resData.error === 1357031) {
          throw {error: "Trying to change emoji of a chat that doesn't exist. Have at least one message in the thread before trying to change the emoji."};
        }
        if (resData.error) {
          throw resData;
        }

        return callback();
      })
      .catch(function(err) {
        log.error("Error in changeThreadEmoji", err);
        return callback(err);
      });
  };
};

Fix callback parameter as optional


export const cardName = {
    key: 'cardName',
    prompt: 'what card are you searching for?\n',
    type: 'string'
}

export const soundKind = {
    key: 'soundKind',
    prompt: '',
    type: 'string',
    default: 'play'
}

export const bnetServer = {
    key: 'bnetServer',
    prompt: 'which battle.net server do you play on?\n',
    type: 'string',
    parse: value => { return value.toLowerCase() },
    validate: value => {
        if (['americas', 'europe', 'asia'].includes(value.toLowerCase())) { return true }
        return 'please choose a server from `americas`, `europe`, `asia`.\n'
    }
}

export const bnetId = {
    key: 'bnetId',
    prompt: 'what is your battle.net id?\n',
    type: 'string'
}

export const cardName = {
    key: 'cardName',
    prompt: 'what card are you searching for?\n',
    type: 'string'
}

export const soundKind = {
    key: 'soundKind',
    prompt: '',
    type: 'string',
    default: 'play'
}

const bnetServerChoices = ['americas', 'na', 'europe', 'eu', 'asia']
export const bnetServer = {
    key: 'bnetServer',
    prompt: 'which battle.net server do you play on?\n',
    type: 'string',
    parse: value => {
        value = value.toLowerCase()
        if (value === 'na') { return 'americas' }
        if (value === 'eu') { return 'europe'}
        return value
    },
    validate: value => {
        if (bnetServerChoices.includes(value.toLowerCase())) { return true }
        return `please choose a server from \`${bnetServerChoices.join('`, `')}\`.\n`
    }
}

export const bnetId = {
    key: 'bnetId',
    prompt: 'what is your battle.net id?\n',
    type: 'string'
}

Update bnetServer argument to accept some shorthand


import React from 'react';
import styled from '@emotion/styled';

const Content = styled.footer`
  text-align: center;
  color: rgba(0, 0, 0, 0.25);
`;

const Paragraph = styled.p`
  margin-bottom: 8px;
  font-size: 12px;

  &:first-of-type {
    font-size: 14px;
  }
`;

export default () => (
  <Content>
    <Paragraph>
      We hope these lines of code will be useful to you and help you build great
      products.
    </Paragraph>
    <Paragraph>
      Built with ♥ by the team @ Mirego.
    </Paragraph>
  </Content>
);

import React from 'react';
import styled from '@emotion/styled';

const Content = styled.footer`
  text-align: center;
  color: rgba(0, 0, 0, 0.25);
`;

const Paragraph = styled.p`
  margin-bottom: 8px;
  font-size: 12px;

  &:first-of-type {
    font-size: 14px;
  }
`;

export default () => (
  <Content>
    <Paragraph>
      We hope these lines of code will be useful to you and help you build great
      products.
    </Paragraph>
    <Paragraph>

        Built
      {' '}
      with ♥ (and{' '}

        Gatsby.js

      ) by the team @ Mirego.
    </Paragraph>
  </Content>
);

Add more links in footer


$(document).ready(function() {
    $("#search-lots").autocomplete({
        minLength: 2,
        source: "/lots/search-ajax/",
        focus: function( event, ui ) {
            $("#search-lots").val( ui.item.name );
            return false;
        },
        select: function( event, ui ) {
            window.location.href = "/revenue/receipt/add/" + ui.item.name;
            return false;
        }
    }).autocomplete("instance")._renderItem = function(ul, item) {
        return $( "
" )
            .append( "
" + item.name + " " + item.address + "
" + item.owner__name + "
" )
            .appendTo( ul );
    };
});

function parse_decimal(str) {
    return new BigNumber(str.replace(/[^0-9\.]+/g,""))
}

$(document).ready(function() {
    $("#search-lots").autocomplete({
        minLength: 2,
        source: "/lots/search-ajax/",
        focus: function( event, ui ) {
            $("#search-lots").val( ui.item.name );
            return false;
        },
        select: function( event, ui ) {
            window.location.href = "/revenue/receipt/add/" + ui.item.name;
            return false;
        }
    }).autocomplete("instance")._renderItem = function(ul, item) {
        return $( "
" )
            .append( "
" + item.name + " " + item.address + "
" + item.owner__name + "
" )
            .appendTo( ul );
    };
});

function parse_decimal(str) {
    return str !== '' ? new BigNumber(str.replace(/[^0-9\.]+/g,"")) : new BigNumber('0.00');
}

Fix error when empty string


import RNFS from 'react-native-fs';

const Constants = {
    API_PATH: 'http://localhost:3000',
    IMAGE_DIR: RNFS.DocumentDirectoryPath + '/images/',
}

export default Constants;

import RNFS from 'react-native-fs';

const Constants = {
    API_PATH: 'http://ec2-52-87-152-32.compute-1.amazonaws.com/',
    IMAGE_DIR: RNFS.DocumentDirectoryPath + '/images/',
}

export default Constants;

Change to targeting a public server


module.exports = {
  keys: ['FEEDPARSER', 'RSS_PARSER', 'FEEDME'],
  RSS_PARSER: require('./rss-parser'),
  FEEDPARSER: require('./feedparser'),
  FEEDME: require('./feedme'),
}

module.exports = {
  keys: ['FEEDPARSER', 'RSS_PARSER'],
  RSS_PARSER: require('./rss-parser'),
  FEEDPARSER: require('./feedparser'),
  FEEDME: require('./feedme'),
}

Remove feedme from list of parsers


import { connect } from 'react-redux';
import Player from '../presentational/audio-player/Player';
import { togglePlay, changeCurrentSeconds, seekToSeconds, setDuration } from './../actions/player';

export const mapStateToProps = (state) => ({
  ...state
});

const mapDispatchToProps = (dispatch) => ({
  onPlayClick: () => dispatch(togglePlay),
  onTimeUpdate: (e) => dispatch(changeCurrentSeconds(e.target.currentTime)),
  onSlide: (e) => dispatch(seekToSeconds(e.target.value)),
  onDurationSet: (dur) => dispatch(setDuration(dur))
});

const PlayerContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(Player);

export default PlayerContainer;

import { connect } from 'react-redux';
import Player from '../presentational/audio-player/Player';
import { togglePlay, changeCurrentSeconds, seekToSeconds, setDuration, changeVolume } from './../actions/player';

export const mapStateToProps = (state) => ({
  ...state
});

const mapDispatchToProps = (dispatch) => ({
  onPlayClick: () => dispatch(togglePlay),
  onTimeUpdate: (e) => dispatch(changeCurrentSeconds(e.target.currentTime)),
  onSeekerChange: (e) => dispatch(seekToSeconds(e.target.value)),
  onDurationSet: (dur) => dispatch(setDuration(dur)),
  onVolumeChange: (e) => dispatch(changeVolume(e.target.value / 100))
});

const PlayerContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(Player);

export default PlayerContainer;

Add new action volume change


$(document).ready(function(){

  function load_new_items(){
    sign = load_path.indexOf('?') >= 0 ? '&' : '?'
    $.get(load_path + sign + 'page=' + (++page_num), function(data, e) {
      if(data.length < 5) {
        page_num = 0;
        return false;
      }
      data_container.append(data);
    }).complete(function() {
      loading_pic.hide();
    });
  }

  function loading_hidden(){
    return !loading_pic.is(':visible');
  }

  function near_bottom(){
    return $(window).scrollTop() > $(document).height() - $(window).height() - bottom_px_limit;
  }

  if($('*[data-scrollinity-path]').length > 0) {
    var page_num = 1
    load_path = $('*[data-scrollinity-path]').data('scrollinity-path');
    loading_pic = $('*[data-scrollinity-loading-pic]');
    data_container = $('#' + $('*[data-scrollinity-data-container]').data('scrollinity-data-container'));
    bottom_px_limit = $('*[data-scrollinity-bottom-px-limit]').data('scrollinity-bottom-px-limit');

    $(window).on('scroll', function(){
      if(loading_hidden() && near_bottom()) {
        if(page_num > 0) {
          loading_pic.show();
          load_new_items();
        }
      }
    });
  }
});

$(document).ready(function(){

  function load_new_items(){
    var sign = load_path.indexOf('?') >= 0 ? '&' : '?'
    $.get(load_path + sign + 'page=' + (++page_num), function(data, e) {
      if(data.length < 5) {
        var page_num = 0;
        return false;
      }
      data_container.append(data);
    }).complete(function() {
      loading_pic.hide();
    });
  }

  function loading_hidden(){
    return !loading_pic.is(':visible');
  }

  function near_bottom(){
    return $(window).scrollTop() > $(document).height() - $(window).height() - bottom_px_limit;
  }

  if($('*[data-scrollinity-path]').length > 0) {
    var page_num        = 1
    var load_path       = $('*[data-scrollinity-path]').data('scrollinity-path');
    var loading_pic     = $('*[data-scrollinity-loading-pic]');
    var data_container  = $('#' + $('*[data-scrollinity-data-container]').data('scrollinity-data-container'));
    var bottom_px_limit = $('*[data-scrollinity-bottom-px-limit]').data('scrollinity-bottom-px-limit');

    $(window).on('scroll', function(){
      if(loading_hidden() && near_bottom()) {
        if(page_num > 0) {
          loading_pic.show();
          load_new_items();
        }
      }
    });
  }
});

Fix var declaration for strict mode.


import path from 'path';
import renderer from 'react-test-renderer';
import shallow from 'react-test-renderer/shallow';
import 'jest-specific-snapshot';

function getRenderedTree(story, context, options) {
  const storyElement = story.render(context);
  return renderer.create(storyElement, options).toJSON();
}

function getSnapshotFileName(context) {
  const fileName = context.fileName;

  if (!fileName) {
    return null;
  }

  const { dir, name } = path.parse(fileName);
  return path.format({ dir, name, ext: '.storyshot' });
}

export const snapshotWithOptions = options => ({ story, context }) => {
  const tree = getRenderedTree(story, context, options);
  expect(tree).toMatchSnapshot();
};

export const multiSnapshotWithOptions = options => ({ story, context }) => {
  const tree = getRenderedTree(story, context, options);
  const snapshotFileName = getSnapshotFileName(context);

  if (!snapshotFileName) {
    expect(tree).toMatchSnapshot();
    return;
  }

  expect(tree).toMatchSpecificSnapshot(snapshotFileName);
};

export const snapshot = snapshotWithOptions({});

export function shallowSnapshot({ story, context }) {
  const shallowRenderer = shallow.createRenderer();
  const result = shallowRenderer.render(story.render(context));
  expect(result).toMatchSnapshot();
}

export function renderOnly({ story, context }) {
  const storyElement = story.render(context);
  renderer.create(storyElement);
}

import path from 'path';
import renderer from 'react-test-renderer';
import shallow from 'react-test-renderer/shallow';
import 'jest-specific-snapshot';

function getRenderedTree(story, context, options) {
  const storyElement = story.render(context);
  return renderer.create(storyElement, options).toJSON();
}

function getSnapshotFileName(context) {
  const fileName = context.fileName;

  if (!fileName) {
    return null;
  }

  const { dir, name } = path.parse(fileName);
  return path.format({ dir: path.join(dir, '__snapshots__'), name, ext: '.storyshot' });
}

export const snapshotWithOptions = options => ({ story, context }) => {
  const tree = getRenderedTree(story, context, options);
  expect(tree).toMatchSnapshot();
};

export const multiSnapshotWithOptions = options => ({ story, context }) => {
  const tree = getRenderedTree(story, context, options);
  const snapshotFileName = getSnapshotFileName(context);

  if (!snapshotFileName) {
    expect(tree).toMatchSnapshot();
    return;
  }

  expect(tree).toMatchSpecificSnapshot(snapshotFileName);
};

export const snapshot = snapshotWithOptions({});

export function shallowSnapshot({ story, context }) {
  const shallowRenderer = shallow.createRenderer();
  const result = shallowRenderer.render(story.render(context));
  expect(result).toMatchSnapshot();
}

export function renderOnly({ story, context }) {
  const storyElement = story.render(context);
  renderer.create(storyElement);
}

CHANGE path of the snapshots files into the regular `__snapshots__` folder


const assert = require('assert');

const Account     = require('../Contents/Scripts/account.js').Account;
const PullRequest = require('../Contents/Scripts/pull-request.js').PullRequest;
const Repository  = require('../Contents/Scripts/repository.js').Repository;

describe('PullRequest', function() {
  let number = '42';
  let title  = 'Fix off by one bug';

  let owner       = new Account('obama');
  let repo        = new Repository(owner, 'whitehouse.gov', 'My blog.');
  let pullRequest = new PullRequest(number, repo, title);

  describe('#url', function() {
    it('returns the URL of the pull request', function() {
      assert.equal('https://github.com/obama/whitehouse.gov/pull/42', pullRequest.url);
    });
  });

  describe('#shortURL', function() {
    it('returns the short URL of the pull request', function() {
      assert.equal('obama/whitehouse.gov#42', pullRequest.shortURL);
    });
  });

  describe('#toMenuItem()', function() {
    it('returns a JSON formatted menu item for LaunchBar', function() {
      assert.deepEqual({
        title: 'Fix off by one bug',
        subtitle: 'obama/whitehouse.gov#42',
        alwaysShowsSubtitle: true,
        icon: 'pull-request.png',
        url: 'https://github.com/obama/whitehouse.gov/pull/42',
      }, pullRequest.toMenuItem());
    });
  });
});

const assert = require('assert');

const Account     = require('../Contents/Scripts/account.js').Account;
const PullRequest = require('../Contents/Scripts/pull-request.js').PullRequest;
const Repository  = require('../Contents/Scripts/repository.js').Repository;

describe('PullRequest', function() {
  let number = '42';
  let title  = 'Fix off by one bug';

  let owner       = new Account('obama');
  let repo        = new Repository(owner, 'whitehouse.gov', 'My blog.');
  let pullRequest = new PullRequest(number, repo, title);

  describe('#url', function() {
    it('returns the URL of the pull request', function() {
      assert.equal('https://github.com/obama/whitehouse.gov/pull/42', pullRequest.url);
    });
  });

  describe('#shortURL', function() {
    it('returns the short URL of the pull request', function() {
      assert.equal('obama/whitehouse.gov#42', pullRequest.shortURL);
    });
  });

  describe('#toMenuItem()', function() {
    it('returns a JSON formatted menu item for LaunchBar', function() {
      assert.deepEqual({
        title: 'Fix off by one bug',
        subtitle: 'obama/whitehouse.gov#42',
        alwaysShowsSubtitle: true,
        icon: 'pullRequestTemplate.png',
        url: 'https://github.com/obama/whitehouse.gov/pull/42',
      }, pullRequest.toMenuItem());
    });
  });
});

Update expected icon name in tests


define([
  'views/root',
  'backbone'
], function (RootView, Backbone) {
  return new (Backbone.Router.extend({
    routes: {

    }
  }));
});

define([
  'views/root',
  'backbone'
], function (RootView, Backbone) {
  return Backbone.Router.extend({
    routes: {

    }
  });
});

Return router class, instead of instance


//= require jquery.tokeninput

$(document).ready(function(){
  $('input.token-input').tokenInput(function($input){
    var modelName = $input.data("model-name");
    return "/admin/autocomplete/"+modelName;
  }, {
    minChars: 3,
    propertyToSearch: "value",
    theme: "facebook",
    tokenLimit: 1,
    preventDuplicates: true
  });
});

//= require jquery.tokeninput

$(document).ready(function(){

  function enableTokenizer() {
    $('input.token-input').tokenInput(function($input){
      var modelName = $input.data("model-name");
      return "/admin/autocomplete/"+modelName;
    }, {
      minChars: 3,
      propertyToSearch: "value",
      theme: "facebook",
      tokenLimit: 1,
      preventDuplicates: true
    });
  }

  function enablePagination() {
    $('.resource-table-footer a').on('ajax:success', function(e, data, status, xhr) {
     $('.relationship-table').replaceWith(data);
     enablePagination();
     enableTokenizer();
    });
  }

  enablePagination();
  enableTokenizer();
});

Allow pagination on edit pages.


var configFilePath = process.argv[2];
var config = {};

if (configFilePath) {
  try {
    config = require(configFilePath);
  } catch(e) {
    console.log('Cannot load '+configFilePath);
    process.exit(1);
  }
}

var utils = require('./lib')(config);

var path = require('path');
var configFilePath = process.argv[2];
var config = {};

if (configFilePath) {
  configFilePath = path.resolve(configFilePath);

  try {
    config = require(configFilePath);
  } catch(e) {
    console.log('Cannot load '+configFilePath);
    process.exit(1);
  }
}

var utils = require('./lib')(config);

Resolve configuration file to absolute path before loading


import React from 'react';
import Button from 'react-bootstrap/Button';

import '../../styles/add-trip.css'

/**
 * Component corresponding to add trips modal.
 */
const AddTrip = (props) => {
  const showHideClass = props.show ? "modal-container show" :
                                     "modal-container hide";

  /**
   * Creates a new Trip document, adds it to Firestore, and closes the modal.
   */
  function submitNewTrip() {
    // TODO(Issue #43): Create doc and add to firestore here

    props.handleClose();
  }

  return (

        {/* TODO(Issue #43): Add trips functionality */}

Enter the stuff for a new trip: ...

Enter the stuff for a new trip: ...

Enter the stuff for a new trip: ...

Enter the stuff for a new trip: ...

Enter the stuff for a new trip: ...

Enter the stuff for a new trip: ...

        <Button type='button' onClick={submitNewTrip}>
          Submit New Trip
        </Button>

        <Button type='button' onClick={props.handleClose}>
          Close
        </Button>

  );
};

export default AddTrip;

import React from 'react';
import Button from 'react-bootstrap/Button';

import '../../styles/add-trip.css'

/**
 * Component corresponding to add trips modal.
 */
const AddTrip = (props) => {
  const showHideClass = props.show ? "modal-container show" :
                                     "modal-container hide";

  /**
   * Creates a new Trip document, adds it to Firestore, and closes the modal.
   */
  function submitNewTrip() {
    // TODO(Issue #43): Create doc and add to firestore here

    props.handleClose();
  }

  return (

        {/* TODO(Issue #43): Add trips functionality */}

Enter the stuff for a new trip: ...

        <Button type='button' onClick={submitNewTrip}>
          Submit New Trip
        </Button>

        <Button type='button' onClick={props.handleClose}>
          Close
        </Button>

  );
};

export default AddTrip;

Delete duplicate filler text in modal.


'use strict'

module.exports = new AsyncState()

function AsyncState () {
  var tracing
  var state = this

  try {
    tracing = require('tracing')
  } catch (e) {
    tracing = require('./lib/tracing_polyfill.js')
  }

  tracing.addAsyncListener({
    'create': asyncFunctionInitialized,
    'before': asyncCallbackBefore,
    'error': function () {},
    'after': asyncCallbackAfter
  })

  function asyncFunctionInitialized () {
    var data = {}
    for (var key in state) {
      data[key] = state[key]
    }
    return data
  }

  function asyncCallbackBefore (context, data) {
    for (var key in data) {
      state[key] = data[key]
    }
  }

  function asyncCallbackAfter (context, data) {
    for (var key in state) {
      delete state[key]
    }
  }
}

'use strict'

module.exports = new AsyncState()

function AsyncState () {
  var tracing
  var state = this

  try {
    tracing = require('tracing')
  } catch (e) {
    tracing = require('./lib/tracing_polyfill.js')
  }

  tracing.addAsyncListener({
    'create': asyncFunctionInitialized,
    'before': asyncCallbackBefore,
    'error': function () {},
    'after': asyncCallbackAfter
  })

  function asyncFunctionInitialized () {
    // Record the state currently set on on the async-state object and return a
    // snapshot of it. The returned object will later be passed as the `data`
    // arg in the functions below.

    var data = {}
    for (var key in state) {
      data[key] = state[key]
    }
    return data
  }

  function asyncCallbackBefore (context, data) {
    // We just returned from the event-loop: We'll now restore the state
    // previously saved by `asyncFunctionInitialized`.

    for (var key in data) {
      state[key] = data[key]
    }
  }

  function asyncCallbackAfter (context, data) {
    // Clear the state so that it doesn't leak between isolated async stacks.

    for (var key in state) {
      delete state[key]
    }
  }
}

Improve code comments for undocumented API


require('./common');

var
  DB_NAME = 'node-couchdb-test',

  callbacks = {
    A: false,
    B1: false,
    B2: false,
    C: false,
  },

  db = client.db(DB_NAME);

// Init fresh db
db.remove();
db
  .create(function(er) {
    if (er) throw er;

    var stream = db.changesStream();
    stream
      .addListener('change', function(change) {
        callbacks['B'+change.seq] = true;
        if (change.seq == 2) {
          stream.close();
        }
      })
      .addListener('end', function() {
        callbacks.C = true;
      });
  });

db.saveDoc({test: 1});
db.saveDoc({test: 2});

db.changes({since: 1}, function(er, r) {
  if (er) throw er;
  callbacks.A = true;
  assert.equal(2, r.results[0].seq);
  assert.equal(1, r.results.length);
});

process.addListener('exit', function() {
  checkCallbacks(callbacks);
});

require('./common');

var
  DB_NAME = 'node-couchdb-test',

  callbacks = {
    A: false,
    B1: false,
    B2: false,
    C: false,
  },

  db = client.db(DB_NAME);

// Init fresh db
db.remove();
db
  .create(function(er) {
    if (er) throw er;

    var stream = db.changesStream();
    stream
      .addListener('data', function(change) {
        callbacks['B'+change.seq] = true;
        if (change.seq == 2) {
          stream.close();
        }
      })
      .addListener('end', function() {
        callbacks.C = true;
      });
  });

db.saveDoc({test: 1});
db.saveDoc({test: 2});

db.changes({since: 1}, function(er, r) {
  if (er) throw er;
  callbacks.A = true;
  assert.equal(2, r.results[0].seq);
  assert.equal(1, r.results.length);
});

process.addListener('exit', function() {
  checkCallbacks(callbacks);
});

Update the event name in the test, too.


var yaml = require('js-yaml');

module.exports = function (source) {
  this.cacheable && this.cacheable();
  var res = yaml.safeLoad(source);
  return JSON.stringify(res, undefined, '\t');
};

var yaml = require('js-yaml');

module.exports = function (source) {
  this.cacheable && this.cacheable();
  var res = yaml.safeLoad(source);
  return 'module.exports = ' + JSON.stringify(res, undefined, '\t');
};

Revert "Return JSON as string. yaml-loader should be chained with json-loader."


'use strict';

var exec = require('child_process').exec;
var plist = require('simple-plist');

module.exports = function(path, callback){
  exec('codesign -d --entitlements :- ' + path, function(error, output){
    if(error){
      return callback(error);
    }

    callback(null, plist.parse(output));
  });
};

'use strict';

var plist = require('simple-plist');
var os = require('os').platform();

module.exports = function(path, callback){
  if(os === 'darwin') {
    var exec = require('child_process').exec;
    exec('codesign -d --entitlements :- ' + path, function(error, output){
      if(error){
        return callback(error);
      }
      console.log(output);
      callback(null, plist.parse(output));
    });
  } else {
    console.log('nothing');
    callback(null, {});
  }
};

Check if node is running on darwin before running a darwin only command


var Transform = require( 'stream' ).Transform;

var MyactStreamTransform = module.exports = function( keyProperty ) {
    Transform.call( this, { objectMode: true });

    this.keyProperty = keyProperty;
};

MyactStreamTransform.prototype = Object.create( Transform.prototype );

MyactStreamTransform.prototype._transform = function( chunk, encoding, done ) {
    this.push({
        key: chunk[ this.keyProperty ],
        data: chunk
    });

    done();
};

var Transform = require( 'stream' ).Transform;

var MyactStreamTransform = module.exports = function( key ) {
    Transform.call( this, { objectMode: true });

    if ( 'undefined' === typeof key ) key = 'id';
    this.keyProperty = key;
};

MyactStreamTransform.prototype = Object.create( Transform.prototype );

MyactStreamTransform.prototype._transform = function( chunk, encoding, done ) {
    this.push({
        key: chunk[ this.keyProperty ],
        data: chunk
    });

    done();
};

Use "id" as default key if none provided


const test = require("ava")
const header = require("../../lib/utils/")
const themes = require("../../lib/themes")
const pkg = require("../../package.json")

let darkThemeHeader = null

test.before(() => {
  darkThemeHeader = header.header(themes[0])
})

test("license comment", t => {
  t.regex(darkThemeHeader, /\/\*!/, "Header is missing the license special comment /*!")
})

test("theme name", t => {
  t.regex(darkThemeHeader, /GitHub Dark/)
})

test("the current package version", t => {
  t.regex(darkThemeHeader, new RegExp(pkg.version))
})

test("correct year", t => {
  t.regex(darkThemeHeader, /2016/)
})

test("package author", t => {
  t.regex(darkThemeHeader, new RegExp(pkg.author))
})

test("mentions the license", t => {
  t.regex(darkThemeHeader, /Licensed under MIT/)
})

const test = require("ava")
const header = require("../../lib/utils/")
const themes = require("../../lib/themes")
const pkg = require("../../package.json")

let darkThemeHeader = null

test.before(() => {
  darkThemeHeader = header.header(themes[0])
})

test("license comment", t => {
  t.regex(darkThemeHeader, /\/\*!/, "Header is missing the license special comment /*!")
})

test("theme name", t => {
  t.regex(darkThemeHeader, /GitHub Dark/)
})

test("the current package version", t => {
  t.regex(darkThemeHeader, new RegExp(pkg.version))
})

test("correct year", t => {
  t.regex(darkThemeHeader, /2017/)
})

test("package author", t => {
  t.regex(darkThemeHeader, new RegExp(pkg.author))
})

test("mentions the license", t => {
  t.regex(darkThemeHeader, /Licensed under MIT/)
})

Update copyright year to make tests pass


'use strict';

var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var NanoTimer = require('nanotimer');

function StepSequencer(tempo, division, sequence) {
  this.tempo = tempo;
  this.division = division;
  this.sequence = sequence;
  this.step = 0;
  this.timer = new NanoTimer();
  this.timeout = Math.floor((60 / (tempo * division)) * 10e8) + 'n';
  EventEmitter.call(this);
}

inherits(StepSequencer, EventEmitter);

StepSequencer.prototype._advance = function () {
  this.emit('' + this.step, this.sequence[this.step]);
  this.step = (this.step + 1);
  if (this.step === this.sequence.length) this.step = 0;
}

StepSequencer.prototype.play = function () {
  var self = this;
  self.step = 0;
  self.timer.setInterval(function () {
    self._advance.call(self);
  }, '', self.timeout);
};

StepSequencer.prototype.resume = function () {
  var self = this;
  self.timer.setInterval(function () {
    self._advance.call(self);
  }, '', self.timeout);
};

StepSequencer.prototype.stop = function () {
  this.timer.clearInterval();
};

module.exports = StepSequencer;

'use strict';

var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var NanoTimer = require('nanotimer');

function StepSequencer(tempo, division, sequence) {
  this.tempo = tempo || 120;
  this.division = division || 4;
  this.sequence = sequence || [];
  this.step = 0;
  this.timer = new NanoTimer();
  this.timeout = Math.floor((60 / (tempo * division)) * 10e8) + 'n';
  EventEmitter.call(this);
}

inherits(StepSequencer, EventEmitter);

StepSequencer.prototype._advance = function () {
  this.emit('' + this.step, this.sequence[this.step]);
  this.step = (this.step + 1);
  if (this.step === this.sequence.length) this.step = 0;
}

StepSequencer.prototype.play = function () {
  var self = this;
  self.step = 0;
  self.timer.setInterval(function () {
    self._advance.call(self);
  }, '', self.timeout);
};

StepSequencer.prototype.resume = function () {
  var self = this;
  self.timer.setInterval(function () {
    self._advance.call(self);
  }, '', self.timeout);
};

StepSequencer.prototype.stop = function () {
  this.timer.clearInterval();
};

module.exports = StepSequencer;

Set defaults for tempo, division, sequence


Kwf.onElementReady('.kwcStatisticsOptBox', function(el, config) {
    if (Kwf.Statistics.getDefaultOptValue() == 'out' && !Kwf.Statistics.issetUserOptValue()) {
        el.slideIn('t', { duration: 2 });
    }
});

Kwf.onElementReady('.kwcStatisticsOptBox', function(el, config) {
    if (Kwf.Statistics.getDefaultOptValue() == 'out' && !Kwf.Statistics.issetUserOptValue()) {
//        el.slideIn('t', { easing: 'linear', duration: 1.25 });
        el.show(true);
    }
});

Disable Animation on OptBox because of not so smooth movement


const {app, BrowserWindow, protocol} = require('electron')

require('electron-debug')({ showDevTools: true, enable: true })

var path = require('path')

var main = null

app.on('ready', function () {
  main = new BrowserWindow({
    height: 720,
    resizable: true,
    title: 'sciencefair',
    width: 1050,
    titleBarStyle: 'hidden',
    fullscreen: false,
    icon: './icon/logo.png',
    show: false
  })

  main.setMenu(null)

  main.maximize()

  main.loadURL(path.join('file://', __dirname, '/app/index.html'))

  // hack to avoid a blank white window showing briefly at startup
  // hide the window until content is loaded
  main.webContents.on('did-finish-load', () => {
    setTimeout(() => main.show(), 40)
  })

  main.on('close', event => {
    main.webContents.send('quitting')
  })

  main.on('closed', function () {
    main = null
  })

  protocol.registerFileProtocol('sciencefair', (request, callback) => {
    console.log(request.url)
    callback()
  }, error => {
    if (error) console.error('Failed to register protocol')
  })
})

app.on('window-all-closed', () => app.quit())

Quit app on window close


document.write('

');

Ember.testing = true;

var App = requireModule('appkit/app');

App.rootElement = '#ember-testing';
App.setupForTesting();
App.injectTestHelpers();

function exists(selector) {
  return !!find(selector).length;
}

window.exists = exists;

Ember.Container.prototype.stub = function(fullName, instance) {
  instance.destroy = instance.destroy || function() {};
  this.cache.dict[fullName] = instance;
};

document.write('

');

Ember.testing = true;

var App = requireModule('appkit/app');

App.rootElement = '#ember-testing';
App.setupForTesting();
App.injectTestHelpers();

function exists(selector) {
  return !!find(selector).length;
}

function equal(actual, expected, message) {
  message = message || QUnit.jsDump.parse(expected) + " expected but was " + QUnit.jsDump.parse(actual);
  QUnit.equal.call(this, expected, actual, message);
}

window.exists = exists;
window.equal = equal;

Ember.Container.prototype.stub = function(fullName, instance) {
  instance.destroy = instance.destroy || function() {};
  this.cache.dict[fullName] = instance;
};

Fix QUnit output for equal assertions


var GLOBAL_DAILY_LIMIT = 50000;
var GLOBAL_HOURLY_LIMIT = 10000;
var USER_DAILY_LIMIT = 500;
var USER_HOURLY_LIMIT = 3;

var RateLimiter = require('./ratelimiter');

var rl = new RateLimiter("fitbit", GLOBAL_DAILY_LIMIT, GLOBAL_HOURLY_LIMIT, USER_DAILY_LIMIT, USER_HOURLY_LIMIT);

var blocked = function(limits) {
	// Add the request to retry scheduler or something
	console.log("LIMIT REACHED! Type: " + limits);
};

var allowed = function(uid) {
	// Let the request go through
	console.log("REQUEST ALLOWED for user: " + uid);
};

rl.call('123456', allowed, blocked);

Add callbacks for allow and block functions with a test call


var lib = require('phosphide/index');

lib.listPlugins().then((plugins) => {
    console.log(plugins);
    lib.loadPlugin('foo').then(() => {
        console.log('foo finished loading');
        lib.loadPlugin('bar').then(() => {
            console.log('bar finished loading');
            lib.unloadPlugin('bar');
            lib.unloadPlugin('foo');
        });
    });
});

var lib = require('phosphide/index');

lib.listPlugins().then((plugins) => {
    console.log(plugins);
    lib.loadPlugin('foo').then(() => {
        console.log('foo finished loading');
        lib.loadPlugin('bar').then(() => {
            console.log('bar finished loading');
            lib.unloadPlugin('bar');
            lib.unloadPlugin('foo');
            console.log('all plugins unloaded');
        });
    });
});

Add another log statement to example


const Gitter = require('./gitter');
const Lexicon = require('./lexicon');

const client = new Gitter();

function listenAndReply(roomId) {
  client.readChatMessages(roomId, (message) => {
    if (message.operation === 'create') {
      if (message.model.fromUser.id === process.env.GITTER_LEXICON_USER_ID) return;

      let prefix = '';

      if (roomId === process.env.GITTER_ESOUI_ROOM_ID) {
        if (!/^@?lex(icon)?/.test(message.model.text)) return;
        prefix += `@${message.model.fromUser.username} `;
      }

      Lexicon.parse(message.model.text, (text) => {
        client.sendChatMessage(roomId, `${prefix}${text}`);
      });
    }
  });
}

const rooms = [];

client.watchRoomUpdates(process.env.GITTER_LEXICON_USER_ID, (message) => {
  const roomId = message.model.id;
  if (message.operation === 'create' || message.operation === 'patch') {
    if (message.operation === 'create') {
      Lexicon.parse('', (text) => {
        client.sendChatMessage(roomId, text);
      });
    }
    if (rooms.indexOf(roomId) < 0) {
      listenAndReply(roomId);
      rooms.push(roomId);
    }
  }
});

listenAndReply(process.env.GITTER_ESOUI_ROOM_ID);

const Gitter = require('./gitter');
const Lexicon = require('./lexicon');

const client = new Gitter();

function listenAndReply(roomId) {
  client.readChatMessages(roomId, (data) => {
    if (data.operation === 'create') {
      if (data.model.fromUser.id === process.env.GITTER_LEXICON_USER_ID) return;

      let prefix = '';
      let message = data.model.text;

      if (roomId === process.env.GITTER_ESOUI_ROOM_ID) {
        const match = message.match(/^@?lex(icon)?(\s(.+)|$)/);
        if (!match) return;
        prefix += `@${data.model.fromUser.username} `;
        message = match[3];
      }

      Lexicon.parse(message, (text) => {
        client.sendChatMessage(roomId, `${prefix}${text}`);
      });
    }
  });
}

const rooms = [];

client.watchRoomUpdates(process.env.GITTER_LEXICON_USER_ID, (message) => {
  const roomId = message.model.id;
  if (message.operation === 'create' || message.operation === 'patch') {
    if (message.operation === 'create') {
      Lexicon.parse('', (text) => {
        client.sendChatMessage(roomId, text);
      });
    }
    if (rooms.indexOf(roomId) < 0) {
      listenAndReply(roomId);
      rooms.push(roomId);
    }
  }
});

listenAndReply(process.env.GITTER_ESOUI_ROOM_ID);

Fix bot trying to parse message with lexicon prefix


/**
 * @typedef {import('./lib/index.js').StringifyEntitiesLightOptions} StringifyEntitiesLightOptions
 * @typedef {import('./lib/index.js').StringifyEntitiesOptions} StringifyEntitiesOptions
 */

export * from './lib/index.js'

Add options to exported types


'use strict';

exports.register = function register(skill) {
  skill.onIntent('LaunchIntent', () => ({ reply: 'Intent.Launch', to: 'entry' }));
  skill.onIntent('AMAZON.HelpIntent', () => ({ reply: 'Intent.Help', to: 'die' }));
};

'use strict';

const Reply = require('voxa').Reply;

exports.register = function register(skill) {
  // This event is triggered after new session has started
  skill.onSessionStarted((alexaEvent) => {});
  // This event is triggered before the current session has ended
  skill.onSessionEnded((alexaEvent) => {});
  // This can be used to plug new information in the request
  skill.onRequestStated((alexaEvent) => {});

  // This handler will catch all errors generated when trying to make transitions in the stateMachine, this could include errors in the state machine controllers
  skill.onStateMachineError((alexaEvent, reply, error) =>
    // it gets the current reply, which could be incomplete due to an error.
    new Reply(alexaEvent, { tell: 'An error in the controllers code' }).write(),
  );

  // This is the more general handler and will catch all unhandled errors in the framework
  skill.onError((alexaEvent, error) =>
    new Reply(alexaEvent, { tell: 'An unrecoverable error occurred.' }).write(),
  );

  // This event is triggered before every time the user response to an Alexa event...
  // and it have two parameters, alexaEvent and the reply controller.
  skill.onBeforeReplySent((alexaEvent, reply) => { });
  // Launch intent example
  skill.onIntent('LaunchIntent', () => ({ reply: 'Intent.Launch', to: 'entry' }));
  // AMAMAZON Build-in Help Intent example
  skill.onIntent('AMAZON.HelpIntent', () => ({ reply: 'Intent.Help', to: 'die' }));
};

Use onSessionHandlers, also onError, onStateMachineError and onBeforeReplySent


import React from 'react'
import {Breadcrumb} from 'react-bootstrap'
import {IndexLinkContainer as Link} from 'react-router-bootstrap'

export default class Breadcrumbs extends React.Component {
	makeItem(item) {
		return (
			<Link key={item[0]} to={item[1]}>
				<Breadcrumb.Item>{item[0]}</Breadcrumb.Item>
			</Link>
		)
	}

	render() {
		let {items, ...props} = this.props

		return (
			<Breadcrumb {...props}>
				{this.makeItem(['ANET', '/'])}
				{items.map(this.makeItem)}
			</Breadcrumb>
		)
	}
}

Breadcrumbs.defaultProps = {items: []}

import React from 'react'
import {Breadcrumb} from 'react-bootstrap'
import {IndexLinkContainer as Link} from 'react-router-bootstrap'

export default class Breadcrumbs extends React.Component {
	makeItem(item) {
		return (
			<Link key={item[1]} to={item[1]}>
				<Breadcrumb.Item>{item[0]}</Breadcrumb.Item>
			</Link>
		)
	}

	render() {
		let {items, ...props} = this.props

		return (
			<Breadcrumb {...props}>
				{this.makeItem(['ANET', '/'])}
				{items.map(this.makeItem)}
			</Breadcrumb>
		)
	}
}

Breadcrumbs.defaultProps = {items: []}

Use URL as unique key for breadcrumb items


App.Slide = DS.Model.extend({
    url: 'banners',

    title: DS.attr('string'),
    body: DS.attr('string'),
    image: DS.attr('string'),
    imageBackground: DS.attr('string'),
    video: DS.attr('string'),

    language: DS.attr('string'),

    sequence: DS.attr('number'),
    style: DS.attr('string'),
    tab_text: DS.attr('string'),
    link_text: DS.attr('string'),
    link_url: DS.attr('string'),
    isFirst: function() {
        var lowestValue = null;
        App.Slide.find().forEach(function(slide) {
            var sequence = slide.get("sequence");
            if(lowestValue == null || sequence < lowestValue)
                lowestValue = sequence;
        });
        return (this.get('sequence') === lowestValue);
    }.property('@each.sequence')
});

App.Slide = DS.Model.extend({
    url: 'banners',

    title: DS.attr('string'),
    body: DS.attr('string'),
    image: DS.attr('string'),
    imageBackground: DS.attr('string'),
    video: DS.attr('string'),

    language: DS.attr('string'),

    sequence: DS.attr('number'),
    style: DS.attr('string'),
    tab_text: DS.attr('string'),
    link_text: DS.attr('string'),
    link_url: DS.attr('string'),
    isFirst: function() {
        return (this.get('sequence') == 1);
    }.property('sequence')
});

Simplify how to determine if it's first slide