"use strict";

/**
 * @ngdoc service
 * @name arethusa.core.arethusaLocalStorage
 *
 * @description
 * Arethusa's API to communicate with `localStorage`. All values stored are
 * prefixed with `arethusa.`.
 *
 * Performs type coercion upon retrieval for Booleans, so that `true`, `false`
 * and `null` can be used properly.
 *
 * @requires localStorageService
 */
angular.module('arethusa.core').service('arethusaLocalStorage', [
  'localStorageService',
  function(localStorageService) {
    /**
     * @ngdoc function
     * @name arethusa.core.arethusaLocalStorage#get
     * @methodOf arethusa.core.arethusaLocalStorage
     *
     * @param {String} key The key
     * @returns {*} The stored value
     */
    this.get = function(key) {
      return coerce(localStorageService.get(key));
    };

    /**
     * @ngdoc function
     * @name arethusa.core.arethusaLocalStorage#set
     * @methodOf arethusa.core.arethusaLocalStorage
     *
     * @param {String} key The key
     * @param {*} value The value
     */
    this.set = localStorageService.set;

    var JSONBooleans = ['true', 'false', 'null'];
    function coerce(value) {
      if (JSONBooleans.indexOf(value) === -1) {
        return value;
      } else {
        return JSON.parse(value);
      }
    }
  }
]);

"use strict";

/**
 * @ngdoc service
 * @name arethusa.core.arethusaLocalStorage
 *
 * @description
 * Arethusa's API to communicate with `localStorage`. All values stored are
 * prefixed with `arethusa.`.
 *
 * Performs type coercion upon retrieval for Booleans, so that `true`, `false`
 * and `null` can be used properly.
 *
 * @requires localStorageService
 */
angular.module('arethusa.core').service('arethusaLocalStorage', [
  'localStorageService',
  function(localStorageService) {
    /**
     * @ngdoc function
     * @name arethusa.core.arethusaLocalStorage#get
     * @methodOf arethusa.core.arethusaLocalStorage
     *
     * @param {String} key The key
     * @returns {*} The stored value
     */
    this.get = function(key) {
      return coerce(localStorageService.get(key));
    };

    /**
     * @ngdoc function
     * @name arethusa.core.arethusaLocalStorage#set
     * @methodOf arethusa.core.arethusaLocalStorage
     *
     * @param {String} key The key
     * @param {*} value The value
     */
    this.set = localStorageService.set;

    this.keys = localStorageService.keys;

    var JSONBooleans = ['true', 'false', 'null'];
    function coerce(value) {
      if (JSONBooleans.indexOf(value) === -1) {
        return value;
      } else {
        return JSON.parse(value);
      }
    }
  }
]);

Add delegator for keys() in arethusaLocalStorage


import CompleteBlock from './completeBlock.html';
import './completeBlock.scss';

class CompleteBlock {
	static detailsComponent() {
		return {
			templateUrl: CompleteBlock,
			bindings: {
				headerImg: '@',
				headerTitle: '@',
				headerPicto: '@',
				headerColor: '@',
				headerBackgroundColor: '@',
				bodyBackgroundColor: '@',
				bodyTitle: '@',
				bodyText: '@'
			}
		};
	}
}

export default CompleteBlock.detailsComponent();

import TemplateCompleteBlock from './completeBlock.html';
import './completeBlock.scss';

class CompleteBlock {
	static detailsComponent() {
		return {
			templateUrl: TemplateCompleteBlock,
			bindings: {
				headerImg: '@',
				headerTitle: '@',
				headerPicto: '@',
				headerColor: '@',
				headerBackgroundColor: '@',
				bodyBackgroundColor: '@',
				bodyTitle: '@',
				bodyText: '@'
			}
		};
	}
}

export default CompleteBlock.detailsComponent();

Rename variable because the template and the class has the same name


exports.config = {
    specs: ['ui/**/*.spec.js'],
    sauceUser: process.env.SAUCE_USERNAME,
    sauceKey: process.env.SAUCE_ACCESS_KEY,
    baseUrl: 'http://localhost:3000',
    capabilities: {
      'browserName': 'firefox',
      'platform': 'Linux',
      'version': '32',
      'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
      'build': process.env.TRAVIS_BUILD_NUMBER,
      'name': 'AgileJS Training Repo'
    }
};

exports.config = {
    specs: ['ui/**/*.spec.js'],
    sauceUser: process.env.SAUCE_USERNAME,
    sauceKey: process.env.SAUCE_ACCESS_KEY,
    baseUrl: 'http://localhost:3000',
    capabilities: {
      'browserName': 'firefox',
      'platform': 'Windows 7',
      'version': '32',
      'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
      'build': process.env.TRAVIS_BUILD_NUMBER,
      'name': 'AgileJS Training Repo'
    }
};

Use Windows on Sauce to avoid unsupported platform combinations


export function asyncHandler(handler) {
  return function(req, res, next) {
    if (!handler) {
      throw new Error(`Invalid handler ${handler}, it must be a function.`);
    }

    handler(req, res, next)
      .then(function(response) {
        if (response) {
          res.send(response);
        }
      })
      .catch(function(err) {
        let message = err.message;
        let status = 500;

        if (message.match(/^[\d]{3}:/)) {
          message = message.substring(4).trim();
          const code = parseInt(message.substr(0, 3), 10);
          if (!isNaN(code)) {
            status = code;
          }
        }

        res.status(status).json({
          error: message
        });

        if (status >= 500) {
          console.error(err.stack);
        }
      });
  };
}

export function asyncHandler(handler) {
  return function(req, res, next) {
    if (!handler) {
      throw new Error(`Invalid handler ${handler}, it must be a function.`);
    }

    handler(req, res, next)
      .then(function(response) {
        if (response) {
          res.send(response);
        }
      })
      .catch(function(err) {
        let message = err.message;
        let status = 500;

        if (message.match(/^[\d]{3}:/)) {
          status = parseInt(message.substr(0, 3), 10);
          message = message.substring(4).trim();
        }

        res.status(status).json({
          error: message
        });

        if (status >= 500) {
          console.error(err.stack);
        }
      });
  };
}

Fix incorrect extraction of error code


//= require jquery
//= require jquery_ujs
//= require jquery.remotipart
//= require select2
//= require cocoon
//= require dropzone
//= require govuk/selection-buttons
//= require moj
//= require modules/moj.cookie-message.js
//= require_tree .

(function () {
  'use strict';
  delete moj.Modules.devs;

  $('#fixed-fees, #misc-fees, #expenses, #documents').on('cocoon:after-insert', function (e, insertedItem) {
    $(insertedItem).find('.select2').select2();
  });
  $('.select2').select();

  //Stops the form from submitting when the user presses 'Enter' key
  $('#claim-form, #claim-status').on('keypress', function(e) {
    if (e.keyCode === 13) {
      return false;
    }
  });

  var selectionButtons = new GOVUK.SelectionButtons("label input[type='radio'], label input[type='checkbox']");

  moj.init();
}());

//= require jquery
//= require jquery_ujs
//= require jquery.remotipart
//= require select2
//= require cocoon
//= require dropzone
//= require vendor/polyfills/bind
//= require govuk/selection-buttons
//= require moj
//= require modules/moj.cookie-message.js
//= require_tree .

(function () {
  'use strict';
  delete moj.Modules.devs;

  $('#fixed-fees, #misc-fees, #expenses, #documents').on('cocoon:after-insert', function (e, insertedItem) {
    $(insertedItem).find('.select2').select2();
  });
  $('.select2').select();

  //Stops the form from submitting when the user presses 'Enter' key
  $('#claim-form, #claim-status').on('keypress', function(e) {
    if (e.keyCode === 13) {
      return false;
    }
  });

  var selectionButtons = new GOVUK.SelectionButtons("label input[type='radio'], label input[type='checkbox']");

  moj.init();
}());

Fix issue with PhantomJS and Function.prototype.bind support


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

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

Remove require_self and back to require ./wookmark...


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

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

Remove loading of scrollify, not using


import ReactDOM from 'react-dom';
import React from 'react';
import { shallow, render } from 'enzyme';
import { expect } from 'chai';
import { Text } from 'recharts';

describe('<Text />', () => {
  it('Does not wrap long text if enough width', () => {
    const wrapper = shallow(
      <Text width={200}>This is really long text</Text>
    );

    expect(wrapper.instance().state.wordsByLines.length).to.equal(1);
  });

  it('Wraps long text if not enough width', () => {
    const wrapper = shallow(
      <Text width={125}>This is really long text</Text>
    );

    expect(wrapper.instance().state.wordsByLines.length).to.equal(2);
  });

  it('Wraps long text if styled and not enough room', () => {
    const wrapper = shallow(
      <Text width={200} style={{ fontSize: '32px' }}>This is really long text</Text>
    );

    expect(wrapper.instance().state.wordsByLines.length).to.equal(2);
  });

  it('Does not perform word length calculation if width or fit props not set', () => {
    const wrapper = shallow(
      <Text>This is really long text</Text>
    );

    expect(wrapper.instance().state.wordsByLines.length).to.equal(1);
    expect(wrapper.instance().state.wordsByLines[0].width).to.equal(undefined);
  });
});

import ReactDOM from 'react-dom';
import React from 'react';
import { shallow, render } from 'enzyme';
import { expect } from 'chai';
import { Text } from 'recharts';

describe('<Text />', () => {
  it('Does not wrap long text if enough width', () => {
    const wrapper = shallow(
      <Text width={300} style={{ fontFamily: 'Courier' }}>This is really long text</Text>
    );

    expect(wrapper.instance().state.wordsByLines.length).to.equal(1);
  });

  it('Wraps long text if not enough width', () => {
    const wrapper = shallow(
      <Text width={200} style={{ fontFamily: 'Courier' }}>This is really long text</Text>
    );

    expect(wrapper.instance().state.wordsByLines.length).to.equal(2);
  });

  it('Wraps long text if styled but would have had enough room', () => {
    const wrapper = shallow(
      <Text width={300} style={{ fontSize: '2em', fontFamily: 'Courier' }}>This is really long text</Text>
    );

    expect(wrapper.instance().state.wordsByLines.length).to.equal(2);
  });

  it('Does not perform word length calculation if width or fit props not set', () => {
    const wrapper = shallow(
      <Text>This is really long text</Text>
    );

    expect(wrapper.instance().state.wordsByLines.length).to.equal(1);
    expect(wrapper.instance().state.wordsByLines[0].width).to.equal(undefined);
  });
});

Set fontFamily on <Text> tests to guarantee consistent regardless of environment (hopefully fix failures in CI)


import TYPES from 'types';

function isMember(element) {
  if (element.element) {
    return element.element === TYPES.MEMBER;
  }

  return element === TYPES.MEMBER;
}

function getValueType({element, content}) {
  if (isMember(element)) {
    return content.value.element;
  }

  return element;
}

function getType(element) {
  if (isMember(element.element)) {
    return getValueType(element);
  }

  return element.element;
}

function isObject(element) {
  if (!element) {
    return false;
  }

  if (element.element) {
    return getType(element) === TYPES.OBJECT;
  }

  return element === TYPES.OBJECT;
}

function isArray(element) {
  if (!element) {
    return false;
  }

  if (element.element) {
    return getType(element) === TYPES.ARRAY;
  }

  return element === TYPES.ARRAY;
}

function isEnum(element) {
  if (element.element) {
    return getType(element) === TYPES.ARRAY;
  }

  return element === TYPES.ENUM;
}

function isNestedObject(element) {
  return isObject(element) || isArray(element) || isEnum(element);
function hasSamples(element) {
  const attributes = element.attributes;

  let samples = null;

  if (attributes) {
    return !!attributes.samples;
  } else {
    return false;
  }
}
}

export {
  getValueType,
  getType,
  isMember,
  isEnum,
  isObject,
  isArray,
  isNestedObject,
  hasSamples,
};

import TYPES from 'types';

function isMember(element) {
  if (element.element) {
    return element.element === TYPES.MEMBER;
  }

  return element === TYPES.MEMBER;
}

function getValueType({element, content}) {
  if (isMember(element)) {
    return content.value.element;
  }

  return element;
}

function getType(element) {
  if (isMember(element.element)) {
    return getValueType(element);
  }

  return element.element;
}

function isObject(element) {
  if (!element) {
    return false;
  }

  if (element.element) {
    return getType(element) === TYPES.OBJECT;
  }

  return element === TYPES.OBJECT;
}

function isArray(element) {
  if (!element) {
    return false;
  }

  if (element.element) {
    return getType(element) === TYPES.ARRAY;
  }

  return element === TYPES.ARRAY;
}

function isEnum(element) {
  if (element.element) {
    return getType(element) === TYPES.ARRAY;
  }

  return element === TYPES.ENUM;
}

function isObjectOrArray(element) {
  return isObject(element) || isArray(element);
}

function hasSamples(element) {
  const attributes = element.attributes;

  let samples = null;

  if (attributes) {
    return !!attributes.samples;
  } else {
    return false;
  }
}
}

export {
  getValueType,
  getType,
  isMember,
  isEnum,
  isObject,
  isArray,
  isObjectOrArray,
  hasSamples,
};

Remove the ‘isNestedObject’ helper, no longer used.


var initialized = false;
var options = {};

Pebble.addEventListener("ready", function() {
  console.log("ready called!");
  initialized = true;
});

Pebble.addEventListener("showConfiguration", function() {
  console.log("showing configuration");
  Pebble.openURL('http://assets.getpebble.com.s3-website-us-east-1.amazonaws.com/pebble-js/configurable.html?'+encodeURIComponent(JSON.stringify(options)));
});

Pebble.addEventListener("webviewclosed", function(e) {
  console.log("configuration closed");
  // webview closed
  if (e.response != "{}") {
    options = JSON.parse(decodeURIComponent(e.response));
    console.log("Options = " + JSON.stringify(options));
  } else {
    console.log("Cancelled");
  }
});

var initialized = false;
var options = {};

Pebble.addEventListener("ready", function() {
  console.log("ready called!");
  initialized = true;
});

Pebble.addEventListener("showConfiguration", function() {
  console.log("showing configuration");
  Pebble.openURL('http://assets.getpebble.com.s3-website-us-east-1.amazonaws.com/pebble-js/configurable.html?'+encodeURIComponent(JSON.stringify(options)));
});

Pebble.addEventListener("webviewclosed", function(e) {
  console.log("configuration closed");
  // webview closed
	//Using primitive JSON validity and non-empty check
	if (e.response.charAt(0) == "{" && e.response.slice(-1) == "}" && e.response.length > 5) {
    options = JSON.parse(decodeURIComponent(e.response));
    console.log("Options = " + JSON.stringify(options));
  } else {
    console.log("Cancelled");
  }
});

Improve robustness of cancellation detection.


import Promise from 'bluebird'
import { omit, defaults } from 'lodash/object'
import { partialRight } from 'lodash/partialRight'
import * as defaultTransformers from './transformers'

const spaceEntities = [
  'contentTypes', 'entries', 'assets', 'locales', 'webhooks'
]

/**
 * Run transformer methods on each item for each kind of entity, in case there
 * is a need to transform data when copying it to the destination space
 */
export default function (
  sourceSpace, destinationSpace, customTransformers, entities = spaceEntities
) {
  const transformers = defaults(customTransformers, defaultTransformers)
  const newSpace = omit(sourceSpace, ...entities)
  // Use bluebird collection methods to support async transforms.
  return Promise.reduce(entities, (newSpace, type) => {
    const transformer = transformers[type]
    const sourceEntities = sourceSpace[type]
    const destinationEntities = destinationSpace[type]
    const typeTransform = partialRight(applyTransformer, transformer,
      destinationEntities, destinationSpace)

    return Promise.map(sourceEntities, typeTransform).then((entities) => {
      newSpace[type] = entities
      return newSpace
    })
  }, newSpace)
}

function applyTransformer (entity, transformer, destinationEntities, destinationSpace) {
  return Promise.props({
    original: entity,
    transformed: transformer(entity, destinationEntities, destinationSpace)
  })
}

import Promise from 'bluebird'
import { omit, defaults } from 'lodash/object'
import * as defaultTransformers from './transformers'

const spaceEntities = [
  'contentTypes', 'entries', 'assets', 'locales', 'webhooks'
]

/**
 * Run transformer methods on each item for each kind of entity, in case there
 * is a need to transform data when copying it to the destination space
 */
export default function (
  space, destinationSpace, customTransformers, entities = spaceEntities
) {
  const transformers = defaults(customTransformers, defaultTransformers)
  // TODO maybe we don't need promises here at all
  const newSpace = omit(space, ...entities)
  return Promise.reduce(entities, (newSpace, type) => {
    return Promise.map(
      space[type],
      (entity) => Promise.resolve({
        original: entity,
        transformed: transformers[type](entity, destinationSpace[type])
      })
    )
      .then((entities) => {
        newSpace[type] = entities
        return newSpace
      })
  }, newSpace)
}

Revert Enable async entity transforms


// @flow

import { RANGE } from "constants/controlTypes";

import type { ColorRGBA } from "types";

const optionTypes = {
  levels: { type: RANGE, range: [1, 256], default: 2 }
};

const defaults = {
  levels: optionTypes.levels.default
};

// Gets nearest color
const getColor = (
  color: ColorRGBA,
  options: { levels: number } = defaults
): ColorRGBA => {
  const step = 255 / (options.levels - 1);

  // $FlowFixMe
  return color.map(c => {
    const bucket = Math.round(c / step);
    return Math.round(bucket * step);
  });
};

export default {
  name: "nearest",
  getColor,
  options: defaults,
  optionTypes,
  defaults
};

// @flow

import { RANGE } from "constants/controlTypes";

import type { ColorRGBA } from "types";

const optionTypes = {
  levels: { type: RANGE, range: [1, 256], default: 2 }
};

const defaults = {
  levels: optionTypes.levels.default
};

// Gets nearest color
const getColor = (
  color: ColorRGBA,
  options: { levels: number } = defaults
): ColorRGBA => {
  if (options.levels >= 256) {
    return color;
  }

  const step = 255 / (options.levels - 1);

  // $FlowFixMe
  return color.map(c => {
    const bucket = Math.round(c / step);
    return Math.round(bucket * step);
  });
};

export default {
  name: "nearest",
  getColor,
  options: defaults,
  optionTypes,
  defaults
};

Return early if full colour


import { danger, warn } from 'danger';

const packageChanged = danger.git.modified_files.includes('package.json');
const lockfileChanged = danger.git.modified_files.includes('package-lock.json');
if (packageChanged && !lockfileChanged) {
	warn(`Changes were made to package.json, but not to package-lock.json.

Perhaps you need to run \`rm package-lock.json && npm install\`. Make sure you’re using npm 5+.`);
}

import { danger, warn } from 'danger';

const packageChanged = danger.git.modified_files.includes('package.json');
const lockfileChanged = danger.git.modified_files.includes('package-lock.json');
if (packageChanged && !lockfileChanged) {
	warn(`Changes were made to package.json, but not to package-lock.json.

Perhaps you need to run \`npm install\` and commit changes in package-lock.json.. Make sure you’re using npm 5+.`);
}

Update message when package-lock.json should be updated


'use strict'

var github = require('octonode')
var client = github.client()
var evRepo = client.repo('nodejs/evangelism')

var https = require('https')
var path = require('path')
var fs = require('fs')

/* Currently proof-of-concept work. Outstanding:
 * ================
 * - [ ] gulpify
 * - [ ] add to local boot process or trigger at key times
 * - [ ] support other content patterns (meeting notes, release notes, etc.)
 * - [ ] support similar patterns for other locales
 * - [ ] prepend predictable markdown metadata on download
 */

function checkOrFetchFile (file) {
  let name = file.name
  let downloadUrl = file.download_url

  let localPath = path.join(__dirname, '..', '..', 'locale', 'en', 'blog', 'weekly-updates', name)
  if (fs.existsSync(localPath)) {
    console.log(`Weekly Update ${name} exists. (No SHA check, yet.)`)
    return
  }

  console.log(`Weekly Update ${name} does not exist. Downloading.`)

  var outputFile = fs.createWriteStream(localPath)
  https.get(downloadUrl, function (response) {
    response.pipe(outputFile)
  })
}

evRepo.contents('weekly-updates', function (err, files) {
  if (err) { throw err }
  files.forEach(checkOrFetchFile)
})

'use strict'

const github = require('octonode')
const https = require('https')
const path = require('path')
const fs = require('fs')

const basePath = path.join(__dirname, '..', '..', 'locale', 'en', 'blog', 'weekly-updates')
const repo = github.client().repo('nodejs/evangelism')

/* Currently proof-of-concept work. Outstanding:
 * ================
 * - [ ] gulpify
 * - [ ] add to local boot process or trigger at key times
 * - [ ] support other content patterns (meeting notes, release notes, etc.)
 * - [ ] support similar patterns for other locales
 * - [ ] prepend predictable markdown metadata on download
 */

function checkOrFetchFile (file) {
  const filePath = path.join(basePath, file.name)

  fs.access(filePath, function (err) {
    if (!err) {
      console.log(`Weekly Update ${filePath} exists. (No SHA check, yet.)`)
      return
    }

    console.log(`Weekly Update ${filePath} does not exist. Downloading.`)

    https.get(file.download_url, function (response) {
      console.log(`Weekly Update ${filePath} downloaded.`)
      response.pipe(fs.createWriteStream(filePath))
    })
  })
}

repo.contents('weekly-updates', function (err, files) {
  if (err) { throw err }
  files.forEach(checkOrFetchFile)
})

Use `fs.access` instead of `fs.existsSync`


#!/usr/bin/env node

'use strict';

var childProcess = require('child_process');

/**
 * play
 * To run the process to match the platform.
 * @param {string} filepath
 * @param {function} callback
 * @return {Object} childProcess
 */
function play(filepath, callback) {
  var mediaSoundPlayer = '(New-Object System.Media.SoundPlayer "' + filepath + '").Play()';
  var player = process.platform === 'darwin' ? 'afplay' : 'aplay';

  if (typeof filepath === 'string') {
    filepath = [filepath];
  }

  if (! callback) {
    callback = function(error) {
      if (error) throw error;
    }
  }

  return process.platform === 'win32'
    ? childProcess.exec('powershell.exe -NoExit ' + mediaSoundPlayer, callback)
    : childProcess.execFile(player, filepath, callback);
}

module.exports = play;

if (require.main && require.main.id === module.id) {
  play(process.argv.slice(2));
}

#!/usr/bin/env node

'use strict';

var childProcess = require('child_process');

/**
 * play
 * To run the process to match the platform.
 * @param {string} filepath
 * @param {function} callback
 * @return {Object} childProcess
 */
function play(filepath, callback) {
  var mediaSoundPlayer = '"(New-Object System.Media.SoundPlayer "' + filepath + '").Play(); exit $LASTEXITCODE"';
  var player = process.platform === 'darwin' ? 'afplay' : 'aplay';

  if (typeof filepath === 'string') {
    filepath = [filepath];
  }

  if (! callback) {
    callback = function(error) {
      if (error) throw error;
    }
  }

  return process.platform === 'win32'
    ? childProcess.exec('powershell.exe -NoExit ' + mediaSoundPlayer, callback)
    : childProcess.execFile(player, filepath, callback);
}

module.exports = play;

if (require.main && require.main.id === module.id) {
  play(process.argv.slice(2));
}

Fix no exit to the powershell in windows


class ResultController {
  constructor(SocketService, GameService, toastr, _, GameFactory) {
    'ngInject';

    this._ = _;
    this.GameService = GameService;
    this.players = GameService.players;
    this.toastr = toastr;

    SocketService.extendedHandler = (message) => {
      if(message.type === 'player_create') {
        this.handlePlayerCreate(message.player);

      } else if(message.type === 'player_select') {
        this.handlePlayerSelect(message);
      }
    }
  }

  handlePlayerCreate(playerId) {
    const player = this._.find(this.GameService.players, (n) => n.id === playerId);
    this.toastr.info(player + ' is creating a new game.');
  }

  handlePlayerSelect(message) {
    const mode_id = message.mode_id;

    this.GameFactory.createGame(mode_id).then((result) => {
      this.toastr.info('Moving to lobby');

      this.GameService.storeGameData(result.data);
      this.GameService.retrieveAssets();

      const player = this._.find(this.GameService.players, (n) => n.id === message.player);
      this.GameService.players = [{
        name: player.name,
        id: player.id,
        score: 0,
        lastAnswer: null
      }];

      this.$state.go('lobby');
    }, (error) => {
      this.$log.log(error);
    });
  }
}

export default ResultController;

class ResultController {
  constructor(SocketService, GameService, toastr, _, GameFactory) {
    'ngInject';

    this._ = _;
    this.GameService = GameService;
    this.players = GameService.players;
    this.toastr = toastr;

    SocketService.extendedHandler = (message) => {
      if(message.type === 'player_create') {
        this.handlePlayerCreate(message.player);

      } else if(message.type === 'player_select') {
        this.handlePlayerSelect(message);
      }
    }
  }

  handlePlayerCreate(playerId) {
    const player = this._.find(this.GameService.players, (n) => n.id === playerId);
    this.toastr.info(player.name + ' is creating a new game.');
  }

  handlePlayerSelect(message) {
    const mode_id = message.mode_id;

    this.GameFactory.createGame(mode_id).then((result) => {
      this.toastr.info('Moving to lobby');

      this.GameService.storeGameData(result.data);
      this.GameService.retrieveAssets();

      const player = this._.find(this.GameService.players, (n) => n.id === message.player);
      this.GameService.players = [{
        name: player.name,
        id: player.id,
        score: 0,
        lastAnswer: null
      }];

      this.$state.go('lobby');
    }, (error) => {
      this.$log.log(error);
    });
  }
}

export default ResultController;

Fix toast not showing player name


//@Authors: Oliver Barnwell(ob6160) Harry Dalton(Hoolean)

//Requires
var net = require("net");
var util = require("util");
var EventEmitter = require("event").EventEmitter;

//Exports
module.exports = function(options) {
	return new clifton-smtp(options);
}

function clifton-smtp(options) {
	EventEmitter.call(this);
	this.server = net.createServer(function(s) {
		//Bind event handlers here
	}.bind(this));
}

clifton-smtp.prototype.listen = function(port, options, callback) {
	this.server.listen(port, function() { })

}

util.Inherits(clifton-smtp, EventEmitter);

Add cliffton-server object & some base functionality


const P = require('bluebird');
const preq = require('preq');
const uuid = require('cassandra-uuid').TimeUuid;

function getResourceChangeEvent(resourceUri, domain) {
  return {
    meta: {
      topic: 'resource_change',
      uri: resourceUri,
      id: uuid.now().toString(),
      dt: new Date().toISOString(),
      domain,
    },
    tags: ['tilerator'],
  };
}

class EventService {
  constructor(eventBusUri, domain, sources, logger) {
    this.eventBusUri = eventBusUri;
    this.domain = domain;
    this.sources = sources;
    this.logger = logger;

    this.emitEvents = (events) => {
      P.try(() => preq.post({
        uri: this.eventBusUri,
        headers: { 'content-type': 'application/json' },
        body: events,
      })).catch(e => this.logger.log('error/events/emit', e)).thenReturn({ status: 200 });
    };

    // eslint-disable-next-line max-len
    this.emitResourceChangeEvents = uris => this.emitEvents(uris.map(uri => getResourceChangeEvent(uri, this.domain)));

    this.notifyTileChanged = (z, x, y) => {
      this.emitResourceChangeEvents(this.sources.map(s => `//${this.domain}/${s}/${z}/${x}/${y}.png`));
    };
  }
}

module.exports = EventService;

const P = require('bluebird');
const preq = require('preq');
const uuid = require('cassandra-uuid').TimeUuid;

function getResourceChangeEvent(resourceUri, domain) {
  return {
    meta: {
      topic: 'resource_change',
      uri: resourceUri,
      id: uuid.now().toString(),
      dt: new Date().toISOString(),
      domain,
    },
    tags: ['tilerator'],
  };
}

class EventService {
  constructor(eventBusUri, domain, sources, logger, protocol = 'https') {
    this.eventBusUri = eventBusUri;
    this.domain = domain;
    this.sources = sources;
    this.logger = logger;
    this.protocol = protocol;

    this.emitEvents = (events) => {
      P.try(() => preq.post({
        uri: this.eventBusUri,
        headers: { 'content-type': 'application/json' },
        body: events,
      })).catch(e => this.logger.log('error/events/emit', e)).thenReturn({ status: 200 });
    };

    // eslint-disable-next-line max-len
    this.emitResourceChangeEvents = uris => this.emitEvents(uris.map(uri => getResourceChangeEvent(uri, this.domain)));

    this.notifyTileChanged = (z, x, y) => {
      this.emitResourceChangeEvents(this.sources.map(s => `${this.protocol}://${this.domain}/${s}/${z}/${x}/${y}.png`));
    };
  }
}

module.exports = EventService;

Add protocol to resource_change event URIs


require('./lib/seed')()
const { compose } = require('ramda')
const http = require('http')
const util = require('util')

const { logger, methods, mount, parseJson,
        redirect, routes, static } = require('..')

const {
  createCourse,
  fetchCourse,
  fetchCourses,
  updateCourse
} = require('./api/courses')

const { home } = require('./api/pages')

const port = 3000

const listening = err =>
  err ? console.error(err) : console.info(`Listening on port: ${port}`)

const endpoints = routes({
  '/': methods({
    GET: home
  }),

  '/public/:path+': static({ root: 'demo/public' }),

  '/courses': methods({
    GET:  fetchCourses,
    POST: createCourse
  }),

  '/courses/:id': methods({
    GET:   fetchCourse,
    PATCH: updateCourse,
    PUT:   updateCourse
  }),

  '/error': () => { throw new Error('this code is broken') },

  '/old-courses': () => redirect('/courses')
})

const app  = compose(endpoints, parseJson)
const opts = { errLogger: logger, logger }

http.createServer(mount(app, opts)).listen(port, listening)

require('./lib/seed')()
const { compose } = require('ramda')
const http = require('http')
const util = require('util')

const { logger, methods, mount, parseJson,
        redirect, routes, static } = require('..')

const {
  createCourse,
  fetchCourse,
  fetchCourses,
  updateCourse
} = require('./api/courses')

const { home } = require('./api/pages')

const port = process.env.PORT || 3000

const listening = err =>
  err ? console.error(err) : console.info(`Listening on port: ${port}`)

const endpoints = routes({
  '/': methods({
    GET: home
  }),

  '/public/:path+': static({ root: 'demo/public' }),

  '/courses': methods({
    GET:  fetchCourses,
    POST: createCourse
  }),

  '/courses/:id': methods({
    GET:   fetchCourse,
    PATCH: updateCourse,
    PUT:   updateCourse
  }),

  '/error': () => { throw new Error('this code is broken') },

  '/old-courses': () => redirect('/courses')
})

const app  = compose(endpoints, parseJson)
const opts = { errLogger: logger, logger }

http.createServer(mount(app, opts)).listen(port, listening)

Use PORT env var for heroku demo


  function chart(selection) {

    // Merging the various user params
    vars.user_vars = vistk.utils.merge(vars.user_vars, vars._user_vars);

    // Merging with current charts parameters set by the user in the HTML file
    vars = vistk.utils.merge(vars, vars.user_vars);

    // Create the top level element conaining the visualization
    if(!vars.svg) {
       if(vars.type !== "table") {

        vars.svg = d3.select(vars.container).append("svg")
          .attr("width", vars.width)
          .attr("height", vars.height)
          .append("g")
            .attr("transform", "translate(" + vars.margin.left + "," + vars.margin.top + ")");

      } else {
        // HTML Container for table
        vars.svg = d3.select(vars.container).append("div")
            .style({height: vars.height+"px", width: vars.width+"px", overflow: "scroll"});

      }
   }

  function chart(selection) {

    // Merging the various user params
    vars.user_vars = vistk.utils.merge(vars.user_vars, vars._user_vars);

    // Merging with current charts parameters set by the user in the HTML file
    vars = vistk.utils.merge(vars, vars.user_vars);

    // Create the top level element conaining the visualization
    if(!vars.svg) {
       if(vars.type !== "table") {

        vars.svg = d3.select(vars.container).append("svg")
          .attr("width", vars.width)
          .attr("height", vars.height)
          .style('overflow', 'visible')
          .style('z-index', 0)
          .append("g")
            .attr("transform", "translate(" + vars.margin.left + "," + vars.margin.top + ")");

      } else {
        // HTML Container for table
        vars.svg = d3.select(vars.container).append("div")
            .style({height: vars.height+"px", width: vars.width+"px", overflow: "scroll"});

      }
   }

Allow elements within SVG to overflow


import { isArray } from 'utils/is';
import { getKeypath, normalise } from 'shared/keypaths';
import runloop from 'global/runloop';
import getNewIndices from 'shared/getNewIndices';

var arrayProto = Array.prototype;

export default function ( methodName ) {
	return function ( keypath, ...args ) {
		var array, newIndices = [], len, promise, result;

		keypath = getKeypath( normalise( keypath ) );

		array = this.viewmodel.get( keypath );
		len = array.length;

		if ( !isArray( array ) ) {
			throw new Error( 'Called ractive.' + methodName + '(\'' + keypath + '\'), but \'' + keypath + '\' does not refer to an array' );
		}

		newIndices = getNewIndices( array, methodName, args );

		result = arrayProto[ methodName ].apply( array, args );
		promise = runloop.start( this, true ).then( () => result );

		if ( !!newIndices ) {
			this.viewmodel.smartUpdate( keypath, array, newIndices );
		} else {
			this.viewmodel.mark( keypath );
		}

		runloop.end();

		return promise;
	};
}

import { isArray } from 'utils/is';
import { getKeypath, normalise } from 'shared/keypaths';
import runloop from 'global/runloop';
import getNewIndices from 'shared/getNewIndices';

var arrayProto = Array.prototype;

export default function ( methodName ) {
	return function ( keypath, ...args ) {
		var array, newIndices = [], len, promise, result;

		keypath = getKeypath( normalise( keypath ) );

		array = this.viewmodel.get( keypath );
		len = array.length;

		if ( !isArray( array ) ) {
			throw new Error( 'Called ractive.' + methodName + '(\'' + keypath.str + '\'), but \'' + keypath.str + '\' does not refer to an array' );
		}

		newIndices = getNewIndices( array, methodName, args );

		result = arrayProto[ methodName ].apply( array, args );
		promise = runloop.start( this, true ).then( () => result );

		if ( !!newIndices ) {
			this.viewmodel.smartUpdate( keypath, array, newIndices );
		} else {
			this.viewmodel.mark( keypath );
		}

		runloop.end();

		return promise;
	};
}

Use keypath.str property to prevent coercion erros


import SelectStateItemsCollection from '../collections/SelectStateItemsCollection';
import meta from '../meta';

export default Backbone.Model.extend({
    initialize() {
        const items = this.get('items');
        const itemsCollection = new SelectStateItemsCollection(items);
        this.listenTo(itemsCollection, 'select:one', model => this.set('iconClass', model.get('iconClass')));
        this.set('items', itemsCollection);

        const firstNotHeadlineModel = itemsCollection.find(model => model.get('type') !== meta.toolbarItemType.HEADLINE);
        itemsCollection.select(firstNotHeadlineModel);

        this.__addDropdownClass();
    },

    __addDropdownClass() {
        const dropdownClass = this.get('dropdownClass');
        let SelectStateDropdownClass = 'toolbar-panel_container__select-state';
        dropdownClass && (SelectStateDropdownClass = `${dropdownClass} ${SelectStateDropdownClass}`);
        this.set('dropdownClass', SelectStateDropdownClass);
    }
});

import SelectStateItemsCollection from '../collections/SelectStateItemsCollection';
import meta from '../meta';

export default Backbone.Model.extend({
    initialize() {
        const items = this.get('items');
        const itemsCollection = new SelectStateItemsCollection(items);
        this.listenTo(itemsCollection, 'select:one', model => this.set('iconClass', model.get('iconClass')));
        this.set('items', itemsCollection);

        this.__selectDefaultModel(itemsCollection);
        this.__addDropdownClass();
    },

    __addDropdownClass() {
        const dropdownClass = this.get('dropdownClass');
        let SelectStateDropdownClass = 'toolbar-panel_container__select-state';
        dropdownClass && (SelectStateDropdownClass = `${dropdownClass} ${SelectStateDropdownClass}`);
        this.set('dropdownClass', SelectStateDropdownClass);
    },

    __selectDefaultModel(itemsCollection) {
        let firstNotHeadlineModel = null;

        firstNotHeadlineModel = itemsCollection.find(model => model.get('type') !== meta.toolbarItemType.HEADLINE && model.get('default'));

        if (!firstNotHeadlineModel) {
            firstNotHeadlineModel = itemsCollection.find(model => model.get('type') !== meta.toolbarItemType.HEADLINE);
        }

        itemsCollection.select(firstNotHeadlineModel);
    }
});

Add default selected state to toolbar selections.


import AdaptiveStore from 'ember-simple-auth/session-stores/adaptive';

export default AdaptiveStore.extend({
    localStorageKey: 'ghost:session',
    cookieName: 'ghost:session'
});

import AdaptiveStore from 'ember-simple-auth/session-stores/adaptive';
import ghostPaths from 'ghost/utils/ghost-paths';

const paths = ghostPaths();
const keyName = `ghost${(paths.subdir.indexOf('/') === 0 ? `-${paths.subdir.substr(1)}` : ``) }:session`;

export default AdaptiveStore.extend({
    localStorageKey: keyName,
    cookieName: keyName
});

Set localStorage key based on subdir


'use strict';

const readConfig = require('restore-server-config');

// singleton
let config;

/**
 * Loads the configuration and stores it in the config singleton.
 * @param {string} baseDir Directory which contains the folder cfg with the config files.
 * @param [Logger] logger
 */
function load(baseDir, logger) {
  return (cb) => {
    readConfig(baseDir, logger, (cfg) => {
      config = cfg;
      cb(null, cfg);
    });
  };
}

/**
 * Get config from singleton.
 * If singelton is undefined load id from current working directory.
 * @param [Logger] logger
 * @return {Object} nconf configuration object
 */
module.exports.get = function* get(logger) {
  if (config) {
    return config;
  }
  yield load(process.cwd(), logger);
  return config;
};

module.exports.load = load;

'use strict';

const readConfig = require('restore-server-config');

// singleton
let config;

/**
 * Loads the configuration and stores it in the config singleton.
 * @param {string} baseDir Directory which contains the folder cfg with the config files.
 * @param [Logger] logger
 */
function load(baseDir, logger) {
  return (cb) => {
    readConfig(baseDir, logger, (err, cfg) => {
      if (err) {
        cb(err, cfg);
      } else {
        config = cfg;
        cb(null, cfg);
      }
    });
  };
}

/**
 * Get config from singleton.
 * If singelton is undefined load id from current working directory.
 * @param [Logger] logger
 * @return {Object} nconf configuration object
 */
module.exports.get = function* get(logger) {
  if (config) {
    return config;
  }
  yield load(process.cwd(), logger);
  return config;
};

module.exports.load = load;

Replace config load callback wrapper


//= require jquery
//= require leaflet
//= require leaflet-ajax
//= require leaflet-routing-machine
//= require osmtogeojson
//= require maps

$("#agua").change(function () {
    if (this.checked) {
        showWindsLayer();
    } else {
        hideWindsLayer();
    }
})

$("#lluvia").change(function () {
    if (this.checked) {
        showWeatherLayer();
    } else {
        hideWeatherLayer();
    }
})

$("#bosque").change(function () {
    if (this.checked) {
        showForestLayer();
    } else {
        hideForestLayer();
    }
})

//= require jquery
//= require leaflet
//= require leaflet-ajax
//= require leaflet-routing-machine
//= require osmtogeojson
//= require maps

$( document ).ready(function() {

    $("#agua").change(function () {
        if (this.checked) {
            showWindsLayer();
        } else {
            hideWindsLayer();
        }
    })

    $("#lluvia").change(function () {
        if (this.checked) {
            showWeatherLayer();
        } else {
            hideWeatherLayer();
        }
    })

    $("#bosque").change(function () {
        if (this.checked) {
            showForestLayer();
        } else {
            hideForestLayer();
        }
    })

}

Add checkbox event listeners after document load.


/*
 * Copyright (c) 2014 Giovanni Capuano <webmaster@giovannicapuano.net>
 * Released under the MIT License
 * http://opensource.org/licenses/MIT
 */

var config = {};

// Images configuration
config.images = {
  path: '../images',
  url : '/images'
};

// Set the following line to false if you have a webserver that serves you static resources or you have just put your images folder in public/.
// Your images will be available to /dirname/ (where dirname is the name of your images folder).
config.serve = true;

// The image extensions to show
config.exts  = [ '.jpg', '.jpeg', '.bmp', '.png', '.gif' ];

// Sort images and folders
config.sort  = {
  mode: 'DESC',      // ASC   DESC
  by  : 'lastModify' // name  lastModify
};

// Image served by Vasilij will be put in browser cache for 30 days
config.maxAge = '2592000';

// Thumbnails configuration
config.thumbs = {
  path   : '../thumbs',
  url    : '/thumbs',
  width  : 500,
  cpu    : 8
};

module.exports = config;

/*
 * Copyright (c) 2014 Giovanni Capuano <webmaster@giovannicapuano.net>
 * Released under the MIT License
 * http://opensource.org/licenses/MIT
 */

var config = {};

// Images configuration
config.images = {
  path: '../images',
  url : '/images'
};

// Set the following line to false if you have a webserver that serves you static resources or you have just put your images folder in public/.
// Your images will be available to /dirname/ (where dirname is the name of your images folder).
config.serve = true;

// The image extensions to show
config.exts  = [ '.jpg', '.jpeg', '.bmp', '.png', '.gif' ];

// Sort images and folders
config.sort  = {
  mode: 'DESC',      // ASC   DESC
  by  : 'lastModify' // name  lastModify
};

// Image served by Vasilij will be put in browser cache for 30 days
config.maxAge = '2592000';

// Thumbnails configuration
config.thumbs = {
  path   : '../thumbs',
  url    : '/thumbs',
  width  : 250,
  cpu    : 8
};

module.exports = config;

Set default thumbs width to 250


import React, { PureComponent } from 'react';
import Link from '../../components/link';

class LinkTest extends PureComponent {
  handleClick = () => {
    console.log('Clicked on a link');
  };

  render () {
    return (

Links

<Link url="#">I'm a link</Link>

<Link onClick={this.handleClick}>I'm a link</Link>

    );
  }
}

export default LinkTest;

import React, { PureComponent } from 'react';
import { Link, Heading1, Section } from '../../components/';
import { BrowserRouter as Router, Link as ReactRouterLink } from 'react-router-dom';

Link.use(ReactRouterLink);

class LinkTest extends PureComponent {
  handleClick = () => {
    console.log('Clicked on a link');
  };

  render () {
    return (
      <Router>

            <Heading1>Links</Heading1>

<Link to="http://www.facebook.com" target="_blank">I'm an external link</Link>

<Link to="#" onClick={this.handleClick}>I'm an internal link</Link>

      </Router>
    );
  }
}

export default LinkTest;

Use the Link component from react-router in combination with our custom styled Link


import React from 'react';
import shallowRender from '../utils/shallowRender';

import Thumbnail from '../../src/components/Thumbnail';
jest.unmock('../../src/components/Thumbnail');

describe('Thumbnail Component', () => {
  it('should be a div HTML element', () => {
    const output = shallowRender(<Thumbnail />);

    expect(output.type).toBe('div');
  });

  it('should have a cssName of wrr-thumbnail', () => {
    const output = shallowRender(<Thumbnail />);

    expect(output.props.className).toBe('wrr-thumbnail');
  });
});

import React from 'react';
import shallowRender from '../utils/shallowRender';

import Thumbnail from '../../src/components/Thumbnail';
jest.unmock('../../src/components/Thumbnail');

describe('Thumbnail Component', () => {
  it('should be a div HTML element', () => {
    const output = shallowRender(<Thumbnail />);

    expect(output.type).toBe('div');
  });

  it('should have a cssName of wrr-thumbnail', () => {
    const output = shallowRender(<Thumbnail />);

    expect(output.props.className).toBe('wrr-thumbnail');
  });

  it('should have a default color #000', () => {
    const expected = { color: '#000' };
    const output = shallowRender(<Thumbnail />);

    expect(output.props.style).toEqual(expected);
  });

  it('should properly set color to #fff when passed', () => {
    const expected = { color: '#fff' };
    const output = shallowRender(<Thumbnail color={expected.color} />);

    expect(output.props.style).toEqual(expected);
  });
});

Update tests for Thumbnail component


import { app } from "electron";

const isDevelopment = process.env.NODE_ENV !== "production";
import os from "os";
import path from "path";

let walletPath = "";

if (process.type !== "renderer") {
  if (os.platform() === "linux") {
    walletPath = path.join(
      app.getPath("home"),
      isDevelopment ? ".gulden_dev" : ".gulden"
    );
  } else {
    walletPath = app.getPath("userData");
    if (isDevelopment) walletPath = walletPath + "_dev";
  }
}

export default walletPath;

import { app } from "electron";

const isDevelopment = process.env.NODE_ENV !== "production";
import os from "os";
import path from "path";

let walletPath = "";

if (process.type !== "renderer") {
  if (os.platform() === "linux") {
    walletPath = path.join(
      app.getPath("home"),
      isDevelopment ? ".gulden_lite_dev" : ".gulden_lite"
    );
  } else {
    walletPath = app.getPath("userData");
    if (isDevelopment) walletPath = walletPath + "_dev";
  }
}

export default walletPath;

Change wallet path for lite wallet


function resetPage() {
    // Prevent default behaviour of all form submits throught Florence
    $(document).on('submit', 'form', function(e) {
        e.preventDefault();
        console.log('Form submit stopped on: ', e);
    });

    // Fix for modal form submits not being detected
    $(document).on('click', 'button', function() {
        var $closestForm = $(this).closest('form');
        $closestForm.submit(function(e) {
            e.preventDefault();
        });
    });

    // Stop anchors doing default behaviour
    // $(document).on('click', 'a', function(e) {
    //     e.preventDefault();
    //     console.log('Anchor click stopped on: ', e);
    // });

    // $(document).on('click', 'form', function(e) {
    //     e.preventDefault();
    //     console.log('Form clicked');
    // });

    // $(document).on('click', 'button', function (e) {
    //     e.preventDefault();
    //     console.log(e);
    // });

    // $(document).on('submit', '#cancel-form', function(e) {
    //     e.preventDefault();
    //     console.log(e);
    // });
}

function resetPage() {
    // Prevent default behaviour of all form submits throught Florence
    $(document).on('submit', 'form', function(e) {
        e.preventDefault();
    });

    // Fix for modal form submits not being detected
    $(document).on('click', 'button', function() {
        var $closestForm = $(this).closest('form');
        $closestForm.submit(function(e) {
            e.preventDefault();
        });
    });

    // Stop anchors doing default behaviour
    // $(document).on('click', 'a', function(e) {
    //     e.preventDefault();
    //     console.log('Anchor click stopped on: ', e);
    // });

    // $(document).on('click', 'form', function(e) {
    //     e.preventDefault();
    //     console.log('Form clicked');
    // });

    // $(document).on('click', 'button', function (e) {
    //     e.preventDefault();
    //     console.log(e);
    // });

    // $(document).on('submit', '#cancel-form', function(e) {
    //     e.preventDefault();
    //     console.log(e);
    // });
}

Remove form submit prevention logging


/* @flow */

import React from "react-native";
import routeMapper from "../routes/routeMapper";
import Colors from "../../Colors.json";

const {
	NavigationCard,
	StyleSheet,
	View
} = React;

const styles = StyleSheet.create({
	container: {
		flex: 1,
		backgroundColor: Colors.lightGrey
	},

	normal: {
		marginTop: 56
	}
});

const renderScene = function(navState: Object, onNavigation: Function): Function {
	return props => {
		const route = navState.get(navState.index);
		const {
			component: RouteComponent,
			passProps
		} = routeMapper(route);

		return (
			<NavigationCard {...props}>
				<View style={[ styles.container, route.fullscreen ? null : styles.normal ]}>
					<RouteComponent
						 {...passProps}
						 style={styles.container}
						 onNavigation={onNavigation}
					/>
				</View>
			</NavigationCard>
		);
	};
};

export default renderScene;

/* @flow */

import React from "react-native";
import routeMapper from "../routes/routeMapper";
import Colors from "../../Colors.json";

const {
	NavigationCard,
	StyleSheet,
	View
} = React;

const styles = StyleSheet.create({
	container: {
		flex: 1,
		backgroundColor: Colors.lightGrey
	},

	normal: {
		marginTop: 56
	}
});

const renderScene = function(navState: Object, onNavigation: Function): Function {
	return props => {
		const route = props.sceneRecord.get("route"); // eslint-disable-line react/prop-types

		const {
			component: RouteComponent,
			passProps
		} = routeMapper(route);

		return (
			<NavigationCard {...props}>
				<View style={[ styles.container, route.fullscreen ? null : styles.normal ]}>
					<RouteComponent
						 {...passProps}
						 style={styles.container}
						 onNavigation={onNavigation}
					/>
				</View>
			</NavigationCard>
		);
	};
};

export default renderScene;

Use sceneRecord.get to get current route


import {
    fromPairs,
    isEmpty,
    head,
    last,
    max,
    mapObjIndexed,
    sortBy,
    toPairs,
} from 'ramda'

export function calculateReserves (cumulative, reserveData, mineral, column, series) {
    const reserves = getReserves(reserveData, mineral)

    if (!reserves) {
        // console.debug('No reserves!')
        return {}
    }

    const [reserveYear, reserveAmount] = last(sortBy(head, toPairs(reserves)))
    const cumulativeOnReserveYear = cumulative.data[reserveYear][column]

    return mapObjIndexed((row, year) => {
        return fromPairs([
            ['Year', year],
            [series, max(1, reserveAmount - (row[column] - cumulativeOnReserveYear))]
        ])
    }, cumulative.data)
}

export function getReserves (reserves, mineral) {
    return reserves.data && reserves.data[mineral]
}

import {
    fromPairs,
    isEmpty,
    head,
    last,
    max,
    mapObjIndexed,
    sortBy,
    toPairs,
} from 'ramda'

export function calculateReserves (cumulative, reserveData, mineral, column, series) {
    const reserves = getReserves(reserveData, mineral)

    if (!reserves) {
        // console.debug('No reserves!')
        return {}
    }

    const [reserveYear, reserveAmount] = last(sortBy(head, toPairs(reserves)))
    const cumulativeOnReserveYear = cumulative.data[reserveYear][column]

    return mapObjIndexed((row, year) => {
        return fromPairs([
            ['Year', year],
            [series, max(0, reserveAmount - (row[column] - cumulativeOnReserveYear))]
        ])
    }, cumulative.data)
}

export function getReserves (reserves, mineral) {
    return reserves.data && reserves.data[mineral]
}

Change calculateReserves minimum value to zero


export default ({ borders, colors, radii, space }) => {
  const defaultStyles = {
    mx: 'auto',
    bg: colors.white,
    width: ['100%', '85%'],
    mb: `${space[6]}px`,
  };

  const large = {
    ...defaultStyles,
    maxWidth: '1280px',
  };

  const medium = {
    ...defaultStyles,
    maxWidth: '840px',
  };

  const small = {
    ...defaultStyles,
    maxWidth: '600px',
  };

  const bordered = {
    borderLeft: ['none', borders.default],
    borderRight: ['none', borders.default],
    borderTop: borders.default,
    borderBottom: borders.default,
    borderRadius: ['none', radii.default],
  };

  return {
    large,
    largeBordered: {
      ...large,
      ...bordered,
    },
    medium,
    mediumBordered: {
      ...medium,
      ...bordered,
    },
    small,
    smallBordered: {
      ...small,
      ...bordered,
    },
  };
};

export default ({ borders, colors, radii, space }) => {
  const defaultStyles = {
    mx: 'auto',
    bg: colors.white,
    width: ['100%', '85%'],
    mb: `${space[6]}px`,
    position: 'relative',
  };

  const large = {
    ...defaultStyles,
    maxWidth: '1280px',
  };

  const medium = {
    ...defaultStyles,
    maxWidth: '840px',
  };

  const small = {
    ...defaultStyles,
    maxWidth: '600px',
  };

  const bordered = {
    borderLeft: ['none', borders.default],
    borderRight: ['none', borders.default],
    borderTop: borders.default,
    borderBottom: borders.default,
    borderRadius: ['none', radii.default],
  };

  return {
    large,
    largeBordered: {
      ...large,
      ...bordered,
    },
    medium,
    mediumBordered: {
      ...medium,
      ...bordered,
    },
    small,
    smallBordered: {
      ...small,
      ...bordered,
    },
  };
};

Add relative position to default container styles


'use strict';

function ticketTypes (args, callback) {
  var ticketTypes = [
    {name: 'ninja', title: 'Ninja', tooltip: 'Select Ninja to create tickets for attendees under 13 or over 13.'},
    {name: 'parent-guardian', title: 'Parent/Guardian', tooltip: 'Select Parent/Guardian to create tickets for parents/guardians of the attendees.'},
    {name: 'mentor', title: 'Mentor', tooltip: 'Select Mentor to create tickets for Mentors.'},
    {name: 'other', title: 'Other', tooltip: 'Select Other to create tickets for unlisted types e.g. laptops.'}
  ];
  setImmediate(function () {
    return callback(null, ticketTypes);
  });
}

module.exports = ticketTypes;

'use strict';

function ticketTypes (args, callback) {
  /* ninja is defined as a key in many areas, keep it for backward compatibility */
  var ticketTypes = [
    {name: 'ninja', title: 'Youth', tooltip: 'Select Youth to create tickets for attendees under 18.'},
    {name: 'parent-guardian', title: 'Parent/Guardian', tooltip: 'Select Parent/Guardian to create tickets for parents/guardians of the attendees.'},
    {name: 'mentor', title: 'Mentor', tooltip: 'Select Mentor to create tickets for Mentors.'},
    {name: 'other', title: 'Other', tooltip: 'Select Other to create tickets for unlisted types e.g. laptops.'}
  ];
  setImmediate(function () {
    return callback(null, ticketTypes);
  });
}

module.exports = ticketTypes;

Rename ninja to youth to reduce confusion


/* global malarkey:false, moment:false */

import { config } from './index.config';
import { routerConfig } from './index.route';
import { themeConfig } from './index.theme';
import { runBlock } from './index.run';
import { MainController } from './main/main.controller';
import { DetailsController } from './details/details.controller';
import { RetractableSearchbarDirective } from '../app/components/retractableSearchbar/retractableSearchbar.directive';

angular.module('generic-search-flow', ['ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ngMessages', 'ngAria', 'ngResource', 'ngRoute', 'ngMaterial', 'toastr', 'infinite-scroll'])
  .constant('malarkey', malarkey)
  .constant('moment', moment)
  .config(config)
  .config(routerConfig)
  .config(themeConfig)
  .run(runBlock)
  .controller('MainController', MainController)
  .controller('DetailsController', DetailsController)
  .directive('retractableSearchbar', RetractableSearchbarDirective)

/* global moment:false */

import { config } from './index.config';
import { routerConfig } from './index.route';
import { themeConfig } from './index.theme';
import { runBlock } from './index.run';
import { MainController } from './main/main.controller';
import { DetailsController } from './details/details.controller';
import { RetractableSearchbarDirective } from '../app/components/retractableSearchbar/retractableSearchbar.directive';

angular.module('generic-search-flow', ['ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ngMessages', 'ngAria', 'ngResource', 'ngRoute', 'ngMaterial', 'toastr'])
  .constant('moment', moment)
  .config(config)
  .config(routerConfig)
  .config(themeConfig)
  .run(runBlock)
  .controller('MainController', MainController)
  .controller('DetailsController', DetailsController)
  .directive('retractableSearchbar', RetractableSearchbarDirective)

FIX - Removed unused dependencies


import AsyncStorage from '@react-native-community/async-storage';
import { persistStore, persistReducer } from 'redux-persist';
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import { createReactNavigationReduxMiddleware } from 'react-navigation-redux-helpers';
import reducers from './reducers';

// Create middleware and connect
const appNavigatorMiddleware = createReactNavigationReduxMiddleware(state => state.nav, 'root');

const persistConfig = {
  keyPrefix: '',
  key: 'root',
  storage: AsyncStorage,
  blacklist: ['nav'],
};

const persistedReducer = persistReducer(persistConfig, reducers);

const store = createStore(persistedReducer, {}, applyMiddleware(thunk, appNavigatorMiddleware));

const persistedStore = persistStore(store);

export { store, persistedStore };

import AsyncStorage from '@react-native-community/async-storage';
import { persistStore, persistReducer } from 'redux-persist';
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import { createReactNavigationReduxMiddleware } from 'react-navigation-redux-helpers';
import reducers from './reducers';

// Create middleware and connect
const appNavigatorMiddleware = createReactNavigationReduxMiddleware(state => state.nav, 'root');

const persistConfig = {
  keyPrefix: '',
  key: 'root',
  storage: AsyncStorage,
  blacklist: ['nav', 'pages'],
};

const persistedReducer = persistReducer(persistConfig, reducers);

const store = createStore(persistedReducer, {}, applyMiddleware(thunk, appNavigatorMiddleware));

const persistedStore = persistStore(store);

export { store, persistedStore };

Add blacklist pages store state


'use strict';

/**
 * Make a web element pannable.
 */

module.exports = {
	init: (elem, containerElem) => {
		var container = containerElem ?
			containerElem : elem.parentElement;

		elem.addEventListener('mousemove', (e) => {
			e.preventDefault();

			if (e && e.which === 1) {

				if (e.movementX !== 0) {
					container.scrollLeft -= e.movementX;
				}

				if (e.movementY !== 0) {
					container.scrollTop -= e.movementY;
				}
			}
		});

		return elem;
	}
};

'use strict';

/**
 * Limit execution of onReady function
 * to once over the given wait time.
 *
 * @function _throttle
 * @private
 *
 * @param {function} onReady
 * @param {function} onStandby
 * @param {number} wait
 *
 * @returns {function}
 */
const _throttle = (onReady, onStandby, wait) => {
    let time = Date.now();

    return (e) => {
        onStandby(e);

        if ((time + wait - Date.now()) < 0) {
            onReady(e);
            time = Date.now();
        }
    };
};

/**
 * Initialize element to be pannable
 * within the given contanerElement.
 *
 * @function init
 * @public
 *
 * @param {Object} elem
 * @param {Object} containerElem
 *
 * @returns {Object}
 */
const init = (elem, containerElem) => {
    const container = containerElem ?
        containerElem : elem.parentElement;

    let scrollLeft = 0;
    let scrollTop = 0;

    const handleMove = _throttle(
        (e) => {
            e.preventDefault();

            requestAnimationFrame(() => {
                if (e && e.which === 1) {

                    if (e.movementX !== 0) {
                        container.scrollLeft -= scrollLeft;
                    }

                    if (e.movementY !== 0) {
                        container.scrollTop -= scrollTop;
                    }

                    scrollLeft = 0;
                    scrollTop = 0;
                }
            });
        },
        (e) => {
            if (e && e.which === 1) {

                if (e.movementX !== 0) {
                    scrollLeft += e.movementX;
                }

                if (e.movementY !== 0) {
                    scrollTop += e.movementY;
                }
            }
        },
        10
    );

    elem.addEventListener(
        'mousemove',
        handleMove
    );

    return elem;
};

/**
 * Make a web element pannable.
 */

module.exports = {
    init
};

Implement throttling on event listener


'use strict';
/* globals window, angular */

angular.module('vlui', [
  'LocalStorageModule',
  'ui.select',
  ])
  .constant('_', window._)
  // datalib, vegalite, vega
  .constant('dl', window.dl)
  .constant('vl', window.vl)
  .constant('vg', window.vg)
  // other libraries
  .constant('Blob', window.Blob)
  .constant('URL', window.URL)
  .constant('Drop', window.Drop)
  .constant('Heap', window.Heap)
  // constants
  .constant('consts', {
    addCount: true, // add count field to Dataset.dataschema
    debug: true,
    useUrl: true,
    logging: false,
    defaultConfigSet: 'large',
    appId: 'vlui',
    // embedded polestar and voyager with known data
    embeddedData: window.vguiData || undefined,
    priority: {
      bookmark: 0,
      popup: 0,
      vislist: 1000
    },
    myriaRest: 'http://ec2-52-1-38-182.compute-1.amazonaws.com:8753'
  });

'use strict';
/* globals window, angular */

angular.module('vlui', [
  'LocalStorageModule',
  'ui.select',
  'angular-google-analytics'
  ])
  .constant('_', window._)
  // datalib, vegalite, vega
  .constant('dl', window.dl)
  .constant('vl', window.vl)
  .constant('vg', window.vg)
  // other libraries
  .constant('Blob', window.Blob)
  .constant('URL', window.URL)
  .constant('Drop', window.Drop)
  .constant('Heap', window.Heap)
  // constants
  .constant('consts', {
    addCount: true, // add count field to Dataset.dataschema
    debug: true,
    useUrl: true,
    logging: true,
    defaultConfigSet: 'large',
    appId: 'vlui',
    // embedded polestar and voyager with known data
    embeddedData: window.vguiData || undefined,
    priority: {
      bookmark: 0,
      popup: 0,
      vislist: 1000
    },
    myriaRest: 'http://ec2-52-1-38-182.compute-1.amazonaws.com:8753'
  })
  .config(function (AnalyticsProvider) {
    AnalyticsProvider
      .setAccount(//{ tracker: 'UA-44428446-4', name: 'voyager', trackEvent: true },
                  { tracker: 'UA-38453955-1', name: 'test', trackEvent: true });
  });

Add angular analytics as dependency


/* @flow */

import matches from 'matches-selector-ng';

const proto = global.Element && global.Element.prototype;
const vendor = proto && proto.closest;

export default function closest(element: HTMLElement, selector: string): ?HTMLElement {
  if (vendor) return vendor.call(element, selector);
  let parent = element;
  do {
    if (matches(parent, selector)) return parent;
    parent = parent.parentNode;
  } while (parent && parent !== global.document);
  return null;
}

/* @flow */

import matches from 'matches-selector-ng';

const proto = global.Element && global.Element.prototype;
const vendor = proto && proto.closest;

export default function closest(element: HTMLElement, selector: string): HTMLElement | null {
  if (vendor) return vendor.call(element, selector);
  let parent = element;
  do {
    if (matches(parent, selector)) return parent;
    parent = parent.parentNode;
  } while (parent && parent !== global.document);
  return null;
}

Make return type more specific.


define(['backbone', 'backbone.marionette', 'communicator', 'leaflet', 'leaflet.draw'],
  function(Backbone, Marionette, Communicator, L, LeafletDraw) {

    return Marionette.Object.extend({

      map: null,
      mapController: null,

      initialize: function(o) {

        var self = this;
        Communicator.mediator.on("map:ready", function(map) {
          self.map = map;
          self.initDraw();
        }, this);
      },

      initDraw: function() {

        // Leaflet map
        var map = this.map;
        var drawnItems = new L.FeatureGroup();

        map.addLayer(drawnItems);

        var drawControl = new L.Control.Draw({
          edit: {
            featureGroup: drawnItems
          }
        });
        map.addControl(drawControl);

        map.on('draw:created', function (e) {
          var type = e.layerType,
            layer = e.layer;

          if (type === 'marker') {
            // Do marker specific actions

          }
          drawnItems.addLayer(layer);
        });

      }
    })

});

define(['backbone', 'backbone.marionette', 'communicator', 'leaflet', 'leaflet.draw'],
  function(Backbone, Marionette, Communicator, L, LeafletDraw) {

    return Marionette.Object.extend({

      map: null,
      mapController: null,

      initialize: function(o) {

        var self = this;
        Communicator.mediator.on("map:ready", function(map) {
          self.map = map;
          self.initDraw();
        }, this);
      },

      initDraw: function() {

        // Leaflet map
        var map = this.map;
        var drawnItems = new L.FeatureGroup();

        map.addLayer(drawnItems);

        var drawControl = new L.Control.Draw({
          draw: {
            rectangle: false,
            circle: false,
          },
          edit: {
            featureGroup: drawnItems
          }
        });
        map.addControl(drawControl);

        map.on('draw:created', function (e) {
          var type = e.layerType,
            layer = e.layer;

          if (type === 'marker') {
            // Do marker specific actions

          }
          drawnItems.addLayer(layer);
        });

      }
    })

});

Remove circle and rectangle tools


var ForgePlugins = ForgePlugins || {};

ForgePlugins.MediaEvents = function()
{
    this._video = null;
};

ForgePlugins.MediaEvents.prototype =
{
    boot: function()
    {
        this._setupVideo();
    },

    reset: function()
    {
        this._clearVideo();
        this._setupVideo();
    },

    _setupVideo: function()
    {
        if(this.viewer.renderer.media !== null)
        {
            this._video = this.viewer.renderer.media.displayObject;
            this._video.onEnded.add(this._onEndedHandler, this);
        }
        else
        {
            if(this.viewer.renderer.onMediaReady.has(this._setupVideo, this) === false)
            {
                this.viewer.renderer.onMediaReady.addOnce(this._setupVideo, this);
            }
        }
    },

    _clearVideo: function()
    {
        if(this._video !== null)
        {
            this._video.onEnded.remove(this._onEndedHandler, this);
        }

        this._video = null;
    },

    _onEndedHandler: function()
    {
        this.plugin.events.onEnded.dispatch();
    },

    destroy: function()
    {
        this._clearVideo();
    }
};

var ForgePlugins = ForgePlugins || {};

ForgePlugins.MediaEvents = function()
{
    this._video = null;
};

ForgePlugins.MediaEvents.prototype =
{
    boot: function()
    {
        this._setupVideo();
    },

    reset: function()
    {
        this._clearVideo();
        this._setupVideo();
    },

    _setupVideo: function()
    {
        this._video = this.viewer.story.scene.media.displayObject;
        this._video.onEnded.add(this._onEndedHandler, this);
    },

    _clearVideo: function()
    {
        if(this._video !== null)
        {
            this._video.onEnded.remove(this._onEndedHandler, this);
        }

        this._video = null;
    },

    _onEndedHandler: function()
    {
        this.plugin.events.onEnded.dispatch();
    },

    destroy: function()
    {
        this._clearVideo();
    }
};

Fix MediaEvent plugin of immersive project for new scene media


/**
 * This file is covered by the AGPLv3 license, which can be found at the LICENSE file in the root of this project.
 * @copyright 2020 subtitulamos.tv
 */

import "../css/rules.scss";
import { onDomReady } from "./utils";

onDomReady(() => {
  for (const $spoilerWrapper of document.querySelectorAll(".spoiler-wrapper")) {
    $spoilerWrapper.addEventListener("click", function () {
      const $spoiler = this.querySelector(".spoiler-content");

      const $spoilerName = this.querySelector(".spoiler-name");
      if ($spoilerName.innerHTML.includes("VER")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("VER", "OCULTAR");
      } else if ($spoilerName.innerHTML.includes("OCULTAR")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("OCULTAR", "VER");
      }
      if ($spoilerName.innerHTML.includes("MÁS")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("MÁS", "MENOS");
      } else if ($spoilerName.innerHTML.includes("MENOS")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("MENOS", "MÁS");
      }

      const $icon = this.querySelector(".spoiler-name i");
      $icon.classList.toggle("fa-chevron-down");
      $icon.classList.toggle("fa-chevron-up");

      $spoiler.classList.toggle("expanded");
    });
  }
});

/**
 * This file is covered by the AGPLv3 license, which can be found at the LICENSE file in the root of this project.
 * @copyright 2020 subtitulamos.tv
 */

import "../css/rules.scss";
import { get_all, onDomReady } from "./utils";

onDomReady(() => {
  for (const $spoilerName of get_all(".spoiler-name")) {
    $spoilerName.addEventListener("click", function () {
      const $spoilerWrapper = this.closest(".spoiler-wrapper");
      const $spoiler = $spoilerWrapper.querySelector(".spoiler-content");

      if ($spoilerName.innerHTML.includes("VER")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("VER", "OCULTAR");
      } else if ($spoilerName.innerHTML.includes("OCULTAR")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("OCULTAR", "VER");
      }
      if ($spoilerName.innerHTML.includes("MÁS")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("MÁS", "MENOS");
      } else if ($spoilerName.innerHTML.includes("MENOS")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("MENOS", "MÁS");
      }

      const $icon = this.querySelector(".spoiler-name i");
      $icon.classList.toggle("fa-chevron-down");
      $icon.classList.toggle("fa-chevron-up");

      $spoiler.classList.toggle("expanded");
    });
  }
});

Fix collapsing of rule explanations


const defaults = require('./defaults.js');

const parseBoolean = boolean => boolean === 'true';

const parseIntervals = (intervals) => {
  if (Array.isArray(intervals)) {
    return intervals
      .filter(i => !isNaN(i))
      .map(parseFloat)
      .sort((a, b) => a - b);
  }
  const interval = parseFloat(intervals);
  return interval ? [interval] : defaults.intervals;
};

const parseUnits = (units) => {
  if (units === 'kilometers' || units === 'miles') {
    return units;
  }
  return defaults.units;
};

const parsers = {
  lng: parseFloat,
  lat: parseFloat,
  radius: parseFloat,
  cellSize: parseFloat,
  concavity: parseFloat,
  lengthThreshold: parseFloat,
  deintersect: parseBoolean,
  intervals: parseIntervals,
  units: parseUnits
};

const parseQuery = query =>
  Object.keys(parsers).reduce((acc, paramKey) => {
    if (query[paramKey]) {
      const parser = parsers[paramKey];
      acc[paramKey] = parser(query[paramKey]);
    }
    return acc;
  }, defaults);

module.exports = parseQuery;

const defaults = require('./defaults.js');

const parseBoolean = boolean => boolean === 'true';

const parseIntervals = (intervals) => {
  if (Array.isArray(intervals)) {
    return intervals
      .filter(i => !isNaN(i))
      .map(parseFloat)
      .sort((a, b) => a - b);
  }
  const interval = parseFloat(intervals);
  return interval ? [interval] : defaults.intervals;
};

const parseUnits = (units) => {
  if (units === 'kilometers' || units === 'miles') {
    return units;
  }
  return defaults.units;
};

const parsers = {
  lng: parseFloat,
  lat: parseFloat,
  radius: parseFloat,
  cellSize: parseFloat,
  concavity: parseFloat,
  lengthThreshold: parseFloat,
  deintersect: parseBoolean,
  intervals: parseIntervals,
  units: parseUnits
};

const parseQuery = query =>
  Object.keys(parsers).reduce((acc, paramKey) => {
    if (query[paramKey]) {
      const parser = parsers[paramKey];
      acc[paramKey] = parser(query[paramKey]);
    }
    return acc;
  }, Object.assign({}, defaults));

module.exports = parseQuery;

Clone `defaults` object to avoid member assignment


var initialized = false;

Pebble.addEventListener("ready", function() {
  console.log("ready called!");
  initialized = true;
});

Pebble.addEventListener("showConfiguration", function() {
  console.log("showing configuration");
  var shakeToCheat = 1; // get current setting from pebble
  Pebble.openURL('http://rawgithub.com/kesselborn/line-maze/SDK-2.0-with-shake-to-cheat-config/configuration.html?stc=' + shakeToCheat);
});

Pebble.addEventListener("appmessage", function(e) {
  console.log("Received message: " + e.payload);
});

Pebble.addEventListener("webviewclosed", function(e) {
  console.log("configuration closed");
  // webview closed
  var options = JSON.parse(decodeURIComponent(e.response));

  console.log("sending config to pebble");
  Pebble.sendAppMessage( { "shake-to-cheat": options["shake-to-cheat"] },
                        function(e) {
                          console.log("Successfully delivered message with transactionId=" +
                                      e.data.transactionId + " ... shake-to-cheat=" + options["shake-to-cheat"]);
                        },
                        function(e) {
                          console.log("Unable to deliver message with transactionId=" +
                                      e.data.transactionId +
                                      " Error is: " + e.error.message);
                        }
                       );

  console.log("Options = " + JSON.stringify(options));
});

var initialized = false;

Pebble.addEventListener("ready", function() {
  console.log("ready called!");
  initialized = true;
});

Pebble.addEventListener("showConfiguration", function() {
  console.log("showing configuration");
  var shakeToCheat = localStorage.getItem("shake-to-cheat") || 1;
  Pebble.openURL('http://rawgithub.com/kesselborn/line-maze/SDK-2.0-with-shake-to-cheat-config/configuration.html?stc=' + shakeToCheat);
});

Pebble.addEventListener("appmessage", function(e) {
  console.log("Received message: " + e.payload);
});

Pebble.addEventListener("webviewclosed", function(e) {
  console.log("configuration closed");
  // webview closed
  var options = JSON.parse(decodeURIComponent(e.response));

  console.log("sending config to pebble");
  Pebble.sendAppMessage( { "shake-to-cheat": options["shake-to-cheat"] },
                        function(e) {
                          console.log("Successfully delivered message with transactionId=" +
                                      e.data.transactionId + " ... shake-to-cheat=" + options["shake-to-cheat"]);

                          // only save when it worked to stay in sync with watch
                          localStorage.setItem("shake-to-cheat", options["shake-to-cheat"]);
                        },
                        function(e) {
                          console.log("Unable to deliver message with transactionId=" +
                                      e.data.transactionId +
                                      " Error is: " + e.error.message);
                        }
                       );

  console.log("Options = " + JSON.stringify(options));
});

Load and persist config on phone app


import React from 'react';

import SideLink from '../common/SideLink';
import { Link } from 'react-router';

const Sidebar = (props) => {  // eslint-disable-line no-unused-vars
  return (

Test user

                     Online

OPTIONS

                <SideLink to="/">Home</SideLink>
                <SideLink to="/settings">Settings</SideLink>

 Subscribers

<Link to="/add-email">Add email</Link>

Link in level 2

  );
};

export default Sidebar;

import React from 'react';

import SideLink from '../common/SideLink';
import { Link } from 'react-router';

const Sidebar = (props) => {  // eslint-disable-line no-unused-vars
  return (

Test user

                     Online

OPTIONS

                <SideLink to="/">Home</SideLink>
                <SideLink to="/settings">Settings</SideLink>

 Subscribers

<Link to="/add-email">Add email</Link>

<Link to="/import-subscribers">Import</Link>

  );
};

export default Sidebar;

Add a link to the import-subscribers page in the sidebar


//TODO: Implement helper functions for:
// open/close, lock/unlock, and other player and NPC interactions with doors.

'use strict';
const CommandUtil = require('./command_util').CommandUtil;
const util = require('util');

/*
 * Handy little util functions.
 */
const has = (collection, thing) => collection.indexOf(thing) !== -1;

const findExit = (room, dir) => room.getExits()
  .filter(exit => has(exit.direction, dir));

const updateDestination = (player, dest, callback) => dest
  .getExits()
  .filter(exit => exit.location === player.getLocation())
  .forEach(callback);

exports.DoorUtil = {
  has,       updateDestination,
  findExit,  openOrClose };

function openOrClose(verb, args, player, players, rooms) {

  const isOpen = verb === 'open';

  player.emit('action', 0);

  if (player.isInCombat()) {
    player.say('You are too busy for that right now.');
    return;
  }

  args = args.toLowerCase().split(' ');

  if (!args) {
    return player.say("Which door do you want to " + verb + "?");
  }
  const room  = rooms.getAt(player.getLocation());
  const dir   = args[0];
  const exits = findExit(room, dir);

  if (exits.length !== 1) {
    return player.say('Be more specific...');
  }

  const exit = exits[0];

  if (!exit.door) {
    return player.say('There is no door.');
  }

  if (exit.door.open === isOpen) {
    const pastTense = isOpen ? 'opened' : 'closed';
    return player.say('That door is already ' + pastTense +'.');
  }

  const dest = rooms.getAt(exit.location);

  const srcTitle  = room.getTitle('en');
  const destTitle = dest.getTitle('en');

  player.say('You ' + verb + ' the door to ' + destTitle + '.');
  exit.door.open = isOpen;

  updateDestination(player, dest,
    exit => {
      exit.door.open = isOpen;

      const nounPhrase = isOpen ? 'swings open' : 'slams shut';
      players.eachIf(
        p => p.getLocation() === exit.location,
        p => p.say('The door to ' + srcTitle + ' ' + nounPhrase + '.'));
    });

  players.eachIf(
    p => CommandUtil.inSameRoom(p, player),
    p => p.say(player.getName() + ' ' + verb + 's the door to ' + dest + '.'));

};

Refactor open and close function to be the same thing, exported to be called from the command files.


var store = null;

$(function() {
  $(document).on("dragstart", "img", false);

  $("#page-back").click(function() {
    utils.page(utils.page() - 1);
  });
  $("#page-next").click(function() {
    utils.page(utils.page() + 1);
  });

  $.getJSON("data.json").done(function(data) {
    if(data.length == 0) alert("No data.json, or data invalid.");

    store = data;

    window.router = new router();
    router.init();
    if(location.hash == "#" || location.hash == "") location.hash = "#index!1";
  });
});

var store = null;

$(function() {
  $(document).on("dragstart", "img", false);

  $("#page-back").click(function() {
    utils.page(utils.page() - 1);
  });
  $("#page-next").click(function() {
    utils.page(utils.page() + 1);
  });

  $(window).keydown(function(event) {
    if(event.keyCode == 39) {
      event.preventDefault();
      utils.page(utils.page() + 1);
    }
    else if(event.keyCode == 8 || event.keyCode == 37) {
      event.preventDefault();
      utils.page(utils.page() - 1);
    }
  });

  $.getJSON("data.json").done(function(data) {
    if(data.length == 0) alert("No data.json, or data invalid.");

    store = data;

    window.router = new router();
    router.init();
    if(location.hash == "#" || location.hash == "") location.hash = "#index!1";
  });
});

Allow using keyboard to move between pages as well


// let messanger = require('./mailer/transport')();

module.exports = function(_db, messanger){

    if(!SlickSources[_db]) return {start: () => false};

    let _req = {db:SlickSources[_db]},

        Mails = Models.Mails(_req),
        fetchMails = cb => {

            Mails.find({limit:100,sent:'0'}, function(e, recs){
                if(!e){
                    cb(recs);
                }else{
                    console.error(e);
                }

            })

        },
        start = () => {
            fetchMails( dispatch );
        },
        dispatch = recs => {

            let run = function(){

                if(recs.length){

                    sendMail(recs.pop());

                }else setTimeout(start,100);

            },
            sendMail = r => {

                r.to = `${r.receiver_name} <${r.receiver_email}>`;
                r.message = r.body;

                messanger.sendMail(r, function (e, info) {

                    if (!e) {

                        Mails.destroy({id:r.id}, function(err, res){
                            run();
                        });

                    } else{

                        Mails.update({id:r.id, sent:'1'}, function(){
                            run();
                        });
                    }

                });

            };
            run();
        };

        console.log('Registered Mailer for ', _db);

        return {
            start:start
        }
};

// let messanger = require('./mailer/transport')();

module.exports = function(_db, messanger){

    if(!SlickSources[_db]) return {start: () => false};

    let _req = {db:SlickSources[_db]},

        Mails = Models.Mails(_req),
        fetchMails = cb => {

            Mails.find({limit:100}, function(e, recs){

                if(!e){

                    cb(recs);

                }else{

                    console.error(e);
                }

            })

        },
        start = () => {
            fetchMails( dispatch );
        },
        dispatch = recs => {

            let run = function(){

                if(recs.length){

                    sendMail(recs.pop());

                }else setTimeout(start,100);

            },
            sendMail = r => {

                r.to = `${r.receiver_name} <${r.receiver_email}>`;
                r.message = r.body;

                messanger.sendMail(r, function (e, info) {

                    if (!e) {

                        Mails.destroy({id:r.id}, function(err, res){
                            run();
                        });

                    }
                    // else{

                    //     Mails.update({id:r.id, sent:'1'}, function(){
                    //         run();
                    //     });
                    // }

                });

            };
            run();
        };

        console.log('Registered Mailer for ', _db);

        return {
            start:start
        }
};

Remove handling of failed send error


//
// Error handling
//
export var onError

export function catchFunctionErrors(delegate) {
    return onError ? function () {
        try {
            return delegate.apply(this, arguments);
        } catch (e) {
            onError && onError(e);
            throw e;
        }
    } : delegate;
}

export function deferError(error) {
    safeSetTimeout(function () {
        onError && onError(error);
        throw error;
    }, 0);
}

function safeSetTimeout(handler, timeout) {
    return setTimeout(catchFunctionErrors(handler), timeout);
}

export { safeSetTimeout as setTimeout }

//
// Error handling
// ---
//
// The default onError handler is to re-throw.
export var onError = function (e) { throw(e); };

export function catchFunctionErrors(delegate) {
    return onError ? function () {
        try {
            return delegate.apply(this, arguments);
        } catch (e) {
            onError(e);
        }
    } : delegate;
}

export function deferError(error) {
    safeSetTimeout(function () {
        onError && onError(error);
        throw error;
    }, 0);
}

function safeSetTimeout(handler, timeout) {
    return setTimeout(catchFunctionErrors(handler), timeout);
}

export { safeSetTimeout as safeSetTimeout };

Change onError behaviour; export safeSetTimeout


function initTabs() {
  $(".sub-nav.with-tabs dl a").on("click", function(e) {
    e.preventDefault();
    var anchor = e.target
    var dl = anchor.closest("dl");
    var dd = anchor.closest("dd");
    var idToDeactivate = $("dd.active a", dl).attr("href");
    $("dd.active", dl).removeClass("active")
    $(dd).addClass("active");
    var idToActivate = anchor.getAttribute("href");
    $(idToDeactivate).hide();
    $(idToActivate).show();
  })
}

$(function() {
  initTabs();
});

function initTabs() {
  $(".sub-nav.with-tabs dl a").on("click", function(e) {
    e.preventDefault();
    var anchor = e.target
    var dl = $(anchor).closest("dl");
    var dd = $(anchor).closest("dd");
    var idToDeactivate = $("dd.active a", dl).attr("href");
    $("dd.active", dl).removeClass("active")
    $(dd).addClass("active");
    var idToActivate = anchor.getAttribute("href");
    $(idToDeactivate).hide();
    $(idToActivate).show();
  })
}

$(function() {
  initTabs();
});

Fix an IE11 js bug in tab toggling


AV.SourceCollectionView = Backbone.View.extend({
    el: '#list_container',
    initialize: function() {
        this.listenTo(this.collection, 'all', this.render);
        console.log("Initialize View");
    },
    events: {
	"click #deleteButton": "delete",
	//"click #uploadButton": "upload",
    },
    template: _.template( $("#list_template").html()),
    render: function () {
        this.$el.empty();
        console.log("Rendered");
        this.$el.html(this.template({sources: this.collection.models}));
    	test();
    },
    delete: function(ev) {
	    //ev is the mouse event. We receive the data-value which contains
	    //the id.
	    test("in delete in source collection");
	    var idToDelete = $(ev.target).data('value');
	    var sourceToRemove = this.collection.find(function (source) {
		    return source.id == idToDelete;});
	    sourceToRemove.urlRoot = 'php/redirect.php/source/';
	    sourceToRemove.destroy();

    },
    upload: function(ev) {

	uploadModel = new AV.SourceModel({name: $("#upload").val(), data: $("#uploadContent").val()});
	uploadModel.save();

    },

});

AV.SourceCollectionView = Backbone.View.extend({
    el: '#list_container',
    initialize: function() {
        this.listenTo(this.collection, 'all', this.render);
        console.log("Initialize View");
    },
    events: {
	    "click #deleteButton": "delete",
	    //"click #uploadButton": "upload",
    },
    template: _.template( $("#list_template").html()),
    render: function () {
        this.$el.empty();
        console.log("Rendered");
        this.$el.html(this.template({sources: this.collection.models}));
    	test();
    },
    delete: function(ev) {
	    //ev is the mouse event. We receive the data-value which contains
	    //the id.
	    test("in delete in source collection");
	    var idToDelete = $(ev.target).data('value');
	    var sourceToRemove = this.collection.find(function (source) {
		    return source.id == idToDelete;});
	    sourceToRemove.urlRoot = 'php/redirect.php/source/';
	    sourceToRemove.destroy();

    }
});

Remove upload view from sourcecollectionview


/** @jsx dom */
import dom from 'magic-virtual-element';
import formSerialize from 'form-serialize';

export const propTypes = {
	onSubmit: {
		type: 'function'
	},
	transform: {
		type: 'function'
	}
};

export function render({props}) {
	const {children, transform, onSubmit} = props;

	function handle(e) {
		e.preventDefault();

		const el = e.target;

		if (el.checkValidity()) {
			const data = formSerialize(el, transform);

			if (onSubmit) {
				onSubmit(data, el);
			}
		}
	}

	return (
		<form class={['Form', props.class]} novalidate onSubmit={handle}>
			{children}
		</form>
	);
}

/** @jsx dom */
import dom from 'magic-virtual-element';
import formSerialize from 'form-serialize';

export const propTypes = {
	onSubmit: {
		type: 'function'
	},
	transform: {
		type: 'function'
	}
};

export function render({props}) {
	const {children, transform, onSubmit} = props;

	function handle(e) {
		e.preventDefault();

		const el = e.target;

		if (el.checkValidity()) {
			const data = formSerialize(el, transform);

			if (onSubmit) {
				onSubmit(data, el, e);
			}
		}
	}

	return (
		<form class={['Form', props.class]} novalidate onSubmit={handle}>
			{children}
		</form>
	);
}

Return the submit event in callback


(function () {

    module.exports = function (onCreate, onAppend) {
        var baseApp;

        return {
            /**
             * Array of properties to be copied from main app to sub app
             */
            merged: ['view engine', 'views'],

            /**
             * Array of properties to be copied from main app to sub app locals
             */
            locals: [],

            /**
             * Register the main express application, the source of merged/locals properties
             *
             * @param app
             */
            create: function (app) {
                baseApp = app;
                onCreate && onCreate(app);
                onAppend && onAppend(app);

                return app;
            },

            /**
             * Register a sub application, when the root is supplied the app is also bound to the main app.
             *
             * @param {string} [root]
             * @param app
             */
            route: function(root, app) {
                if (arguments.length === 1) {
                    app = root;
                    root = null;
                }

                module.exports.merged.forEach(function (merged) {
                    var baseAppValue = baseApp.get(merged);
                    if (baseAppValue !== undefined) {
                        app.set(merged, baseAppValue);
                    }
                });

                module.exports.locals.forEach(function (local) {
                    app.locals[local] = baseApp.locals[local];
                });

                onAppend && onAppend(app);

                if (root > 1) {
                    baseApp.use(root, app);
                }

                return app;
            }
        };
    };

}());

(function () {

    module.exports = function (onCreate, onAppend) {
        var baseApp;

        return {
            /**
             * Array of properties to be copied from main app to sub app
             */
            merged: ['view engine', 'views'],

            /**
             * Array of properties to be copied from main app to sub app locals
             */
            locals: [],

            /**
             * Register the main express application, the source of merged/locals properties
             *
             * @param app
             */
            create: function (app) {
                baseApp = app;
                onCreate && onCreate(app);
                onAppend && onAppend(app);

                return app;
            },

            /**
             * Register a sub application, when the root is supplied the app is also bound to the main app.
             *
             * @param {string} [root]
             * @param app
             */
            route: function(root, app) {
                if (arguments.length === 1) {
                    app = root;
                    root = null;
                }

                module.exports.merged.forEach(function (merged) {
                    var baseAppValue = baseApp.get(merged);
                    if (baseAppValue !== undefined) {
                        app.set(merged, baseAppValue);
                    }
                });

                module.exports.locals.forEach(function (local) {
                    app.locals[local] = baseApp.locals[local];
                });

                onAppend && onAppend(app, baseApp);

                if (root > 1) {
                    baseApp.use(root, app);
                }

                return app;
            }
        };
    };

}());

Include the baseApp when running onAppend function


var Laminate = {};

Laminate.Badge = require('./components/badge.js');
Laminate.Button = require('./components/button.js');
Laminate.Card = require('./components/card.js');
Laminate.Content = require('./components/content.js');
Laminate.Icon = require('./components/icon.js');
Laminate.Modal = require('./components/modal.js');
Laminate.TitleBar = require('./components/title-bar.js');
Laminate.Toggle = require('./components/toggle.js');

var Laminate = {};

Laminate.Badge = require('./components/badge');
Laminate.Button = require('./components/button');
Laminate.Card = require('./components/card');
Laminate.Content = require('./components/content');
Laminate.Icon = require('./components/icon');
Laminate.InputGroup = require('./components/input-group');
Laminate.Modal = require('./components/modal');
Laminate.TitleBar = require('./components/title-bar');
Laminate.Toggle = require('./components/toggle');

Remove the .js from requires.


window.onload = initializeData()

function initializeData() {
  var div = document.getElementById("right_now");
  var date = new Date();
  div.textContent = date.getFullYear() + '-' + date.getMonth() + '-' + date.getDate();
  var text = div.textContent;
}

window.onload = initializeData()

function initializeData() {
  var right_now = document.getElementById("right_now");
  var date = new Date();
  right_now.textContent = date.getFullYear() + '-' + date.getMonth() + '-' + date.getDate();
  var text = right_now.textContent;
}

Rename div variable to right_now