'use strict';

(function (window, document) {

  //import("pathformer.js");
  //import("vivus.js");

  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define([], function() {
      return Vivus;
    });
  } else if (typeof exports === 'object') {
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like environments that support module.exports,
    // like Node.
    module.exports = Vivus;
  } else {
    // Browser globals
    window.Vivus = Vivus;
  }

}(window, document));

'use strict';

(function () {

  //import("pathformer.js");
  //import("vivus.js");

  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define([], function() {
      return Vivus;
    });
  } else if (typeof exports === 'object') {
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like environments that support module.exports,
    // like Node.
    module.exports = Vivus;
  } else {
    // Browser globals
    window.Vivus = Vivus;
  }

}());

Patch build for universal environments


import React from "react";
import { map, sortBy } from "lodash-es";
import { Row, Col, ListGroup, ListGroupItem, Panel } from "react-bootstrap";

import { LoadingPlaceholder } from "../../base";

export default function RebuildHistory ({ unbuilt, error }) {

    const extraChanges = unbuilt.page_count > 1
        ? (
            <ListGroupItem key="last-item">

                    + {unbuilt.total_count - unbuilt.per_page} more changes

            </ListGroupItem>
        ) : null;

    let content;

    if (unbuilt === null) {
        content = <LoadingPlaceholder margin="22px" />;
    } else {
        const historyComponents = map(sortBy(unbuilt.documents, "otu.name"), change =>
            <ListGroupItem key={change.id}>
                <Row>
                    <Col md={5}>
                        {change.otu.name}
                    </Col>
                    <Col md={7}>
                        {change.description || "No Description"}
                    </Col>
                </Row>
            </ListGroupItem>
        );

        content = (
            <ListGroup style={{overflowY: "auto", maxHeight: "700px"}}>
                {historyComponents}
                {extraChanges}
            </ListGroup>
        );
    }

    const panelStyle = error ? "panel-danger" : "panel-default";

    return (
        <Panel className={panelStyle}>
            <Panel.Heading>Changes</Panel.Heading>
            <Panel.Body>
                {content}
            </Panel.Body>
        </Panel>
    );
}

import React from "react";
import { map, sortBy } from "lodash-es";
import { Row, Col, ListGroup, ListGroupItem, Panel } from "react-bootstrap";

import { LoadingPlaceholder } from "../../base";

export default function RebuildHistory ({ unbuilt, error }) {

    const extraChanges = unbuilt.page_count > 1
        ? (
            <ListGroupItem key="last-item">

                    + {unbuilt.total_count - unbuilt.per_page} more changes

            </ListGroupItem>
        ) : null;

    let content;

    if (unbuilt === null) {
        content = <LoadingPlaceholder margin="22px" />;
    } else {
        const historyComponents = map(sortBy(unbuilt.documents, "otu.name"), change =>
            <ListGroupItem key={change.id}>
                <Row>
                    <Col md={5}>
                        {change.otu.name}
                    </Col>
                    <Col md={7}>
                        {change.description || "No Description"}
                    </Col>
                </Row>
            </ListGroupItem>
        );

        content = (
            <ListGroup style={{overflowY: "auto", maxHeight: "700px"}}>
                {historyComponents}
                {extraChanges}
            </ListGroup>
        );
    }

    const panelStyle = error ? "panel-danger" : "panel-default";

    return (
        <Panel className={panelStyle}>
            <Panel.Heading>Changes</Panel.Heading>
            {content}
        </Panel>
    );
}

Remove modal padding around rebuild change list


'use strict';

VIRTUAL_DEP_IDS = ['\0helper'];

module.exports = {
  name: 'virtual-dep-injector',
  resolveId: (importee, importer) => {
    if (VIRTUAL_DEP_IDS.indexOf(importee) !== -1) return importee;
  },
  load: (id) => {
    if(VIRTUAL_DEP_IDS.indexOf(id) !== -1) return 'export default "hello world";';
  },
  transform: (code, id) => {
    if (VIRTUAL_DEP_IDS.indexOf(id) === -1) {
      let addedImports = '';
      let i = 0;
      for (let id of VIRTUAL_DEP_IDS) {
        addedImports += `import * as dependency${i++} from '${id}';\n`;
      }
      code = addedImports + code;
    }
    return {code, map: null};
  }
};

'use strict';

const VIRTUAL_DEP_IDS = ['\0helper'];

module.exports = {
  name: 'virtual-dep-injector',
  resolveId: (importee, importer) => {
    if (VIRTUAL_DEP_IDS.indexOf(importee) !== -1) return importee;
  },
  load: (id) => {
    if(VIRTUAL_DEP_IDS.indexOf(id) !== -1) return 'export default "hello world";';
  },
  transform: (code, id) => {
    if (VIRTUAL_DEP_IDS.indexOf(id) === -1) {
      let addedImports = '';
      let i = 0;
      for (let id of VIRTUAL_DEP_IDS) {
        addedImports += `import * as dependency${i++} from '${id}';\n`;
      }
      code = addedImports + code;
    }
    return {code, map: null};
  }
};

Fix missing const keyword in test code


'use strict';

// flatten arbitrarily nested array
// data NestedList a = Elem a | List [NestedList a]
// flatten :: NestedList a -> [a]
module.exports = function flatten(arr) {
  const _flatten = function(p, c) {
    if (Array.isArray(c)) p = p.concat(c.reduce(_flatten, []));
    else p.push(c);
    return p;
  };

  return arr.reduce(_flatten, []);
};

'use strict';

// flatten arbitrarily nested array
// data NestedList a = Elem a | List [NestedList a]
// flatten :: NestedList a -> [a]
function flatten(arr) {
  const _flatten = function(p, c) {
    if (Array.isArray(c)) p = p.concat(c.reduce(_flatten, []));
    else p.push(c);
    return p;
  };

  return arr.reduce(_flatten, []);
}

Remove all the module system junk


const Base = require('yeoman-generator').Base;
const yarnInstall = require('yarn-install');

class ESLintGenerator extends Base {
  install() {
    yarnInstall([
      'babel-core',
      'babel-eslint',
      'babel-preset-react-native',
      'eslint',
      'eslint-config-airbnb',
      'eslint-plugin-import',
      'eslint-plugin-jsx-a11y@^2.0.0',
      'eslint-plugin-react',
    ], {
      dev: true,
      cwd: this.destinationRoot(),
    });
  }

  writing() {
    this.fs.copyTpl(
      this.templatePath('eslintrc'),
      this.destinationPath('.eslintrc')
    );
    this.fs.copyTpl(
      this.templatePath('eslintignore'),
      this.destinationPath('.eslintignore')
    );
    this.fs.extendJSON('package.json', {
      scripts: { 'test:lint': 'eslint . --quiet' },
    }, null, 2);
  }

  end() {
    this.config.set('eslint', true);
    this.config.save();
  }
}

module.exports = ESLintGenerator;

const Base = require('yeoman-generator').Base;
const yarnInstall = require('yarn-install');

class ESLintGenerator extends Base {
  install() {
    yarnInstall([
      'babel-core',
      'babel-eslint',
      'babel-preset-react-native',
      'eslint',
      'eslint-config-airbnb',
      'eslint-plugin-import',
      'eslint-plugin-jsx-a11y@^3.0.2',
      'eslint-plugin-react',
    ], {
      dev: true,
      cwd: this.destinationRoot(),
    });
  }

  writing() {
    this.fs.copyTpl(
      this.templatePath('eslintrc'),
      this.destinationPath('.eslintrc')
    );
    this.fs.copyTpl(
      this.templatePath('eslintignore'),
      this.destinationPath('.eslintignore')
    );
    this.fs.extendJSON('package.json', {
      scripts: { 'test:lint': 'eslint . --quiet' },
    }, null, 2);
  }

  end() {
    this.config.set('eslint', true);
    this.config.save();
  }
}

module.exports = ESLintGenerator;

Upgrade eslint-plugin-jsx-a11y to match eslint-config-airbnb peer dependency


/**
 * Copyright (c) 2015-present, Alibaba Group Holding Limited.
 * All rights reserved.
 *
 * @providesModule ReactStatusBar
 */

'use strict';

var emptyFunction = function() {};

var StatusBar = {
  setHidden: emptyFunction,
  setBarStyle: emptyFunction,
  setNetworkActivityIndicatorVisible: emptyFunction,
  setBackgroundColor: emptyFunction,
  setTranslucent: emptyFunction,
};

module.exports = StatusBar;

/**
 * Copyright (c) 2015-present, Alibaba Group Holding Limited.
 * All rights reserved.
 *
 * @providesModule ReactStatusBar
 */

'use strict';

import React from 'react';

var emptyFunction = function() {};

var StatusBar = React.createClass({
  setHidden: emptyFunction,
  setBarStyle: emptyFunction,
  setNetworkActivityIndicatorVisible: emptyFunction,
  setBackgroundColor: emptyFunction,
  setTranslucent: emptyFunction,
  render: function() {
    return <div />;
  },
});

StatusBar.isReactNativeComponent = true;

export default StatusBar;

Fix <StatusBar /> will cause `Uncaught Invariant Violation: Element type is invalid`


'use strict';

var helpers = require('../helpers'),
    yaml = require('js-yaml'),
    fs = require('fs'),
    path = require('path');

var properties = yaml.safeLoad(fs.readFileSync(path.join(__dirname, '../../data', 'properties.yml'), 'utf8')).split(' ');

module.exports = {
  'name': 'no-misspelled-properties',
  'defaults': {
    'extra-properties': []
  },
  'detect': function (ast, parser) {
    var result = [];

    ast.traverseByType('property', function (node) {
      if (node.first().is('ident')) {
        var curProperty = node.first().content;

        if (curProperty.charAt(0) === '-') {
          curProperty = helpers.stripPrefix(curProperty);
        }

        if (properties.indexOf(curProperty) === -1 && parser.options['extra-properties'].indexOf(curProperty) === -1) {
          result = helpers.addUnique(result, {
            'ruleId': parser.rule.name,
            'line': node.start.line,
            'column': node.start.column,
            'message': 'Property `' + curProperty + '` appears to be spelled incorrectly',
            'severity': parser.severity
          });
        }
      }
    });
    return result;
  }
};

'use strict';

var helpers = require('../helpers'),
    yaml = require('js-yaml'),
    fs = require('fs'),
    path = require('path');

var properties = yaml.safeLoad(fs.readFileSync(path.join(__dirname, '../../data', 'properties.yml'), 'utf8')).split(' ');

module.exports = {
  'name': 'no-misspelled-properties',
  'defaults': {
    'extra-properties': []
  },
  'detect': function (ast, parser) {
    var result = [];

    ast.traverseByType('property', function (node) {
      if (node.first().is('ident')) {
        var curProperty = node.first().content;

        if (curProperty.charAt(0) === '-') {
          curProperty = helpers.stripPrefix(curProperty);
        }

        if (curProperty.length > 0 && properties.indexOf(curProperty) === -1 && parser.options['extra-properties'].indexOf(curProperty) === -1) {
          result = helpers.addUnique(result, {
            'ruleId': parser.rule.name,
            'line': node.start.line,
            'column': node.start.column,
            'message': 'Property `' + curProperty + '` appears to be spelled incorrectly',
            'severity': parser.severity
          });
        }
      }
    });
    return result;
  }
};

Fix empty properties error in misspelled properties


/* global angular */
import {web3, Chess} from '../../contract/Chess.sol';
angular.module('dappChess').controller('InitializeGameCtrl',
  function ($rootScope, $scope, accounts) {
    $scope.availableAccounts = accounts.availableAccounts;
    $scope.selectedAccount = accounts.defaultAccount;
    $scope.startcolor = 'white';
    $scope.username = null;
    $scope.etherbet = 0;

    $scope.isSelectedAccount = function (account) {
      return $scope.selectedAccount === account;
    };

    function initializeGame() {
      $rootScope.$broadcast('message', 'Your game is being created, please wait a moment...',
                            'loading', 'startgame');
      try {
        Chess.initGame($scope.username, $scope.startcolor === 'white',
         {
           from: $scope.selectedAccount,
           value: web3.toWei($scope.etherbet, 'ether')
         });
      }
      catch(e) {
        console.log('Error on initialize game', e);
        $rootScope.$broadcast('message', 'Could not initialize the game', 'loading', 'startgame');
      }
    }

    $scope.selectOrCreateAccount = function($event) {
      $event.preventDefault();

      accounts.requestAccount(function(e, address) {
        $scope.selectedAccount = address;
      });
    };

    $scope.initializeGame = function (form) {
      if(form.$valid) {
        initializeGame();
      }
    };

    $scope.getBalance = accounts.getBalance;
  });

/* global angular */
import {web3, Chess} from '../../contract/Chess.sol';
angular.module('dappChess').controller('InitializeGameCtrl',
  function ($rootScope, $scope, accounts) {
    $scope.availableAccounts = accounts.availableAccounts;
    $scope.selectedAccount = accounts.defaultAccount;
    $scope.startcolor = 'white';
    $scope.username = null;
    $scope.etherbet = 0;

    $scope.isSelectedAccount = function (account) {
      return $scope.selectedAccount === account;
    };

    function initializeGame() {
      $rootScope.$broadcast('message', 'Your game is being created, please wait a moment...',
                            'loading', 'startgame');
      try {
        console.log('Trying to initialize game', $scope.username,
          {
            from: $scope.selectedAccount,
            value: web3.toWei($scope.etherbet, 'ether')
          });
        Chess.initGame($scope.username, $scope.startcolor === 'white',
         {
           from: $scope.selectedAccount,
           value: web3.toWei($scope.etherbet, 'ether')
         });
      }
      catch(e) {
        console.log('Error on initialize game', e);
        $rootScope.$broadcast('message', 'Could not initialize the game', 'loading', 'startgame');
      }
    }

    $scope.selectOrCreateAccount = function($event) {
      $event.preventDefault();

      accounts.requestAccount(function(e, address) {
        $scope.selectedAccount = address;
      });
    };

    $scope.initializeGame = function (form) {
      if(form.$valid) {
        initializeGame();
      }
    };

    $scope.getBalance = accounts.getBalance;
  });

Add additional logging to initialise game


'use strict';

// Load our modules
const logger = __require('libs/log'),
      task = __require('libs/task'),
      queue = __require('libs/queue');

// General task file handling
module.exports = function(options) {

  // Does the user want to run this?
  if ( options.no_task ) {
    return;
  }

  // Variables
  let count = 0;

  // Load tasks from file
  task.load('tasks.json').then((tasks) => {

    // Check for tasks
    if ( ! tasks ) {
      return new Error('No tasks found');
    }

    // Variables
    let jobs = [];

    // Extract jobs from tasks
    tasks.forEach((t) => { t.jobs.forEach((j, i) => { jobs.push(j); }); });

    // Update count
    count = jobs.length;

    // Add jobs into queue and convert
    return queue.add({jobs: jobs});

  // Conversion queue complete
  }).then((complete) => {
    logger.info('Completed %s tasks', count);

  // Error handler
  }).catch((err) => {
    logger.error(err);
  });
};

'use strict';

// Load our modules
const logger = __require('libs/log'),
      task = __require('libs/task'),
      queue = __require('libs/queue');

// General task file handling
module.exports = function(options) {

  // Does the user want to run this?
  if ( options.no_task ) {
    return;
  }

  // Let the user know
  logger.info('Loading tasks from configuration file');

  // Load tasks from file
  task.load('tasks.json').then((tasks) => {

    // Check for tasks
    if ( ! tasks ) {
      return new Error('No tasks found');
    }

    // Variables
    let jobs = [];

    // Extract jobs from tasks
    tasks.forEach((t) => { t.jobs.forEach((j, i) => { jobs.push(j); }); });

    // Add jobs into queue and convert
    return queue.add({jobs: jobs});

  // Conversion queue complete
  }).then((complete) => {
    logger.info('Completed available tasks');

  // Error handler
  }).catch((err) => {
    logger.error(err);
  });
};

Clean up task file source


import React from 'react';

class UserSelect extends React.Component {
  constructor(props) {
    super(props);

    this.defaultValue = 'unassigned';

    this.state = {
      selectedUserId: this.defaultValue
    };

    this.handleUserChange = this.handleUserChange.bind(this);
  }

  handleUserChange(evt) {
    let prevUserId;
    const selectedUserId = evt.target.value;
    if (this.state.selectedUserId !== this.defaultValue) {
      prevUserId = this.state.selectedUserId;
    }
    this.setState({
      selectedUserId
    });
    this.props.onUserChange(Number(selectedUserId), Number(prevUserId));
  }

  render() {
    return (
      <select onChange={this.handleUserChange} value={this.state.selectedUserId}>
        <option value={this.defaultValue}>Select User</option>
        {this.props.users.map((user) => <option key={user.id} value={user.id}>{user.name}</option>)}
      </select>
    );
  }
}

UserSelect.propTypes = {
  users: React.PropTypes.array,
  onUserChange: React.PropTypes.func.isRequired
};

export default UserSelect;

import React from 'react';

class UserSelect extends React.Component {
  defaultValue = 'unassigned';

  state = {
    selectedUserId: this.defaultValue
  }

  handleUserChange = (evt) => {
    let prevUserId;
    const selectedUserId = evt.target.value;
    if (this.state.selectedUserId !== this.defaultValue) {
      prevUserId = this.state.selectedUserId;
    }
    this.setState({
      selectedUserId
    });
    this.props.onUserChange(Number(selectedUserId), Number(prevUserId));
  }

  render() {
    return (
      <select onChange={this.handleUserChange} value={this.state.selectedUserId}>
        <option value={this.defaultValue}>Select User</option>
        {this.props.users.map((user) => <option key={user.id} value={user.id}>{user.name}</option>)}
      </select>
    );
  }
}

UserSelect.propTypes = {
  users: React.PropTypes.array,
  onUserChange: React.PropTypes.func.isRequired
};

export default UserSelect;

Change user select to use public class fields


angular.module('sparrow')
.controller('TimedCtrl', function() {
  //data is the information send when a template is added.
  this.data= {};
  this.userId = 'xyz';
  //workout contain template created by user.
  this.workout = [];
  //addWorkout() create a template of named workout
  this.addWorkout = function() {
    var obj = {};
    obj.activity = this.activity;
    obj.duration = Number(this.minutes || 0) * 60 + Number(this.seconds || 0);
    obj.sets = '';
    obj.reps = '';
    this.workout.push(obj);
    this.createData();
  };

  //addTemplate add template to the database.
  this.sendTemplate = function() {
    console.log('Send present workout to the database via http services',this.data);
  };
})
.component('timed', {
  controller: 'TimedCtrl',
  templateUrl: 'templates/timed.html'
});

angular.module('sparrow')
.controller('TimedCtrl', function() {
  //data is the information send when a template is added.
  this.data= {};
  this.userId = 'xyz';
  //workout contain template created by user.
  this.workout = [];
  //addWorkout() create a template of named workout
  this.addWorkout = function() {
    var obj = {};
    obj.activity = this.activity;
    obj.duration = Number(this.minutes || 0) * 60 + Number(this.seconds || 0);
    obj.sets = '';
    obj.reps = '';
    this.workout.push(obj);
  };

  //addTemplate add template to the database.
  this.sendTemplate = function() {
    this.createData();
    console.log('Send present workout to the database via http services',this.data);
  };

  this.createData  = function() {
    this.data.userId = this.userId;
    this.data.workout = this.workout;
    this.data.templateName = this.templateName;
    this.data.timed = true;
  };
})
.component('timed', {
  controller: 'TimedCtrl',
  templateUrl: 'templates/timed.html'
});

Add helper function createData() which prepare data for the client to send it to server


#!/usr/bin/env node
require('ts-node').register()

const latexCommands = require('../src/latexCommands')
const mathSvg = require('../server/mathSvg')
const fs = require('fs')
const util = require('util')

Promise.all(
    latexCommands.map(o => {
        if (typeof o === 'string') {
            return Promise.resolve(o)
        }
        return new Promise(resolve => {
            const latex = o.label ? o.label.replace(/X/g, '\\square') : o.action
            mathSvg.latexToSvg(latex, svg =>
                resolve(Object.assign(o, { svg: 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64') }))
            )
        })
    })
).then(data => {
    fs.writeFileSync(
        __dirname + '/../src/latexCommandsWithSvg.js',
        '/* eslint-disable indent */\nmodule.exports = ' + util.inspect(data, { depth: null }),
        'utf8'
    )
})

#!/usr/bin/env node
require('ts-node').register()

const latexCommands = require('../src/latexCommands').default
const mathSvg = require('../server/mathSvg')
const fs = require('fs')
const util = require('util')

Promise.all(
    latexCommands.map(o => {
        if (typeof o === 'string') {
            return Promise.resolve(o)
        }
        return new Promise(resolve => {
            const latex = o.label ? o.label.replace(/X/g, '\\square') : o.action
            mathSvg.latexToSvg(latex, svg =>
                resolve(Object.assign(o, { svg: 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64') }))
            )
        })
    })
).then(data => {
    fs.writeFileSync(
        __dirname + '/../src/latexCommandsWithSvg.js',
        '/* eslint-disable indent */\nmodule.exports = ' + util.inspect(data, { depth: null }),
        'utf8'
    )
})

Fix import of latexCommands module


const path = require('path');

module.exports = {
  rootDir: path.resolve(__dirname, '../..'),
  moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
  moduleDirectories: ['node_modules', '<rootDir>/src', '<rootDir>'],
  testRegex: String.raw`.*/__tests__/.*\.test\.(j|t)s$`,
  transform: {
    [String.raw`^.*\.tsx?$`]: 'ts-jest',
    [String.raw`^.*\.jsx?$`]: 'babel-jest',
  },
  collectCoverageFrom: ['<rootDir>/src/**/*.{js,jsx,ts,tsx}'],
  coverageReporters: ['text-lcov'],
};

const path = require('path');

module.exports = {
  rootDir: path.resolve(__dirname, '../..'),
  moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
  moduleDirectories: ['node_modules', '<rootDir>/src', '<rootDir>'],
  testRegex: String.raw`.*/__tests__/.*\.test\.(j|t)s$`,
  transform: {
    [String.raw`^.*\.tsx?$`]: 'ts-jest',
    [String.raw`^.*\.jsx?$`]: 'babel-jest',
  },
  collectCoverageFrom: [
    '<rootDir>/src/**/*.{js,jsx,ts,tsx}',
    '!**/__tests__/**/*',
    '!**/__mocks__/**/*',
    '!**/__snapshots__/**/*',
  ],
  coverageReporters: ['text-lcov'],
};

Add some extra ecludes for coverage collection


////////////////////////////////////////////////////////////////////
// SourceCollectionView displays all of the sources available     //
// on the Juxta server and displays them in a compelling way.     //
//                                                                //
// It allows the user to delete sources, view individual sources, //
// and convert sources into witnesses.                            //
////////////////////////////////////////////////////////////////////

AV.SourceCollectionView = Backbone.View.extend({
    el: '#list_source_container',
    initialize: function() {
        this.listenTo(this.collection, 'all', this.render);
    },
    events: {
	    "click #deleteSourceButton": "delete"
    },
    template: _.template( $("#list_source_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 = '/juxta/source';
	    sourceToRemove.destroy();

    }
});

////////////////////////////////////////////////////////////////////
// SourceCollectionView displays all of the sources available     //
// on the Juxta server and displays them in a compelling way.     //
//                                                                //
// It allows the user to delete sources, view individual sources, //
// and convert sources into witnesses.                            //
////////////////////////////////////////////////////////////////////

AV.SourceCollectionView = Backbone.View.extend({
    el: '#list_source_container',
    initialize: function() {
        this.listenTo(this.collection, 'all', this.render);
    },
    events: {
	    "click #deleteSourceButton": "delete",
        "click #transformButton": "transform"
    },
    template: _.template( $("#list_source_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 = '/juxta/source';
	    sourceToRemove.destroy();
    },
    transform: function(ev){
        var checkedBoxes = _.filter($('input:checkbox.transformCheckbox'),
                                    function (box) {return box.checked === true;});
        var checkedIDs = _.pluck(checkedBoxes, 'value');
        _.forEach(checkedIDs, function(id) {
	        var url = "/juxta/transform";
	        var request = { source: id };
	        //We use AJAX to send the request directly from here.
	        $.ajax({
		        type: 'POST',
		        url: url,
		        data: JSON.stringify(request),
		        contentType: 'application/json'
            });
        });

    }

});

Add support for batch collation


/*
 * Copyright 2016 Resin.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const Bluebird = require('bluebird');
const drivelist = Bluebird.promisifyAll(require('drivelist'));

/**
 * @summary List all available removable drives
 * @function
 * @public
 *
 * @fulfil {Object[]} - available removable drives
 * @returns {Promise}
 *
 * @example
 * drives.listRemovable().then(function(drives) {
 *   drives.forEach(function(drive) {
 *     console.log(drive.device);
 *   });
 * });
 */
exports.listRemovable = function() {
  return drivelist.listAsync().then(function(drives) {
    return drives.filter(function(drive) {
      return !drive.system;
    });
  });
};

/*
 * Copyright 2016 Resin.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const Bluebird = require('bluebird');
const drivelist = Bluebird.promisifyAll(require('drivelist'));

/**
 * @summary List all available removable drives
 * @function
 * @public
 *
 * @fulfil {Object[]} - available removable drives
 * @returns {Promise}
 *
 * @example
 * drives.listRemovable().then(function(drives) {
 *   drives.forEach(function(drive) {
 *     console.log(drive.device);
 *   });
 * });
 */
exports.listRemovable = function() {
  return drivelist.listAsync().filter(function(drive) {
    return !drive.system;
  });
};

Use Bluebird's filter to simplify listRemovable()


(function() {
    'use strict';

    /**
     * Controller for the gw app root view -- handles header bar and search bar logic
     */
    /* ngInject */
    function RootController($log, $state, Geocoder) {

        var ctl = this;

        initialize();

        function initialize() {
            ctl.searchText = '';
            ctl.suggest = Geocoder.suggest;
            ctl.search = search;
        }

        function search(searchText, magicKey) {
            Geocoder.search(searchText, magicKey)
            .then(loadMap)
            .catch(function (error) {
                $log.debug('Error searching:', error);
            });
        }

        function loadMap(results) {
            if (results && results.length) {
                var result = results[0];
                $log.debug(result);

                var sw = L.latLng(result.extent.ymin, result.extent.xmin);
                var ne = L.latLng(result.extent.ymax, result.extent.xmax);
                var bounds = L.latLngBounds(sw, ne);
                $state.go('map', { bbox: bounds.toBBoxString() });
            }
        }
    }

    angular.module('gw.views.root')
    .controller('RootController', RootController);
})();

(function() {
    'use strict';

    /**
     * Controller for the gw app root view -- handles header bar and search bar logic
     */
    /* ngInject */
    function RootController($log, $state, Geocoder) {

        var ctl = this;

        initialize();

        function initialize() {
            ctl.searchText = '';
            ctl.suggest = Geocoder.suggest;
            ctl.search = search;
        }

        function search(searchText, magicKey) {
            Geocoder.search(searchText, magicKey)
            .then(function (results) {
                if (results && results.length) {
                    loadMap(results[0]);
                }
            })
            .catch(function (error) {
                $log.debug('Error searching:', error);
            });
        }

        function loadMap(result) {
            $log.debug(result);

            var sw = L.latLng(result.extent.ymin, result.extent.xmin);
            var ne = L.latLng(result.extent.ymax, result.extent.xmax);
            var bounds = L.latLngBounds(sw, ne);
            $state.go('map', { bbox: bounds.toBBoxString() }, { reload: true });
        }
    }

    angular.module('gw.views.root')
    .controller('RootController', RootController);
})();

Refactor loadMap to take result


export const BLOCK_LAYOUTS = [
  [{sm_size: 12, md_size: 12, lg_size: 12}],
  [
    {sm_size: 12, md_size: 6, lg_size: 6},
    {sm_size: 12, md_size: 6, lg_size: 6}
  ],
  [
    {sm_size: 12, md_size: 12, lg_size: 4},
    {sm_size: 12, md_size: 12, lg_size: 4},
    {sm_size: 12, md_size: 12, lg_size: 4}
  ],
  [
    {sm_size: 12, md_size: 6, lg_size: 4},
    {sm_size: 12, md_size: 6, lg_size: 8}
  ]
]

export const BLOCK_LAYOUTS = [
  [{sm_size: 12, md_size: 12, lg_size: 12}],
  [
    {sm_size: 12, md_size: 6, lg_size: 6},
    {sm_size: 12, md_size: 6, lg_size: 6}
  ],
  [
    {sm_size: 12, md_size: 12, lg_size: 4},
    {sm_size: 12, md_size: 12, lg_size: 4},
    {sm_size: 12, md_size: 12, lg_size: 4}
  ],
  [
    {sm_size: 12, md_size: 6, lg_size: 4},
    {sm_size: 12, md_size: 6, lg_size: 8}
  ],
  [
    {sm_size: 12, md_size: 6, lg_size: 3},
    {sm_size: 12, md_size: 6, lg_size: 3},
    {sm_size: 12, md_size: 6, lg_size: 3},
    {sm_size: 12, md_size: 6, lg_size: 3}
  ]
]

Add layout 4 columns to better make layout


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

/**
 * TestFixture for search engine manager WebUI testing.
 * @extends {testing.Test}
 * @constructor
 **/
function SearchEngineManagerWebUITest() {}

SearchEngineManagerWebUITest.prototype = {
  __proto__: testing.Test.prototype,

  /**
   * Browse to the search engine manager.
   **/
  browsePreload: 'chrome://settings/searchEngines',
};

// Test opening the search engine manager has correct location.
TEST_F('SearchEngineManagerWebUITest', 'testOpenSearchEngineManager',
       function() {
         assertEquals(this.browsePreload, document.location.href);
       });

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

/**
 * TestFixture for search engine manager WebUI testing.
 * @extends {testing.Test}
 * @constructor
 **/
function SearchEngineManagerWebUITest() {}

SearchEngineManagerWebUITest.prototype = {
  __proto__: testing.Test.prototype,

  /**
   * Browse to the search engine manager.
   **/
  browsePreload: 'chrome://settings/searchEngines',
};

// See crosbug.com/22673
GEN('#if defined(OS_CHROMEOS)');
GEN('#define MAYBE_testOpenSearchEngineManager ' +
        'DISABLED_testOpenSearchEngineManager');
GEN('#else');
GEN('#define MAYBE_testOpenSearchEngineManager testOpenSearchEngineManager');
GEN('#endif  // defined(OS_CHROMEOS)');

// Test opening the search engine manager has correct location.
TEST_F('SearchEngineManagerWebUITest', 'MAYBE_testOpenSearchEngineManager',
       function() {
         assertEquals(this.browsePreload, document.location.href);
       });

Mark SearchEngineManagerWebUITest.testOpenSearchEngineManager as flaky. SearchEngineManagerWebUITest.testOpenSearchEngineManager fails when run on a ChromeOS VM for the first time, all subsequent runs pass. Marking it as flaky.


/*************************************************
 * Copyright (c) 2015 Ansible, Inc.
 *
 * All Rights Reserved
 *************************************************/

export default
    angular.module('rbacUiControl', [])
        .service('rbacUiControlService', ['$q', 'GetBasePath', 'Rest', 'Wait', function($q, GetBasePath, Rest, Wait){
            this.canAdd = function(apiPath) {
                var canAddVal = $q.defer();

                if (apiPath.indexOf("api/v1") > -1) {
                    Rest.setUrl(apiPath);
                } else {
                    Rest.setUrl(GetBasePath(apiPath));
                }

                Wait("start");
                Rest.options()
                    .success(function(data) {
                        if (data.actions.POST) {
                            canAddVal.resolve(true);
                        } else {
                            canAddVal.reject(false);
                        }
                        Wait("stop");
                    });

                return canAddVal.promise;
            };
        }]);

/*************************************************
 * Copyright (c) 2015 Ansible, Inc.
 *
 * All Rights Reserved
 *************************************************/

export default
    angular.module('rbacUiControl', [])
        .service('rbacUiControlService', ['$q', 'GetBasePath', 'Rest', 'Wait', function($q, GetBasePath, Rest, Wait){
            this.canAdd = function(apiPath) {
                var canAddVal = $q.defer();

                if (/api\/v[0-9]+\//.test(apiPath)) {
                    Rest.setUrl(apiPath);
                } else {
                    Rest.setUrl(GetBasePath(apiPath));
                }

                Wait("start");
                Rest.options()
                    .success(function(data) {
                        if (data.actions.POST) {
                            canAddVal.resolve(true);
                        } else {
                            canAddVal.reject(false);
                        }
                        Wait("stop");
                    });

                return canAddVal.promise;
            };
        }]);

Fix issue preventing inventory groups from loading


/* eslint-disable global-require */
/* global window */
import { createStore, applyMiddleware, combineReducers } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { isTest } from 'worona-deps';
import { reduxReactRouter, routerStateReducer as router } from 'redux-router';
import { composeWithDevTools } from 'redux-devtools-extension';
import build from '../reducers';

const sagaMiddleware = createSagaMiddleware();

const reducers = { build: build(), router };
const sagas = {};

const composeEnhancers = composeWithDevTools({
  serializeState: false,
});

export const store = createStore(
  combineReducers(reducers),
  composeEnhancers(
    reduxReactRouter({
      createHistory: !isTest ? require('history').createHistory : require('history').createMemoryHistory,
    }),
    applyMiddleware(sagaMiddleware),
  )
);

export default store;

export const dispatch = action => store.dispatch(action);
export const reloadReducers = () => store.replaceReducer(combineReducers(reducers));
export const addReducer = (namespace, reducer) => { if (reducer) reducers[namespace] = reducer; };
export const removeReducer = namespace => { if (reducers[namespace]) delete reducers[namespace]; };
export const startSaga = (namespace, saga) => { sagas[namespace] = sagaMiddleware.run(saga); };
export const stopSaga = (namespace) => { if (sagas[namespace]) sagas[namespace].cancel(); };
export const getState = store.getState.bind(store);
export const history = store.history;

/* eslint-disable global-require */
/* global window */
import { createStore, applyMiddleware, combineReducers } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { isTest } from 'worona-deps';
import { reduxReactRouter, routerStateReducer as router } from 'redux-router';
import { composeWithDevTools } from 'redux-devtools-extension';
import build from '../reducers';

const sagaMiddleware = createSagaMiddleware();

const reducers = { build: build(), router };
const sagas = {};

const composeEnhancers = composeWithDevTools({
  serialize: false,
});

export const store = createStore(
  combineReducers(reducers),
  composeEnhancers(
    reduxReactRouter({
      createHistory: !isTest ? require('history').createHistory : require('history').createMemoryHistory,
    }),
    applyMiddleware(sagaMiddleware),
  )
);

export default store;

export const dispatch = action => store.dispatch(action);
export const reloadReducers = () => store.replaceReducer(combineReducers(reducers));
export const addReducer = (namespace, reducer) => { if (reducer) reducers[namespace] = reducer; };
export const removeReducer = namespace => { if (reducers[namespace]) delete reducers[namespace]; };
export const startSaga = (namespace, saga) => { sagas[namespace] = sagaMiddleware.run(saga); };
export const stopSaga = (namespace) => { if (sagas[namespace]) sagas[namespace].cancel(); };
export const getState = store.getState.bind(store);
export const history = store.history;

Fix deprecated option of Redux Dev Tools.


function(doc) {
    if (doc.doc_type == "CommCareUser" && doc.location_id) {
        emit([doc.location_id, doc._id], null);
    }
}

function(doc) {
    if (doc.base_doc === "CouchUser") {

        if (doc.doc_type === "CommCareUser" && doc.location_id) {
            emit([doc.location_id, doc.doc_type, doc._id], null);

        } else if (doc.doc_type === "WebUser") {
            for (i = 0; i < doc.domain_memberships.length; i += 1) {
                var domain_membership = doc.domain_memberships[i];
                if (domain_membership.location_id) {
                    emit([domain_membership.location_id, doc.doc_type, doc._id], null);
                }
            }
        }
    }
}

Add web users to users_by_location_id view


var socket = io('http://localhost:13374');
var elems = document.getElementsByClassName("status");

socket.on('status', function (data) {
  console.log(data);

  for (var i = 0; i < elems.length; i++) {
    var elem  = elems[i];
    var key   = elem.getAttribute("data-key");
    var unit  = elem.getAttribute("data-unit");

    var value = data[key];

    if (value != undefined) {
      elem.innerHTML = value + " " + unit;
    }
  }

  var body = document.body;
  var img = document.getElementById("lock_image");
  var data_fields = document.getElementById("data");

  if (data["state"] == "open") {
    body.style.background = "#00FF00";
    img.setAttribute("src", "assets/images/open.svg");
    data_fields.style.color = "#00FF00";
  } else {
    body.style.background = "#FF0000";
    img.setAttribute("src", "assets/images/closed.svg");
    data_fields.style.color = "#FF0000";
  }
});

var socket = io('http://was.geht.im.hackspace.siegen.so:13374');
var elems = document.getElementsByClassName("status");

socket.on('status', function (data) {
  console.log(data);

  for (var i = 0; i < elems.length; i++) {
    var elem  = elems[i];
    var key   = elem.getAttribute("data-key");
    var unit  = elem.getAttribute("data-unit");

    var value = data[key];

    if (value != undefined) {
      elem.innerHTML = value + " " + unit;
    }
  }

  var body = document.body;
  var img = document.getElementById("lock_image");
  var data_fields = document.getElementById("data");

  if (data["state"] == "open") {
    body.style.background = "#00FF00";
    img.setAttribute("src", "assets/images/open.svg");
    data_fields.style.color = "#00FF00";
  } else {
    body.style.background = "#FF0000";
    img.setAttribute("src", "assets/images/closed.svg");
    data_fields.style.color = "#FF0000";
  }
});

Update socket URL for production use.


'use strict';

const D3 = require('D3');

exports.parser = function(rows) {

    var overviewResponse = D3.values(rows[0]);
    var overview = D3.nest()
        .key(function(d) {
            return d.region_code;
        })
        .rollup(function(v) {
            return {
                'region_code': v[0].region_code,
                'region_name': v[0].region_name,
                'current_total': v[0].current_total,
                'delayed_total': v[0].delayed_total,
                'days_to_payment': v[0].days_to_payment
            }
        })
        .entries(overviewResponse)
        .map(function(d) {
            return d.values;
        });

    var data = {
        'overview': overview
    };

    return data;

};

'use strict';

const D3 = require('D3');

exports.parser = function(rows) {

    var overviewResponse = D3.values(rows[0]);

    var overview = D3.nest()
        .key(function(d) {
            return d.region_code;
        })
        .rollup(function(v) {
            return {
                'region_code': v[0],
                'region_name': v[1],
                'current_total': v[2],
                'delayed_total': v[3],
                'days_to_payment': v[4]
            }
        })
        .entries(overviewResponse)
        .map(function(d) {
            return d.value;
        });

    var data = {
        'overview': overview
    };

    return data;

};

Fix issue in overview parser


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

var localStrings = new LocalStrings();

/**
 * Window onload handler, sets up the page.
 */
function load() {
  chrome.send('getPrinters');
};

/**
 * Fill the printer list drop down.
 */
function setPrinters(printers) {
  if (printers.length > 0) {
    for (var i = 0; i < printers.length; ++i) {
      var option = document.createElement('option');
      option.textContent = printers[i];
      $('printer-list').add(option);
    }
  } else {
    var option = document.createElement('option');
    option.textContent = localStrings.getString('no-printer');
    $('printer-list').add(option);
    $('printer-list').disabled = true;
    $('print-button').disabled = true;
  }
}

window.addEventListener('DOMContentLoaded', load);

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

var localStrings = new LocalStrings();

/**
 * Window onload handler, sets up the page.
 */
function load() {
  $('cancel-button').addEventListener('click', function(e) {
    window.close();
  });

  chrome.send('getPrinters');
};

/**
 * Fill the printer list drop down.
 */
function setPrinters(printers) {
  if (printers.length > 0) {
    for (var i = 0; i < printers.length; ++i) {
      var option = document.createElement('option');
      option.textContent = printers[i];
      $('printer-list').add(option);
    }
  } else {
    var option = document.createElement('option');
    option.textContent = localStrings.getString('no-printer');
    $('printer-list').add(option);
    $('printer-list').disabled = true;
    $('print-button').disabled = true;
  }
}

window.addEventListener('DOMContentLoaded', load);

Print Preview: Hook up the cancel button.


var Flickity = require('flickity');

(function(Flickity) {
    "use strict";

    if(document.querySelector('.rotate') !== null) {
        new Flickity('.rotate', {
            on: {
                ready: function() {
                    // Get the height of the first image that loaded and dynamically set the height
                    if(document.querySelector('.flickity-enabled .is-selected img') != null) {
                        document.querySelector('.flickity-viewport').style.height = document.querySelector('.flickity-enabled .is-selected img').offsetHeight + 'px';
                    }

                    // Visually hide the dots for accessibility
                    if(document.querySelector('.flickity-page-dots') != null) {
                        document.querySelector('.flickity-page-dots').classList.add('visually-hidden');
                    }
                }
            },
            accessibility: true,
            prevNextButtons: true,
            pageDots: true,
            resize: true,
            setGallerySize: true,
            wrapAround: true,
        });

        document.querySelector('.rotate').removeAttribute('tabindex');
    }
})(Flickity);

var Flickity = require('flickity');

(function(Flickity) {
    "use strict";

    if(document.querySelector('.rotate') !== null) {
        new Flickity('.rotate', {
            on: {
                ready: function() {
                    // Get the height of the first image that loaded and dynamically set the height
                    if(document.querySelector('.flickity-enabled .is-selected img') != null) {
                        document.querySelector('.flickity-viewport').style.height = document.querySelector('.flickity-enabled .is-selected img').offsetHeight + 'px';
                    }

                    // Visually hide the dots for accessibility
                    if(document.querySelector('.flickity-page-dots') != null) {
                        document.querySelector('.flickity-page-dots').classList.add('visually-hidden');
                    }
                }
            },
            accessibility: true,
            prevNextButtons: true,
            pageDots: true,
            resize: true,
            setGallerySize: true,
            wrapAround: true,
        });

        document.querySelector('.rotate').removeAttribute('tabindex');

        let EnableTabbableItems = function () {
            // Don't allow tabbing to items that aren't selected
            document.querySelectorAll('.rotate .content a').forEach(function (item) {
                item.classList.add('hidden');
            });

            // Allow tabbing to the selected item
            document.querySelectorAll('.rotate .is-selected .content a').forEach(function (item) {
                item.classList.remove('hidden');
            });
        }

        EnableTabbableItems();

        document.querySelectorAll('.flickity-button').forEach(function (item) {
           item.addEventListener('click', EnableTabbableItems);
        });
    }
})(Flickity);

Hide the links that aren't selectable yet so you can't tab to them


import React from 'react';
import {Decorator as Cerebral} from 'cerebral-view-react';
import styles from './styles.css';

const boilerplates = {
  'react': 'React',
  'cerebral': 'Cerebral with React',
  'redux': 'Redux Counter',
  'cycle': 'Cycle JS'
};

@Cerebral()
class Boilerplates extends React.Component {
  render() {
    return (
      <div className={styles.wrapper}>
        {Object.keys(boilerplates).map((key, index) => {
          return (
            <div
              className={styles.boilerplate}
              key={index}
              onClick={() => this.props.signals.bin.boilerplateClicked({name: key})}>
              {boilerplates[key]}
            </div>
          );
        })}
      </div>
    );
  }
}

 export default Boilerplates;

import React from 'react';
import {Decorator as Cerebral} from 'cerebral-view-react';
import styles from './styles.css';

const boilerplates = {
  'react': 'React',
  'cerebral': 'Cerebral',
  'redux': 'Redux',
  'cycle': 'Cycle JS',
  'vue': 'Vue JS'
};

@Cerebral()
class Boilerplates extends React.Component {
  render() {
    return (
      <div className={styles.wrapper}>
        {Object.keys(boilerplates).map((key, index) => {
          return (
            <div
              className={styles.boilerplate}
              key={index}
              onClick={() => this.props.signals.bin.boilerplateClicked({name: key})}>
              {boilerplates[key]}
            </div>
          );
        })}
      </div>
    );
  }
}

 export default Boilerplates;

Add due and change names of boilerplates


import path from 'path';
import os from 'os';
import { Tray } from 'electron';

export default function buildTray(win) {
  const PLATFORM = os.platform();

  let icon;
  if (process.env.NODE_ENV === 'development') {
    icon = PLATFORM === 'darwin' || PLATFORM === 'linux'
      ? path.join(__dirname, '../../assets/images/icon-mac@2x.png')
      : path.join(__dirname, '../../assets/images/icon-windows@2x.png');
  } else {
    icon = PLATFORM === 'darwin' || PLATFORM === 'linux'
      ? path.join(__dirname, 'assets/images/icon-mac@2x.png')
      : path.join(__dirname, 'assets/images/icon-windows@2x.png');
  }

  const tray = new Tray(icon);

  tray.on('click', () => win.isVisible() ? win.hide() : win.show());

  return tray;
}

import path from 'path';
import os from 'os';
import { Tray, Menu } from 'electron';

export default function buildTray(win) {
  const PLATFORM = os.platform();

  let icon;
  if (process.env.NODE_ENV === 'development') {
    icon = PLATFORM === 'darwin' || PLATFORM === 'linux'
      ? path.join(__dirname, '../../assets/images/icon-mac@2x.png')
      : path.join(__dirname, '../../assets/images/icon-windows@2x.png');
  } else {
    icon = PLATFORM === 'darwin' || PLATFORM === 'linux'
      ? path.join(__dirname, 'assets/images/icon-mac@2x.png')
      : path.join(__dirname, 'assets/images/icon-windows@2x.png');
  }

  const trayMenu = Menu.buildFromTemplate([
    {
      label: 'ZenFocus',
      click() {
        win.show();
      }
    },
    {
      label: 'Minimize to tray',
      click() {
        win.hide();
      }
    },
    { type: 'separator' },
    {
      label: 'Quit',
      click() {
        win.close();
      }
    }
  ]);

  const tray = new Tray(icon);
  tray.setToolTip('ZenFocus');
  tray.setContextMenu(trayMenu);
  tray.on('double-click', () => win.show());

  return tray;
}

Add tray minimize, restore and quit


'use strict';

angular.module('vleApp')
  .directive('lyraExport', function () {
    return {
      template: 'export to lyra...',
      restrict: 'E',
      controller: function ($scope, $timeout, Vegalite, Alerts) {
        $scope.export = function() {
          var vegaSpec = Vegalite.vegaSpec;
          if (!vegaSpec) {
            Alerts.add('No vega spec present.');
          }

          var lyraURL = 'http://idl.cs.washington.edu/projects/lyra/app/';
          var lyraWindow = window.open(lyraURL, '_blank');

          // HACK
          // lyraWindow.onload doesn't work across domains
          $timeout(function() {
            Alerts.add('Please check whether lyra loaded the vega spec correctly. This feature is experimental and may not work.', 5000);
            lyraWindow.postMessage({spec: vegaSpec}, lyraURL);
          }, 5000);
        }
      }
    };
  });

'use strict';

angular.module('vleApp')
  .directive('lyraExport', function () {
    return {
      template: 'export to lyra...',
      restrict: 'E',
      controller: function ($scope, $timeout, Vegalite, Alerts) {
        $scope.export = function() {
          var vegaSpec = Vegalite.vegaSpec;
          if (!vegaSpec) {
            Alerts.add('No vega spec present.');
          }

          // Hack needed. See https://github.com/uwdata/lyra/issues/214
          vegaSpec.marks[0]['lyra.groupType'] = 'layer';
          console.log(vegaSpec)

          var lyraURL = 'http://idl.cs.washington.edu/projects/lyra/app/';
          var lyraWindow = window.open(lyraURL, '_blank');

          // HACK
          // lyraWindow.onload doesn't work across domains
          $timeout(function() {
            Alerts.add('Please check whether lyra loaded the vega spec correctly. This feature is experimental and may not work.', 5000);
            lyraWindow.postMessage({spec: vegaSpec}, lyraURL);
          }, 5000);
        }
      }
    };
  });

Make lyra export work thanks to @RussellSprouts and @arvind.


import extend from 'extend';
import webpack from 'webpack';
import webpackConfig from './webpack.config.js';

var config = extend(true, {}, webpackConfig, {
    entry: './src/entry/main.js',

    plugins: webpackConfig.plugins.concat([
        new webpack.DefinePlugin({
            'process.env': { NODE_ENV: JSON.stringify('production') }
        }),
        new webpack.optimize.OccurrenceOrderPlugin(),
        new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } })
    ])
});

export default config;

import extend from 'extend';
import webpack from 'webpack';
import webpackConfig from './webpack.config.js';

var config = extend(true, {}, webpackConfig, {
    entry: './src/entry/main.js',

    devtool: '#source-map',

    plugins: webpackConfig.plugins.concat([
        new webpack.DefinePlugin({
            'process.env': { NODE_ENV: JSON.stringify('production') }
        }),
        new webpack.optimize.OccurrenceOrderPlugin(),
        new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } })
    ])
});

export default config;

Add souremap to production build


var api = require('./api');

module.exports = {

  login(username, password, callback) {
    api.login(username, password)
    .then((response) => {
      if(response.status === 400){
        if(callback) {
          callback(false);
        }
      } else if (response.status === 200) {
        console.log("$$$$$$$$", document.cookie);
        response.json()
        .then(function(body) {
          localStorage.token = true;
          if(callback) {
            callback(true);
          }
        });
      }
    })
    .catch((err) => {
      console.error('Error with login' + err);
    });
  },

  logout(callback) {
    delete localStorage.token;
    api.logout()
    .then(() => {
      if (callback) {
        callback();
      }
    });
  },

  loggedIn() {
    return !!localStorage.token
  },

  checkForSession(next) {
    delete localStorage.token;
    api.checkForSession()
    .then((response) => {
      if(response.status === 200) {
        response.json().then((data) => {
          if(data) {
            localStorage.token = true;
          }
          next(data);
        });
      }
    })
    .catch((err) => {
      console.error('Error checking session', err);
      next(false);
    });
    // send api request to protected route
    // if response.status === 401, i don't have access
      // return false;
    // else,
      // localStorage.token = true;
      // it will return userID / login data
  }

}

var api = require('./api');

module.exports = {

  login(username, password, callback) {
    api.login(username, password)
    .then((response) => {
      if(response.status === 400){
        if(callback) {
          callback(false);
        }
      } else if (response.status === 200) {
        console.log("$$$$$$$$", document.cookie);
        response.json()
        .then(function(body) {
          localStorage.token = true;
          if(callback) {
            callback(true);
          }
        });
      }
    })
    .catch((err) => {
      console.error('Error with login' + err);
    });
  },

  logout(callback) {
    delete localStorage.token;
    api.logout()
    .then(() => {
      if (callback) {
        callback();
      }
    });
  },

  loggedIn() {
    return !!localStorage.token
  },

  checkForSession(next) {
    delete localStorage.token;
    api.checkForSession()
    .then((response) => {
      if(response.status === 200) {
        response.json().then((data) => {
          if(data.user) {
            localStorage.token = true;
          }
          next(data.user);
        });
      }
    })
    .catch((err) => {
      console.error('Error checking session', err);
      next(false);
    });
    // send api request to protected route
    // if response.status === 401, i don't have access
      // return false;
    // else,
      // localStorage.token = true;
      // it will return userID / login data
  }

}

Refactor checkAuth function to account for change on server


import React from 'react'
import {connect} from 'react-redux';

const Article = ({article, channelName}) => {
  return (

{article.title}

Posted at: {article.publishedAt}

{article.description}

      Read full article on {channelName}

  )
}

function mapStateToProps(state, ownProps) {
  var articles = []
    if (state.articles.find(channel => channel.name === ownProps.channelName)) {
      articles = state.articles.find(channel => channel.name === ownProps.channelName).articles;
    }
  const article = articles.find(article => article.title === ownProps.match.params.article)
    if (article) {
      return {
          article: article
      }
    } else {
    return {
      article: {}
    }
  }
}

export default connect(mapStateToProps)(Article)

import React from 'react'
import {connect} from 'react-redux';

const Article = ({article, channelName}) => {
  return (

{article.title}

Posted at: {article.publishedAt}

{article.description}

      Read full article on {channelName}

  )
}

//Fix glitch in Match object encoding so match.params matches article.title

function fixMatchParams(matchParams) {
  if (matchParams.split("%3A")) {
    return matchParams.split("%3A").join(":")
  } else {
    return matchParams
  }
}

function mapStateToProps(state, ownProps) {
  const matchParams = fixMatchParams(ownProps.match.params.article)
  var articles = []
    if (state.articles.find(channel => channel.name === ownProps.channelName)) {
      articles = state.articles.find(channel => channel.name === ownProps.channelName).articles;
    }
  const article = articles.find(article => article.title === matchParams)
    if (article) {
      return {
          article: article
      }
    } else {
    return {
      article: {}
    }
  }
}

export default connect(mapStateToProps)(Article)

Write function to deal with colons in url


var gulp = require('gulp'),
    exec = require('exec'),
    watch = require('gulp-watch');

const ADD = 'add';

var paths = {
  jobs: ['../uploads/**/*.fds'],
  parcel: ['../parcel.php']
};

//var runCommand = "php -r 'require \"" + paths.parcel + "\"; P::run();'";

gulp.task('jobs', function () {

});

gulp.task('watch', function() {
  watch(paths.jobs, function (vinyl) {
    if (vinyl.event == ADD)
      console.log("Houston, we have a new FDS job");
  });
});

gulp.task('default', ['watch']);

let gulp = require('gulp'),
    exec = require('exec'),
    watch = require('gulp-watch');

let ADD = 'add';

let paths = {
  jobs: ['../uploads/**/*.fds'],
  parcel: ['../parcel.php']
};

//var runCommand = "php -r 'require \"" + paths.parcel + "\"; P::run();'";

gulp.task('jobs', function () {

});

gulp.task('watch', function() {
  watch(paths.jobs, function (vinyl) {
    if (vinyl.event == ADD)
      console.log("Houston, we have a new FDS job");
  });
});

gulp.task('default', ['watch']);

Revert "Change let to var declarations"


/**
 * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

define(([], function () {
    var DeployMenu = {
        id: "deploy",
        label: "Deploy",
        items: [
            {
                id: "deploy-to-server",
                label: "Deploy To Server",
                command: {
                    id: "deploy-to-server",
                    shortcuts: {
                        mac: {
                            key: "command+shift+d",
                            label: "\u2318\u21E7D"
                        },
                        other: {
                            key: "ctrl+shift+d",
                            label: "Ctrl+Shift+D"
                        }
                    }
                },
                disabled: false
            }
        ]
    };
    return DeployMenu;
}));

/**
 * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

define(([], function () {
    var DeployMenu = {
        id: "deploy",
        label: "Deploy",
        items: [
            {
                id: "deploy-to-server",
                label: "Deploy To Server",
                command: {
                    id: "deploy-to-server",
                    shortcuts: {
                        mac: {
                            key: "command+shift+p",
                            label: "\u2318\u21E7P"
                        },
                        other: {
                            key: "ctrl+shift+p",
                            label: "Ctrl+Shift+P"
                        }
                    }
                },
                disabled: false
            }
        ]
    };
    return DeployMenu;
}));

Change Shortcut for Deploy as Ctrl+Shift+P


'use strict';
const langKeys = Object.keys(require('../locales/languages')());
const jsonfile = require('jsonfile');

let enMessageKeys = Object.keys(jsonfile.readFileSync('../locales/en.json'));

for (let langKey of langKeys) {
  if (langKey == 'en')
    continue;

  let messageKeys = Object.keys(jsonfile.readFileSync(`../locales/${langKey}.json`));
  let missingKeys = enMessageKeys.filter(getMissingKeys(messageKeys));

  if (missingKeys.length) {
    console.log(`The following keys are missing from ${langKey}.json:`);
    console.log(missingKeys.join('\n'));
  }
}

function getMissingKeys(xxMessageKeys) {
  return function(ele) {
    if (xxMessageKeys.indexOf(ele) == -1)
      return true;
    else
      return false;
  };
}

'use strict';
const langKeys = Object.keys(require('../locales/languages')());
const jsonfile = require('jsonfile');

let enMessageKeys = Object.keys(jsonfile.readFileSync('../locales/en.json'));

for (let langKey of langKeys) {
  if (langKey == 'en')
    continue;

  let messageKeys = Object.keys(jsonfile.readFileSync(`../locales/${langKey}.json`));
  let missingKeys = enMessageKeys.filter(getKeyFilter(messageKeys));

  if (missingKeys.length) {
    console.log(`The following keys are missing from ${langKey}.json:`);
    console.log(missingKeys.join('\n'));
  }

  let extraKeys = messageKeys.filter(getKeyFilter(enMessageKeys));
  if (extraKeys.length) {
    console.log(`\nThe following keys exist in ${langKey}.json which are not in the English version:`);
    console.log(extraKeys.join('\n'));
  }
}

function getKeyFilter(xxMessageKeys) {
  return function(ele) {
    if (xxMessageKeys.indexOf(ele) == -1)
      return true;
    else
      return false;
  };
}

Throw in extra keys for good measure.


chrome.app.runtime.onLaunched.addListener(function() {
	chrome.app.window.create('index.html', {
		'bounds': {
			'width': 1280,
			'height': 800
		}
	});
});

chrome.app.runtime.onLaunched.addListener(function() {
	chrome.app.window.create('index.html', {
		'bounds': {
			'width': 1600,
			'height': 900
		}
	});
});

Change default window resolution to 1600x900


export default {
  API_URL: 'https://api.hel.fi/respa/v1',
  DATE_FORMAT: 'YYYY-MM-DD',
  NOTIFICATION_DEFAULTS: {
    message: '',
    type: 'info',
    timeOut: 5000,
    hidden: false,
  },
  REQUIRED_API_HEADERS: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
  SUPPORTED_SEARCH_FILTERS: {
    date: '',
    people: '',
    purpose: '',
    search: '',
  },
  TIME_FORMAT: 'H:mm',
};

export default {
  API_URL: 'https://api.hel.fi/respa/v1',
  DATE_FORMAT: 'YYYY-MM-DD',
  NOTIFICATION_DEFAULTS: {
    message: '',
    type: 'info',
    timeOut: 5000,
    hidden: false,
  },
  REQUIRED_API_HEADERS: {
    'Accept': 'application/json',
    'Accept-Language': 'fi',
    'Content-Type': 'application/json',
  },
  SUPPORTED_SEARCH_FILTERS: {
    date: '',
    people: '',
    purpose: '',
    search: '',
  },
  TIME_FORMAT: 'H:mm',
};

Add Accept-Language header to all API requests


/* eslint-env mocha */
const prepareLearnApp = require('../../src/state/prepareLearnApp');
const sinon = require('sinon');
const kolibri = require('kolibri');

const { MembershipResource } = kolibri.resources;

describe.only('prepareLearnApp action', () => {
  const mockStore = {
    dispatch: sinon.spy(),
    state: {
      core: {
        session: {}
      },
    },
  };

  const dispatchSpy = mockStore.dispatch;

  beforeEach(() => {
    dispatchSpy.reset();
    mockStore.state.core.session = {};
    MembershipResource.__resetMocks();
  });

  it('prepares app state for guest user', () => {
    mockStore.state.core.session.user_id = null;

    return prepareLearnApp(mockStore)
    .then(() => {
      sinon.assert.notCalled(MembershipResource.getCollection);
      sinon.assert.calledWith(dispatchSpy, 'LEARN_SET_MEMBERSHIPS', []);
    });
  });

  it('prepares app state for logged-in user', () => {
    mockStore.state.core.session.user_id = 101;
    const fakeMemberships = [
      { id: 'membership_1' },
      { id: 'membership_2' },
    ];
    MembershipResource.__getCollectionFetchReturns(fakeMemberships);

    return prepareLearnApp(mockStore)
    .then(() => {
      sinon.assert.calledWith(MembershipResource.getCollection, { user_id: 101 });
    });
  });
});

/* eslint-env mocha */
const prepareLearnApp = require('../../src/state/prepareLearnApp');
const Vue = require('vue');
const Vuex = require('vuex');
const sinon = require('sinon');
const assert = require('assert');
const kolibri = require('kolibri');

Vue.use(Vuex);

const { MembershipResource } = kolibri.resources;

function makeStore() {
  return new Vuex.Store({
    state: {
      learnAppState: {},
      core: {
        session: {}
      },
    },
  });
}

describe.only('prepareLearnApp action', () => {
  let mockStore;

  beforeEach(() => {
    mockStore = makeStore();
    mockStore.state.core.session = {};
    MembershipResource.__resetMocks();
  });

  it('prepares app state for guest user', () => {
    mockStore.state.core.session.user_id = null;

    return prepareLearnApp(mockStore)
    .then(() => {
      sinon.assert.notCalled(MembershipResource.getCollection);
      assert.equal(mockStore.state.learnAppState.userMemberships, []);
    });
  });

  it('prepares app state for logged-in user', () => {
    mockStore.state.core.session.user_id = 101;
    const fakeMemberships = [
      { id: 'membership_1' },
      { id: 'membership_2' },
    ];
    MembershipResource.__getCollectionFetchReturns(fakeMemberships);

    return prepareLearnApp(mockStore)
    .then(() => {
      sinon.assert.calledWith(MembershipResource.getCollection, { user_id: 101 });
    });
  });
});

Use a real vuex store in test, write new failing test


import React, { Component } from "react";
import { connect } from "react-redux";

import { timelineSelectors } from "../../redux/selectors";
import { timelineActions } from "../../redux/actions";

import { Container } from "../_controls";

class Timeline extends Component {
  componentDidMount() {
    this.props.load();
  }

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

    return (
      <Container>
        Timeline
        {entries.map(entry => {
          return (
            <div>
              {entry.eventTimeDisplay}: {entry.title}
            </div>
          );
        })}
      </Container>
    );
  }
}

function mapStateToProps(state, props) {
  return {
    entries: timelineSelectors.all(state)
  };
}

function mapDispatchToProps(dispatch, props) {
  return {
    load: () => dispatch(timelineActions.load())
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Timeline);

import React, { Component } from "react";
import { connect } from "react-redux";

import { timelineSelectors } from "../../redux/selectors";
import { timelineActions } from "../../redux/actions";

import { Container } from "../_controls";

class Timeline extends Component {
  componentDidMount() {
    this.props.load();
  }

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

    return (
      <Container>
        Timeline
        {entries.map(entry => {
          return (
            <div key={entry.id}>
              {entry.eventTimeDisplay}: {entry.title}
            </div>
          );
        })}
      </Container>
    );
  }
}

function mapStateToProps(state, props) {
  return {
    entries: timelineSelectors.all(state)
  };
}

function mapDispatchToProps(dispatch, props) {
  return {
    load: () => dispatch(timelineActions.load())
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Timeline);

Fix error relating to timeline key


function loadDashboard(page, dashboard) {
	var url = "/dashboards/page/"+dashboard+"/"+page+"/";
	console.log("Getting", url);
	axios.get(url).then(function (response) {
		$('#content').html(response.data);
	}).catch(function (error) {
		console.log(error);
	});
	$(".sparkline-embeded").each(function () {
	  var $this = $(this);
	  $this.sparkline('html', $this.data());
	});
}

$(document).ready(function () {
	loadDashboard("index", "{{ dashboard }}");
	$(".sparkline").each(function () {
	  var $this = $(this);
	  $this.sparkline('html', $this.data());
	});
});

function loadDashboard(page, dashboard) {
	var url = "/dashboards/page/"+dashboard+"/"+page+"/";
	console.log("Getting", url);
	var spinner = '
';
	spinner = spinner + '\n
';
	$("#content").html(spinner);
	axios.get(url).then(function (response) {
		$('#content').html(response.data);
	}).catch(function (error) {
		console.log(error);
	});
	$(".sparkline-embeded").each(function () {
	  var $this = $(this);
	  $this.sparkline('html', $this.data());
	});
}

$(document).ready(function () {
	loadDashboard("index", "{{ dashboard }}");
	$(".sparkline").each(function () {
	  var $this = $(this);
	  $this.sparkline('html', $this.data());
	});
});

Add a spinner while waiting for ajax calls to load


import { isEqual } from 'lodash';

const TAG_COMPARISON = {
  TOTAL: Symbol(),
  PARTIAL: Symbol(),
  MISMATCH: Symbol()
};

function compareTags (a, b) {
  /* Returns a TAG_COMPARISON value */
  if (isEqual(a, b)) return TAG_COMPARISON.TOTAL;
  // If the tags are unequal but have the same length, it stands to reason
  // there is a mismatch.
  if (a.length === b.length) return TAG_COMPARISON.MISMATCH;
  const [shorter, longer] = a < b ? [a, b] : [b, a];
  if (shorter.find((x, i) => x !== longer[i])) return TAG_COMPARISON.MISMATCH;
  return TAG_COMPARISON.PARTIAL;
}

export default {
  mismatchFilter (group, model) {
    /* Ensures that the group and model don't have any mismatched tags */
    const result = group.tags.find(function (groupTag) {
      // Look for a mismatch.
      const matched = model.tags.find(modelTag => modelTag[0] === groupTag[0]);
      if (matched === undefined) return false;
      if (compareTags(groupTag, matched) === TAG_COMPARISON.MISMATCH) return true;
      return false;
    });
    if (result === undefined) return 0;
    return null;
  }
};

import { isEqual } from 'lodash';

const TAG_COMPARISON = {
  TOTAL: Symbol(),
  PARTIAL: Symbol(),
  MISMATCH: Symbol()
};

function compareTags (a, b) {
  /* Returns a TAG_COMPARISON value */
  if (isEqual(a, b)) return TAG_COMPARISON.TOTAL;
  // If the tags are unequal but have the same length, it stands to reason
  // there is a mismatch.
  if (a.length === b.length) return TAG_COMPARISON.MISMATCH;
  const [shorter, longer] = a < b ? [a, b] : [b, a];
  if (shorter.find((x, i) => x !== longer[i])) return TAG_COMPARISON.MISMATCH;
  return TAG_COMPARISON.PARTIAL;
}

function mismatchFilterSub (group, model) {
  /* Ensures that the group and model don't have any mismatched tags */
  const result = group.tags.find(function (groupTag) {
    // Look for a mismatch.
    const matched = model.tags.find(modelTag => modelTag[0] === groupTag[0]);
    if (matched === undefined) return false;
    if (compareTags(groupTag, matched) === TAG_COMPARISON.MISMATCH) return true;
    return false;
  });
  if (result === undefined) return 0;
  return null;
}

export default {
  mismatchFilter () { return mismatchFilterSub; }
};

Make mismatchFilter a factory for API consistency


/**
 * Copyright 2014, Yahoo! Inc.
 * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */
'use strict';

var React = require('react/addons'),
    NavLink,
    navigateAction = require('../actions/navigate'),
    debug = require('debug')('NavLink');

NavLink = React.createClass({
    propTypes: {
      context: React.PropTypes.object.isRequired
    },
    dispatchNavAction: function (e) {
        var context = this.props.context;
        debug('dispatchNavAction: action=NAVIGATE path=' + this.props.href + ' params=' + JSON.stringify(this.props.navParams));
        if (context) {
            e.preventDefault();
            e.stopPropagation();
            context.executeAction(navigateAction, {
                type: 'click',
                path: this.props.href,
                params: this.props.navParams
            });
        } else {
            console.warn('NavLink.dispatchNavAction: missing dispatcher, will load from server');
        }
    },
    render: function() {
        var context = this.props.context,
            routeName = this.props.routeName;
        if (!this.props.href && routeName && context && context.makePath) {
            this.props.href = context.makePath(routeName, this.props.navParams);
        }
        return React.createElement(
                'a',
                {onClick:this.dispatchNavAction, href:this.props.href},
                this.props.children
            );
    }
});

module.exports = NavLink;

/**
 * Copyright 2014, Yahoo! Inc.
 * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */
'use strict';

var React = require('react/addons'),
    NavLink,
    navigateAction = require('../actions/navigate'),
    debug = require('debug')('NavLink');

NavLink = React.createClass({
    propTypes: {
      context: React.PropTypes.object.isRequired
    },
    dispatchNavAction: function (e) {
        var context = this.props.context;
        debug('dispatchNavAction: action=NAVIGATE path=' + this.props.href + ' params=' + JSON.stringify(this.props.navParams));
        if (context) {
            e.preventDefault();
            e.stopPropagation();
            context.executeAction(navigateAction, {
                type: 'click',
                path: this.props.href,
                params: this.props.navParams
            });
        } else {
            console.warn('NavLink.dispatchNavAction: missing dispatcher, will load from server');
        }
    },
    render: function() {
        var context = this.props.context,
            routeName = this.props.routeName;
        if (!this.props.href && routeName && context && context.makePath) {
            this.props.href = context.makePath(routeName, this.props.navParams);
        }
        return React.createElement(
                'a',
                {
                    onClick: this.dispatchNavAction,
                    href: this.props.href,
                    id: this.props.id,
                    className: this.props.className,
                    title: this.props.title,
                    target: this.props.target,
                    rel: this.props.rel
                },
                this.props.children
            );
    }
});

module.exports = NavLink;

Add anchor tag props for transferring


/*
 * grunt-html
 * https://github.com/jzaefferer/grunnt-html
 *
 * Copyright (c) 2012 Jörn Zaefferer
 * Licensed under the MIT license.
 */

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

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

  grunt.registerMultiTask('htmllint', 'Validate html files', function() {
    var done = this.async(),
      files = grunt.file.expand(this.filesSrc);

    htmllint(grunt, files, function(error, result) {
      if (error) {
        grunt.log.error(error);
        done(false);
        return;
      }
      if (!result.length) {
        grunt.log.writeln(files.length + ' file(s) valid');
        done();
        return;
      }
      grunt.log.writeln(result.join('\n'));
      done(false);
    });
  });

};

/*
 * grunt-html
 * https://github.com/jzaefferer/grunnt-html
 *
 * Copyright (c) 2012 Jörn Zaefferer
 * Licensed under the MIT license.
 */

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

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

  grunt.registerMultiTask('htmllint', 'Validate html files', function() {
    var done = this.async(),
      files = grunt.file.expand(this.filesSrc);

    htmllint(grunt, files, function(error, result) {
      if (error) {
        grunt.log.error(error);
        done(false);
        return;
      }
      if (!result.length) {
        grunt.log.ok(files.length + ' file' + (files.length === 1 ? '' : 's') + ' lint free.');
        done();
        return;
      } else {
        var parts = [], loc = [];
        for (var i = 0, l = result.length; i < l; i++) {
          parts = result[i].split(':'); // 0=file, 1=line, 2=error, 3=msg

          if (parts.length < 4) {
            parts.splice(1, 0, '--.--.--');
          }
          loc = parts[1].split('.');
          grunt.log.writeln('Linting ' + parts[0].replace('"', '') + '...' + (parts[2].toUpperCase()).red);
          grunt.log.writeln('['.red + ('L' + loc[0]).yellow + ':'.red + ('C' + loc[2]).yellow + ']'.red + (parts[3]).yellow);
        }
      }
      done(false);
    });
  });

};

Use error syntax colouring to be similar to jshint. Update valid message to match jshint & csslint output style. Closes gh-14


// Copyright (c) 2017 The Regents of the University of Michigan.
// All Rights Reserved. Licensed according to the terms of the Revised
// BSD License. See LICENSE.txt for details.

module.exports = function() {
  Ticker = {};
  internal = {};

  Ticker.tick = n => new Promise(function(resolve, reject) {
    if (typeof n === "undefined")
      n = 1;

    for (let i = 0; i < n; i += 1) {
      internal.counter += 1;
      let promise = Promise.resolve();

      if (internal.callbacks.has(internal.counter))
        for (let callback of internal.callbacks.get(internal.counter))
          promise = internal.appendPromise(promise, callback);
    }

    resolve();
  });

  Ticker.at = function(n, callback) {
    if (!internal.callbacks.has(n))
      internal.callbacks.set(n, []);

    internal.callbacks.get(n).push(callback);
  };

  internal.callbacks = new Map();
  internal.counter = 0;

  internal.appendPromise = function(promise, callback) {
    return new Promise(function(done) {
      promise.then(function() {
        Promise.resolve(callback()).then(done);
      });
    });
  };

  return Ticker;
};

// Copyright (c) 2017 The Regents of the University of Michigan.
// All Rights Reserved. Licensed according to the terms of the Revised
// BSD License. See LICENSE.txt for details.

module.exports = function() {
  Ticker = {};
  internal = {};

  Ticker.tick = n => new Promise(function(resolve, reject) {
    let promise = Promise.resolve();

    if (typeof n === "undefined")
      n = 1;

    for (let i = 0; i < n; i += 1) {
      internal.counter += 1;

      if (internal.callbacks.has(internal.counter))
        for (let callback of internal.callbacks.get(internal.counter))
          promise = internal.appendPromise(promise, callback);
    }

    promise.then(resolve);
  });

  Ticker.at = function(n, callback) {
    if (!internal.callbacks.has(n))
      internal.callbacks.set(n, []);

    internal.callbacks.get(n).push(callback);
  };

  internal.callbacks = new Map();
  internal.counter = 0;

  internal.appendPromise = function(promise, callback) {
    return new Promise(function(done) {
      promise.then(function() {
        Promise.resolve(callback()).then(done);
      });
    });
  };

  return Ticker;
};

Resolve tick() after callbacks resolve


// Download the Node helper library from twilio.com/docs/node/install
// These consts are your accountSid and authToken from https://www.twilio.com/console
const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const authToken = 'your_auth_token';
const client = require('twilio')(accountSid, authToken);

client
  .queues('QU5ef8732a3c49700934481addd5ce1659')
  .fetch(queue => console.log(queue));

// Download the Node helper library from twilio.com/docs/node/install
// These consts are your accountSid and authToken from https://www.twilio.com/console
const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const authToken = 'your_auth_token';
const client = require('twilio')(accountSid, authToken);

client
  .queues('QU5ef8732a3c49700934481addd5ce1659')
  .fetch()
  .then(queue => console.log(queue));

Update "Get a Queue" Node.js fetch


import candela from './../../src/candela';
import mainContent from './index.jade';
import visContent from './vis.jade';
import 'javascript-detect-element-resize/detect-element-resize';
import './index.styl';
import iris from '../../src/vcharts/data/iris.json';
import visualizations from './visualizations.json';

let datasets = {
  iris
};
let visMap = {};
visualizations.forEach((v) => {
  visMap[v.hash] = v;
});

function showPage () {
  let pageId = 'main';
  if (window.location.hash.length > 1) {
    pageId = window.location.hash.slice(1);
  }
  if (pageId === 'main') {
    document.getElementsByTagName('body')[0].innerHTML = mainContent({
      visualizations
    });
  } else {
    let properties = visMap[pageId];
    document.getElementsByTagName('body')[0].innerHTML = visContent(
      properties
    );
    let el = document.getElementById('vis-element');
    let vis = new candela.components[properties.component](
      el,
      datasets[properties.data],
      properties.options
    );
    vis.render();
    window.addResizeListener(el, () => vis.render());
  }
}

window.addEventListener('load', () => {
  showPage();
  window.addEventListener('hashchange', showPage, false);
});

import candela from './../../src/candela';
import mainContent from './index.jade';
import visContent from './vis.jade';
import 'javascript-detect-element-resize/detect-element-resize';
import './index.styl';
import iris from '../../src/vcharts/data/iris.json';
import visualizations from './visualizations.json';

let datasets = {
  iris
};
let visMap = {};
visualizations.forEach((v) => {
  visMap[v.hash] = v;
});

function showPage () {
  let pageId = 'main';
  if (window.location.hash.length > 1) {
    pageId = window.location.hash.slice(1);
  }
  if (pageId === 'main') {
    document.getElementsByTagName('body')[0].innerHTML = mainContent({
      visualizations
    });
  } else {
    let properties = visMap[pageId];
    document.getElementsByTagName('body')[0].innerHTML = visContent(
      properties
    );
    let el = document.getElementById('vis-element');
    let vis;
    if (properties.data) {
      vis = new candela.components[properties.component](el, datasets[properties.data], properties.options);
    } else {
      vis = new candela.components[properties.component](el, properties.options);
    }
    vis.render();
    window.addResizeListener(el, () => vis.render());
  }
}

window.addEventListener('load', () => {
  showPage();
  window.addEventListener('hashchange', showPage, false);
});

Allow example visualizations to be passed only an options object


$(function(){

    // Delete users/groups via ajax
    $('.listFilterContainer').find(".deleteIcon").bind("click", function(){
        var me    = $(this);
        var url   = me.attr("href");
        var title = me.attr("data-name");

        if(!$('body').data('mbPopup')) {
            $("body").mbPopup();
            $("body").mbPopup('showModal',
                              {title:"Confirm delete",
                                      subTitle: " - " + title,
                                      content:"Do you really want to delete the group/user " + title + "?"},
                                      function(){
                                        $.ajax({
                                            url: url,
                                            type: 'POST',
                                            success: function(data) {
                                              me.parent().parent().remove();
                                            }
                                        });
                                      });
        }
        return false;
    });

    $(".checkbox").bind("click", function(e){
      $("#selectedUsersGroups").text(($(".tableUserGroups").find(".iconCheckboxActive").length))
    });

    // Delete group via Ajax
    $('#listFilterGroups').find(".iconRemove").bind("click", function(){
        var me  = $(this);
        var title = me.attr('title');

        if(!$('body').data('mbPopup')) {
            $("body").mbPopup();
            $("body").mbPopup('showModal',
                {
                    title:"Confirm delete",
                    subTitle: title,
                    content:"Do you really want to delete the group " + title + "?"
                },
                function(){
                    $.ajax({
                        url: me.attr('data-url'),
                        data : {'id': me.attr('data-id')},
                        type: 'POST',
                        success: function(data) {
                            window.location.reload();
                        }
                    });
                });
        }
        return false;
    });

    // Delete user via Ajax
    $('#listFilterUsers').find(".iconRemove").bind("click", function(){
        var me  = $(this);
        var title = me.attr('title');

        if(!$('body').data('mbPopup')) {
            $("body").mbPopup();
            $("body").mbPopup('showModal',
                {
                    title:"Confirm delete",
                    subTitle: title,
                    content:"Do you really want to delete the user " + title + "?"
                },
                function(){
                    $.ajax({
                        url: me.attr('data-url'),
                        data : {'id': me.attr('data-id')},
                        type: 'POST',
                        success: function(data) {
                            window.location.reload();
                        }
                    });
                });
        }
        return false;
    });
});

Delete Group and User Dialog


$(function () {
	$(document).ready(function() {
		new SimpleMDE({
			element: document.getElementById("simplemde"),
			spellChecker: false,
			hideIcons: ["side-by-side", "fullscreen", "image"],
			forceSync: true
		});
  });
});

$(function () {
	$(".editor").each(function(){
		var simplemde = new SimpleMDE({
			element: this,
			forceSync: true,
			hideIcons: ["side-by-side", "fullscreen"],
			spellChecker: false,
			toolbar: ["bold", "italic", "heading", "|",
				"quote", "unordered-list", "ordered-list", "|",
				"link", "table", "|",
				"undo", "redo", "|",
				"preview", "guide"
			]
		});
	});
});

Support multiple SimpleMDE instances in a form


catalogApp.directive('assetThumb', function() {

  var linkFn = function ($scope, $elem, attrs) {

  }

  return {
    restrict: 'E',
    template: '',
    scope: {
      asset: '='
    },
    link: linkFn
  };
});

catalogApp.directive('assetThumb', function() {

  var tpl = '';
  tpl += '';
  tpl += '

{{ asset.Item.Title }}';
  tpl += '{{ asset.Item.ReleaseYear }}';
  tpl += '{{ asset.Item.RunTimeSec | readableDuration | date:\'HH:mm\' }}';
  tpl += '

';
  tpl += '';

  var linkFn = function ($scope, $elem, attrs) {

  }

  return {
    restrict: 'E',
    template: tpl,
    scope: {
      asset: '='
    },
    link: linkFn
  };
});

Extend directive tpl to include releaseYear and Duration


/**
 * @export
 * @param {Object} store
 * @param {Object} state - Immutable state object
 */
export default function createDispatcher(store, state) {

  /**
   * Dispatches an action function
   * @param {Function} action
   * @param {Object} [payload={}]
   * @returns {Promise}
   */
  return function dispatch(action, payload = {}) {
    const dispatchPromise = new Promise((resolve, reject) => {
        action({
            store,
            state,
            payload,
            resolve,
            reject
        });
    });

    /**
     * Adds custom hooks onto all promise resolves trigggering 'state:change'
     * events for all state mutations.
     *
     * If resolve(state) is not called, the new state will not be set from
     * the reducers.
     */
    Promise.all([dispatchPromise]).then((newState) => {
        state = newState;
        store.trigger('state:change');
    });

    return dispatchPromise;
  }
}

/**
 * @export
 * @param {Object} store
 * @param {Object} state - Immutable state object
 */
export default function createDispatcher(store, state) {

  /**
   * Dispatches an action function
   * @param {Function} action
   * @param {Object} [payload={}]
   * @returns {Promise}
   */
  return function dispatch(action, payload = {}) {
    const dispatchPromise = new Promise((resolve, reject) => {
        action({
            store,
            state,
            payload,
            resolve,
            reject
        });
    });

    /**
     * Adds custom hooks onto all promise resolves trigggering 'state:change'
     * events for all state mutations.
     *
     * If resolve(state) is not called, the new state will not be set from
     * the reducers.
     */
    Promise.all([dispatchPromise]).then((newState, events) => {
        state = newState;

        store.trigger('state:change');

        // If events is array, trigger all events in the array
        if (Array.isArray(events)) {
          for (const event of events) {
            store.trigger(event);
          }
        }

        // If events is not an array and is just a string, trigger that event.
        else if (typeof events === 'string') {
          store.trigger(events);
        }

        return Promise.resolve(state);
    });

    return dispatchPromise;
  }
}

Update all promise to allow for the ability to trigger events AFTER the state has changed.


var redis = require('redis'),
    url = require('url'),
    redis = require('redis'),
    url = require('url'),
    redisURL = url.parse(process.env.REDISCLOUD_URL || 'redis://localhost:6379');

exports.redisClient = redis.createClient(redisURL.port, redisURL.hostname, {no_ready_check: true});
exports.appNamespace = 'stats:app';
exports.globalNamespace = 'stats:global';

exports.santizeAppId = function(appId) {
  return appId.replace(':', '_'); // santize appId as : is reserved for namespacing
};

var redis = require('redis'),
    url = require('url'),
    redis = require('redis'),
    url = require('url'),
    redisURL = url.parse(process.env.REDISCLOUD_URL || 'redis://localhost:6379');

exports.redisClient = redis.createClient(redisURL.port, redisURL.hostname, {no_ready_check: true});
if (redisURL.auth) { exports.redisClient.auth(redisURL.auth.split(":")[1]); }

exports.appNamespace = 'stats:app';
exports.globalNamespace = 'stats:global';

exports.santizeAppId = function(appId) {
  return appId.replace(':', '_'); // santize appId as : is reserved for namespacing
};

Support Auth for Redis connections


/*global window*/
(function(NS) {
  NS = NS || {};
  var isInBrowser = typeof module === 'undefined' && typeof window !== 'undefined';
  //Container for all the post renderings functions.
  var postRenderingHelpers = {};
  //Register a helper inside the application.
  var registerHelper = function registerHelper(helper) {
    postRenderingHelpers[helper.name] = {fn: helper.fn, options: helper.options};
  };
  //Options must have a selector property and a helperName one.
  var callHelper = function( config) {
    if(config.selector === undefined || config.selector.size() === 0){
      return;
    }
    config.selector[postRenderingHelpers[config.helperName].fn](postRenderingHelpers[config.helperName].options);
    //config.selector[config.helperName](options);
  };
  var mdl = {
    registerHelper: registerHelper,
    callHelper: callHelper
  };
    // Differenciating export for node or browser.
  if (isInBrowser) {
    NS.Helpers = NS.Helpers || {};
    NS.Helpers.postRenderingHelper = mdl;
  } else {
    module.exports = mdl;
  }

})(typeof module === 'undefined' && typeof window !== 'undefined' ? window.Fmk : module.exports);

/*global window*/
(function(NS) {
  NS = NS || {};
  var isInBrowser = typeof module === 'undefined' && typeof window !== 'undefined';
  //Container for all the post renderings functions.
  var postRenderingHelpers = {};
  //Register a helper inside the application.
  var registerHelper = function registerHelper(helper) {
    postRenderingHelpers[helper.name] = {fn: helper.fn, options: helper.options};
  };
  //Options must have a selector property and a helperName one.
  var callHelper = function( config) {
    //If there is nothing selected.
    if(config.selector === undefined || config.selector.size() === 0){
      return;
    }
    //If the function  desn not exist on the selection.
    if(config.selector[postRenderingHelpers[config.helperName].fn] === undefined){
      return;
    }
    config.selector[postRenderingHelpers[config.helperName].fn](postRenderingHelpers[config.helperName].options);
    //config.selector[config.helperName](options);
  };
  var mdl = {
    registerHelper: registerHelper,
    callHelper: callHelper
  };
    // Differenciating export for node or browser.
  if (isInBrowser) {
    NS.Helpers = NS.Helpers || {};
    NS.Helpers.postRenderingHelper = mdl;
  } else {
    module.exports = mdl;
  }

})(typeof module === 'undefined' && typeof window !== 'undefined' ? window.Fmk : module.exports);

Improve tests on the post render.


var PIXI = require('pixi.js');

class GameObject extends PIXI.Graphics {
    constructor(x, y) {
        super();
        this.modelx = x;
        this.modely = y;
    }

    getSpriteWidth() {
        //return document.body.clientWidth / 26;
        return 900 / 26;
    }

    getSpriteHeight() {
        //return document.body.clientHeight / 29;
        return 900 / 29;
}

module.exports = GameObject;

var PIXI = require('pixi.js');

class GameObject extends PIXI.Graphics {
    constructor(x, y) {
        super();
        this.modelx = x;
        this.modely = y;
    }

    getSpriteWidth() {
        //return document.body.clientWidth / 26;
        return 900 / 26;
    }

    getSpriteHeight() {
        //return document.body.clientHeight / 29;
        return 900 / 29;
    }
}

module.exports = GameObject;

Fix small merge issue with GameObject


// As defined in https://github.com/mozilla/pdf.js/blob/master/src/shared/util.js#L378-L381

const PasswordResponses = {
  NEED_PASSWORD: 1,
  INCORRECT_PASSWORD: 2,
};

export default PasswordResponses;

// As defined in https://github.com/mozilla/pdf.js/blob/d9fac3459609a807be6506fb3441b5da4b154d14/src/shared/util.js#L371-L374

const PasswordResponses = {
  NEED_PASSWORD: 1,
  INCORRECT_PASSWORD: 2,
};

export default PasswordResponses;

Update link in the comment