import alt from "alt-instance"
import WalletUnlockActions from "actions/WalletUnlockActions"
import WalletDb from "stores/WalletDb"

class WalletUnlockStore {

    constructor() {
        this.bindActions(WalletUnlockActions)
        this.state = {locked: true}
    }

    onUnlock({resolve, reject}) {
        let locked = WalletDb.isLocked()
        if(!locked) resolve(true)
        else this.setState({resolve, reject, locked})
    }

    onLock({resolve}) {
        //DEBUG console.log('... WalletUnlockStore\tprogramatic lock', WalletDb.isLocked())
        if(WalletDb.isLocked()) {
            resolve(false)
            return
        }
        WalletDb.onLock()
        resolve(true)
        this.setState({resolve:null, reject:null, locked: WalletDb.isLocked()})
    }

    onCancel() {
        this.setState({resolve:null, reject:null})
    }

    onChange() {
        this.setState({locked: WalletDb.isLocked()})
    }
}

export default alt.createStore(WalletUnlockStore, 'WalletUnlockStore')

import alt from "alt-instance"
import WalletUnlockActions from "actions/WalletUnlockActions"
import WalletDb from "stores/WalletDb"

class WalletUnlockStore {

    constructor() {
        this.bindActions(WalletUnlockActions)
        this.state = {locked: true}
    }

    onUnlock({resolve, reject}) {
        //DEBUG console.log('... onUnlock setState', WalletDb.isLocked())
        if( ! WalletDb.isLocked()) {
            resolve(false)
            return
        }
        this.setState({resolve, reject, locked: WalletDb.isLocked()})
    }

    onLock({resolve}) {
        //DEBUG console.log('... WalletUnlockStore\tprogramatic lock', WalletDb.isLocked())
        if(WalletDb.isLocked()) {
            resolve(false)
            return
        }
        WalletDb.onLock()
        resolve(true)
        this.setState({resolve:null, reject:null, locked: WalletDb.isLocked()})
    }

    onCancel() {
        this.setState({resolve:null, reject:null})
    }

    onChange() {
        this.setState({locked: WalletDb.isLocked()})
    }
}

export default alt.createStore(WalletUnlockStore, 'WalletUnlockStore')

Revert "fix transfer form unlock wallet issue"


var fs = require("fs")
var path = require("path")

var Parser = require("./index")

module.exports = less

function less(location, callback, preprocess) {
    var options = {
        paths: [path.dirname(location)],
        filename: path.basename(location),
        preprocess: preprocess || passThrough
    }
    var parser = new Parser(options)

    fs.readFile(location, {encoding: "utf8"}, function (err, src) {
        if (err) {
            return callback(err)
        }

        src = options.preprocess(location, src)
        parser.parse(src, callback)
    })
}

function passThrough (file, src) {
    return src
}

var fs = require("fs")
var path = require("path")

var Parser = require("./index")

module.exports = less

function less(location, options, callback) {
    if (typeof options === 'function') {
        callback = options
        options = {}
    }

    options.paths = options.paths || [path.dirname(location)]
    options.filename = options.filename || path.basename(location)
    options.preprocess = options.preprocess || passThrough

    var parser = new Parser(options)

    fs.readFile(location, {encoding: "utf8"}, function (err, src) {
        if (err) {
            return callback(err)
        }

        src = options.preprocess(location, src)
        parser.parse(src, callback)
    })
}

function passThrough (file, src) {
    return src
}

Switch params order and allow full options object


(function($) {
	$.fn.FlowupLabels = function( options ){

		var defaults = {
        		// Useful if you pre-fill input fields or if localstorage/sessionstorage is used.
				feature_onLoadInit: false,
				// Class names used for focus and populated statuses
				class_focused: 		'focused',
				class_populated: 	'populated'
			},
			settings = $.extend({}, defaults, options);

		return this.each(function(){
			var $scope  = $(this);

			$scope.on('focus.flowupLabelsEvt', '.fl_input', function() {
				$(this).parent().addClass(settings.class_focused);
			})
			.on('blur.flowupLabelsEvt', '.fl_input', function() {
				var $this = $(this);

				if ($this.val().length) {
					$this.parent()
						.addClass(settings.class_populated)
						.removeClass(settings.class_focused);
				}
				else {
					$this.parent()
						.removeClass(settings.class_populated + ' ' + settings.class_focused);
				}
			});

			// On page load, make sure it looks good
			if (settings.feature_onLoadInit) {
				$scope.find('.fl_input').trigger('blur.flowupLabelsEvt');
			}
		});
	};
})( jQuery );

(function($) {
	$.fn.FlowupLabels = function( options ){

		var defaults = {
        		// Useful if you pre-fill input fields or if localstorage/sessionstorage is used.
				feature_onLoadInit: false,
				// Class names used for focus and populated statuses
				class_focused: 		'focused',
				class_populated: 	'populated'
			},
			settings = $.extend({}, defaults, options);

		return this.each(function(){
			var $scope  = $(this);

			$scope.on('focus.flowupLabelsEvt', '.fl_input', function() {
				$(this).closest('.fl_wrap').addClass(settings.class_focused);
			})
			.on('blur.flowupLabelsEvt', '.fl_input', function() {
				var $this = $(this);

				if ($this.val().length) {
					$this.closest('.fl_wrap')
						.addClass(settings.class_populated)
						.removeClass(settings.class_focused);
				}
				else {
					$this.closest('.fl_wrap')
						.removeClass(settings.class_populated + ' ' + settings.class_focused);
				}
			});

			// On page load, make sure it looks good
			if (settings.feature_onLoadInit) {
				$scope.find('.fl_input').trigger('blur.flowupLabelsEvt');
			}
		});
	};
})( jQuery );

Update classes of closest('.fl_wrap') instead of parent()


Router.configure({
  layoutTemplate: 'layout',
  loadingTemplate: 'loading',
  notFoundTemplate: 'notFound',
  waitOn: function() {
    return [Meteor.subscribe('posts'), Meteor.subscribe('comments')];
  }
});

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

Router.route('/posts/:_id', {
  name: 'postPage',
  data: function() {
    return Posts.findOne(this.params._id);
  }
});

Router.route('posts/:_id/edit', {
  name: 'postEdit',
  data: function() {
    return Posts.findOne(this.params._id);
  }
});

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

var requireLogin = function() {
  if (!Meteor.user()) {
    if (Meteor.loggingIn()) {
      this.render(this.loadingTemplate);
    } else {
      this.render('accessDenied');
    }
  } else {
    this.next();
  }
}

Router.onBeforeAction('dataNotFound', { only: 'postPage' });
Router.onBeforeAction(requireLogin, { only: 'postSubmit' });

Router.configure({
  layoutTemplate: 'layout',
  loadingTemplate: 'loading',
  notFoundTemplate: 'notFound',
  waitOn: function() {
    return Meteor.subscribe('posts');
  }
});

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

Router.route('/posts/:_id', {
  name: 'postPage',

  waitOn: function() {
    return Meteor.subscribe('comments', this.params._id);
  },

  data: function() {
    return Posts.findOne(this.params._id);
  }
});

Router.route('posts/:_id/edit', {
  name: 'postEdit',
  data: function() {
    return Posts.findOne(this.params._id);
  }
});

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

var requireLogin = function() {
  if (!Meteor.user()) {
    if (Meteor.loggingIn()) {
      this.render(this.loadingTemplate);
    } else {
      this.render('accessDenied');
    }
  } else {
    this.next();
  }
}

Router.onBeforeAction('dataNotFound', { only: 'postPage' });
Router.onBeforeAction(requireLogin, { only: 'postSubmit' });

Make comments collection load only when loading a post


/* global $ */

'use strict';

angular.module('memoApp')
  .controller('MemoEditCtrl', function ($scope, $routeParams, memoService) {
    $('.nav li').removeClass('active');
    $('#nav-memos').addClass('active');

    var height = (window.innerHeight - $('#md_area').offset().top - 100) + 'px';
    // console.log(height);
    $('#md_area').height(height);
    $('#html_area').height(height);

    $scope.file = $routeParams.file + '.md';
    // console.log($scope.file);

    var file = 'memos/' + $scope.file;
    var index = file.lastIndexOf('/');
    if (index !== -1) {
      $scope.dir = file.substr(0, index);
      $scope.dirSplit = memoService.getDirSplit($scope.dir);
    }

    $scope.memo = null;

    memoService.watch($scope.file);

    memoService.load($scope.file, function (memo) {
      // console.log(memo);
      $scope.memo = memo;
      $scope.$apply();
    });

    $('#save').click(function () {
      // console.log('Save');
      memoService.save($scope.file, $scope.memo);
    });
  });

/* global $ */

'use strict';

angular.module('memoApp')
  .controller('MemoEditCtrl', function ($scope, $routeParams, memoService) {
    $('.nav li').removeClass('active');
    $('#nav-memos').addClass('active');

    function resize() {
      var height = (window.innerHeight - $('#md_area').offset().top - 100) + 'px';
      // console.log(height);

      $('#md_area').height(height);
      $('#html_area').height(height);
    }
    window.onresize = resize;
    resize();

    $scope.file = $routeParams.file + '.md';
    // console.log($scope.file);

    var file = 'memos/' + $scope.file;
    var index = file.lastIndexOf('/');
    if (index !== -1) {
      $scope.dir = file.substr(0, index);
      $scope.dirSplit = memoService.getDirSplit($scope.dir);
    }

    $scope.memo = null;

    memoService.watch($scope.file);

    memoService.load($scope.file, function (memo) {
      // console.log(memo);
      $scope.memo = memo;
      $scope.$apply();
    });

    $('#save').click(function () {
      // console.log('Save');
      memoService.save($scope.file, $scope.memo);
    });
  });

Adjust elements' height for window size


import React, { Component } from 'react'
import { connect } from 'react-redux'
import { evaluateText } from '../actions'
import TextArea from '../components/TextArea'

const mapStateToProps = function(state) {
  return {
    currentPattern: state.currentPattern
  }
}

class MainTextContainer extends Component {
  render() {
    return (
      <TextArea
        name="input"
        title="Test Text"
        onChange={ (event) => this.props.onTextChange(this.props.currentPattern, event.target.value) }
      />
    )
  }
}

export default connect(mapStateToProps,
  (dispatch) => {
    return {
      onTextChange: (pattern, text) => {
        dispatch(evaluateText(pattern, text))
      }
    }
  })(MainTextContainer)

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { evaluateText } from '../actions'
import TextArea from '../components/TextArea'

const mapStateToProps = function(state) {
  return {
    currentPattern: state.currentPattern
  }
}

class MainTextContainer extends Component {
  render() {
    return (
      <TextArea
        name="input"
        title="Test Text"
      />
    )
  }
}

export default connect(mapStateToProps,
  (dispatch) => {
    return {
      onTextChange: (pattern, text) => {
        dispatch(evaluateText(pattern, text))
      }
    }
  })(MainTextContainer)

Remove blasting API on each keyPress. Fix TBD.


'use strict';

var zone = require('mdast-zone');
var u = require('unist-builder');
var sort = require('alphanum-sort/lib/compare');
var gemoji = require('gemoji').unicode;
var emotion = require('..');

module.exports = support;

function support() {
  return transformer;
}

function transformer(tree) {
  zone(tree, 'support', replace);
}
function replace(start, nodes, end) {
  console.log('repl!');
  return [start, table(), end];
}

function table() {
  var header = ['Emoji', 'Name(s)', 'Polarity'];

  return u('table', {align: []}, [
    u('tableRow', header.map(cell))
  ].concat(
    emotion
      .sort(function (a, b) {
        return a.polarity - b.polarity ||
          sort({}, gemoji[a.emoji].name, gemoji[b.emoji].name);
      })
      .map(function (emotion) {
        return u('tableRow', [
          cell(emotion.emoji),
          cell(gemoji[emotion.emoji].names.join('; ')),
          cell(emotion.polarity)
        ]);
      })
  ));
}

function cell(value) {
  return u('tableCell', [u('text', value)]);
}

'use strict';

var zone = require('mdast-zone');
var u = require('unist-builder');
var sort = require('alphanum-sort/lib/compare');
var gemoji = require('gemoji').unicode;
var emotion = require('..');

module.exports = support;

function support() {
  return transformer;
}

function transformer(tree) {
  zone(tree, 'support', replace);
}

function replace(start, nodes, end) {
  return [start, table(), end];
}

function table() {
  var header = ['Emoji', 'Name(s)', 'Polarity'];

  return u('table', {align: []}, [
    u('tableRow', header.map(cell))
  ].concat(
    emotion
      .sort(function (a, b) {
        return a.polarity - b.polarity ||
          sort({}, gemoji[a.emoji].name, gemoji[b.emoji].name);
      })
      .map(function (emotion) {
        return u('tableRow', [
          cell(emotion.emoji),
          cell(gemoji[emotion.emoji].names.join('; ')),
          cell(emotion.polarity)
        ]);
      })
  ));
}

function cell(value) {
  return u('tableCell', [u('text', value)]);
}

Remove call to `console.log` in build script


if (Meteor.isServer) {
  const _resetDatabase = function (options) {
    if (process.env.NODE_ENV !== 'development') {
      throw new Error(
        'resetDatabase is not allowed outside of a development mode. ' +
        'Aborting.'
      );
    }

    options = options || {};
    var excludedCollections = ['system.indexes'];
    if (options.excludedCollections) {
      excludedCollections = excludedCollections.concat(options.excludedCollections);
    }

    var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
    var getCollections = Meteor.wrapAsync(db.collections, db);
    var collections = getCollections();
    var appCollections = _.reject(collections, function (col) {
      return col.collectionName.indexOf('velocity') === 0 ||
        excludedCollections.indexOf(col.collectionName) !== -1;
    });

    _.each(appCollections, function (appCollection) {
      var remove = Meteor.wrapAsync(appCollection.remove, appCollection);
      remove({}, {});
    });
  };

  Meteor.methods({
    'xolvio:cleaner/resetDatabase': function (options) {
      _resetDatabase(options);
    }
  });

  resetDatabase = function(options, callback) {
    _resetDatabase(options);
    if (typeof callback === 'function') { callback(); }
  }

}
if (Meteor.isClient) {
  resetDatabase = function(options, callback) {
    Meteor.call('xolvio:cleaner/resetDatabase', options, callback);
  }
}

if (Meteor.isServer) {
  const _resetDatabase = function (options) {
    if (process.env.NODE_ENV !== 'development') {
      throw new Error(
        'resetDatabase is not allowed outside of a development mode. ' +
        'Aborting.'
      );
    }

    options = options || {};
    var excludedCollections = ['system.indexes'];
    if (options.excludedCollections) {
      excludedCollections = excludedCollections.concat(options.excludedCollections);
    }

    var db = options.db || MongoInternals.defaultRemoteCollectionDriver().mongo.db;
    var getCollections = Meteor.wrapAsync(db.collections, db);
    var collections = getCollections();
    var appCollections = _.reject(collections, function (col) {
      return col.collectionName.indexOf('velocity') === 0 ||
        excludedCollections.indexOf(col.collectionName) !== -1;
    });

    _.each(appCollections, function (appCollection) {
      var remove = Meteor.wrapAsync(appCollection.remove, appCollection);
      remove({}, {});
    });
  };

  Meteor.methods({
    'xolvio:cleaner/resetDatabase': function (options) {
      _resetDatabase(options);
    }
  });

  resetDatabase = function(options, callback) {
    _resetDatabase(options);
    if (typeof callback === 'function') { callback(); }
  }

}
if (Meteor.isClient) {
  resetDatabase = function(options, callback) {
    Meteor.call('xolvio:cleaner/resetDatabase', options, callback);
  }
}

Allow user to set a different database


// @flow weak

const WebpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');
const webpackConfig = require('./webpack.dev.config');

const serverOptions = {
  publicPath: webpackConfig.output.publicPath,
  hot: true,
  historyApiFallback: true,
  stats: { colors: true },
};

new WebpackDevServer(webpack(webpackConfig), serverOptions)
  .listen(3000, '0.0.0.0', (err) => {
    if (err) {
      return console.log(err); // eslint-disable-line no-console
    }

    return console.info('Webpack dev server listening at http://0.0.0.0:3000/'); // eslint-disable-line no-console
  });

// @flow weak

const WebpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');
const webpackConfig = require('./webpack.dev.config');

const serverOptions = {
  publicPath: webpackConfig.output.publicPath,
  hot: true,
  historyApiFallback: true,
  stats: {
    modules: false,
    chunks: false,
    chunkModules: false,
    colors: true,
  },
};

new WebpackDevServer(webpack(webpackConfig), serverOptions)
  .listen(3000, '0.0.0.0', (err) => {
    if (err) {
      return console.log(err); // eslint-disable-line no-console
    }

    return console.info('Webpack dev server listening at http://0.0.0.0:3000/'); // eslint-disable-line no-console
  });

Reduce the size of the wepback output in the console


import axios from 'axios';
import createApiUrl from 'helpers/createApiUrl';
import queryParams from 'query-params';

const apiUrl = (requestPath) => {
  const prefix = process.env.API_URL;

  if (!prefix) {
    throw new Error('Please define an API_URL in .env');
  }

  return createApiUrl(prefix, requestPath);
};

export default {
  request: (requestPath = '', data = {}, method = 'GET') => {
    if (method === 'GET') {
      const requestParams = queryParams.encode(data);

      if (requestParams.length > 0) {
        requestPath += `?${requestParams}`;
        data = {};
      }
    }

    return axios({
      method: method,
      url: apiUrl(requestPath),
      data: data
    });
  }
};

import axios from 'axios';
import createApiUrl from 'helpers/createApiUrl';
import queryParams from 'query-params';

const apiUrl = (requestPath) => {
  const prefix = process.env.API_URL;

  if (!prefix) {
    throw new Error('Please define an API_URL in .env');
  }

  return createApiUrl(prefix, requestPath);
};

export default {
  request: (requestPath = '', data = {}, method = 'GET') => {
    if (method === 'GET') {
      const requestParams = queryParams.encode(data);

      if (requestParams.length > 0) {
        requestPath += `?${requestParams}`;
        data = {};
      }
    }

    return axios({
      method: method,
      url: apiUrl(requestPath),
      data: data,
      withCredentials: true
    });
  }
};

Add important withCredentials flag to API calls


import Sound from 'react-sound';

import {
  START_PLAYBACK,
  PAUSE_PLAYBACK,
  UPDATE_PLAYBACK_PROGRESS,
  UPDATE_SEEK
} from '../actions/player';

const initialState = {
  playbackStatus: Sound.status.PAUSED,
  playbackProgress: 0,
  seek: 0
};

export default function PlayerReducer(state=initialState, action) {
  switch(action.type) {
    case START_PLAYBACK:
      return Object.assign({}, state, {
        playbackStatus: Sound.status.PLAYING
      });
    case PAUSE_PLAYBACK:
      return Object.assign({}, state, {
        playbackStatus: Sound.status.PAUSED
      });
    case UPDATE_PLAYBACK_PROGRESS:
      return Object.assign({}, state, {
        playbackProgress: action.payload.progress,
        seek: action.payload.seek
      });
    case UPDATE_SEEK:
      return Object.assign({}, state, {
        seek: action.payload
      });
    default:
      return state;
  }
}

import Sound from 'react-sound';

import {
  START_PLAYBACK,
  PAUSE_PLAYBACK,
  UPDATE_PLAYBACK_PROGRESS,
  UPDATE_SEEK
} from '../actions/player';

import {
  NEXT_SONG,
  PREVIOUS_SONG,
  SELECT_SONG
} from '../actions/queue';

const initialState = {
  playbackStatus: Sound.status.PAUSED,
  playbackProgress: 0,
  seek: 0
};

export default function PlayerReducer(state=initialState, action) {
  switch(action.type) {
    case START_PLAYBACK:
      return Object.assign({}, state, {
        playbackStatus: Sound.status.PLAYING
      });
    case PAUSE_PLAYBACK:
      return Object.assign({}, state, {
        playbackStatus: Sound.status.PAUSED
      });
    case UPDATE_PLAYBACK_PROGRESS:
      return Object.assign({}, state, {
        playbackProgress: action.payload.progress,
        seek: action.payload.seek
      });
    case UPDATE_SEEK:
      return Object.assign({}, state, {
        seek: action.payload
      });
    case NEXT_SONG:
    case PREVIOUS_SONG:
    case SELECT_SONG:
      console.log('derp');
      return Object.assign({}, state, {
        playbackProgress: 0,
        seek: 0
      });
    default:
      return state;
  }
}

Reset seek progress on song change


// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React from 'react';
import Animation from '../animation';

/**
 * Property type for the animation.
 */
export const AnimationPropType = React.PropTypes.oneOfType([
  React.PropTypes.shape(Animation.propTypes),
  React.PropTypes.oneOf([true])
]);

// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React from 'react';
import Animation from '../animation';

/**
 * Property type for the animation.
 */
export const AnimationPropType = React.PropTypes.oneOfType([
  React.PropTypes.shape(Animation.propTypes),
  React.PropTypes.bool
]);

Allow boolean values for Animation PropType


/**
 * Return an array of function of the form x => { type, payload = x }
 */
export function createActions(...types) {
  return types.map(createAction)
}

/**
 * Create a single action of the form x => { type, payload = x }
 */
export function createAction(type) {
  return (payload, error) => (Object.assign({ type }, payload ? { payload } : {}, error ? { error } : {}))
}

/**
 * Create a single action of the form x => { type }
 * Use this for actions that do not carry any payload, this is useful especially if the action is
 * going to be used as an event handler to avoid accidentally including the DOM event as payload.
 */
export function createAction0(type) {
  return () => ({ type })
}

/**
 * Return an array of action creators.
 * Use this for actions that do not carry any payload.
 *
 * @returns {Array}
 */
export function creactActions0(...types) {
  return types.map(createAction0)
}

/**
 * Return an array of function of the form x => { type, payload = x }
 */
export function createActions(...types) {
  return types.map(createAction)
}

/**
 * Create a single action of the form x => { type, payload = x }
 *
 * @returns {Array}
 */
export function createAction(type) {
  return (payload, error) => (Object.assign({ type }, payload ? { payload } : {}, error ? { error } : {}))
}

/**
 * Create a single action of the form x => { type }
 * Use this for actions that do not carry any payload, this is useful especially if the action is
 * going to be used as an event handler to avoid accidentally including the DOM event as payload.
 */
export function createAction0(type) {
  return () => ({ type })
}

/**
 * Return an array of action creators.
 * Use this for actions that do not carry any payload.
 *
 * @returns {Array}
 */
export function creactActions0(...types) {
  return types.map(createAction0)
}

Add newline in file to keep github happy


const uppy = require('uppy-server')
const app = require('express')()

app.use(require('cors')())
app.use(require('body-parser').json())

app.use(uppy.app({
  providerOptions: {
    s3: {
      getKey: (req, filename) =>
        `whatever/${Math.random().toString(32).slice(2)}/${filename}`,
      key: process.env.UPPYSERVER_AWS_KEY,
      secret: process.env.UPPYSERVER_AWS_SECRET,
      bucket: process.env.UPPYSERVER_AWS_BUCKET,
      region: process.env.UPPYSERVER_AWS_REGION
    }
  },
  server: { host: 'localhost:3020' }
}))

app.listen(3020, () => {
  console.log('listening on port 3020')
})

const uppy = require('uppy-server')
const app = require('express')()

app.use(require('cors')())
app.use(require('body-parser').json())

const options = {
  providerOptions: {
    s3: {
      getKey: (req, filename) =>
        `whatever/${Math.random().toString(32).slice(2)}/${filename}`,
      key: process.env.UPPYSERVER_AWS_KEY,
      secret: process.env.UPPYSERVER_AWS_SECRET,
      bucket: process.env.UPPYSERVER_AWS_BUCKET,
      region: process.env.UPPYSERVER_AWS_REGION
    }
  },
  server: { host: 'localhost:3020' }
}

app.use(uppy.app(options))

const server = app.listen(3020, () => {
  console.log('listening on port 3020')
})

uppy.socket(server, options)

Add missing uppy.socket call to S3 example


'use strict';

import React from 'react';
import { browserHistory } from 'react-router';

import EnterpriseSummary from './EnterpriseSummaryComponent.js';

class DirectoryComponent extends React.Component {
  render() {
    var directory = this.props.directory,
      jsx = [];

    // Directory hasn't loaded yet, display 'loading' notice
    if (directory === null) {
      jsx.push(
Loading...
);
    } else {
      jsx.push(
        directory.map(function(enterprise, index) {
          return (

              <EnterpriseSummary enterprise={enterprise} />

          );
        })
      );
    }

    return (

        Back

          {jsx}

    );
  }
}

DirectoryComponent.displayName = 'DirectoryComponent';

// Uncomment properties you need
// DirectoryComponent.propTypes = {};
// DirectoryComponent.defaultProps = {};

export default DirectoryComponent;

'use strict';

import React from 'react';
import { browserHistory } from 'react-router';

import EnterpriseSummary from './EnterpriseSummaryComponent.js';

class DirectoryComponent extends React.Component {
  render() {
    var directory = this.props.directory,
      jsx = [],
      app = this;

    // Directory hasn't loaded yet, display 'loading' notice
    if (directory === null) {
      jsx.push(
Loading...
);
    } else {
      jsx.push(
        directory.map(function(enterprise) {
          return (

              <EnterpriseSummary enterprise={enterprise} api_root={app.props.config.api_root} />

          );
        })
      );
    }

    return (

        Back

          {jsx}

    );
  }
}

DirectoryComponent.displayName = 'DirectoryComponent';

// Uncomment properties you need
// DirectoryComponent.propTypes = {};
// DirectoryComponent.defaultProps = {};

export default DirectoryComponent;

Fix logos on 'Browse' page ('/directory')


chrome.app.runtime.onLaunched.addListener(function() {
  var screenWidth = Math.round(screen.width * 0.75);
  var screenHeight = Math.round(screen.height * 0.75);

  chrome.app.window.create('app.html', {
    outerBounds: {
      width: screenWidth,
      height: screenHeight
    }
  });
});

chrome.app.runtime.onLaunched.addListener(function() {
  var windowOptions = {
    outerBounds: {
      width: Math.round(screen.width * 0.75),
      height: Math.round(screen.height * 0.75)
    }
  };

  chrome.app.window.create('app.html', windowOptions);
});

Move window options into a variable


var React = require("react")

export default class Errors extends React.Component {
  displayName = "FriggingBootstrap.Errors"

  static defaultProps = require("../default_props.js")

  render() {
    return (

        {
          this.props.errors.map((error) => {
            return (
              <div className="col-xs-12" key={`error-${error}`}>
                <div className="frigb-error" ref={`error-${error}`}>

                    Error:
                    {` ${error}`}

            )
          })
        }

    )
  }

}

var React = require("react")

export default class Errors extends React.Component {
  displayName = "FriggingBootstrap.Errors"

  static defaultProps = require("../default_props.js")

  render() {
    return (

        {
          (this.props.errors||[]).map((error) => {
            return (
              <div className="col-xs-12" key={`error-${error}`}>
                <div className="frigb-error" ref={`error-${error}`}>

                    Error:
                    {` ${error}`}

            )
          })
        }

    )
  }

}

Fix Error Component When Theres No Errors


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

import React from 'react';
import PropTypes from 'prop-types';

import { connect } from 'react-redux';

import { Keyboard } from 'react-native';

import { getCurrentRouteName } from '../navigation/selectors';

const mapStateToProps = ({ nav }) => ({ currentRouteName: getCurrentRouteName(nav) });

const extractPropsForPage = props => {
  const { currentRouteName, screenProps, navigation, ...restOfProps } = props;
  const { navigate, goBack, state } = navigation;
  const { params, routeName: thisPageRouteName, ...restOfNavigationState } = state;
  const isCurrentRoute = thisPageRouteName === currentRouteName;
  const navigateTo = (routeName, title, otherParams, type = 'push') => {
    Keyboard.dismiss(); // Dismiss keyboard before navigating to a different scene.
    const push = () => navigate(routeName, { title, ...otherParams });
    const navigationFunctions = {
      push,
      replace: () => {
        goBack();
        push();
      },
      goBack,
    };
    const navigationFunction = navigationFunctions[type] || push;
    navigationFunction();
  };
  return {
    topRoute: isCurrentRoute,
    ...screenProps,
    ...params,
    ...restOfNavigationState,
    navigateTo,
    ...restOfProps,
    navigation,
  };
};

function Page(props) {
  const { page: SpecificPage } = props;
  return <SpecificPage {...extractPropsForPage(props)} />;
}

export const PageContainer = connect(mapStateToProps)(Page);

export default PageContainer;

Page.propTypes = {
  // eslint-disable-next-line react/forbid-prop-types, react/require-default-props
  page: PropTypes.any,
};

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

import React from 'react';
import PropTypes from 'prop-types';

import { connect } from 'react-redux';

import { getCurrentRouteName } from '../navigation/selectors';

const mapStateToProps = ({ nav }) => ({ currentRouteName: getCurrentRouteName(nav) });

const extractPropsForPage = props => {
  const { currentRouteName, screenProps, navigation, ...restOfProps } = props;
  const { state } = navigation;
  const { params, routeName: thisPageRouteName, ...restOfNavigationState } = state;
  const isCurrentRoute = thisPageRouteName === currentRouteName;

  return {
    topRoute: isCurrentRoute,
    ...screenProps,
    ...params,
    ...restOfNavigationState,
    ...restOfProps,
    navigation,
  };
};

function Page(props) {
  const { page: SpecificPage } = props;
  return <SpecificPage {...extractPropsForPage(props)} />;
}

export const PageContainer = connect(mapStateToProps)(Page);

export default PageContainer;

Page.propTypes = {
  // eslint-disable-next-line react/forbid-prop-types, react/require-default-props
  page: PropTypes.any,
};

Add removal of uneeded function in props


module.exports = {
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "extends": "eslint:recommended",
  "parserOptions": {
    "ecmaFeatures": {
      "experimentalObjectRestSpread": true,
      "jsx": true
    },
    "sourceType": "module"
  },
  "plugins": [
    "react"
  ],
  "rules": {
    "indent": [
      "error",
      2
    ],
    "linebreak-style": [
      "error",
      "unix"
    ],
    "quotes": [
      "error",
      "double"
    ],
    "semi": [
      "error",
      "never"
    ]
  }
}

module.exports = {
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "extends": ["eslint:recommended", "plugin:react/recommended"],
  "parserOptions": {
    "ecmaFeatures": {
      "experimentalObjectRestSpread": true,
      "jsx": true
    },
    "sourceType": "module"
  },
  "plugins": [
    "react"
  ],
  "rules": {
    "indent": [
      "error",
      2
    ],
    "linebreak-style": [
      "error",
      "unix"
    ],
    "quotes": [
      "error",
      "double"
    ],
    "semi": [
      "error",
      "never"
    ]
  }
}

Use recommended react plugin for eslint


'use strict'

const { env, platform } = process

module.exports = {
  extends: [
    'eslint:recommended',
    'eslint-config-airbnb-base',
    'plugin:prettier/recommended',
  ],

  env: {
    es6: true,
    jest: true,
    node: true,
  },

  parserOptions: {
    sourceType: 'script',
  },

  rules: {
    // TODO FIXME workaround for git + prettier line ending issue on Travis for Windows OS:
    ...(env.TRAVIS &&
      platform === 'win32' && {
        'prettier/prettier': ['error', { endOfLine: 'auto' }],
      }),

    // TODO FIXME turn off temporary, to make eslint pass
    'class-methods-use-this': 'off',
    'consistent-return': 'off',
    'no-console': 'off',
    'no-multi-assign': 'off',
    'no-param-reassign': 'off',
    'no-restricted-syntax': 'off',
    'no-shadow': 'off',
    'no-underscore-dangle': 'off',
    'no-use-before-define': 'off',
  },
}

'use strict'

const { env, platform } = process

module.exports = {
  extends: [
    'eslint:recommended',
    'eslint-config-airbnb-base',
    'plugin:prettier/recommended',
  ],

  env: {
    jest: true,
  },

  parserOptions: {
    sourceType: 'script',
  },

  rules: {
    // TODO FIXME workaround for git + prettier line ending issue on Travis for Windows OS:
    ...(env.TRAVIS &&
      platform === 'win32' && {
        'prettier/prettier': ['error', { endOfLine: 'auto' }],
      }),

    // TODO FIXME turn off temporary, to make eslint pass
    'class-methods-use-this': 'off',
    'consistent-return': 'off',
    'no-console': 'off',
    'no-multi-assign': 'off',
    'no-param-reassign': 'off',
    'no-restricted-syntax': 'off',
    'no-shadow': 'off',
    'no-underscore-dangle': 'off',
    'no-use-before-define': 'off',
  },
}

Remove unused eslint env options


'use strict';

module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      legacyDecorators: true
    }
  },
  plugins: [
    'ember'
  ],
  extends: [
    'eslint:recommended',
    'plugin:ember/recommended'
  ],
  env: {
    browser: true
  },
  rules: {
    "no-control-regex": "off",
    "no-console": ["error", { allow: ["warn", "debug", "error"] }],
    "ember/no-observers": 1,
    'ember/no-jquery': 'error'
  },
  overrides: [
    // node files
    {
      files: [
        '.eslintrc.js',
        '.template-lintrc.js',
        'ember-cli-build.js',
        'testem.js',
        'config/**/*.js',
        'lib/*/index.js',
        'server/**/*.js'
      ],
      parserOptions: {
        sourceType: 'script'
      },
      env: {
        browser: false,
        node: true
      },
      rules: {
        // this can be removed once the following is fixed
        // https://github.com/mysticatea/eslint-plugin-node/issues/77
        'node/no-unpublished-require': 'off'
      }
    }
  ]
};

'use strict';

module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      legacyDecorators: true
    }
  },
  plugins: [
    'ember'
  ],
  extends: [
    'eslint:recommended',
    'plugin:ember/recommended'
  ],
  env: {
    browser: true
  },
  rules: {
    "no-control-regex": "off",
    "no-console": ["error", { allow: ["warn", "debug", "error"] }],
    "ember/no-observers": "warn",
    "ember/no-jquery": "error",
    "ember/no-string-prototype-extensions": "warn",
    "ember/no-controller-access-in-routes": "warn",
    "ember/no-classic-classes": "warn",
    "ember/no-computed-properties-in-native-classes": "warn",
    "ember/require-tagless-components": "warn",
    "ember/no-assignment-of-untracked-properties-used-in-tracking-contexts": "warn",
    "ember/no-side-effects": "warn"
  },
  overrides: [
    // node files
    {
      files: [
        '.eslintrc.js',
        '.template-lintrc.js',
        'ember-cli-build.js',
        'testem.js',
        'config/**/*.js',
        'lib/*/index.js',
        'server/**/*.js'
      ],
      parserOptions: {
        sourceType: 'script'
      },
      env: {
        browser: false,
        node: true
      },
      rules: {
        // this can be removed once the following is fixed
        // https://github.com/mysticatea/eslint-plugin-node/issues/77
        'node/no-unpublished-require': 'off'
      }
    }
  ]
};

Configure JS linter warnings for Ember deprecations


// http://eslint.org/docs/user-guide/configuring

module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    sourceType: 'module'
  },
  env: {
    browser: true,
  },
  // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
  extends: 'airbnb',
  // required to lint *.vue files
  plugins: [
    'html'
  ],
  // add your custom rules here
  'rules': {
    // allow paren-less arrow functions
    'arrow-parens': 0,
    // allow async-await
    'generator-star-spacing': 0,
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
  }
}

// http://eslint.org/docs/user-guide/configuring

module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    sourceType: 'module'
  },
  env: {
    browser: true,
  },
  // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
  extends: [
    "plugin:vue/essential",
    "plugin:prettier/recommended",
    "eslint:recommended"
  ],
  // required to lint *.vue files
  plugins: [
    'html'
  ],
  // add your custom rules here
  'rules': {
    // allow paren-less arrow functions
    'arrow-parens': 0,
    // allow async-await
    'generator-star-spacing': 0,
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
  }
}

Set ESLint config to Vue rules


define ({
    title: 'Titre',
    format: 'Format',
    layout: 'Disposition',
    settings: 'Paramètres',
    mapScaleExtent: 'Échelle/étendue de la carte',
    preserve: 'Préserver',
    mapScale: 'échelle de la carte',
    mapExtent: 'étendue de la carte',
    fullLayoutOptions: 'Options complètes de mise en page',
    scaleBarUnits: 'Unités de la barre d\'échelle',
    miles: 'Miles',
    kilometers: 'Kilomètres',
    meters: 'Mètres',
    feet: 'Pieds',
    includeLegend: 'Inclure la légende',
    printQualityOptions: 'Qualité d`impression',
    dpiInput: {
        label: 'DPI',
        invalidMessage: 'S\'il vous plaît entrer une valeur numérique.',
        rangeMessage: 'S\'il vous plaît entrer une valeur entre 100 et 300.'
    },
    mapOnlyOptions: 'Options MAP_ONLY',
    width: 'Largeur',
    height: 'Hauteur',
    printButton: {
        busyLabel: 'Impression',
        label: 'Imprimer'
    },
    clearHistoryButton: {
        label: 'Effacer l\'historique d\'impression'
    },
    printResults: {
        progressBar: {
            label: 'Création de l\'impression'
        },
        errorMessage: 'Erreur, réessayez'
    }
});

define ({
    title: 'Titre',
    format: 'Format',
    layout: 'Disposition',
    settings: 'Paramètres',
    mapScaleExtent: 'Échelle/étendue de la carte',
    preserve: 'Préserver',
    mapScale: 'échelle de la carte',
    mapExtent: 'étendue de la carte',
    fullLayoutOptions: 'Options complètes de mise en page',
    scaleBarUnits: 'Unités de la barre d\'échelle',
    miles: 'Miles',
    kilometers: 'Kilomètres',
    meters: 'Mètres',
    feet: 'Pieds',
    includeLegend: 'Inclure la légende',
    printQualityOptions: 'Qualité d`impression',
    dpiInput: {
        label: 'DPI',
        invalidMessage: 'S\\\'il vous plaît entrer une valeur numérique.',
        rangeMessage: 'S\\\'il vous plaît entrer une valeur entre 100 et 300.'
    },
    mapOnlyOptions: 'Options MAP_ONLY',
    width: 'Largeur',
    height: 'Hauteur',
    printButton: {
        busyLabel: 'Impression',
        label: 'Imprimer'
    },
    clearHistoryButton: {
        label: 'Effacer l\'historique d\'impression'
    },
    printResults: {
        progressBar: {
            label: 'Création de l\\'impression'
        },
        errorMessage: 'Erreur, réessayez'
    }
});

Fix the Print widget french language translations


$(document).ready(function () {
  var currentUser = {};

  $('form').on('submit', function () {
    var userName = $('input#user-name').val();
    io.socket.post('/user', { name: userName }, function (user, jwres) {
      currentUser = user;
    });

    return false;
  });

  $('#login-modal').modal({
    backdrop: 'static',
    keyboard: false,
  });
});

$(document).ready(function () {
  var currentUser = {};

  $('form').on('submit', function () {
    var userName = $('input#user-name').val();
    io.socket.post('/user', { name: userName }, function (user, jwres) {
      currentUser = user;
      $('#login-modal').modal('hide');
    });

    return false;
  });

  $('#login-modal').modal({
    backdrop: 'static',
    keyboard: false,
  });
});

Hide modal on valid user creation


import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';

const { service } = Ember.inject;

export default Ember.Route.extend(ApplicationRouteMixin, {
	sessionAccount: service('session-account'),

	beforeModel() {
		return this._loadCurrentUser();
	},

	model: function() {
		return Ember.Object.create ({
			genres: this.store.findAll('genre'),
			factTypes: this.store.findAll('factType'),
			categories: this.store.findAll('category'),
			topics: this.store.findAll('topic')
		});
	},

	sessionAuthenticated() {
		this._super(...arguments);
		this._loadCurrentUser().catch(() => this.get('session').invalidate());
	},

	_loadCurrentUser() {
		return this.get('sessionAccount').loadCurrentUser();
	}
});

import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';

const { service } = Ember.inject;

export default Ember.Route.extend(ApplicationRouteMixin, {
	session:        service('session'),
	sessionAccount: service('session-account'),

	beforeModel() {
		return this._loadCurrentUser();
	},

	model: function() {
		return Ember.Object.create ({
			genres: this.store.findAll('genre'),
			factTypes: this.store.findAll('factType'),
			categories: this.store.findAll('category'),
			topics: this.store.findAll('topic')
		});
	},

	sessionAuthenticated() {
		this._super(...arguments);
		this._loadCurrentUser().catch(() => this.get('session').invalidate());
	},

	_loadCurrentUser() {
		return this.get('sessionAccount').loadCurrentUser();
	}
});

Fix undefined is not an object for Application route


L.Control.EasyButtons = L.Control.extend({
    options: {
        position: 'topleft',
        title: '',
        intentedIcon: 'fa-circle-o'
    },

    onAdd: function () {
        var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control');

        this.link = L.DomUtil.create('a', 'leaflet-bar-part', container);
        L.DomUtil.create('i', 'fa fa-lg ' + this.options.intentedIcon , this.link);
        this.link.href = '#';

        L.DomEvent.on(this.link, 'click', this._click, this);
        this.link.title = this.options.title;

        return container;
    },

    intendedFunction: function(){ alert('no function selected');},

    _click: function (e) {
        L.DomEvent.stopPropagation(e);
        L.DomEvent.preventDefault(e);
        this.intendedFunction();
    },
});

L.easyButton = function( btnIcon , btnFunction , btnTitle , btnMap ) {
  var newControl = new L.Control.EasyButtons;
  if (btnIcon) newControl.options.intentedIcon = btnIcon;

  if ( typeof btnFunction === 'function'){
    newControl.intendedFunction = btnFunction;
  }

  if (btnTitle) newControl.options.title = btnTitle;

  if ( btnMap ){
    newControl.addTo(btnMap);
  } else {
    newControl.addTo(map);
  }
};

L.Control.EasyButtons = L.Control.extend({
    options: {
        position: 'topleft',
        title: '',
        intentedIcon: 'fa-circle-o'
    },

    onAdd: function () {
        var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control');

        this.link = L.DomUtil.create('a', 'leaflet-bar-part', container);
        L.DomUtil.create('i', 'fa fa-lg ' + this.options.intentedIcon , this.link);
        this.link.href = '#';

        L.DomEvent.on(this.link, 'click', this._click, this);
        this.link.title = this.options.title;

        return container;
    },

    intendedFunction: function(){ alert('no function selected');},

    _click: function (e) {
        L.DomEvent.stopPropagation(e);
        L.DomEvent.preventDefault(e);
        this.intendedFunction();
    },
});

L.easyButton = function( btnIcon , btnFunction , btnTitle , btnMap ) {
  var newControl = new L.Control.EasyButtons;
  if (btnIcon) newControl.options.intentedIcon = btnIcon;

  if ( typeof btnFunction === 'function'){
    newControl.intendedFunction = btnFunction;
  }

  if (btnTitle) newControl.options.title = btnTitle;

  if ( btnMap ){
    newControl.addTo(btnMap);
  } else {
    newControl.addTo(map);
  }
  return newControl;
};

Return the newly created control


/*globals define*/
define([
  'physics/intersection'
], function( Intersection ) {
  'use strict';

  // Filter all physics entities that may be colliding.
  function broadphase( entities ) {
    var potentials = [];

    var aabb0,
        aabb1;

    entities.forEach(function( a ) {
      aabb0 = a.aabb();

      entities.forEach(function( b ) {
        if ( a === b ) {
          return;
        }

        aabb1 = b.aabb();

        if ( Intersection.aabb( aabb0, aabb1 ) ) {
          potentials.push( [ a, b ] );
        }
      });
    });

    return potentials;
  }

  function sort2d( a, b ) {
    if ( a.x === b.x ) {
      return a.y - b.y;
    }

    return a.x - b.x;
  }

  /**
   * Determine all unique pairs of entities.
   */
  function unique( pairsArray ) {
    pairsArray.forEach(function() {
      pairsArray.forEach(function() {
      });
    });
  }

  return {
    broadphase: broadphase,
    sort2d: sort2d
  };
});

/*globals define*/
define([
  'physics/intersection'
], function( Intersection ) {
  'use strict';

  // Filter all physics entities that may be colliding.
  function broadphase( entities ) {
    var potentials = [];

    var aabbs = entities.map(function( entity ) {
      return entity.aabb();
    });

    var length = entities.length;
    var i, j;
    var aabb0, aabb1;
    for ( i = 0; i < length; i++ ) {
      aabb0 = aabbs[i];

      for ( j = i + 1; j < length; j++ ) {
        aabb1 = aabbs[j];

        if ( Intersection.aabb( aabb0, aabb1 ) ) {
          potentials.push( [ entities[i], entities[j] ] );
        }
      }
    }

    return potentials;
  }

  function sort2d( a, b ) {
    if ( a.x === b.x ) {
      return a.y - b.y;
    }

    return a.x - b.x;
  }

  return {
    broadphase: broadphase,
    sort2d: sort2d
  };
});

Reduce amount of calculations in broadphase.


import { askWorker } from '../../utils';

export default function chessLogic(ctrl) {

  const worker = new Worker('vendor/scalachessjs.js');

  worker.addEventListener('message', function(msg) {
    const payload = msg.data.payload;

    switch (msg.data.topic) {
      case 'dests':
        ctrl.addDests(payload.dests, payload.path);
        break;
      case 'move':
        if (payload.path) {
          var sit = payload.situation;
          var step = {
            ply: sit.ply,
            dests: sit.dests,
            check: sit.check,
            fen: sit.fen,
            uci: sit.lastMove.uci,
            san: sit.pgnMoves[0]
          };
          ctrl.addStep(step, payload.path);
        }
        break;
    }
  });

  return {
    sendStepRequest(req) {
      worker.postMessage({ topic: 'move', payload: req });
    },
    sendDestsRequest(req) {
      worker.postMessage({ topic: 'dests', payload: req });
    },
    getSanMoveFromUci(req, callback) {
      askWorker(worker, { topic: 'move', payload: req }, callback);
    },
    onunload() {
      if (worker) worker.terminate();
    }
  };
}

import { askWorker } from '../../utils';

export default function chessLogic(ctrl) {

  const worker = new Worker('vendor/scalachessjs.js');

  worker.addEventListener('message', function(msg) {
    const payload = msg.data.payload;

    switch (msg.data.topic) {
      case 'dests':
        ctrl.addDests(payload.dests, payload.path);
        break;
      case 'move':
        if (payload.path) {
          var sit = payload.situation;
          var step = {
            ply: sit.ply,
            dests: sit.dests,
            check: sit.check,
            fen: sit.fen,
            uci: sit.uciMoves[0],
            san: sit.pgnMoves[0]
          };
          ctrl.addStep(step, payload.path);
        }
        break;
    }
  });

  return {
    sendStepRequest(req) {
      worker.postMessage({ topic: 'move', payload: req });
    },
    sendDestsRequest(req) {
      worker.postMessage({ topic: 'dests', payload: req });
    },
    getSanMoveFromUci(req, callback) {
      askWorker(worker, { topic: 'move', payload: req }, callback);
    },
    onunload() {
      if (worker) worker.terminate();
    }
  };
}

Fix chess logic in analysis


import React from 'react';
import PropTypes from 'prop-types';
import defaultIcon from '../../../../svg/kitware.svg';

export default function SvgIconWidget(props) {
  const style = Object.assign({}, props.style, {
    width: props.width,
    height: props.height,
  });
  return (
    <svg style={style} className={props.className} onClick={props.onClick}>
      <use xlinkHref={props.icon} />
    </svg>
  );
}

SvgIconWidget.propTypes = {
  className: PropTypes.string,
  height: PropTypes.string,
  icon: PropTypes.string,
  width: PropTypes.string,
  style: PropTypes.object,
  onClick: PropTypes.func,
};

SvgIconWidget.defaultProps = {
  className: '',
  icon: defaultIcon,
  style: {},
  height: undefined,
  width: undefined,
  onClick: undefined,
};

import React from 'react';
import PropTypes from 'prop-types';
import defaultIcon from '../../../../svg/kitware.svg';

export default function SvgIconWidget(props) {
  const { id, viewBox } = props.icon || {};
  let xlink = id || props.icon;
  if (xlink[0] !== '#') {
    xlink = `#${xlink}`;
  }
  const style = Object.assign({}, props.style, {
    width: props.width,
    height: props.height,
  });
  return (
    <svg
      viewBox={viewBox || props.viewBox}
      style={style}
      className={props.className}
      onClick={props.onClick}
    >
      <use xlinkHref={xlink} />
    </svg>
  );
}

SvgIconWidget.propTypes = {
  className: PropTypes.string,
  height: PropTypes.string,
  icon: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
  width: PropTypes.string,
  style: PropTypes.object,
  onClick: PropTypes.func,
  viewBox: PropTypes.string,
};

SvgIconWidget.defaultProps = {
  className: '',
  icon: defaultIcon,
  style: {},
  height: undefined,
  width: undefined,
  onClick: undefined,
  viewBox: '0 0 20 20',
};

Support old and new svg-loader


import test from 'ava';
import {transform} from 'babel-core';
import plugin from '..';

function isEqual (t, fixture, expected) {
    t.deepEqual(transform(fixture, {plugins: [plugin]}).code, expected);
}

function passthrough (t, fixture) {
    return isEqual(t, fixture, fixture);
}

test(
    'object property',
    isEqual,
    'const code = {at: "@".charCodeAt(0)};',
    'const code = { at: 64 };'
);

test(
    'variable declaration',
    isEqual,
    'const at = "@".charCodeAt(0);',
    'const at = 64;'
);

test(
    'template literal',
    isEqual,
    'const at = `@`.charCodeAt(0);',
    'const at = 64;'
);

test(
    'multiple variable declarations',
    isEqual,
    [
        'var minus = "-".charCodeAt(0);',
        'var plus = "+".charCodeAt(0);',
        'var dot = ".".charCodeAt(0);',
    ].join('\n'),
    'var minus = 45;\nvar plus = 43;\nvar dot = 46;'
);

test(
    'pass through template literals containing vars',
    passthrough,
    'const at = `${ someVar }`.charCodeAt(0);'
);

test(
    'pass through other identifiers',
    passthrough,
    'const add = plus.charCodeAt(0);'
);

test(
    'pass through other member expressions',
    passthrough,
    'const at = "@".slice(0);'
);

import test from 'ava';
import {transform} from 'babel-core';
import plugin from '..';

function isEqual (t, fixture, expected) {
    t.deepEqual(transform(fixture, {plugins: [plugin]}).code, expected);
}

function passthrough (t, fixture) {
    return isEqual(t, fixture, fixture);
}

test(
    'object property',
    isEqual,
    'const code = {at: "@".charCodeAt(0)};',
    'const code = { at: 64 };'
);

test(
    'variable declaration',
    isEqual,
    'const at = "@".charCodeAt(0);',
    'const at = 64;'
);

test(
    'template literal',
    isEqual,
    'const at = `@`.charCodeAt(0);',
    'const at = 64;'
);

test(
    'multiple variable declarations',
    isEqual,
    [
        'var minus = "-".charCodeAt(0);',
        'var plus = "+".charCodeAt(0);',
        'var dot = ".".charCodeAt(0);',
    ].join('\n'),
    'var minus = 45;\nvar plus = 43;\nvar dot = 46;'
);

test(
    'pass through template literals containing vars',
    passthrough,
    'const at = `${someVar}`.charCodeAt(0);'
);

test(
    'pass through other identifiers',
    passthrough,
    'const add = plus.charCodeAt(0);'
);

test(
    'pass through other member expressions',
    passthrough,
    'const at = "@".slice(0);'
);

Fix CI failure due to Babel's changed whitespace handling.


import {
  SET_SPEED,
  SET_PAUSE,
  SET_PAUSE_HOVER,
  SET_FALLOFF,
  SET_RADII_SCALE,
  SET_BOUNCE_BODIES,
  SET_BOUNCE_SCREEN,
} from './types'

export function speed(s) {
  return {
    type: SET_SPEED,
    speed: s,
  }
}

export function pause(pause) {
  return {
    type: SET_PAUSE,
    paused: pause,
  }
}

export function pauseHover(pause) {
  return {
    type: SET_PAUSE_HOVER,
    pauseHover: pause,
  }
}

export function falloff(f) {
  return {
    type: SET_FALLOFF,
    falloff: f,
  }
}

export function radiiScale(scale) {
  return {
    type: SET_RADII_SCALE,
    radiiScale: scale,
  }
}

export function bounceBodies(b) {
  return {
    type: SET_BOUNCE_BODIES,
    bounceBodies: b,
  }
}

export function bounceScreen(b) {
  return {
    type: SET_BOUNCE_SCREEN,
    bounceScreen: b,
  }
}

import {
  SET_SPEED,
  SET_PAUSE,
  SET_PAUSE_HOVER,
  SET_FALLOFF,
  SET_RADII_SCALE,
  SET_BOUNCE_BODIES,
  SET_BOUNCE_SCREEN,
} from './types'

export function speed(speed) {
  return {
    type: SET_SPEED,
    speed,
  }
}

export function pause(paused) {
  return {
    type: SET_PAUSE,
    paused,
  }
}

export function pauseHover(pauseHover) {
  return {
    type: SET_PAUSE_HOVER,
    pauseHover,
  }
}

export function falloff(falloff) {
  return {
    type: SET_FALLOFF,
    falloff,
  }
}

export function radiiScale(radiiScale) {
  return {
    type: SET_RADII_SCALE,
    radiiScale,
  }
}

export function bounceBodies(bounceBodies) {
  return {
    type: SET_BOUNCE_BODIES,
    bounceBodies,
  }
}

export function bounceScreen(bounceScreen) {
  return {
    type: SET_BOUNCE_SCREEN,
    bounceScreen,
  }
}

Clean up option action creators


var gulp            = require('gulp')
var gulpSequence    = require('gulp-sequence')
var getEnabledTasks = require('../lib/getEnabledTasks')
var os              = require('os')
var path            = require('path')

var productionTask = function(cb) {
  global.production = true

  // Build to a temporary directory, then move compiled files as a last step
  PATH_CONFIG.finalDest = PATH_CONFIG.dest
  PATH_CONFIG.dest = PATH_CONFIG.temp
    ? path.join(process.env.PWD, PATH_CONFIG.temp)
    : path.join(os.tmpdir(), 'gulp-starter');

  var tasks = getEnabledTasks('production')
  var rev = TASK_CONFIG.production.rev ? 'rev': false
  var static = TASK_CONFIG.static ? 'static' : false

  gulpSequence('clean', tasks.assetTasks, tasks.codeTasks, rev, 'size-report', static, 'replaceFiles', cb)
}

gulp.task('build', productionTask)
module.exports = productionTask

var gulp            = require('gulp')
var gulpSequence    = require('gulp-sequence')
var getEnabledTasks = require('../lib/getEnabledTasks')
var os              = require('os')
var fs              = require('fs')
var path            = require('path')

var productionTask = function(cb) {
  global.production = true

  // Build to a temporary directory, then move compiled files as a last step
  PATH_CONFIG.finalDest = PATH_CONFIG.dest
  PATH_CONFIG.dest = PATH_CONFIG.temp
      ? path.join(process.env.PWD, PATH_CONFIG.temp)
      : path.join(os.tmpdir(), 'gulp-starter');
  fs.mkdirSync(PATH_CONFIG.dest);

  var tasks = getEnabledTasks('production')
  var rev = TASK_CONFIG.production.rev ? 'rev': false
  var static = TASK_CONFIG.static ? 'static' : false

  gulpSequence('clean', tasks.assetTasks, tasks.codeTasks, rev, 'size-report', static, 'replaceFiles', cb)
}

gulp.task('build', productionTask)
module.exports = productionTask

Make sure the temp directory exists (otherwise error is thrown in WebpackManifest)


/**
 * @file courses-widget.js
 *  Additional js functions if the course-overview is loaded as separate widget
 */

/**
 * change normal overview page to separate widget page
 */
function initCourseOverviewWidget(){
  // add url-parameter widget=true to all course page links
  var aElememts = document.getElementsByTagName('a');
  for(var i = 0; i < aElememts.length; ++i) {
    //get old hrefs (that would change the widget)
    var href = aElememts[i].href;
    //check if link goes to model_viewer:
    if(href.indexOf("course.php") > -1){
        //change href
        aElememts[i].href = aElememts[i].href + "&widget=true";
    }
  }

  console.log("overview-widget: initialized widget");
}
//execute init when page is loaded
document.addEventListener('DOMContentLoaded', initCourseOverviewWidget, false);

/**
 * @file courses-widget.js
 *  Additional js functions if the course-overview is loaded as separate widget
 */

/**
 * change normal overview page to separate widget page
 */
function initCourseOverviewWidget(){
  // add url-parameter widget=true to all course page links
  var aElememts = document.getElementsByTagName('a');
  for(var i = 0; i < aElememts.length; ++i) {
    //get old hrefs (that would change the widget)
    var href = aElememts[i].href;
    //check if link goes to model_viewer:
    if(href.indexOf("course.php") > -1) {
      //check whether href already contains parameter and add new parameter accordingly
      if (href.indexOf("?") > -1) {
        aElememts[i].href = aElememts[i].href + "&widget=true";
      }
      else {
        aElememts[i].href = aElememts[i].href + "?widget=true";
      }
    }
  }

  console.log("overview-widget: initialized widget");
}
//execute init when page is loaded
document.addEventListener('DOMContentLoaded', initCourseOverviewWidget, false);

Add course: opens new page correctly in ROLE


#!/usr/bin/env node

var minimist = require('minimist')
var ss = require('../')

var argv = minimist(process.argv.slice(2), {
  boolean: 'close',
  alias: { t: 'timeout' }
})

var browser = ss()

var timeout = parseInt(argv['timeout'], 10)
if (argv['close'] && !timeout) timeout = 1

if (timeout) setupTimeout(browser, timeout)

process.stdin.on('end', function() {
  browser.end()
})

process.stdin
.pipe(browser, {end: false})
.pipe(process.stdout)

browser.on('close', function() {
  process.exit()
})

function setupTimeout(browser, timeout) {
  browser.write(
    'setTimeout(function() {window.close()}, '+timeout+')'
  )
  browser.once('spawn', function(child) {
    var kill = setTimeout(function() {
      child.kill('SIGTERM')
    }, timeout * 2)
    browser.once('connect', function() {
      clearTimeout(kill) // reset kill timeout if we connect
      setTimeout(function() {
        child.kill('SIGTERM')
      }, timeout * 2)
    })
  })
}

#!/usr/bin/env node

var minimist = require('minimist')
var ss = require('../')

var argv = minimist(process.argv.slice(2), {
  boolean: 'close',
  alias: { t: 'timeout' }
})

var browser = ss()

var timeout = parseInt(argv['timeout'], 10)
if (argv['close'] && !timeout) timeout = 1

if (timeout) setupTimeout(browser, timeout)

process.stdin.on('end', function() {
  browser.end()
})

process.stdin
.pipe(browser, {end: false})
.pipe(process.stdout)

browser.on('close', function() {
  process.exit()
})

function setupTimeout(browser, timeout) {
  browser.write(
    ';setTimeout(function() {window.close()}, '+timeout+');\n'
  )
  browser.once('spawn', function(child) {
    var kill = setTimeout(function() {
      child.kill('SIGTERM')
    }, timeout * 2)
    browser.once('connect', function() {
      clearTimeout(kill) // reset kill timeout if we connect
      setTimeout(function() {
        child.kill('SIGTERM')
      }, timeout * 2)
    })
  })
}

Make close script more robust.


import {cozyFetchJSON} from './fetch'
import { DOCTYPE_FILES } from './doctypes'

function updateRelations (verb) {
  return function (cozy, doc, ids) {
    if (!doc) throw new Error('missing doc argument')
    if (!Array.isArray(ids)) ids = [ids]

    const refs = ids.map((id) => ({type: DOCTYPE_FILES, id}))

    return cozyFetchJSON(cozy, verb, makeReferencesPath(doc), {data: refs})
  }
}

export const addReferencedFiles = updateRelations('POST')
export const removeReferencedFiles = updateRelations('DELETE')

export function listReferencedFiles (cozy, doc) {
  if (!doc) throw new Error('missing doc argument')
  return cozyFetchJSON(cozy, 'GET', makeReferencesPath(doc))
    .then((files) => files.map((file) => file._id))
}

function makeReferencesPath (doc) {
  const type = encodeURIComponent(doc._type)
  const id = encodeURIComponent(doc._id)
  return `/data/${type}/${id}/relationships/references`
}

import { cozyFetchJSON } from './fetch'
import { encodeQuery } from './utils'
import { DOCTYPE_FILES } from './doctypes'

function updateRelations (verb) {
  return function (cozy, doc, ids) {
    if (!doc) throw new Error('missing doc argument')
    if (!Array.isArray(ids)) ids = [ids]

    const refs = ids.map((id) => ({type: DOCTYPE_FILES, id}))

    return cozyFetchJSON(cozy, verb, makeReferencesPath(doc), {data: refs})
  }
}

export const addReferencedFiles = updateRelations('POST')
export const removeReferencedFiles = updateRelations('DELETE')

export function listReferencedFiles (cozy, doc, options) {
  if (!doc) throw new Error('missing doc argument')
  return cozyFetchJSON(cozy, 'GET', makeReferencesPath(doc, options))
    .then((files) => files.map((file) => file._id))
}

function makeReferencesPath (doc, options) {
  const type = encodeURIComponent(doc._type)
  const id = encodeURIComponent(doc._id)
  let q = encodeQuery(options)
  if (q !== '') {
    q = '?' + q
  }
  return `/data/${type}/${id}/relationships/references${q}`
}

Add pagination options to listReferencedFiles


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

        bump: {
            options: {
                files: ['package.json'],
                updateConfigs: ['pkg'],
                commit: true,
                commitMessage: 'Release v%VERSION%',
                createTag: true,
                tagName: 'v%VERSION%',
                tagMessage: 'Version %VERSION%',
                push: true,
                pushTo: 'origin',
                gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d'
            }
        },

        cucumberjs: {
            options: {
                format: 'html',
                output: 'reports/my_report.html',
                theme: 'bootstrap'
            },
            features: ['features/**/*.feature']
        },

        jshint: {
            options: {
                esversion: 6,
                reporter: require('jshint-stylish')
            },

            build: ['*.js', 'features/**/*.js', 'public/**/*.js', '!public/lib/**/*.js', '!public/reports/**/*.js']
        },

        karma: {
            unit: {
                configFile: 'karma.conf.js'
            }
        }
    });

    // ALIAS TASKS
    grunt.registerTask('test', ['jshint', 'karma', 'cucumberjs']);

    // LOAD GRUNT PACKAGES
    grunt.loadNpmTasks('grunt-bump');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-cucumberjs');
    grunt.loadNpmTasks('grunt-karma');
};

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

        bump: {
            options: {
                files: ['package.json'],
                updateConfigs: ['pkg'],
                commit: true,
                commitMessage: 'Release v%VERSION%',
                createTag: true,
                tagName: 'v%VERSION%',
                tagMessage: 'Version %VERSION%',
                prereleaseName: 'rc',
                push: true,
                pushTo: 'origin',
                gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d'
            }
        },

        cucumberjs: {
            options: {
                format: 'html',
                output: 'reports/my_report.html',
                theme: 'bootstrap'
            },
            features: ['features/**/*.feature']
        },

        jshint: {
            options: {
                esversion: 6,
                reporter: require('jshint-stylish')
            },

            build: ['*.js', 'features/**/*.js', 'public/**/*.js', '!public/lib/**/*.js', '!public/reports/**/*.js']
        },

        karma: {
            unit: {
                configFile: 'karma.conf.js'
            }
        }
    });

    // ALIAS TASKS
    grunt.registerTask('test', ['jshint', 'karma', 'cucumberjs']);

    // LOAD GRUNT PACKAGES
    grunt.loadNpmTasks('grunt-bump');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-cucumberjs');
    grunt.loadNpmTasks('grunt-karma');
};

Add rc as prerelease name


export const component = {
  "input": true,
  "tableView": true,
  "label": "address",
  "key": "address",
  "placeholder": "",
  "multiple": false,
  "protected": false,
  "clearOnHide": true,
  "unique": false,
  "persistent": true,
  "map": {
    "region": "",
    "key": "",
    "autocompleteOptions": null
  },
  "validate": {
    "required": false
  },
  "type": "address",
  "tags": [

  ],
  "conditional": {
    "show": "",
    "when": null,
    "eq": ""
  }
};

export const component = {
  "input": true,
  "tableView": true,
  "label": "address",
  "key": "address",
  "placeholder": "",
  "multiple": false,
  "protected": false,
  "clearOnHide": true,
  "unique": false,
  "persistent": true,
  "map": {
    "region": "",
    "key": "",
    "autocompleteOptions": null
  },
  "validate": {
    "customPrivate": false,
    "custom": "",
    "required": false
  },
  "type": "address",
  "tags": [

  ],
  "conditional": {
    "show": "",
    "when": null,
    "eq": ""
  }
};

Add custom and customPrivate in Address component to allow custom validation


import React from 'react'
import classnames from 'classnames'
import { statusPageURL } from 'utils/settings'
import classes from './Header.scss'

export const Header = (props) => (

      LambStatus

        View Status Page

)

export default Header

Set https for a link to status page


/* @flow */

import type {ExplodedFragment} from './hosted-git-resolver.js';
import HostedGitResolver from './hosted-git-resolver.js';

export default class GitHubResolver extends HostedGitResolver {
  static protocol = 'github';
  static hostname = 'github.com';

  static isVersion(pattern: string): boolean {
    // github proto
    if (pattern.startsWith('github:')) {
      return true;
    }

    // github shorthand
    if (/^[^:@%/\s.-][^:@%/\s]*[/][^:@\s/%]+(?:#.*)?$/.test(pattern)) {
      return true;
    }

    return false;
  }

  static getTarballUrl(parts: ExplodedFragment, hash: string): string {
    return `https://codeload.github.com/${parts.user}/${parts.repo}/tar.gz/${hash}`;
  }

  static getGitSSHUrl(parts: ExplodedFragment): string {
    return `git@github.com:${parts.user}/${parts.repo}.git${parts.hash ? '#' + decodeURIComponent(parts.hash) : ''}`;
  }

  static getGitHTTPUrl(parts: ExplodedFragment): string {
    return `https://github.com/${parts.user}/${parts.repo}.git`;
  }

  static getHTTPFileUrl(parts: ExplodedFragment, filename: string, commit: string): string {
    return `https://raw.githubusercontent.com/${parts.user}/${parts.repo}/${commit}/${filename}`;
  }
}

/* @flow */

import type {ExplodedFragment} from './hosted-git-resolver.js';
import HostedGitResolver from './hosted-git-resolver.js';

export default class GitHubResolver extends HostedGitResolver {
  static protocol = 'github';
  static hostname = 'github.com';

  static isVersion(pattern: string): boolean {
    // github proto
    if (pattern.startsWith('github:')) {
      return true;
    }

    // github shorthand
    if (/^[^:@%/\s.-][^:@%/\s]*[/][^:@\s/%]+(?:#.*)?$/.test(pattern)) {
      return true;
    }

    return false;
  }

  static getTarballUrl(parts: ExplodedFragment, hash: string): string {
    return `https://codeload.${this.hostname}/${parts.user}/${parts.repo}/tar.gz/${hash}`;
  }

  static getGitSSHUrl(parts: ExplodedFragment): string {
    return `git@${this.hostname}:${parts.user}/${parts.repo}.git` +
      `${parts.hash ? '#' + decodeURIComponent(parts.hash) : ''}`;
  }

  static getGitHTTPUrl(parts: ExplodedFragment): string {
    return `https://${this.hostname}/${parts.user}/${parts.repo}.git`;
  }

  static getHTTPFileUrl(parts: ExplodedFragment, filename: string, commit: string): string {
    return `https://raw.githubusercontent.com/${parts.user}/${parts.repo}/${commit}/${filename}`;
  }
}

Switch to using vars for hostname in gh resolver.


angular.module('app', [])
.factory("settings", [function() {

}])

angular.module('app', [])
    .factory("settings", [function () {
        function parseBool(val) { return val === true || val === "true" }

        // Redundant - should access tabCtrl.getSetting
        function getSetting(name) {
            var ret = localStorage.getItem(name);
            // Setze Default-Wert
            if (ret == null) {
                if (name == "reconnect")
                    ret = false;
                else if (name == "duration")
                    ret = 5;

                localStorage.setItem(name, ret);
            }
            if (name == "reconnect")
                ret = parseBool(ret);

            return ret;
        }

        function setSetting(name, value) {
            localStorage.setItem(name, value);
        }
    }])

Copy get and set Functions to new service


controller = new BasicController({
    supports: {
        playpause: true,
        next: true,
        previous: true,
        favorite: true
    },
    playPauseSelector: '.player-controls__btn_play',
    previousSelector: '.player-controls__btn_prev',
    nextSelector: '.player-controls__btn_next',
    titleSelector: '.player-controls .track__title',
    artistSelector: '.player-controls .track__artists',
    playStateSelector: '.player-controls__btn_play',
    playStateClass: 'player-controls__btn_pause',
    artworkImageSelector: '.player-controls .album-cover',
    favoriteSelector: '.player-controls .like',
    isFavoriteSelector: '.player-controls .like_on'
});

controller.override('getAlbumArt', function() {
    var img = document.querySelector(this.artworkImageSelector);
    if (img) {
        return img.src.replace('40x40', '150x150');
    }
    return undefined
});

controller = new BasicController({
    supports: {
        playpause: true,
        next: true,
        previous: true,
        favorite: true,
        thumbsDown: false  // Changes dynamically
    },
    playPauseSelector: '.player-controls__btn_play',
    previousSelector: '.player-controls__btn_prev',
    nextSelector: '.player-controls__btn_next',
    titleSelector: '.player-controls .track__title',
    artistSelector: '.player-controls .track__artists',
    playStateSelector: '.player-controls__btn_play',
    playStateClass: 'player-controls__btn_pause',
    artworkImageSelector: '.player-controls .album-cover',
    favoriteSelector: '.player-controls .like',
    isFavoriteSelector: '.player-controls .like_on',
    thumbsDownSelector: '.player-controls__btn_dislike'
});

controller.override('getAlbumArt', function() {
    var img = document.querySelector(this.artworkImageSelector);
    if (img) {
        return img.src.replace('40x40', '150x150');
    }
    return undefined
});

controller.override('isPlaying', function(_super) {
    this.supports.thumbsDown = window.getComputedStyle(document.querySelector(this.thumbsDownSelector)).display != 'none'
    return _super()
});

Add support for thumbs down in radio mode on Yandex.Music


var xhr = require('xhr');
var defaults = require('lodash.defaults');

module.exports = function (options, callback) {
  // Set default options
  defaults(options, {
    method: 'GET',
    useCache: true,
    json: {},
    timeout: 60000    // 60 seconds
  });

  // Use URI and prepend the API host
  if (typeof options.url !== 'undefined') {
    options.uri = options.url;
    delete options.url;
  }
  options.uri = 'https://gist.githubusercontent.com/thisandagain' + options.uri;

  // Set cache key
  var key = 'cache::' + options.method.toLowerCase() + '::' + options.uri;

  // Use device cache if window.Android is available & options.useCache is true
  if (window.Android && options.useCache === true) {
    window.Android.logText('Fetching from cache "' + key + '"');
    var hit = window.Android.getSharedPreferences(key);
    if (typeof hit !== 'undefined') return callback(null, JSON.parse(hit));
  }

  // XHR request
  xhr(options, function (err, res, body) {
    if (err) return callback(err);

    // Set cache if window.Android is available
    if (window.Android) {
      window.Android.setSharedPreferences(key, JSON.stringify(body));
    }

    // Return response body
    callback(null, body);
  });
};

var xhr = require('xhr');
var defaults = require('lodash.defaults');

module.exports = function (options, callback) {
  // Set default options
  defaults(options, {
    method: 'GET',
    useCache: true,
    json: {},
    timeout: 60000    // 60 seconds
  });

  // Use URI and prepend the API host
  if (typeof options.url !== 'undefined') {
    options.uri = options.url;
    delete options.url;
  }
  options.uri = 'https://gist.githubusercontent.com/thisandagain' + options.uri;

  // Set cache key
  var key = 'cache::' + options.method.toLowerCase() + '::' + options.uri;

  // Use device cache if window.Android is available & options.useCache is true
  if (window.Android && options.useCache === true) {
    window.Android.logText('Fetching from cache "' + key + '"');
    var hit = window.Android.getSharedPreferences(key);
    if (typeof hit === 'string') return callback(null, JSON.parse(hit));
  }

  // XHR request
  xhr(options, function (err, res, body) {
    if (err) return callback(err);

    // Set cache if window.Android is available
    if (window.Android) {
      window.Android.setSharedPreferences(key, JSON.stringify(body));
    }

    // Return response body
    callback(null, body);
  });
};

Check for typeof string on cache hit


const fs = require('fs')
const path = require('path')
const showdown = require('showdown')
const temp = require('temp')
const url = require('url')

exports.reloadMarkdownFile = function (mainWindow, markdownFileName) {
  fs.readFile(markdownFileName, 'utf8', function (err, markdown) {
    if (err) throw err

    var converter = new showdown.Converter()
    var html = converter.makeHtml(markdown)
    html = '
' + html + '
'

    const tempHtmlPath = temp.path({suffix: '.html'})

    fs.writeFile(tempHtmlPath, html, function (err) {
      if (err) throw err

      mainWindow.loadURL(url.format({
        pathname: tempHtmlPath,
        protocol: 'file:',
        slashes: true
      }))

      var cssPath = 'node_modules/github-markdown-css/github-markdown.css'

      fs.readFile(path.join(__dirname, cssPath), 'utf8', function (err, css) {
        if (err) throw err

        mainWindow.webContents.on('did-finish-load', function() {
          mainWindow.webContents.insertCSS(css)
        })
      })
    })
  })
}

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

exports.reloadMarkdownFile = function (mainWindow, markdownFileName) {
  fs.readFile(markdownFileName, 'utf8', function (err, markdown) {
    if (err) throw err

    var converter = new showdown.Converter()
    var html = converter.makeHtml(markdown)
    html = '
' + html + '
'
    html = 'data:text/html;charset=UTF-8,' + html

    mainWindow.loadURL(html)

    var cssPath = 'node_modules/github-markdown-css/github-markdown.css'

    fs.readFile(path.join(__dirname, cssPath), 'utf8', function (err, css) {
      if (err) throw err

      mainWindow.webContents.on('did-finish-load', function() {
        mainWindow.webContents.insertCSS(css)
      })
    })
  })
}

Use html string instead of temp html file


const argv = require("yargs").argv;
const Metalsmith = require("metalsmith");
const superstatic = require("superstatic").server;
const watch = require("metalsmith-watch");

const metalsmith = new Metalsmith(__dirname);
const server = superstatic({
    port: 8000,
    cwd: __dirname,
    config: {
        public: "./build"
    }
});

metalsmith.source("website")
    .use(argv.watch && watch({
        log: _ => { }
    }))
    .build((err, files) => {
        if (err) {
            console.error(err);
            return;
        }

        if (!argv.serve) {
            return;
        }

        server.listen(e => {
            if (e) {
                console.error("Server error");
                console.error(e);
                return;
            }

            console.log("Server started on port 8000");
        });
    });

const argv = require("yargs").argv;
const Metalsmith = require("metalsmith");
const superstatic = require("superstatic").server;
const watch = require("metalsmith-watch");

const metalsmith = new Metalsmith(__dirname);
const server = superstatic({
    port: 8000,
    cwd: __dirname,
    config: {
        public: "./build"
    }
});

metalsmith.source("website").clean(false)
    .use(argv.watch && watch({
        log: _ => { }
    }))
    .build((err, files) => {
        if (err) {
            console.error(err);
            return;
        }

        if (!argv.serve) {
            return;
        }

        server.listen(e => {
            if (e) {
                console.error("Server error");
                console.error(e);
                return;
            }

            console.log("Server started on port 8000");
        });
    });

Disable cleaning build/ dir before building


// @flow

import * as React from 'react';
import { View } from 'react-native';
import { Dimensions } from 'react-native';
import {
  BottomSheet as CommonBottomSheet,
  Device,
  type OnDimensionsChange,
} from '@kiwicom/react-native-app-shared';

import { getWidth, openHeight, closedHeight } from '../bottomSheetDimensions';

type Props = {|
  children: React.Node,
|};

type State = {|
  screenWidth: number,
|};

export default class BottomSheet extends React.Component<Props, State> {
  isTablet: boolean;

  constructor(props: Props) {
    super(props);
    this.isTablet = Device.isTablet();

    this.state = {
      screenWidth: Dimensions.get('screen').width,
    };
  }

  componentDidMount = () => {
    Dimensions.addEventListener('change', this.onDimensionsChanged);
  };

  componentWillUnmount = () => {
    Dimensions.removeEventListener('change', this.onDimensionsChanged);
  };

  onDimensionsChanged = ({ screen: { width } }: OnDimensionsChange) => {
    this.setState({ screenWidth: width });
  };

  getWidth = () => getWidth(this.state.screenWidth, this.isTablet);

  render = () => {
    const { children } = this.props;

    return (
      <View style={{ width: this.getWidth() }}>
        <CommonBottomSheet openHeight={openHeight} closedHeight={closedHeight}>
          {children}
        </CommonBottomSheet>
      </View>
    );
  };
}

// @flow

import * as React from 'react';
import { View } from 'react-native';
import { Dimensions } from 'react-native';
import {
  BottomSheet as CommonBottomSheet,
  Device,
  type OnDimensionsChange,
} from '@kiwicom/react-native-app-shared';

import { getWidth, openHeight, closedHeight } from '../bottomSheetDimensions';

type Props = {|
  children: React.Node,
|};

type State = {|
  screenWidth: number,
|};

export default class BottomSheet extends React.Component<Props, State> {
  isTablet: boolean;

  constructor(props: Props) {
    super(props);
    this.isTablet = Device.isTablet();

    this.state = {
      screenWidth: Dimensions.get('screen').width,
    };
  }

  componentDidMount = () => {
    Dimensions.addEventListener('change', this.onDimensionsChanged);
  };

  componentWillUnmount = () => {
    Dimensions.removeEventListener('change', this.onDimensionsChanged);
  };

  onDimensionsChanged = ({ screen: { width } }: OnDimensionsChange) => {
    this.setState({ screenWidth: width });
  };

  getWidth = () => getWidth(this.state.screenWidth, this.isTablet);

  render = () => {
    const { children } = this.props;

    return (
      <View style={{ width: this.getWidth(), alignSelf: 'center' }}>
        <CommonBottomSheet openHeight={openHeight} closedHeight={closedHeight}>
          {children}
        </CommonBottomSheet>
      </View>
    );
  };
}

Align bottom sheet to center


var Helpers = {};

Helpers.last = function(op) {
  if (op.type === "compound") {
    return op.ops[op.ops.length-1];
  }
  return op;
};

Helpers.each = function(op, iterator, context) {
  if (op.type === "compound") {
    for (var i = 0; i < op.ops.length; i++) {
      var child = op.ops[i];
      if (child.type === "compound") Helpers.each(child, iterator, context);
      else iterator.call(context, child);
    }
  } else {
    iterator.call(context, op);
  }
};

module.exports = Helpers;

var Helpers = {};

Helpers.last = function(op) {
  if (op.type === "compound") {
    return op.ops[op.ops.length-1];
  }
  return op;
};

// Iterates all atomic operations contained in a given operation
// --------
//
// - op: an Operation instance
// - iterator: a `function(op)`
// - context: the `this` context for the iterator function
// - reverse: if present, the operations are iterated reversely

Helpers.each = function(op, iterator, context, reverse) {
  if (op.type === "compound") {
    var l = op.ops.length;
    for (var i = 0; i < l; i++) {
      var child = op.ops[i];
      if (reverse) {
        child = op.ops[l-i-1];
      }
      if (child.type === "compound") {
        if (Helpers.each(child, iterator, context, reverse) === false) {
          return false;
        }
      }
      else {
        if (iterator.call(context, child) === false) {
          return false;
        }
      }
    }
    return true;
  } else {
    return iterator.call(context, op);
  }
};

module.exports = Helpers;

Add more sugar to helper for iterating compound operations.


if ('angular' in window) {
  angular.module('ng').run(['$rootScope', '$window', function ($rootScope, $window) {

  	$rootScope.$watch(function() {
		  return $window.location.pathname + $window.location.search;
  	}, function (newUrl, oldUrl) {
      if (newUrl === oldUrl) {
        return;
      }

      var evt = document.createEvent('CustomEvent');
      evt.initCustomEvent('spriteLoaderLocationUpdated', false, false, {
        newUrl: newUrl
      });
      window.dispatchEvent(evt);
    });
  }]);
}

if ('angular' in window) {
  angular.module('ng').run(['$rootScope', function ($rootScope) {

    function dispatchLocationUpdateEvent(newUrl) {
      var evt = document.createEvent('CustomEvent');
      evt.initCustomEvent('spriteLoaderLocationUpdated', false, false, {
        newUrl: newUrl
      });
      window.dispatchEvent(evt);
    }

    $rootScope.$on('$locationChangeSuccess', function (e, newUrl) {
      dispatchLocationUpdateEvent(newUrl);
    });
  }]);
}

Return listening to $locationChangeSuccess back because it is not the source of broken query error


var tgr = require("__buttercup/classes/commands/command.tgr.js");

module.exports = {
  errors: {
    groupNotFoundThrowsError: function (test) {
      var command = new tgr();

      var fakeSearching = {
        findGroupByID: function (a, b) {
          return false;
        }
      };

      command.injectSearching(fakeSearching);

      test.throws(function() {
        command.execute({ }, 1, 'a');
      }, 'Group not found for ID', 'An error was thrown when no group found');
      test.done();
    }
  }
};

var tgr = require("__buttercup/classes/commands/command.tgr.js");

module.exports = {
  errors: {
    groupNotFoundThrowsError: function (test) {
      var command = new tgr();

      var fakeSearching = {
        findGroupByID: function (a, b) {
          return false;
        }
      };

      command.injectSearching(fakeSearching);

      test.throws(function() {
        command.execute({ }, 1, 'a');
      }, 'Group not found for ID', 'An error was thrown when no group found');
      test.done();
    }
  },

  titleSet: {
    titleSetCorrectlyForBla: function (test) {
      var expectedTitle = 'Bla';
      var command = new tgr();

      var fakeGroup = {
        title: ''
      };

      var fakeSearching = {
        findGroupByID: function (a, b) {
          return fakeGroup;
        }
      };

      command.injectSearching(fakeSearching);

      command.execute({ }, 1, expectedTitle);

      test.strictEqual(fakeGroup.title, expectedTitle, 'The title was set to ' + expectedTitle + ' correctly');
      test.done();
    },

    titleSetCorrectlyForJames: function (test) {
      var expectedTitle = 'James';
      var command = new tgr();

      var fakeGroup = {
        title: ''
      };

      var fakeSearching = {
        findGroupByID: function (a, b) {
          return fakeGroup;
        }
      };

      command.injectSearching(fakeSearching);

      command.execute({ }, 1, expectedTitle);

      test.strictEqual(fakeGroup.title, expectedTitle, 'The title was set to ' + expectedTitle + ' correctly');
      test.done();
    }
  }
};

Add tests around title setting for tgr command


/*
 * Things Gateway Default Configuration.
 *
 * 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/.
 */

'use strict';

module.exports = {
  // Expose CLI
  cli: true,

  ports: {
    https: 4443,
    http:  8080
  },
  adapters : {
    gpio: {
      enabled: false,
      path: './adapters/gpio',
      pins: {
        18: {
          name: 'led',
          direction: 'out',   // 'in' or 'out'
          value: 0            // value on required when 'direction' is 'out'
        },
      }
    },
    zigbee: {
      enabled: true,
      path: './adapters/zigbee',
    },
    zwave: {
      enabled: true,
      path: './adapters/zwave',
    }
  },
  adapterManager: {
    pairingTimeout: 3 * 60,   // seconds
  },
  database: {
    filename: './db.sqlite3',
    removeBeforeOpen: false,
  },
  authentication: {
    enabled: true,
    defaultUser: null,
    secret: 'top secret 51'      // DO NOT USE THIS IN PRODUCTION
  },
  ssltunnel: {
    enabled: true,
    registration_endpoint: 'mozilla-iot.org',
    domain: 'box.mozilla-iot.org',
    pagekite_cmd: './pagekite.py',
    port: 443
  }
};

/*
 * Things Gateway Default Configuration.
 *
 * 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/.
 */

'use strict';

module.exports = {
  // Expose CLI
  cli: true,

  ports: {
    https: 4443,
    http:  8080
  },
  adapters : {
    gpio: {
      enabled: false,
      path: './adapters/gpio',
      pins: {
        18: {
          name: 'led',
          direction: 'out',   // 'in' or 'out'
          value: 0            // value on required when 'direction' is 'out'
        },
      }
    },
    zigbee: {
      enabled: true,
      path: './adapters/zigbee',
    },
    zwave: {
      enabled: true,
      path: './adapters/zwave',
    }
  },
  adapterManager: {
    pairingTimeout: 3 * 60,   // seconds
  },
  database: {
    filename: './db.sqlite3',
    removeBeforeOpen: false,
  },
  authentication: {
    enabled: true,
    defaultUser: null,
    secret: 'top secret 51'      // DO NOT USE THIS IN PRODUCTION
  },
  ssltunnel: {
    enabled: true,
    registration_endpoint: 'mozilla-iot.org',
    domain: 'mozilla-iot.org',
    pagekite_cmd: './pagekite.py',
    port: 443
  }
};

Remove box prefix from config


/**
 * HiveModel.js
 */
(function (spiderOakApp, window, undefined) {
  "use strict";
  var console = window.console || {};
  console.log = console.log || function(){};
  var Backbone    = window.Backbone,
      _           = window._,
      $           = window.$,
      s           = window.s;

  spiderOakApp.HiveModel = spiderOakApp.FolderModel.extend({
    defaults: {
      hasHive: true
    },
    parse: function(resp, xhr) {
      if (resp.syncfolder) {
        resp.syncfolder.name = s("SpiderOak Hive");
        return resp.syncfolder;
      }
      else {
        this.set("hasHive", false);
        return this.attributes;
      }
    },
    composedUrl: function(bare) {
      var urlTail = encodeURI(this.get("device_path"));
      var urlHead = this.url.replace("s\/","") + this.get("device_encoded");
      if (bare) {
        urlHead = urlHead && urlHead.split("?")[0];
        urlTail = urlTail && urlTail.split("?")[0];
      }
      return (urlHead || "") + (urlTail || "");
    }
  });

})(window.spiderOakApp = window.spiderOakApp || {}, window);

/**
 * HiveModel.js
 */
(function (spiderOakApp, window, undefined) {
  "use strict";
  var console = window.console || {};
  console.log = console.log || function(){};
  var Backbone    = window.Backbone,
      _           = window._,
      $           = window.$,
      s           = window.s;

  spiderOakApp.HiveModel = spiderOakApp.FolderModel.extend({
    defaults: {
      hasHive: true
    },
    parse: function(resp, xhr) {
      if (resp.syncfolder) {
        resp.syncfolder.name = s("SpiderOak Hive");
        return resp.syncfolder;
      }
      else {
        this.set("hasHive", false);
        return this.attributes;
      }
    },
    composedUrl: function(bare) {
      var urlTail = this.get("url");
      var urlHead = this.url;
      if (bare) {
        urlHead = urlHead && urlHead.split("?")[0];
        urlTail = urlTail && urlTail.split("?")[0];
      }
      return (urlHead || "") + (urlTail || "");
    }
  });

})(window.spiderOakApp = window.spiderOakApp || {}, window);

Change `composedUrl` to use the abstracted s/ URL


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

export { ROUTES } from './constants';
export { getCurrentRouteName, getCurrentParams } from './selectors';
export { MainStackNavigator } from './Navigator';
export { RootNavigator, rootNavigatorRef } from './RootNavigator';

Add exporting of root navigation ref and functions


function smoothed_count(
    iterations,
    final_modulus_squared){

    final_modulus = Math.sqrt(final_modulus_squared);

    if (final_modulus< 1){
        return iterations
    }
    else{

        return iterations - (Math.log(Math.log(final_modulus)) / Math.log(2))
    }
}

function mandelbrot_smoothed_iteration_count(
    escape_radius_squared,
    max_iterations,
    initial_real,
    initial_imaginary){

    mod_z = (initial_real * initial_real) + (initial_imaginary * initial_imaginary);

    real = initial_real;
    imaginary = initial_imaginary;

    iterations = 0;

    while (mod_z < escape_radius_squared && iterations != max_iterations){

        new_real = (real * real) - (imaginary * imaginary) + initial_real;
        imaginary = (2 * real * imaginary) + initial_imaginary;
        real = new_real;
        mod_z = (real * real) + (imaginary * imaginary);
        iterations++;
    }

    return smoothed_count(iterations, mod_z)
}

function smoothed_count(
    iterations,
    final_modulus_squared){

    final_modulus = Math.sqrt(final_modulus_squared);

    if (final_modulus< 1){
        return iterations
    }
    else{

        return iterations - (Math.log(Math.log(final_modulus)) / Math.log(2))
    }
}

function mandelbrot_smoothed_iteration_count(
    escape_radius_squared,
    max_iterations,
    initial_real,
    initial_imaginary){

    var mod_z = (initial_real * initial_real) + (initial_imaginary * initial_imaginary);

    var real = initial_real;
    var imaginary = initial_imaginary;

    var iterations = 0;

    while (mod_z < escape_radius_squared && iterations != max_iterations){

        var new_real = (real * real) - (imaginary * imaginary) + initial_real;
        imaginary = (2 * real * imaginary) + initial_imaginary;
        real = new_real;
        mod_z = (real * real) + (imaginary * imaginary);
        iterations++;
    }

    return smoothed_count(iterations, mod_z)
}

Fix silly javascript bug - was using global variables


var NanoProxyDbFunctions = function(nano, prefix) {
  this.nano = nano;
  this.prefix = prefix;
};

NanoProxyDbFunctions.prototype.create = function(name, callback) {
  return this.nano.db.create(prefixName.call(this, name), callback);
};

function prefixName(name) {
  return [this.prefix, name].join("_");
}

NanoProxyDbFunctions.prototype.get = function(name, callback) {

};

NanoProxyDbFunctions.prototype.destroy = function(name, callback) {

};

NanoProxyDbFunctions.prototype.list = function(callback) {

};

NanoProxyDbFunctions.prototype.compact = function(name, designname, callback) {

};

NanoProxyDbFunctions.prototype.replicate = function(source, target, opts, callback) {

};

NanoProxyDbFunctions.prototype.changes = function(name, params, callback) {

};

NanoProxyDbFunctions.prototype.follow = function(name, params, callback) {

};

NanoProxyDbFunctions.prototype.use = function(name) {
  return this.nano.use([this.prefix, name].join("_"));
};

module.exports = NanoProxyDbFunctions;

var NanoProxyDbFunctions = function(nano, prefix) {
  this.nano = nano;
  this.prefix = prefix;
};

NanoProxyDbFunctions.prototype.create = function(name, callback) {
  return this.nano.db.create(prefixName.call(this, name), callback);
};

function prefixName(name) {
  return [this.prefix, name].join("_");
}

NanoProxyDbFunctions.prototype.get = function(name, callback) {

};

NanoProxyDbFunctions.prototype.destroy = function(name, callback) {

};

NanoProxyDbFunctions.prototype.list = function(callback) {

};

NanoProxyDbFunctions.prototype.compact = function(name, designname, callback) {

};

NanoProxyDbFunctions.prototype.replicate = function(source, target, opts, callback) {

};

NanoProxyDbFunctions.prototype.changes = function(name, params, callback) {

};

NanoProxyDbFunctions.prototype.follow = function(name, params, callback) {

};

NanoProxyDbFunctions.prototype.use = function(name) {
  return this.nano.use(prefixName.call(this, name));
};

module.exports = NanoProxyDbFunctions;

Use prefixName abstraction in nano.use