Add assertions about the constructor parameter

--- index.js
+++ index.js
@@ -1,6 +1,12 @@
 module.exports = EventSender;

+var assert = require('assert');
+
 function EventSender(httpResponse) {
+  assert(httpResponse);
+  assert.equal(typeof(httpResponse.setHeader), 'function', "The HTTP response must have a #setHeader() method.");
+  assert.equal(typeof(httpResponse.write), 'function', "The HTTP response must have a #write() method.");
+
   this.httpResponse = httpResponse;
   this.httpResponse.setHeader('Content-Type', 'text/event-stream');
 }

<|original_code|>
module.exports = EventSender;

function EventSender(httpResponse) {
  this.httpResponse = httpResponse;
  this.httpResponse.setHeader('Content-Type', 'text/event-stream');
}

EventSender.prototype.send = function EventSender_send(event) {
  if (typeof(event.data) === 'undefined') {
    event.data = '';
  } else if (typeof(event.data) !== 'string') {
    event.data = JSON.stringify(event.data);
  }
  this.httpResponse.write(
    (event.name ? 'event: ' + event.name + '\n' : '') +
    dataize(event.data) +
    '\n\n'
  );
};

function dataize(string) {
  return string.split('\n').map(function (line) {
    return 'data: ' + line;
  }).join('\n');
}

<|edits_diff|>
--- index.js
+++ index.js
@@ -1,4 +1,6 @@
 module.exports = EventSender;
+
+var assert = require('assert');

 function EventSender(httpResponse) {
   this.httpResponse = httpResponse;
<|current_version|>
module.exports = EventSender;

var assert = require('assert');

function EventSender(httpResponse) {
  this.httpResponse = httpResponse;
  this.httpResponse.setHeader('Content-Type', 'text/event-stream');
}

EventSender.prototype.send = function EventSender_send(event) {
  if (typeof(event.data) === 'undefined') {
    event.data = '';
  } else if (typeof(event.data) !== 'string') {
    event.data = JSON.stringify(event.data);
  }
  this.httpResponse.write(
    (event.name ? 'event: ' + event.name + '\n' : '') +
    dataize(event.data) +
    '\n\n'
  );
};

function dataize(string) {
  return string.split('\n').map(function (line) {
    return 'data: ' + line;
  }).join('\n');
}

<|next_version|>
module.exports = EventSender;

var assert = require('assert');

function EventSender(httpResponse) {
  assert(httpResponse);
  assert.equal(typeof(httpResponse.setHeader), 'function', "The HTTP response must have a #setHeader() method.");
  assert.equal(typeof(httpResponse.write), 'function', "The HTTP response must have a #write() method.");

  this.httpResponse = httpResponse;
  this.httpResponse.setHeader('Content-Type', 'text/event-stream');
}

EventSender.prototype.send = function EventSender_send(event) {
  if (typeof(event.data) === 'undefined') {
    event.data = '';
  } else if (typeof(event.data) !== 'string') {
    event.data = JSON.stringify(event.data);
  }
  this.httpResponse.write(
    (event.name ? 'event: ' + event.name + '\n' : '') +
    dataize(event.data) +
    '\n\n'
  );
};

function dataize(string) {
  return string.split('\n').map(function (line) {
    return 'data: ' + line;
  }).join('\n');
}


import $ from 'jquery';
import { onMessage, addMessage } from '../messageQueue.js';
import { getCurrentScene } from '../currentScene.js';

export default class {
  constructor() {
    this.$frame = $('#game');
  }
  start() {
    this.messageBox = $('<div class="message-box">');
    this.$frame.append(this.messageBox);
    onMessage((message) => this.displayMessage(message));
    addMessage(getCurrentScene().descriptionMessage());
  }
  displayMessage(message) {
    this.messageBox.children().addClass('disabled');
    this.messageBox.append(message);
    this.$frame.scrollTop(this.$frame[0].scrollHeight);
  }
}

import $ from 'jquery';
import Tooltip from '../Tooltip.js';
import { onMessage, addMessage } from '../messageQueue.js';
import { getCurrentScene } from '../currentScene.js';

export default class {
  constructor() {
    this.$frame = $('#game');
  }
  start() {
    this.messageBox = $('<div class="message-box">');
    this.$frame.append(this.messageBox);
    onMessage((message) => this.displayMessage(message));
    addMessage(getCurrentScene().descriptionMessage());
  }
  displayMessage(message) {
    this.messageBox.children().addClass('disabled');
    this.messageBox.append(message);
    for (var el of this.messageBox.find(".tooltip")) {
      new Tooltip(el);
    }
    this.$frame.scrollTop(this.$frame[0].scrollHeight);
  }
}

Initialize tooltips when showing message

--- app/webpack/views/CircleBurgerGameView.js
+++ app/webpack/views/CircleBurgerGameView.js
@@ -1,4 +1,5 @@
 import $ from 'jquery';
+import Tooltip from '../Tooltip.js';
 import { onMessage, addMessage } from '../messageQueue.js';
 import { getCurrentScene } from '../currentScene.js';

@@ -15,6 +16,9 @@
   displayMessage(message) {
     this.messageBox.children().addClass('disabled');
     this.messageBox.append(message);
+    for (var el of this.messageBox.find(".tooltip")) {
+      new Tooltip(el);
+    }
     this.$frame.scrollTop(this.$frame[0].scrollHeight);
   }
 }

import $ from 'jquery';
import Tooltip from '../Tooltip.js';
import { onMessage, addMessage } from '../messageQueue.js';
import { getCurrentScene } from '../currentScene.js';

export default class {
  constructor() {
    this.$frame = $('#game');
  }
  start() {
    this.messageBox = $('<div class="message-box">');
    this.$frame.append(this.messageBox);
    onMessage((message) => this.displayMessage(message));
    addMessage(getCurrentScene().descriptionMessage());
  }
  displayMessage(message) {
    this.messageBox.children().addClass('disabled');
    this.messageBox.append(message);
    this.$frame.scrollTop(this.$frame[0].scrollHeight);
  }
}

<|original_code|>
import $ from 'jquery';
import { onMessage, addMessage } from '../messageQueue.js';
import { getCurrentScene } from '../currentScene.js';

export default class {
  constructor() {
    this.$frame = $('#game');
  }
  start() {
    this.messageBox = $('
');
    this.$frame.append(this.messageBox);
    onMessage((message) => this.displayMessage(message));
    addMessage(getCurrentScene().descriptionMessage());
  }
  displayMessage(message) {
    this.messageBox.children().addClass('disabled');
    this.messageBox.append(message);
    this.$frame.scrollTop(this.$frame[0].scrollHeight);
  }
}

<|edits_diff|>
--- app/webpack/views/CircleBurgerGameView.js
+++ app/webpack/views/CircleBurgerGameView.js
@@ -1,4 +1,5 @@
 import $ from 'jquery';
+import Tooltip from '../Tooltip.js';
 import { onMessage, addMessage } from '../messageQueue.js';
 import { getCurrentScene } from '../currentScene.js';

<|current_version|>
import $ from 'jquery';
import Tooltip from '../Tooltip.js';
import { onMessage, addMessage } from '../messageQueue.js';
import { getCurrentScene } from '../currentScene.js';

export default class {
  constructor() {
    this.$frame = $('#game');
  }
  start() {
    this.messageBox = $('
');
    this.$frame.append(this.messageBox);
    onMessage((message) => this.displayMessage(message));
    addMessage(getCurrentScene().descriptionMessage());
  }
  displayMessage(message) {
    this.messageBox.children().addClass('disabled');
    this.messageBox.append(message);
    this.$frame.scrollTop(this.$frame[0].scrollHeight);
  }
}

<|next_version|>
import $ from 'jquery';
import Tooltip from '../Tooltip.js';
import { onMessage, addMessage } from '../messageQueue.js';
import { getCurrentScene } from '../currentScene.js';

export default class {
  constructor() {
    this.$frame = $('#game');
  }
  start() {
    this.messageBox = $('
');
    this.$frame.append(this.messageBox);
    onMessage((message) => this.displayMessage(message));
    addMessage(getCurrentScene().descriptionMessage());
  }
  displayMessage(message) {
    this.messageBox.children().addClass('disabled');
    this.messageBox.append(message);
    for (var el of this.messageBox.find(".tooltip")) {
      new Tooltip(el);
    }
    this.$frame.scrollTop(this.$frame[0].scrollHeight);
  }
}


Hide log chart when empty

Hide log chart when empty

--- app/webroot/js/log-chart.js
+++ app/webroot/js/log-chart.js
@@ -10,10 +10,13 @@
         table.addColumn('date', 'Date')

         // Just totals
+        var empty = true
         table.addColumn('number', Gabra.i18n.updates)
         for (var day in data) {
+          if (data[day]['total'] > 0) empty = false
           table.addRow([new Date(day), data[day]['total']])
         }
+        if (empty) return

         var options = {
           legend: {

<|original_code|>
/* global google Gabra $ */
// $(document).ready(function () {
  google.load('visualization', '1.0', {'packages': ['corechart']})
  google.setOnLoadCallback(function () {
    $.ajax({
      method: 'GET',
      url: Gabra.api_url + 'logs/chart',
      success: function (data) {
        var table = new google.visualization.DataTable()
        table.addColumn('date', 'Date')

        // Just totals
        table.addColumn('number', Gabra.i18n.updates)
        for (var day in data) {
          table.addRow([new Date(day), data[day]['total']])
        }

        var options = {
          legend: {
            position: 'none'
          },
          hAxis: {
            textPosition: 'none',
            gridlines: {
              count: 0
            }
          },
          vAxis: {
            textPosition: 'none',
            gridlines: {
              count: 4,
              color: '#eeeeee'
            }
          },
          height: 100,
          chartArea: {
            width: '100%',
            height: '90%'
          }
        }
        var chart = new google.visualization.AreaChart(document.getElementById('log-chart'))
        chart.draw(table, options)
      },
      complete: function () {
      }
    })
  })
// })

<|edits_diff|>
--- app/webroot/js/log-chart.js
+++ app/webroot/js/log-chart.js
@@ -10,8 +10,10 @@
         table.addColumn('date', 'Date')

         // Just totals
+        var empty = true
         table.addColumn('number', Gabra.i18n.updates)
         for (var day in data) {
+          if (data[day]['total'] > 0) empty = false
           table.addRow([new Date(day), data[day]['total']])
         }

<|current_version|>
/* global google Gabra $ */
// $(document).ready(function () {
  google.load('visualization', '1.0', {'packages': ['corechart']})
  google.setOnLoadCallback(function () {
    $.ajax({
      method: 'GET',
      url: Gabra.api_url + 'logs/chart',
      success: function (data) {
        var table = new google.visualization.DataTable()
        table.addColumn('date', 'Date')

        // Just totals
        var empty = true
        table.addColumn('number', Gabra.i18n.updates)
        for (var day in data) {
          if (data[day]['total'] > 0) empty = false
          table.addRow([new Date(day), data[day]['total']])
        }

        var options = {
          legend: {
            position: 'none'
          },
          hAxis: {
            textPosition: 'none',
            gridlines: {
              count: 0
            }
          },
          vAxis: {
            textPosition: 'none',
            gridlines: {
              count: 4,
              color: '#eeeeee'
            }
          },
          height: 100,
          chartArea: {
            width: '100%',
            height: '90%'
          }
        }
        var chart = new google.visualization.AreaChart(document.getElementById('log-chart'))
        chart.draw(table, options)
      },
      complete: function () {
      }
    })
  })
// })

<|next_version|>
/* global google Gabra $ */
// $(document).ready(function () {
  google.load('visualization', '1.0', {'packages': ['corechart']})
  google.setOnLoadCallback(function () {
    $.ajax({
      method: 'GET',
      url: Gabra.api_url + 'logs/chart',
      success: function (data) {
        var table = new google.visualization.DataTable()
        table.addColumn('date', 'Date')

        // Just totals
        var empty = true
        table.addColumn('number', Gabra.i18n.updates)
        for (var day in data) {
          if (data[day]['total'] > 0) empty = false
          table.addRow([new Date(day), data[day]['total']])
        }
        if (empty) return

        var options = {
          legend: {
            position: 'none'
          },
          hAxis: {
            textPosition: 'none',
            gridlines: {
              count: 0
            }
          },
          vAxis: {
            textPosition: 'none',
            gridlines: {
              count: 4,
              color: '#eeeeee'
            }
          },
          height: 100,
          chartArea: {
            width: '100%',
            height: '90%'
          }
        }
        var chart = new google.visualization.AreaChart(document.getElementById('log-chart'))
        chart.draw(table, options)
      },
      complete: function () {
      }
    })
  })
// })


Add fallback for unknown commands and help on storybook

--- lib/cli/bin/generate.js
+++ lib/cli/bin/generate.js
@@ -12,4 +12,13 @@
   .option('-h --html', 'Add storybook for HTML')
   .action(options => initiate(options, pkg));

+program.command('*', { noHelp: true }).action(cmd => {
+  console.error('Invalid command: %s.\nSee --help for a list of available commands.', cmd);
+  process.exit(1);
+});
+
 program.version(pkg.version).parse(process.argv);
+
+if (!program.args.length) {
+  program.help();
+}

<|original_code|>
import program from 'commander';
import pkg from '../package.json';
import initiate from '../lib/initiate';

program
  .command('init')
  .description('Initialize Storybook into your project.')
  .option('-f --force', 'Forcely add storybook')
  .option('-s --skip-install', 'Skip installing deps')
  .option('-N --use-npm', 'Use npm to install deps')
  .option('-p --parser <babel | babylon | flow>', 'jscodeshift parser')
  .option('-h --html', 'Add storybook for HTML')
  .action(options => initiate(options, pkg));

program.version(pkg.version).parse(process.argv);

<|edits_diff|>
--- lib/cli/bin/generate.js
+++ lib/cli/bin/generate.js
@@ -12,4 +12,9 @@
   .option('-h --html', 'Add storybook for HTML')
   .action(options => initiate(options, pkg));

+program.command('*', { noHelp: true }).action(cmd => {
+  console.error('Invalid command: %s.\nSee --help for a list of available commands.', cmd);
+  process.exit(1);
+});
+
 program.version(pkg.version).parse(process.argv);
<|current_version|>
import program from 'commander';
import pkg from '../package.json';
import initiate from '../lib/initiate';

program
  .command('init')
  .description('Initialize Storybook into your project.')
  .option('-f --force', 'Forcely add storybook')
  .option('-s --skip-install', 'Skip installing deps')
  .option('-N --use-npm', 'Use npm to install deps')
  .option('-p --parser <babel | babylon | flow>', 'jscodeshift parser')
  .option('-h --html', 'Add storybook for HTML')
  .action(options => initiate(options, pkg));

program.command('*', { noHelp: true }).action(cmd => {
  console.error('Invalid command: %s.\nSee --help for a list of available commands.', cmd);
  process.exit(1);
});

program.version(pkg.version).parse(process.argv);

<|next_version|>
import program from 'commander';
import pkg from '../package.json';
import initiate from '../lib/initiate';

program
  .command('init')
  .description('Initialize Storybook into your project.')
  .option('-f --force', 'Forcely add storybook')
  .option('-s --skip-install', 'Skip installing deps')
  .option('-N --use-npm', 'Use npm to install deps')
  .option('-p --parser <babel | babylon | flow>', 'jscodeshift parser')
  .option('-h --html', 'Add storybook for HTML')
  .action(options => initiate(options, pkg));

program.command('*', { noHelp: true }).action(cmd => {
  console.error('Invalid command: %s.\nSee --help for a list of available commands.', cmd);
  process.exit(1);
});

program.version(pkg.version).parse(process.argv);

if (!program.args.length) {
  program.help();
}


Add a simple assertion for the existence of Backbone

--- test/tests.js
+++ test/tests.js
@@ -11,9 +11,15 @@

 	var fixture;

+	var Adaptor = Ractive.adaptors.Backbone;
+
 	test( 'Ractive.adaptors.Backbone exists and is an object', function ( t ) {
 		t.ok( typeof Ractive.adaptors.Backbone === 'object' );
 	});

+	test( 'Backbone exists', function ( t ) {
+		t.equal( typeof Backbone, 'object' );
+		t.equal( typeof Backbone.Model, 'function' );
+	});

 }());

<|original_code|>
// Ractive-Backbone tests
// ======================
//
// This loads in the render.json sample file and checks that each compiled
// template, in combination with the sample data, produces the expected
// HTML.
//
// TODO: add moar samples

(function () {

	var fixture;

	test( 'Ractive.adaptors.Backbone exists and is an object', function ( t ) {
		t.ok( typeof Ractive.adaptors.Backbone === 'object' );
	});

}());
<|edits_diff|>
--- test/tests.js
+++ test/tests.js
@@ -11,6 +11,8 @@

 	var fixture;

+	var Adaptor = Ractive.adaptors.Backbone;
+
 	test( 'Ractive.adaptors.Backbone exists and is an object', function ( t ) {
 		t.ok( typeof Ractive.adaptors.Backbone === 'object' );
 	});
<|current_version|>
// Ractive-Backbone tests
// ======================
//
// This loads in the render.json sample file and checks that each compiled
// template, in combination with the sample data, produces the expected
// HTML.
//
// TODO: add moar samples

(function () {

	var fixture;

	var Adaptor = Ractive.adaptors.Backbone;

	test( 'Ractive.adaptors.Backbone exists and is an object', function ( t ) {
		t.ok( typeof Ractive.adaptors.Backbone === 'object' );
	});

}());

<|next_version|>
// Ractive-Backbone tests
// ======================
//
// This loads in the render.json sample file and checks that each compiled
// template, in combination with the sample data, produces the expected
// HTML.
//
// TODO: add moar samples

(function () {

	var fixture;

	var Adaptor = Ractive.adaptors.Backbone;

	test( 'Ractive.adaptors.Backbone exists and is an object', function ( t ) {
		t.ok( typeof Ractive.adaptors.Backbone === 'object' );
	});

	test( 'Backbone exists', function ( t ) {
		t.equal( typeof Backbone, 'object' );
		t.equal( typeof Backbone.Model, 'function' );
	});

}());


Revert "Revert "fix: Adds missing qunit-dom setup invocation""

This reverts commit 892641416b64d66dff488ef1eb23331c76845c6a.

--- blueprints/app/files/tests/test-helper.js
+++ blueprints/app/files/tests/test-helper.js
@@ -1,8 +1,12 @@
 import Application from '<%= modulePrefix %>/app';
 import config from '<%= modulePrefix %>/config/environment';
+import * as QUnit from 'qunit';
 import { setApplication } from '@ember/test-helpers';
+import { setup } from 'qunit-dom';
 import { start } from 'ember-qunit';

 setApplication(Application.create(config.APP));

+setup(QUnit.assert);
+
 start();

<|original_code|>
import Application from '<%= modulePrefix %>/app';
import config from '<%= modulePrefix %>/config/environment';
import { setApplication } from '@ember/test-helpers';
import { start } from 'ember-qunit';

setApplication(Application.create(config.APP));

start();

<|edits_diff|>
--- blueprints/app/files/tests/test-helper.js
+++ blueprints/app/files/tests/test-helper.js
@@ -1,6 +1,8 @@
 import Application from '<%= modulePrefix %>/app';
 import config from '<%= modulePrefix %>/config/environment';
+import * as QUnit from 'qunit';
 import { setApplication } from '@ember/test-helpers';
+import { setup } from 'qunit-dom';
 import { start } from 'ember-qunit';

 setApplication(Application.create(config.APP));
<|current_version|>
import Application from '<%= modulePrefix %>/app';
import config from '<%= modulePrefix %>/config/environment';
import * as QUnit from 'qunit';
import { setApplication } from '@ember/test-helpers';
import { setup } from 'qunit-dom';
import { start } from 'ember-qunit';

setApplication(Application.create(config.APP));

start();

<|next_version|>
import Application from '<%= modulePrefix %>/app';
import config from '<%= modulePrefix %>/config/environment';
import * as QUnit from 'qunit';
import { setApplication } from '@ember/test-helpers';
import { setup } from 'qunit-dom';
import { start } from 'ember-qunit';

setApplication(Application.create(config.APP));

setup(QUnit.assert);

start();


Make rules and resolvedRules for new category

This adds in the appropriate data structure to the
category model.

--- src/scripts/directives/grammarCMS/category.edit.js
+++ src/scripts/directives/grammarCMS/category.edit.js
@@ -33,8 +33,14 @@
   $scope.saveRule = function(category, rule) {
     var d = $q.defer();
     rs.saveRule(rule).then(function(ruleId) {
+      if (!category.rules) {
+        category.rules = {};
+      }
       category.rules[ruleId] = true;
       cs.updateCategory(category).then(function() {
+        if (!category.resolvedRules) {
+          category.resolvedRules = [];
+        }
         category.resolvedRules.push(rule);
         $scope.category = category;
         d.resolve();

<|original_code|>
function panel() {
  return {
    restrict: 'E',
    scope: {
      category: '='
    },
    templateUrl: 'category.edit.html',
    controller: 'CategoryCtrl'
  };
}

module.exports.panel = panel;

function controller($scope, cs, rs, $q) {
  $scope.deleteCategory = function(category) {
    return cs.deleteCategory(category).then(function() {
      console.log('deleting category ', category);
    });
  };

  $scope.editCategory = function(category) {
    console.log(category);
  };

  $scope.newRule = function(category) {
    $scope.showNewRuleModal = true;
  };

  $scope.showDeleteCategory = function() {
    $scope.showDeleteCategoryModal = true;
  };

  $scope.saveRule = function(category, rule) {
    var d = $q.defer();
    rs.saveRule(rule).then(function(ruleId) {
      category.rules[ruleId] = true;
      cs.updateCategory(category).then(function() {
        category.resolvedRules.push(rule);
        $scope.category = category;
        d.resolve();
      } ,d.reject);
    }, d.reject);
    return d.promise;
  };
}

module.exports.controller = ['$scope', 'CategoryService', 'RuleService', '$q', controller];

<|edits_diff|>
--- src/scripts/directives/grammarCMS/category.edit.js
+++ src/scripts/directives/grammarCMS/category.edit.js
@@ -33,6 +33,9 @@
   $scope.saveRule = function(category, rule) {
     var d = $q.defer();
     rs.saveRule(rule).then(function(ruleId) {
+      if (!category.rules) {
+        category.rules = {};
+      }
       category.rules[ruleId] = true;
       cs.updateCategory(category).then(function() {
         category.resolvedRules.push(rule);
<|current_version|>
function panel() {
  return {
    restrict: 'E',
    scope: {
      category: '='
    },
    templateUrl: 'category.edit.html',
    controller: 'CategoryCtrl'
  };
}

module.exports.panel = panel;

function controller($scope, cs, rs, $q) {
  $scope.deleteCategory = function(category) {
    return cs.deleteCategory(category).then(function() {
      console.log('deleting category ', category);
    });
  };

  $scope.editCategory = function(category) {
    console.log(category);
  };

  $scope.newRule = function(category) {
    $scope.showNewRuleModal = true;
  };

  $scope.showDeleteCategory = function() {
    $scope.showDeleteCategoryModal = true;
  };

  $scope.saveRule = function(category, rule) {
    var d = $q.defer();
    rs.saveRule(rule).then(function(ruleId) {
      if (!category.rules) {
        category.rules = {};
      }
      category.rules[ruleId] = true;
      cs.updateCategory(category).then(function() {
        category.resolvedRules.push(rule);
        $scope.category = category;
        d.resolve();
      } ,d.reject);
    }, d.reject);
    return d.promise;
  };
}

module.exports.controller = ['$scope', 'CategoryService', 'RuleService', '$q', controller];

<|next_version|>
function panel() {
  return {
    restrict: 'E',
    scope: {
      category: '='
    },
    templateUrl: 'category.edit.html',
    controller: 'CategoryCtrl'
  };
}

module.exports.panel = panel;

function controller($scope, cs, rs, $q) {
  $scope.deleteCategory = function(category) {
    return cs.deleteCategory(category).then(function() {
      console.log('deleting category ', category);
    });
  };

  $scope.editCategory = function(category) {
    console.log(category);
  };

  $scope.newRule = function(category) {
    $scope.showNewRuleModal = true;
  };

  $scope.showDeleteCategory = function() {
    $scope.showDeleteCategoryModal = true;
  };

  $scope.saveRule = function(category, rule) {
    var d = $q.defer();
    rs.saveRule(rule).then(function(ruleId) {
      if (!category.rules) {
        category.rules = {};
      }
      category.rules[ruleId] = true;
      cs.updateCategory(category).then(function() {
        if (!category.resolvedRules) {
          category.resolvedRules = [];
        }
        category.resolvedRules.push(rule);
        $scope.category = category;
        d.resolve();
      } ,d.reject);
    }, d.reject);
    return d.promise;
  };
}

module.exports.controller = ['$scope', 'CategoryService', 'RuleService', '$q', controller];


Fix types and constants not being exported after splitting up module

--- src/store/modules/sms/index.js
+++ src/store/modules/sms/index.js
@@ -2,6 +2,7 @@
 import getters from './getters';
 import mutations from './mutations';
 import actions from './actions';
+import types from './mutation_types';

 export default {
   namespaced: true,
@@ -10,3 +11,6 @@
   mutations,
   actions,
 };
+
+export * from './constants';
+export { types };

<|original_code|>
import state from './state';
import getters from './getters';
import mutations from './mutations';
import actions from './actions';

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
};

<|edits_diff|>
--- src/store/modules/sms/index.js
+++ src/store/modules/sms/index.js
@@ -2,6 +2,7 @@
 import getters from './getters';
 import mutations from './mutations';
 import actions from './actions';
+import types from './mutation_types';

 export default {
   namespaced: true,
<|current_version|>
import state from './state';
import getters from './getters';
import mutations from './mutations';
import actions from './actions';
import types from './mutation_types';

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
};

<|next_version|>
import state from './state';
import getters from './getters';
import mutations from './mutations';
import actions from './actions';
import types from './mutation_types';

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
};

export * from './constants';
export { types };


Add runtime metamodel to Callable

Add runtime metamodel to Callable

--- runtime-js/callable.js
+++ runtime-js/callable.js
@@ -6,6 +6,7 @@
 function Callable(wat) {
     return wat;
 }
+Callable.$$metamodel$$={$nm:'Callable',$tp:{Return:{'var':'out'}, Arguments:{'var':'in'}}};
 initType(Callable, 'ceylon.language::Callable');
 exports.Callable=Callable;

@@ -25,6 +26,7 @@
 	return (o !== null) ? function() { return f.apply(o, arguments); }
 	                    : noop;
 }
+JsCallable.$$metamodel$$={$nm:'Callable',$tp:{Return:{'var':'out'}, Arguments:{'var':'in'}}};

 //This is used for spread method references
 function JsCallableList(value) {
@@ -37,6 +39,7 @@
         return ArraySequence(rval);
     };
 }
+JsCallableList.$$metamodel$$={$nm:'Callable',$tp:{Return:{'var':'out'}, Arguments:{'var':'in'}}};

 exports.JsCallableList=JsCallableList;
 exports.JsCallable=JsCallable;

<|original_code|>
function initType(a,b,c,d,e,f){}//IGNORE
function String$(x){}//IGNORE
function ArraySequence(x){}//IGNORE
var exports;//IGNORE

function Callable(wat) {
    return wat;
}
initType(Callable, 'ceylon.language::Callable');
exports.Callable=Callable;

function $JsCallable(callable,targs) {
    callable.getT$all=Callable.getT$all;
    if (targs !== undefined) {
        callable.$$targs$$=targs;
    }
    return callable;
}
initExistingTypeProto($JsCallable, Function, 'ceylon.language::JsCallable', Callable);

function noop() { return null; }

//This is used for plain method references
function JsCallable(o,f) {
	return (o !== null) ? function() { return f.apply(o, arguments); }
	                    : noop;
}

//This is used for spread method references
function JsCallableList(value) {
    return function() {
        var rval = Array(value.length);
        for (var i = 0; i < value.length; i++) {
            var c = value[i];
            rval[i] = c.f.apply(c.o, arguments);
        }
        return ArraySequence(rval);
    };
}

exports.JsCallableList=JsCallableList;
exports.JsCallable=JsCallable;
exports.$JsCallable=$JsCallable;

<|edits_diff|>
--- runtime-js/callable.js
+++ runtime-js/callable.js
@@ -6,6 +6,7 @@
 function Callable(wat) {
     return wat;
 }
+Callable.$$metamodel$$={$nm:'Callable',$tp:{Return:{'var':'out'}, Arguments:{'var':'in'}}};
 initType(Callable, 'ceylon.language::Callable');
 exports.Callable=Callable;

@@ -25,6 +26,7 @@
 	return (o !== null) ? function() { return f.apply(o, arguments); }
 	                    : noop;
 }
+JsCallable.$$metamodel$$={$nm:'Callable',$tp:{Return:{'var':'out'}, Arguments:{'var':'in'}}};

 //This is used for spread method references
 function JsCallableList(value) {
<|current_version|>
function initType(a,b,c,d,e,f){}//IGNORE
function String$(x){}//IGNORE
function ArraySequence(x){}//IGNORE
var exports;//IGNORE

function Callable(wat) {
    return wat;
}
Callable.$$metamodel$$={$nm:'Callable',$tp:{Return:{'var':'out'}, Arguments:{'var':'in'}}};
initType(Callable, 'ceylon.language::Callable');
exports.Callable=Callable;

function $JsCallable(callable,targs) {
    callable.getT$all=Callable.getT$all;
    if (targs !== undefined) {
        callable.$$targs$$=targs;
    }
    return callable;
}
initExistingTypeProto($JsCallable, Function, 'ceylon.language::JsCallable', Callable);

function noop() { return null; }

//This is used for plain method references
function JsCallable(o,f) {
	return (o !== null) ? function() { return f.apply(o, arguments); }
	                    : noop;
}
JsCallable.$$metamodel$$={$nm:'Callable',$tp:{Return:{'var':'out'}, Arguments:{'var':'in'}}};

//This is used for spread method references
function JsCallableList(value) {
    return function() {
        var rval = Array(value.length);
        for (var i = 0; i < value.length; i++) {
            var c = value[i];
            rval[i] = c.f.apply(c.o, arguments);
        }
        return ArraySequence(rval);
    };
}

exports.JsCallableList=JsCallableList;
exports.JsCallable=JsCallable;
exports.$JsCallable=$JsCallable;

<|next_version|>
function initType(a,b,c,d,e,f){}//IGNORE
function String$(x){}//IGNORE
function ArraySequence(x){}//IGNORE
var exports;//IGNORE

function Callable(wat) {
    return wat;
}
Callable.$$metamodel$$={$nm:'Callable',$tp:{Return:{'var':'out'}, Arguments:{'var':'in'}}};
initType(Callable, 'ceylon.language::Callable');
exports.Callable=Callable;

function $JsCallable(callable,targs) {
    callable.getT$all=Callable.getT$all;
    if (targs !== undefined) {
        callable.$$targs$$=targs;
    }
    return callable;
}
initExistingTypeProto($JsCallable, Function, 'ceylon.language::JsCallable', Callable);

function noop() { return null; }

//This is used for plain method references
function JsCallable(o,f) {
	return (o !== null) ? function() { return f.apply(o, arguments); }
	                    : noop;
}
JsCallable.$$metamodel$$={$nm:'Callable',$tp:{Return:{'var':'out'}, Arguments:{'var':'in'}}};

//This is used for spread method references
function JsCallableList(value) {
    return function() {
        var rval = Array(value.length);
        for (var i = 0; i < value.length; i++) {
            var c = value[i];
            rval[i] = c.f.apply(c.o, arguments);
        }
        return ArraySequence(rval);
    };
}
JsCallableList.$$metamodel$$={$nm:'Callable',$tp:{Return:{'var':'out'}, Arguments:{'var':'in'}}};

exports.JsCallableList=JsCallableList;
exports.JsCallable=JsCallable;
exports.$JsCallable=$JsCallable;


Set status button in serial no

Set status button in serial no

--- erpnext/stock/doctype/serial_no/serial_no.js
+++ erpnext/stock/doctype/serial_no/serial_no.js
@@ -17,4 +17,12 @@

 frappe.ui.form.on("Serial No", "refresh", function(frm) {
 	frm.toggle_enable("item_code", frm.doc.__islocal);
+
+	if(frm.doc.status == "Sales Returned" && frm.doc.warehouse)
+		cur_frm.add_custom_button(__('Set Status as Available'), cur_frm.cscript.set_status_as_available);
 });
+
+cur_frm.cscript.set_status_as_available = function() {
+	cur_frm.set_value("status", "Available");
+	cur_frm.save()
+}

<|original_code|>
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt

cur_frm.add_fetch("customer", "customer_name", "customer_name")
cur_frm.add_fetch("supplier", "supplier_name", "supplier_name")

cur_frm.add_fetch("item_code", "item_name", "item_name")
cur_frm.add_fetch("item_code", "description", "description")
cur_frm.add_fetch("item_code", "item_group", "item_group")
cur_frm.add_fetch("item_code", "brand", "brand")

cur_frm.cscript.onload = function() {
	cur_frm.set_query("item_code", function() {
		return erpnext.queries.item({"is_stock_item": "Yes", "has_serial_no": "Yes"})
	});
};

frappe.ui.form.on("Serial No", "refresh", function(frm) {
	frm.toggle_enable("item_code", frm.doc.__islocal);
});

<|edits_diff|>
--- erpnext/stock/doctype/serial_no/serial_no.js
+++ erpnext/stock/doctype/serial_no/serial_no.js
@@ -17,4 +17,7 @@

 frappe.ui.form.on("Serial No", "refresh", function(frm) {
 	frm.toggle_enable("item_code", frm.doc.__islocal);
+
+	if(frm.doc.status == "Sales Returned" && frm.doc.warehouse)
+		cur_frm.add_custom_button(__('Set Status as Available'), cur_frm.cscript.set_status_as_available);
 });
<|current_version|>
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt

cur_frm.add_fetch("customer", "customer_name", "customer_name")
cur_frm.add_fetch("supplier", "supplier_name", "supplier_name")

cur_frm.add_fetch("item_code", "item_name", "item_name")
cur_frm.add_fetch("item_code", "description", "description")
cur_frm.add_fetch("item_code", "item_group", "item_group")
cur_frm.add_fetch("item_code", "brand", "brand")

cur_frm.cscript.onload = function() {
	cur_frm.set_query("item_code", function() {
		return erpnext.queries.item({"is_stock_item": "Yes", "has_serial_no": "Yes"})
	});
};

frappe.ui.form.on("Serial No", "refresh", function(frm) {
	frm.toggle_enable("item_code", frm.doc.__islocal);

	if(frm.doc.status == "Sales Returned" && frm.doc.warehouse)
		cur_frm.add_custom_button(__('Set Status as Available'), cur_frm.cscript.set_status_as_available);
});

<|next_version|>
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt

cur_frm.add_fetch("customer", "customer_name", "customer_name")
cur_frm.add_fetch("supplier", "supplier_name", "supplier_name")

cur_frm.add_fetch("item_code", "item_name", "item_name")
cur_frm.add_fetch("item_code", "description", "description")
cur_frm.add_fetch("item_code", "item_group", "item_group")
cur_frm.add_fetch("item_code", "brand", "brand")

cur_frm.cscript.onload = function() {
	cur_frm.set_query("item_code", function() {
		return erpnext.queries.item({"is_stock_item": "Yes", "has_serial_no": "Yes"})
	});
};

frappe.ui.form.on("Serial No", "refresh", function(frm) {
	frm.toggle_enable("item_code", frm.doc.__islocal);

	if(frm.doc.status == "Sales Returned" && frm.doc.warehouse)
		cur_frm.add_custom_button(__('Set Status as Available'), cur_frm.cscript.set_status_as_available);
});

cur_frm.cscript.set_status_as_available = function() {
	cur_frm.set_value("status", "Available");
	cur_frm.save()
}


Disable days before today for selection

Disable days before today for selection

--- src/components/MeetingDaysField/MeetingDaysField.js
+++ src/components/MeetingDaysField/MeetingDaysField.js
@@ -1,5 +1,6 @@
 import React from "react";
 import DayPicker, {DateUtils} from "react-day-picker";
+import moment from 'moment';
 import "./MeetingDaysField.scss";

 class MeetingDaysField extends React.Component {
@@ -23,6 +24,10 @@
     return this.props.days.findIndex((d) => DateUtils.isSameDay(d, day)) > -1;
   }

+  isDayBeforeToday(day) {
+    return moment(day).add(1, 'day').isBefore();
+  }
+
   render(){
     let { month, onMonthChange } = this.props;
     return (
@@ -30,6 +35,7 @@
         Wybierz dni

           <DayPicker initialMonth={month} firstDayOfWeek={1} selectedDays={this.isDaySelected.bind(this)}
+                     disabledDays={this.isDayBeforeToday.bind(this)} fromMonth={new Date()}
                      onDayClick={this.handleDayClick.bind(this)} onMonthChange={onMonthChange} />

<|original_code|>
import React from "react";
import DayPicker, {DateUtils} from "react-day-picker";
import "./MeetingDaysField.scss";

class MeetingDaysField extends React.Component {
  static propTypes = {
    days: React.PropTypes.array.isRequired,
    month: React.PropTypes.object.isRequired,
    onDayChange: React.PropTypes.func.isRequired,
    onMonthChange: React.PropTypes.func.isRequired
  };

  handleDayClick(e, day, {selected, disabled}){
    // We need to pass it upwards
    if(disabled){
      return;
    }

    this.props.onDayChange(selected, day);
  }

  isDaySelected(day){
    return this.props.days.findIndex((d) => DateUtils.isSameDay(d, day)) > -1;
  }

  render(){
    let { month, onMonthChange } = this.props;
    return (

        Wybierz dni

          <DayPicker initialMonth={month} firstDayOfWeek={1} selectedDays={this.isDaySelected.bind(this)}
                     onDayClick={this.handleDayClick.bind(this)} onMonthChange={onMonthChange} />

          {this.props.children}

    );
  }
}
export default MeetingDaysField;

<|edits_diff|>
--- src/components/MeetingDaysField/MeetingDaysField.js
+++ src/components/MeetingDaysField/MeetingDaysField.js
@@ -1,5 +1,6 @@
 import React from "react";
 import DayPicker, {DateUtils} from "react-day-picker";
+import moment from 'moment';
 import "./MeetingDaysField.scss";

 class MeetingDaysField extends React.Component {
@@ -23,6 +24,10 @@
     return this.props.days.findIndex((d) => DateUtils.isSameDay(d, day)) > -1;
   }

+  isDayBeforeToday(day) {
+    return moment(day).add(1, 'day').isBefore();
+  }
+
   render(){
     let { month, onMonthChange } = this.props;
     return (
<|current_version|>
import React from "react";
import DayPicker, {DateUtils} from "react-day-picker";
import moment from 'moment';
import "./MeetingDaysField.scss";

class MeetingDaysField extends React.Component {
  static propTypes = {
    days: React.PropTypes.array.isRequired,
    month: React.PropTypes.object.isRequired,
    onDayChange: React.PropTypes.func.isRequired,
    onMonthChange: React.PropTypes.func.isRequired
  };

  handleDayClick(e, day, {selected, disabled}){
    // We need to pass it upwards
    if(disabled){
      return;
    }

    this.props.onDayChange(selected, day);
  }

  isDaySelected(day){
    return this.props.days.findIndex((d) => DateUtils.isSameDay(d, day)) > -1;
  }

  isDayBeforeToday(day) {
    return moment(day).add(1, 'day').isBefore();
  }

  render(){
    let { month, onMonthChange } = this.props;
    return (

        Wybierz dni

          <DayPicker initialMonth={month} firstDayOfWeek={1} selectedDays={this.isDaySelected.bind(this)}
                     onDayClick={this.handleDayClick.bind(this)} onMonthChange={onMonthChange} />

          {this.props.children}

    );
  }
}
export default MeetingDaysField;

<|next_version|>
import React from "react";
import DayPicker, {DateUtils} from "react-day-picker";
import moment from 'moment';
import "./MeetingDaysField.scss";

class MeetingDaysField extends React.Component {
  static propTypes = {
    days: React.PropTypes.array.isRequired,
    month: React.PropTypes.object.isRequired,
    onDayChange: React.PropTypes.func.isRequired,
    onMonthChange: React.PropTypes.func.isRequired
  };

  handleDayClick(e, day, {selected, disabled}){
    // We need to pass it upwards
    if(disabled){
      return;
    }

    this.props.onDayChange(selected, day);
  }

  isDaySelected(day){
    return this.props.days.findIndex((d) => DateUtils.isSameDay(d, day)) > -1;
  }

  isDayBeforeToday(day) {
    return moment(day).add(1, 'day').isBefore();
  }

  render(){
    let { month, onMonthChange } = this.props;
    return (

        Wybierz dni

          <DayPicker initialMonth={month} firstDayOfWeek={1} selectedDays={this.isDaySelected.bind(this)}
                     disabledDays={this.isDayBeforeToday.bind(this)} fromMonth={new Date()}
                     onDayClick={this.handleDayClick.bind(this)} onMonthChange={onMonthChange} />

          {this.props.children}

    );
  }
}
export default MeetingDaysField;


Add ref for file upload dropZone

Add ref for file upload dropZone

--- src/components/file-upload/index.js
+++ src/components/file-upload/index.js
@@ -6,6 +6,7 @@
   constructor(props) {
     super(props);

+    this.dropzoneRef = null;
     this.state = {
       isFileSelected: false,
       file: false,
@@ -33,6 +34,7 @@
             class={style['file']}
             className={style['file']}
             accept=".stl"
+            ref={(node) => { this.dropzoneRef = node; }}
           >

Přidejte model ve formátu STL přetáhnutím souboru nebo kliknutím

           </Dropzone>

<|original_code|>
import { h, Component } from 'preact';
import Dropzone from 'react-dropzone';
import style from './style';

export default class FileUpload extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isFileSelected: false,
      file: false,
    };

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

  handleFileDrop(acceptedFiles, rejectedFiles) {
    this.setState({
      ...this.state,
      isFileSelected: true,
      file: acceptedFiles[0],
    });
    this.props.confirmChooseFile(this.state.file);
  }

  render(props, state) {
    return (
      <div class={`${style['file-upload']} container`}>

          <Dropzone
            onDrop={this.handleFileDrop}
            multiple={false}
            class={style['file']}
            className={style['file']}
            accept=".stl"
          >

Přidejte model ve formátu STL přetáhnutím souboru nebo kliknutím

          </Dropzone>

        {/*<div class={`one column offset-by-two ${style['button']}`}>*/}
        {/*<button class="button-primary disabled" disabled={!state.isFileSelected} onClick={this.handleAnalyzeButtonClick} type="button">Go to details</button>*/}
        {/*
*/}

    );
  }
}

<|edits_diff|>
--- src/components/file-upload/index.js
+++ src/components/file-upload/index.js
@@ -6,6 +6,7 @@
   constructor(props) {
     super(props);

+    this.dropzoneRef = null;
     this.state = {
       isFileSelected: false,
       file: false,
<|current_version|>
import { h, Component } from 'preact';
import Dropzone from 'react-dropzone';
import style from './style';

export default class FileUpload extends Component {
  constructor(props) {
    super(props);

    this.dropzoneRef = null;
    this.state = {
      isFileSelected: false,
      file: false,
    };

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

  handleFileDrop(acceptedFiles, rejectedFiles) {
    this.setState({
      ...this.state,
      isFileSelected: true,
      file: acceptedFiles[0],
    });
    this.props.confirmChooseFile(this.state.file);
  }

  render(props, state) {
    return (
      <div class={`${style['file-upload']} container`}>

          <Dropzone
            onDrop={this.handleFileDrop}
            multiple={false}
            class={style['file']}
            className={style['file']}
            accept=".stl"
          >

Přidejte model ve formátu STL přetáhnutím souboru nebo kliknutím

          </Dropzone>

        {/*<div class={`one column offset-by-two ${style['button']}`}>*/}
        {/*<button class="button-primary disabled" disabled={!state.isFileSelected} onClick={this.handleAnalyzeButtonClick} type="button">Go to details</button>*/}
        {/*
*/}

    );
  }
}

<|next_version|>
import { h, Component } from 'preact';
import Dropzone from 'react-dropzone';
import style from './style';

export default class FileUpload extends Component {
  constructor(props) {
    super(props);

    this.dropzoneRef = null;
    this.state = {
      isFileSelected: false,
      file: false,
    };

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

  handleFileDrop(acceptedFiles, rejectedFiles) {
    this.setState({
      ...this.state,
      isFileSelected: true,
      file: acceptedFiles[0],
    });
    this.props.confirmChooseFile(this.state.file);
  }

  render(props, state) {
    return (
      <div class={`${style['file-upload']} container`}>

          <Dropzone
            onDrop={this.handleFileDrop}
            multiple={false}
            class={style['file']}
            className={style['file']}
            accept=".stl"
            ref={(node) => { this.dropzoneRef = node; }}
          >

Přidejte model ve formátu STL přetáhnutím souboru nebo kliknutím

          </Dropzone>

        {/*<div class={`one column offset-by-two ${style['button']}`}>*/}
        {/*<button class="button-primary disabled" disabled={!state.isFileSelected} onClick={this.handleAnalyzeButtonClick} type="button">Go to details</button>*/}
        {/*
*/}

    );
  }
}


Implement CSV generator logic in collection view

--- public/js/collection-view.js
+++ public/js/collection-view.js
@@ -1,9 +1,11 @@
 import tabsWithCards from "./tabs-with-cards.js";
 import bookmarkButtons from "./bookmark-buttons.js";
 import lazyLoadImages from "./lazy-load-images.js";
+import csvGenerator from "./csv-generator.js";

 document.addEventListener("DOMContentLoaded", () => {
   tabsWithCards.init();
   bookmarkButtons.init();
   lazyLoadImages.init();
+  csvGenerator.init();
 });

<|original_code|>
import tabsWithCards from "./tabs-with-cards.js";
import bookmarkButtons from "./bookmark-buttons.js";
import lazyLoadImages from "./lazy-load-images.js";

document.addEventListener("DOMContentLoaded", () => {
  tabsWithCards.init();
  bookmarkButtons.init();
  lazyLoadImages.init();
});

<|edits_diff|>
--- public/js/collection-view.js
+++ public/js/collection-view.js
@@ -1,6 +1,7 @@
 import tabsWithCards from "./tabs-with-cards.js";
 import bookmarkButtons from "./bookmark-buttons.js";
 import lazyLoadImages from "./lazy-load-images.js";
+import csvGenerator from "./csv-generator.js";

 document.addEventListener("DOMContentLoaded", () => {
   tabsWithCards.init();
<|current_version|>
import tabsWithCards from "./tabs-with-cards.js";
import bookmarkButtons from "./bookmark-buttons.js";
import lazyLoadImages from "./lazy-load-images.js";
import csvGenerator from "./csv-generator.js";

document.addEventListener("DOMContentLoaded", () => {
  tabsWithCards.init();
  bookmarkButtons.init();
  lazyLoadImages.init();
});

<|next_version|>
import tabsWithCards from "./tabs-with-cards.js";
import bookmarkButtons from "./bookmark-buttons.js";
import lazyLoadImages from "./lazy-load-images.js";
import csvGenerator from "./csv-generator.js";

document.addEventListener("DOMContentLoaded", () => {
  tabsWithCards.init();
  bookmarkButtons.init();
  lazyLoadImages.init();
  csvGenerator.init();
});


Add sessions reducer to main reducer

Add sessions reducer to main reducer

--- static/src/store/mainReducer.js
+++ static/src/store/mainReducer.js
@@ -8,6 +8,7 @@
 import modal from '../containers/Modal/reducer';
 import paths from '../containers/Paths/reducer';
 import search from '../containers/Search/reducer';
+import sessions from '../containers/Sessions/reducer';
 import settings from '../containers/Settings/reducer';
 import sidebar from '../containers/Sidebar/reducer';
 import sounds from '../containers/Sounds/reducer';
@@ -24,6 +25,7 @@
   modal,
   paths,
   search,
+  sessions,
   settings,
   sidebar,
   sounds,

<|original_code|>
import { combineReducers } from 'redux';
import audio from '../containers/Audio/reducer';
import midi from '../containers/Midi/reducer';
import login from '../containers/Login/reducer';
import map from '../containers/Map/reducer';
import messagesBox from '../containers/MessagesBox/reducer';
import metronome from '../containers/Metronome/reducer';
import modal from '../containers/Modal/reducer';
import paths from '../containers/Paths/reducer';
import search from '../containers/Search/reducer';
import settings from '../containers/Settings/reducer';
import sidebar from '../containers/Sidebar/reducer';
import sounds from '../containers/Sounds/reducer';
import spaces from '../containers/Spaces/reducer';

const rootReducer = combineReducers({
  audio,
  login,
  map,
  messagesBox,
  metronome,
  midi,
  modal,
  paths,
  search,
  settings,
  sidebar,
  sounds,
  spaces,
});

export default rootReducer;

<|edits_diff|>
--- static/src/store/mainReducer.js
+++ static/src/store/mainReducer.js
@@ -8,6 +8,7 @@
 import modal from '../containers/Modal/reducer';
 import paths from '../containers/Paths/reducer';
 import search from '../containers/Search/reducer';
+import sessions from '../containers/Sessions/reducer';
 import settings from '../containers/Settings/reducer';
 import sidebar from '../containers/Sidebar/reducer';
 import sounds from '../containers/Sounds/reducer';
<|current_version|>
import { combineReducers } from 'redux';
import audio from '../containers/Audio/reducer';
import midi from '../containers/Midi/reducer';
import login from '../containers/Login/reducer';
import map from '../containers/Map/reducer';
import messagesBox from '../containers/MessagesBox/reducer';
import metronome from '../containers/Metronome/reducer';
import modal from '../containers/Modal/reducer';
import paths from '../containers/Paths/reducer';
import search from '../containers/Search/reducer';
import sessions from '../containers/Sessions/reducer';
import settings from '../containers/Settings/reducer';
import sidebar from '../containers/Sidebar/reducer';
import sounds from '../containers/Sounds/reducer';
import spaces from '../containers/Spaces/reducer';

const rootReducer = combineReducers({
  audio,
  login,
  map,
  messagesBox,
  metronome,
  midi,
  modal,
  paths,
  search,
  settings,
  sidebar,
  sounds,
  spaces,
});

export default rootReducer;

<|next_version|>
import { combineReducers } from 'redux';
import audio from '../containers/Audio/reducer';
import midi from '../containers/Midi/reducer';
import login from '../containers/Login/reducer';
import map from '../containers/Map/reducer';
import messagesBox from '../containers/MessagesBox/reducer';
import metronome from '../containers/Metronome/reducer';
import modal from '../containers/Modal/reducer';
import paths from '../containers/Paths/reducer';
import search from '../containers/Search/reducer';
import sessions from '../containers/Sessions/reducer';
import settings from '../containers/Settings/reducer';
import sidebar from '../containers/Sidebar/reducer';
import sounds from '../containers/Sounds/reducer';
import spaces from '../containers/Spaces/reducer';

const rootReducer = combineReducers({
  audio,
  login,
  map,
  messagesBox,
  metronome,
  midi,
  modal,
  paths,
  search,
  sessions,
  settings,
  sidebar,
  sounds,
  spaces,
});

export default rootReducer;


Add a back to overview button

Add a back to overview button

--- app/components/BookDetails/index.js
+++ app/components/BookDetails/index.js
@@ -1,4 +1,5 @@
 import React, { Component } from 'react';
+import { Link } from 'react-router';
 import './BookDetails.scss';

 export default class BookDetails extends Component {
@@ -6,6 +7,11 @@
     const { title, coverUrl } = this.props.book;
     return (

+        <Link to="/">
+
+          Back to Overview
+        </Link>
+

<|original_code|>
import React, { Component } from 'react';
import './BookDetails.scss';

export default class BookDetails extends Component {
  render() {
    const { title, coverUrl } = this.props.book;
    return (

Moby Dick

Herman Melville

Published by Project Gutenberg

            Nulla facilisi. Donec eros erat, molestie et dignissim in, pulvinar
            facilisis risus. Ut at nisl vitae odio malesuada ultrices vitae sit
            amet est. Cras feugiat neque sit amet elementum accumsan. Vestibulum
            pharetra a neque nec congue. Maecenas vel sollicitudin tortor, nec
            mattis mi. Praesent dui lorem, faucibus vitae purus semper, rhoncus
            tincidunt magna.

    );
  }
}

<|edits_diff|>
--- app/components/BookDetails/index.js
+++ app/components/BookDetails/index.js
@@ -1,4 +1,5 @@
 import React, { Component } from 'react';
+import { Link } from 'react-router';
 import './BookDetails.scss';

 export default class BookDetails extends Component {
<|current_version|>
import React, { Component } from 'react';
import { Link } from 'react-router';
import './BookDetails.scss';

export default class BookDetails extends Component {
  render() {
    const { title, coverUrl } = this.props.book;
    return (

Moby Dick

Herman Melville

Published by Project Gutenberg

            Nulla facilisi. Donec eros erat, molestie et dignissim in, pulvinar
            facilisis risus. Ut at nisl vitae odio malesuada ultrices vitae sit
            amet est. Cras feugiat neque sit amet elementum accumsan. Vestibulum
            pharetra a neque nec congue. Maecenas vel sollicitudin tortor, nec
            mattis mi. Praesent dui lorem, faucibus vitae purus semper, rhoncus
            tincidunt magna.

    );
  }
}

<|next_version|>
import React, { Component } from 'react';
import { Link } from 'react-router';
import './BookDetails.scss';

export default class BookDetails extends Component {
  render() {
    const { title, coverUrl } = this.props.book;
    return (

        <Link to="/">

          Back to Overview
        </Link>

Moby Dick

Herman Melville

Published by Project Gutenberg

            Nulla facilisi. Donec eros erat, molestie et dignissim in, pulvinar
            facilisis risus. Ut at nisl vitae odio malesuada ultrices vitae sit
            amet est. Cras feugiat neque sit amet elementum accumsan. Vestibulum
            pharetra a neque nec congue. Maecenas vel sollicitudin tortor, nec
            mattis mi. Praesent dui lorem, faucibus vitae purus semper, rhoncus
            tincidunt magna.

    );
  }
}


Update exports - include NumberBox

Update exports - include NumberBox

--- packages/web/src/index.js
+++ packages/web/src/index.js
@@ -2,6 +2,7 @@
 import TextField from "./components/basic/TextField";
 import SelectedFilters from "./components/basic/SelectedFilters";
 import ToggleButton from "./components/basic/ToggleButton";
+import NumberBox from "./components/basic/NumberBox";

 import SingleList from "./components/list/SingleList";
 import MultiList from "./components/list/MultiList";
@@ -16,6 +17,7 @@
 	TextField,
 	SelectedFilters,
 	ToggleButton,
+	NumberBox,

 	// list
 	SingleList,

<|original_code|>
import ReactiveBase from "./components/basic/ReactiveBase";
import TextField from "./components/basic/TextField";
import SelectedFilters from "./components/basic/SelectedFilters";
import ToggleButton from "./components/basic/ToggleButton";

import SingleList from "./components/list/SingleList";
import MultiList from "./components/list/MultiList";

import DataSearch from "./components/search/DataSearch";

import ReactiveList from "./components/result/ReactiveList";

export {
	// basic
	ReactiveBase,
	TextField,
	SelectedFilters,
	ToggleButton,

	// list
	SingleList,
	MultiList,

	// search
	DataSearch,

	// result
	ReactiveList
}

<|edits_diff|>
--- packages/web/src/index.js
+++ packages/web/src/index.js
@@ -2,6 +2,7 @@
 import TextField from "./components/basic/TextField";
 import SelectedFilters from "./components/basic/SelectedFilters";
 import ToggleButton from "./components/basic/ToggleButton";
+import NumberBox from "./components/basic/NumberBox";

 import SingleList from "./components/list/SingleList";
 import MultiList from "./components/list/MultiList";
<|current_version|>
import ReactiveBase from "./components/basic/ReactiveBase";
import TextField from "./components/basic/TextField";
import SelectedFilters from "./components/basic/SelectedFilters";
import ToggleButton from "./components/basic/ToggleButton";
import NumberBox from "./components/basic/NumberBox";

import SingleList from "./components/list/SingleList";
import MultiList from "./components/list/MultiList";

import DataSearch from "./components/search/DataSearch";

import ReactiveList from "./components/result/ReactiveList";

export {
	// basic
	ReactiveBase,
	TextField,
	SelectedFilters,
	ToggleButton,

	// list
	SingleList,
	MultiList,

	// search
	DataSearch,

	// result
	ReactiveList
}

<|next_version|>
import ReactiveBase from "./components/basic/ReactiveBase";
import TextField from "./components/basic/TextField";
import SelectedFilters from "./components/basic/SelectedFilters";
import ToggleButton from "./components/basic/ToggleButton";
import NumberBox from "./components/basic/NumberBox";

import SingleList from "./components/list/SingleList";
import MultiList from "./components/list/MultiList";

import DataSearch from "./components/search/DataSearch";

import ReactiveList from "./components/result/ReactiveList";

export {
	// basic
	ReactiveBase,
	TextField,
	SelectedFilters,
	ToggleButton,
	NumberBox,

	// list
	SingleList,
	MultiList,

	// search
	DataSearch,

	// result
	ReactiveList
}


Fix actions and types export in settings.

--- client/packages/core-app-worona/src/app/settings-app-extension-worona/index.js
+++ client/packages/core-app-worona/src/app/settings-app-extension-worona/index.js
@@ -1,8 +1,12 @@
+import * as actions from './actions';
+import * as types from './types';
 import * as reducers from './reducers';
 import * as sagas from './sagas';
 import * as selectorCreators from './selectorCreators';

 export {
+  actions,
+  types,
   reducers,
   sagas,
   selectorCreators,

<|original_code|>
import * as reducers from './reducers';
import * as sagas from './sagas';
import * as selectorCreators from './selectorCreators';

export {
  reducers,
  sagas,
  selectorCreators,
};

<|edits_diff|>
--- client/packages/core-app-worona/src/app/settings-app-extension-worona/index.js
+++ client/packages/core-app-worona/src/app/settings-app-extension-worona/index.js
@@ -1,3 +1,5 @@
+import * as actions from './actions';
+import * as types from './types';
 import * as reducers from './reducers';
 import * as sagas from './sagas';
 import * as selectorCreators from './selectorCreators';
<|current_version|>
import * as actions from './actions';
import * as types from './types';
import * as reducers from './reducers';
import * as sagas from './sagas';
import * as selectorCreators from './selectorCreators';

export {
  reducers,
  sagas,
  selectorCreators,
};

<|next_version|>
import * as actions from './actions';
import * as types from './types';
import * as reducers from './reducers';
import * as sagas from './sagas';
import * as selectorCreators from './selectorCreators';

export {
  actions,
  types,
  reducers,
  sagas,
  selectorCreators,
};


Update api status on change

Update api status on change

--- frontend/src/synonymization/InputBoxContainer.js
+++ frontend/src/synonymization/InputBoxContainer.js
@@ -2,6 +2,8 @@
 import {setOriginalText} from './synonymizationActions';
 import InputBox from "./InputBox";
 import apiConstants from "../api/apiConstants";
+import {setApiStatus} from "../api/apiActions";
+import store from "../app/store";

 const mapStateToProps = state => ({
     content: state.synonymization.original,
@@ -11,6 +13,7 @@
 const mapDispatchToProps = dispatch => ({
     onChange: text => {
         dispatch(setOriginalText(text))
+        store.dispatch(setApiStatus(apiConstants.STATUSES.NONE));
     }
 })

<|original_code|>
import {connect} from 'react-redux'
import {setOriginalText} from './synonymizationActions';
import InputBox from "./InputBox";
import apiConstants from "../api/apiConstants";

const mapStateToProps = state => ({
    content: state.synonymization.original,
    disabled: state.api.status === apiConstants.STATUSES.FETCHING
})

const mapDispatchToProps = dispatch => ({
    onChange: text => {
        dispatch(setOriginalText(text))
    }
})

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(InputBox);
<|edits_diff|>
--- frontend/src/synonymization/InputBoxContainer.js
+++ frontend/src/synonymization/InputBoxContainer.js
@@ -2,6 +2,8 @@
 import {setOriginalText} from './synonymizationActions';
 import InputBox from "./InputBox";
 import apiConstants from "../api/apiConstants";
+import {setApiStatus} from "../api/apiActions";
+import store from "../app/store";

 const mapStateToProps = state => ({
     content: state.synonymization.original,
<|current_version|>
import {connect} from 'react-redux'
import {setOriginalText} from './synonymizationActions';
import InputBox from "./InputBox";
import apiConstants from "../api/apiConstants";
import {setApiStatus} from "../api/apiActions";
import store from "../app/store";

const mapStateToProps = state => ({
    content: state.synonymization.original,
    disabled: state.api.status === apiConstants.STATUSES.FETCHING
})

const mapDispatchToProps = dispatch => ({
    onChange: text => {
        dispatch(setOriginalText(text))
    }
})

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(InputBox);
<|next_version|>
import {connect} from 'react-redux'
import {setOriginalText} from './synonymizationActions';
import InputBox from "./InputBox";
import apiConstants from "../api/apiConstants";
import {setApiStatus} from "../api/apiActions";
import store from "../app/store";

const mapStateToProps = state => ({
    content: state.synonymization.original,
    disabled: state.api.status === apiConstants.STATUSES.FETCHING
})

const mapDispatchToProps = dispatch => ({
    onChange: text => {
        dispatch(setOriginalText(text))
        store.dispatch(setApiStatus(apiConstants.STATUSES.NONE));
    }
})

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


Set a timer to detect when the mouse stops moving

--- assets/js/Mouse.js
+++ assets/js/Mouse.js
@@ -8,8 +8,10 @@
     this.dx = 0;
     this.dy = 0;
     this.events = [];
+    this.timeout = null;

     this.UpdatePosition = function(nx, ny) {
+        clearTimeout(this.timeout);
         // It's impossible to know where the mouse is when it's first created,
         // before a mousemove event is fired. If we do nothing the initial
         // dx and dy are incorrect, so we hack this to not update the dx and dy
@@ -23,6 +25,7 @@
             this.x = nx;
             this.y = ny;
         }
+        this.timeout = setTimeout(this.StoppedMoving.bind(this), 100);
     }

     this.ButtonDown = function(button) {
@@ -34,6 +37,11 @@
         }
         return true;
     }
+
+    this.StoppedMoving = function() {
+        this.dx = 0;
+        this.dy = 0;
+    }
 }

 function MouseEvent(button, action) {

<|original_code|>
buttonCodeToChar = {1: "Left", 2: "Middle", 3: "Right"};
buttonCharToCode = {"Left": 1, "Middle": 2, "Right": 3};

function Mouse() {
    // It'd be good to get this info on construction
    this.x = -1;
    this.y = -1;
    this.dx = 0;
    this.dy = 0;
    this.events = [];

    this.UpdatePosition = function(nx, ny) {
        // It's impossible to know where the mouse is when it's first created,
        // before a mousemove event is fired. If we do nothing the initial
        // dx and dy are incorrect, so we hack this to not update the dx and dy
        // till we've gotten one mousemove
        if (this.x == -1 && this.y == -1) {
            this.x = nx;
            this.y = ny;
        } else {
            this.dx = nx - this.x;
            this.dy = ny - this.y;
            this.x = nx;
            this.y = ny;
        }
    }

    this.ButtonDown = function(button) {
        if (button instanceof Number) {
            button = buttonCodeToChar(button);
        }
        if (this[button] == false || this[button] == null) {
            return false;
        }
        return true;
    }
}

function MouseEvent(button, action) {
    this.button = button;
    this.action = action;
}

<|edits_diff|>
--- assets/js/Mouse.js
+++ assets/js/Mouse.js
@@ -8,8 +8,10 @@
     this.dx = 0;
     this.dy = 0;
     this.events = [];
+    this.timeout = null;

     this.UpdatePosition = function(nx, ny) {
+        clearTimeout(this.timeout);
         // It's impossible to know where the mouse is when it's first created,
         // before a mousemove event is fired. If we do nothing the initial
         // dx and dy are incorrect, so we hack this to not update the dx and dy
@@ -23,6 +25,7 @@
             this.x = nx;
             this.y = ny;
         }
+        this.timeout = setTimeout(this.StoppedMoving.bind(this), 100);
     }

     this.ButtonDown = function(button) {
<|current_version|>
buttonCodeToChar = {1: "Left", 2: "Middle", 3: "Right"};
buttonCharToCode = {"Left": 1, "Middle": 2, "Right": 3};

function Mouse() {
    // It'd be good to get this info on construction
    this.x = -1;
    this.y = -1;
    this.dx = 0;
    this.dy = 0;
    this.events = [];
    this.timeout = null;

    this.UpdatePosition = function(nx, ny) {
        clearTimeout(this.timeout);
        // It's impossible to know where the mouse is when it's first created,
        // before a mousemove event is fired. If we do nothing the initial
        // dx and dy are incorrect, so we hack this to not update the dx and dy
        // till we've gotten one mousemove
        if (this.x == -1 && this.y == -1) {
            this.x = nx;
            this.y = ny;
        } else {
            this.dx = nx - this.x;
            this.dy = ny - this.y;
            this.x = nx;
            this.y = ny;
        }
        this.timeout = setTimeout(this.StoppedMoving.bind(this), 100);
    }

    this.ButtonDown = function(button) {
        if (button instanceof Number) {
            button = buttonCodeToChar(button);
        }
        if (this[button] == false || this[button] == null) {
            return false;
        }
        return true;
    }
}

function MouseEvent(button, action) {
    this.button = button;
    this.action = action;
}

<|next_version|>
buttonCodeToChar = {1: "Left", 2: "Middle", 3: "Right"};
buttonCharToCode = {"Left": 1, "Middle": 2, "Right": 3};

function Mouse() {
    // It'd be good to get this info on construction
    this.x = -1;
    this.y = -1;
    this.dx = 0;
    this.dy = 0;
    this.events = [];
    this.timeout = null;

    this.UpdatePosition = function(nx, ny) {
        clearTimeout(this.timeout);
        // It's impossible to know where the mouse is when it's first created,
        // before a mousemove event is fired. If we do nothing the initial
        // dx and dy are incorrect, so we hack this to not update the dx and dy
        // till we've gotten one mousemove
        if (this.x == -1 && this.y == -1) {
            this.x = nx;
            this.y = ny;
        } else {
            this.dx = nx - this.x;
            this.dy = ny - this.y;
            this.x = nx;
            this.y = ny;
        }
        this.timeout = setTimeout(this.StoppedMoving.bind(this), 100);
    }

    this.ButtonDown = function(button) {
        if (button instanceof Number) {
            button = buttonCodeToChar(button);
        }
        if (this[button] == false || this[button] == null) {
            return false;
        }
        return true;
    }

    this.StoppedMoving = function() {
        this.dx = 0;
        this.dy = 0;
    }
}

function MouseEvent(button, action) {
    this.button = button;
    this.action = action;
}


Add JS to make checkboxes work

Add JS to make checkboxes work

--- app/assets/javascripts/application.js
+++ app/assets/javascripts/application.js
@@ -17,6 +17,7 @@
 //
 //= require checkbox_filter
 //= require live_search
+//= require govuk_toolkit

 jQuery(function($) {
@@ -34,4 +35,7 @@
   if($form.length && $results.length){
     new GOVUK.LiveSearch({$form:$form, $results:$results});
   }
+
+  var $buttons = $("label.block-label input[type='checkbox']");
+  new GOVUK.SelectionButtons($buttons);
 });

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

jQuery(function($) {
  var filters = $('.js-openable-filter').map(function(){
    return new GOVUK.CheckboxFilter({el:$(this)});
  });

  if (filters.length > 0 && $('.js-openable-filter').not('.closed').length == 0) {
    filters[0].open();
  }

  var $form = $('.js-live-search-form'),
      $results = $('.js-live-search-results-block');

  if($form.length && $results.length){
    new GOVUK.LiveSearch({$form:$form, $results:$results});
  }
});

<|edits_diff|>
--- app/assets/javascripts/application.js
+++ app/assets/javascripts/application.js
@@ -17,6 +17,7 @@
 //
 //= require checkbox_filter
 //= require live_search
+//= require govuk_toolkit

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

jQuery(function($) {
  var filters = $('.js-openable-filter').map(function(){
    return new GOVUK.CheckboxFilter({el:$(this)});
  });

  if (filters.length > 0 && $('.js-openable-filter').not('.closed').length == 0) {
    filters[0].open();
  }

  var $form = $('.js-live-search-form'),
      $results = $('.js-live-search-results-block');

  if($form.length && $results.length){
    new GOVUK.LiveSearch({$form:$form, $results:$results});
  }
});

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

jQuery(function($) {
  var filters = $('.js-openable-filter').map(function(){
    return new GOVUK.CheckboxFilter({el:$(this)});
  });

  if (filters.length > 0 && $('.js-openable-filter').not('.closed').length == 0) {
    filters[0].open();
  }

  var $form = $('.js-live-search-form'),
      $results = $('.js-live-search-results-block');

  if($form.length && $results.length){
    new GOVUK.LiveSearch({$form:$form, $results:$results});
  }

  var $buttons = $("label.block-label input[type='checkbox']");
  new GOVUK.SelectionButtons($buttons);
});


Update to call skills animations from skillsAnimation.js

--- assets/js/modal.js
+++ assets/js/modal.js
@@ -1,6 +1,7 @@
 var close = document.querySelectorAll('.close');
 var modal = document.querySelector('.modal');
 var modalAbout = document.getElementById('modal-about');
+var skills = document.querySelectorAll('.skill-tag');

 contact.addEventListener('click', function() {
   modal.style.visibility = 'visible';
@@ -10,10 +11,12 @@
 about.addEventListener('click', function() {
   modalAbout.style.visibility = 'visible';
   modalAbout.style.animation = 'quote-start-transition 0.5s ease';
+  animateSkills();
 });

 for (i = 0; i < close.length; i++) {
     close[i].addEventListener('click', function(event) {
+      resetSkillsAnimation();
       _window = event.target.offsetParent;
       _window.style.animation = 'quote-finish-transition 0.8s ease';
       _window.style.visibility = 'hidden';

<|original_code|>
var close = document.querySelectorAll('.close');
var modal = document.querySelector('.modal');
var modalAbout = document.getElementById('modal-about');

contact.addEventListener('click', function() {
  modal.style.visibility = 'visible';
  modal.style.animation = 'quote-start-transition 0.5s ease';
});

about.addEventListener('click', function() {
  modalAbout.style.visibility = 'visible';
  modalAbout.style.animation = 'quote-start-transition 0.5s ease';
});

for (i = 0; i < close.length; i++) {
    close[i].addEventListener('click', function(event) {
      _window = event.target.offsetParent;
      _window.style.animation = 'quote-finish-transition 0.8s ease';
      _window.style.visibility = 'hidden';
    });
}

<|edits_diff|>
--- assets/js/modal.js
+++ assets/js/modal.js
@@ -1,6 +1,7 @@
 var close = document.querySelectorAll('.close');
 var modal = document.querySelector('.modal');
 var modalAbout = document.getElementById('modal-about');
+var skills = document.querySelectorAll('.skill-tag');

 contact.addEventListener('click', function() {
   modal.style.visibility = 'visible';
@@ -10,6 +11,7 @@
 about.addEventListener('click', function() {
   modalAbout.style.visibility = 'visible';
   modalAbout.style.animation = 'quote-start-transition 0.5s ease';
+  animateSkills();
 });

 for (i = 0; i < close.length; i++) {
<|current_version|>
var close = document.querySelectorAll('.close');
var modal = document.querySelector('.modal');
var modalAbout = document.getElementById('modal-about');
var skills = document.querySelectorAll('.skill-tag');

contact.addEventListener('click', function() {
  modal.style.visibility = 'visible';
  modal.style.animation = 'quote-start-transition 0.5s ease';
});

about.addEventListener('click', function() {
  modalAbout.style.visibility = 'visible';
  modalAbout.style.animation = 'quote-start-transition 0.5s ease';
  animateSkills();
});

for (i = 0; i < close.length; i++) {
    close[i].addEventListener('click', function(event) {
      _window = event.target.offsetParent;
      _window.style.animation = 'quote-finish-transition 0.8s ease';
      _window.style.visibility = 'hidden';
    });
}

<|next_version|>
var close = document.querySelectorAll('.close');
var modal = document.querySelector('.modal');
var modalAbout = document.getElementById('modal-about');
var skills = document.querySelectorAll('.skill-tag');

contact.addEventListener('click', function() {
  modal.style.visibility = 'visible';
  modal.style.animation = 'quote-start-transition 0.5s ease';
});

about.addEventListener('click', function() {
  modalAbout.style.visibility = 'visible';
  modalAbout.style.animation = 'quote-start-transition 0.5s ease';
  animateSkills();
});

for (i = 0; i < close.length; i++) {
    close[i].addEventListener('click', function(event) {
      resetSkillsAnimation();
      _window = event.target.offsetParent;
      _window.style.animation = 'quote-finish-transition 0.8s ease';
      _window.style.visibility = 'hidden';
    });
}


Add support for eslint@1 recommended rules

--- index.js
+++ index.js
@@ -1,9 +1,12 @@
 module.exports = {
+  extends: 'eslint:recommended',
+
   env: {
     browser: true,
     es6: true,
     node: true
   },
+
   rules: {
     'global-strict': 0,
     'indent': [2, 2],

<|original_code|>
module.exports = {
  env: {
    browser: true,
    es6: true,
    node: true
  },
  rules: {
    'global-strict': 0,
    'indent': [2, 2],
    'no-console': 2,
    'no-underscore-dangle': 0,
    'no-unused-expressions': 2,
    'no-unused-vars': [2, {vars: 'all', args: 'none'}],
    'no-use-before-define': [2, 'nofunc'],
    'quotes': [2, 'single'],
    'strict': 0
  }
}

<|edits_diff|>
--- index.js
+++ index.js
@@ -1,4 +1,6 @@
 module.exports = {
+  extends: 'eslint:recommended',
+
   env: {
     browser: true,
     es6: true,
<|current_version|>
module.exports = {
  extends: 'eslint:recommended',

  env: {
    browser: true,
    es6: true,
    node: true
  },
  rules: {
    'global-strict': 0,
    'indent': [2, 2],
    'no-console': 2,
    'no-underscore-dangle': 0,
    'no-unused-expressions': 2,
    'no-unused-vars': [2, {vars: 'all', args: 'none'}],
    'no-use-before-define': [2, 'nofunc'],
    'quotes': [2, 'single'],
    'strict': 0
  }
}

<|next_version|>
module.exports = {
  extends: 'eslint:recommended',

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

  rules: {
    'global-strict': 0,
    'indent': [2, 2],
    'no-console': 2,
    'no-underscore-dangle': 0,
    'no-unused-expressions': 2,
    'no-unused-vars': [2, {vars: 'all', args: 'none'}],
    'no-use-before-define': [2, 'nofunc'],
    'quotes': [2, 'single'],
    'strict': 0
  }
}


Handle future event emit for gulp tasks

Handle future event emit for gulp tasks

--- lib/index.js
+++ lib/index.js
@@ -4,6 +4,7 @@
 var configs = require('./configs');
 var originalAddGulpTasks = rump.addGulpTasks;

+// TODO remove on next major core update
 rump.addGulpTasks = function(options) {
   originalAddGulpTasks(options);
   require('./gulp');
@@ -15,6 +16,11 @@
   rump.emit('update:styles');
 });

+rump.on('gulp:main', function(options) {
+  require('./gulp');
+  rump.emit('gulp:styles', options);
+});
+
 Object.defineProperty(rump.configs, 'pleeease', {
   get: function() {
     return configs.pleeease;

<|original_code|>
'use strict';

var rump = module.exports = require('rump');
var configs = require('./configs');
var originalAddGulpTasks = rump.addGulpTasks;

rump.addGulpTasks = function(options) {
  originalAddGulpTasks(options);
  require('./gulp');
  return rump;
};

rump.on('update:main', function() {
  configs.rebuild();
  rump.emit('update:styles');
});

Object.defineProperty(rump.configs, 'pleeease', {
  get: function() {
    return configs.pleeease;
  }
});

<|edits_diff|>
--- lib/index.js
+++ lib/index.js
@@ -4,6 +4,7 @@
 var configs = require('./configs');
 var originalAddGulpTasks = rump.addGulpTasks;

+// TODO remove on next major core update
 rump.addGulpTasks = function(options) {
   originalAddGulpTasks(options);
   require('./gulp');
<|current_version|>
'use strict';

var rump = module.exports = require('rump');
var configs = require('./configs');
var originalAddGulpTasks = rump.addGulpTasks;

// TODO remove on next major core update
rump.addGulpTasks = function(options) {
  originalAddGulpTasks(options);
  require('./gulp');
  return rump;
};

rump.on('update:main', function() {
  configs.rebuild();
  rump.emit('update:styles');
});

Object.defineProperty(rump.configs, 'pleeease', {
  get: function() {
    return configs.pleeease;
  }
});

<|next_version|>
'use strict';

var rump = module.exports = require('rump');
var configs = require('./configs');
var originalAddGulpTasks = rump.addGulpTasks;

// TODO remove on next major core update
rump.addGulpTasks = function(options) {
  originalAddGulpTasks(options);
  require('./gulp');
  return rump;
};

rump.on('update:main', function() {
  configs.rebuild();
  rump.emit('update:styles');
});

rump.on('gulp:main', function(options) {
  require('./gulp');
  rump.emit('gulp:styles', options);
});

Object.defineProperty(rump.configs, 'pleeease', {
  get: function() {
    return configs.pleeease;
  }
});


Bring in PropTypes and and set prop type for portalTo prop

--- src/components/MXFocusTrap.js
+++ src/components/MXFocusTrap.js
@@ -1,5 +1,6 @@
 let traps = [];

+const PropTypes = require('prop-types');
 const React = require('react');
 const FocusTrap = require('focus-trap-react');

@@ -12,6 +13,10 @@
  * This ensures that the previously trapped component is un-paused.
  */
 class MXFocusTrap extends React.Component {
+  static propTypes = {
+    portalTo: PropTypes.string
+  }
+
   state = {
     paused: false
   }

<|original_code|>
let traps = [];

const React = require('react');
const FocusTrap = require('focus-trap-react');

/**
 * MXFocusTrap
 *
 * Why is this needed?
 *
 * FocusTrap does not un-pause the previous trap when the current trap is unmounted.
 * This ensures that the previously trapped component is un-paused.
 */
class MXFocusTrap extends React.Component {
  state = {
    paused: false
  }

  componentWillMount () {
    // FocusTrap does it's own pausing but these React components also need to be paused
    traps.forEach(component => component.setState({ paused: true }));
    traps.push(this);
  }

  componentWillUnmount () {
    traps = traps.filter(component => component !== this);
    const lastTrap = traps[traps.length - 1];

    if (lastTrap) lastTrap.setState({ paused: false });
  }

  render () {
    return (
      <FocusTrap {...this.props} paused={this.state.paused}>
        {this.props.children}
      </FocusTrap>
    );
  }
}

module.exports = MXFocusTrap;
<|edits_diff|>
--- src/components/MXFocusTrap.js
+++ src/components/MXFocusTrap.js
@@ -1,5 +1,6 @@
 let traps = [];

+const PropTypes = require('prop-types');
 const React = require('react');
 const FocusTrap = require('focus-trap-react');

<|current_version|>
let traps = [];

const PropTypes = require('prop-types');
const React = require('react');
const FocusTrap = require('focus-trap-react');

/**
 * MXFocusTrap
 *
 * Why is this needed?
 *
 * FocusTrap does not un-pause the previous trap when the current trap is unmounted.
 * This ensures that the previously trapped component is un-paused.
 */
class MXFocusTrap extends React.Component {
  state = {
    paused: false
  }

  componentWillMount () {
    // FocusTrap does it's own pausing but these React components also need to be paused
    traps.forEach(component => component.setState({ paused: true }));
    traps.push(this);
  }

  componentWillUnmount () {
    traps = traps.filter(component => component !== this);
    const lastTrap = traps[traps.length - 1];

    if (lastTrap) lastTrap.setState({ paused: false });
  }

  render () {
    return (
      <FocusTrap {...this.props} paused={this.state.paused}>
        {this.props.children}
      </FocusTrap>
    );
  }
}

module.exports = MXFocusTrap;
<|next_version|>
let traps = [];

const PropTypes = require('prop-types');
const React = require('react');
const FocusTrap = require('focus-trap-react');

/**
 * MXFocusTrap
 *
 * Why is this needed?
 *
 * FocusTrap does not un-pause the previous trap when the current trap is unmounted.
 * This ensures that the previously trapped component is un-paused.
 */
class MXFocusTrap extends React.Component {
  static propTypes = {
    portalTo: PropTypes.string
  }

  state = {
    paused: false
  }

  componentWillMount () {
    // FocusTrap does it's own pausing but these React components also need to be paused
    traps.forEach(component => component.setState({ paused: true }));
    traps.push(this);
  }

  componentWillUnmount () {
    traps = traps.filter(component => component !== this);
    const lastTrap = traps[traps.length - 1];

    if (lastTrap) lastTrap.setState({ paused: false });
  }

  render () {
    return (
      <FocusTrap {...this.props} paused={this.state.paused}>
        {this.props.children}
      </FocusTrap>
    );
  }
}

module.exports = MXFocusTrap;


Load i18n Vue plugin for Jest tests

Load i18n Vue plugin for Jest tests

--- spec/frontend/test_setup.js
+++ spec/frontend/test_setup.js
@@ -1,3 +1,5 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
 import axios from '~/lib/utils/axios_utils';

 const testTimeoutInMs = 300;
@@ -28,3 +30,5 @@

   done();
 });
+
+Vue.use(Translate);

<|original_code|>
import axios from '~/lib/utils/axios_utils';

const testTimeoutInMs = 300;
jest.setTimeout(testTimeoutInMs);

let testStartTime;

// https://github.com/facebook/jest/issues/6947
beforeEach(() => {
  testStartTime = Date.now();
});

afterEach(() => {
  const elapsedTimeInMs = Date.now() - testStartTime;
  if (elapsedTimeInMs > testTimeoutInMs) {
    throw new Error(`Test took too long (${elapsedTimeInMs}ms > ${testTimeoutInMs}ms)!`);
  }
});

// fail tests for unmocked requests
beforeEach(done => {
  axios.defaults.adapter = config => {
    const error = new Error(`Unexpected unmocked request: ${JSON.stringify(config, null, 2)}`);
    error.config = config;
    done.fail(error);
    return Promise.reject(error);
  };

  done();
});

<|edits_diff|>
--- spec/frontend/test_setup.js
+++ spec/frontend/test_setup.js
@@ -1,3 +1,5 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
 import axios from '~/lib/utils/axios_utils';

 const testTimeoutInMs = 300;
<|current_version|>
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import axios from '~/lib/utils/axios_utils';

const testTimeoutInMs = 300;
jest.setTimeout(testTimeoutInMs);

let testStartTime;

// https://github.com/facebook/jest/issues/6947
beforeEach(() => {
  testStartTime = Date.now();
});

afterEach(() => {
  const elapsedTimeInMs = Date.now() - testStartTime;
  if (elapsedTimeInMs > testTimeoutInMs) {
    throw new Error(`Test took too long (${elapsedTimeInMs}ms > ${testTimeoutInMs}ms)!`);
  }
});

// fail tests for unmocked requests
beforeEach(done => {
  axios.defaults.adapter = config => {
    const error = new Error(`Unexpected unmocked request: ${JSON.stringify(config, null, 2)}`);
    error.config = config;
    done.fail(error);
    return Promise.reject(error);
  };

  done();
});

<|next_version|>
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import axios from '~/lib/utils/axios_utils';

const testTimeoutInMs = 300;
jest.setTimeout(testTimeoutInMs);

let testStartTime;

// https://github.com/facebook/jest/issues/6947
beforeEach(() => {
  testStartTime = Date.now();
});

afterEach(() => {
  const elapsedTimeInMs = Date.now() - testStartTime;
  if (elapsedTimeInMs > testTimeoutInMs) {
    throw new Error(`Test took too long (${elapsedTimeInMs}ms > ${testTimeoutInMs}ms)!`);
  }
});

// fail tests for unmocked requests
beforeEach(done => {
  axios.defaults.adapter = config => {
    const error = new Error(`Unexpected unmocked request: ${JSON.stringify(config, null, 2)}`);
    error.config = config;
    done.fail(error);
    return Promise.reject(error);
  };

  done();
});

Vue.use(Translate);


Add critical to build pipeline

Add critical to build pipeline

--- gulpfile.js
+++ gulpfile.js
@@ -1,4 +1,5 @@
 const gulp = require('gulp');
+const critical = require('critical').stream;
 const htmlmin = require('gulp-htmlmin');
 const terser = require('gulp-terser');
 const cssnano = require('gulp-cssnano');
@@ -7,6 +8,10 @@

 gulp.task('build:html', () => {
     return gulp.src('_site/**/*.html')
+        .pipe(critical({
+            base: '_site',
+            inline: true
+        }))
         .pipe(htmlmin({
             collapseWhitespace: true
         }))

<|original_code|>
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');
const terser = require('gulp-terser');
const cssnano = require('gulp-cssnano');
const imagemin = require('gulp-imagemin');
const imageminJpegOptim = require('imagemin-jpegoptim');

gulp.task('build:html', () => {
    return gulp.src('_site/**/*.html')
        .pipe(htmlmin({
            collapseWhitespace: true
        }))
        .pipe(gulp.dest('_site'));
});

gulp.task('build:js', () => {
    return gulp.src('_site/**/*.{js,mjs}')
        .pipe(terser())
        .pipe(gulp.dest('_site'));
});

gulp.task('build:css', () => {
    return gulp.src('_site/**/*.css')
        .pipe(cssnano())
        .pipe(gulp.dest('_site'));
});

gulp.task('build:images', () => {
    return gulp.src('_site/**/*.{jpg,svg}')
        .pipe(imagemin([
            imageminJpegOptim({
                progressive: true,
                max: 80
            }),
            imagemin.svgo()
        ]))
        .pipe(gulp.dest('_site'));
});

gulp.task('build', gulp.parallel(
    'build:html',
    'build:js',
    'build:css',
    'build:images'
));

<|edits_diff|>
--- gulpfile.js
+++ gulpfile.js
@@ -1,4 +1,5 @@
 const gulp = require('gulp');
+const critical = require('critical').stream;
 const htmlmin = require('gulp-htmlmin');
 const terser = require('gulp-terser');
 const cssnano = require('gulp-cssnano');
<|current_version|>
const gulp = require('gulp');
const critical = require('critical').stream;
const htmlmin = require('gulp-htmlmin');
const terser = require('gulp-terser');
const cssnano = require('gulp-cssnano');
const imagemin = require('gulp-imagemin');
const imageminJpegOptim = require('imagemin-jpegoptim');

gulp.task('build:html', () => {
    return gulp.src('_site/**/*.html')
        .pipe(htmlmin({
            collapseWhitespace: true
        }))
        .pipe(gulp.dest('_site'));
});

gulp.task('build:js', () => {
    return gulp.src('_site/**/*.{js,mjs}')
        .pipe(terser())
        .pipe(gulp.dest('_site'));
});

gulp.task('build:css', () => {
    return gulp.src('_site/**/*.css')
        .pipe(cssnano())
        .pipe(gulp.dest('_site'));
});

gulp.task('build:images', () => {
    return gulp.src('_site/**/*.{jpg,svg}')
        .pipe(imagemin([
            imageminJpegOptim({
                progressive: true,
                max: 80
            }),
            imagemin.svgo()
        ]))
        .pipe(gulp.dest('_site'));
});

gulp.task('build', gulp.parallel(
    'build:html',
    'build:js',
    'build:css',
    'build:images'
));

<|next_version|>
const gulp = require('gulp');
const critical = require('critical').stream;
const htmlmin = require('gulp-htmlmin');
const terser = require('gulp-terser');
const cssnano = require('gulp-cssnano');
const imagemin = require('gulp-imagemin');
const imageminJpegOptim = require('imagemin-jpegoptim');

gulp.task('build:html', () => {
    return gulp.src('_site/**/*.html')
        .pipe(critical({
            base: '_site',
            inline: true
        }))
        .pipe(htmlmin({
            collapseWhitespace: true
        }))
        .pipe(gulp.dest('_site'));
});

gulp.task('build:js', () => {
    return gulp.src('_site/**/*.{js,mjs}')
        .pipe(terser())
        .pipe(gulp.dest('_site'));
});

gulp.task('build:css', () => {
    return gulp.src('_site/**/*.css')
        .pipe(cssnano())
        .pipe(gulp.dest('_site'));
});

gulp.task('build:images', () => {
    return gulp.src('_site/**/*.{jpg,svg}')
        .pipe(imagemin([
            imageminJpegOptim({
                progressive: true,
                max: 80
            }),
            imagemin.svgo()
        ]))
        .pipe(gulp.dest('_site'));
});

gulp.task('build', gulp.parallel(
    'build:html',
    'build:js',
    'build:css',
    'build:images'
));


Store the expense on pounchdb

Store the expense on pounchdb

--- src/middlewares/redux-pouchdb.js
+++ src/middlewares/redux-pouchdb.js
@@ -4,6 +4,7 @@
   NEW_CATEGORY,
   EDIT_SALARY,
   NEW_INCOME,
+  NEW_EXPENSE,
 } from '../constants/ActionTypes';

 import { updateRev } from '../actionCreators/ebudgie';
@@ -16,6 +17,7 @@
     case NEW_CATEGORY:
     case EDIT_SALARY:
     case NEW_INCOME:
+    case NEW_EXPENSE:
       const state = store.getState();
       const ebudgie = state.ebudgie;

<|original_code|>
import * as pouchdbService from '../services/pouchdbService';
import {
  NEW_ITEM,
  NEW_CATEGORY,
  EDIT_SALARY,
  NEW_INCOME,
} from '../constants/ActionTypes';

import { updateRev } from '../actionCreators/ebudgie';

const storage = store => next => async action => {
  const result = next(action);

  switch (action.type) {
    case NEW_ITEM:
    case NEW_CATEGORY:
    case EDIT_SALARY:
    case NEW_INCOME:
      const state = store.getState();
      const ebudgie = state.ebudgie;

      const savedData = await pouchdbService.updateDocument(ebudgie);
      next(updateRev(savedData.rev));
      break;
  }

  return result;
};

export default storage;

<|edits_diff|>
--- src/middlewares/redux-pouchdb.js
+++ src/middlewares/redux-pouchdb.js
@@ -4,6 +4,7 @@
   NEW_CATEGORY,
   EDIT_SALARY,
   NEW_INCOME,
+  NEW_EXPENSE,
 } from '../constants/ActionTypes';

 import { updateRev } from '../actionCreators/ebudgie';
<|current_version|>
import * as pouchdbService from '../services/pouchdbService';
import {
  NEW_ITEM,
  NEW_CATEGORY,
  EDIT_SALARY,
  NEW_INCOME,
  NEW_EXPENSE,
} from '../constants/ActionTypes';

import { updateRev } from '../actionCreators/ebudgie';

const storage = store => next => async action => {
  const result = next(action);

  switch (action.type) {
    case NEW_ITEM:
    case NEW_CATEGORY:
    case EDIT_SALARY:
    case NEW_INCOME:
      const state = store.getState();
      const ebudgie = state.ebudgie;

      const savedData = await pouchdbService.updateDocument(ebudgie);
      next(updateRev(savedData.rev));
      break;
  }

  return result;
};

export default storage;

<|next_version|>
import * as pouchdbService from '../services/pouchdbService';
import {
  NEW_ITEM,
  NEW_CATEGORY,
  EDIT_SALARY,
  NEW_INCOME,
  NEW_EXPENSE,
} from '../constants/ActionTypes';

import { updateRev } from '../actionCreators/ebudgie';

const storage = store => next => async action => {
  const result = next(action);

  switch (action.type) {
    case NEW_ITEM:
    case NEW_CATEGORY:
    case EDIT_SALARY:
    case NEW_INCOME:
    case NEW_EXPENSE:
      const state = store.getState();
      const ebudgie = state.ebudgie;

      const savedData = await pouchdbService.updateDocument(ebudgie);
      next(updateRev(savedData.rev));
      break;
  }

  return result;
};

export default storage;


Add `miniplug:ws` debug namespace, logging all incoming events

--- src/plugins/connect.js
+++ src/plugins/connect.js
@@ -3,6 +3,7 @@
 import createDebug from 'debug'

 const debug = createDebug('miniplug:connect')
+const debugWs = createDebug('miniplug:ws')

 export default function connectPlugin (options = {}) {
   return (mp) => {
@@ -20,6 +21,9 @@

       const ws = socket()
       ws.setMaxListeners(100)
+      ws.on('action', (type, payload) => {
+        debugWs(type, payload)
+      })

       const connected = loginPromise
         .then((res) => new Promise((resolve, reject) => {

<|original_code|>
import login from 'plug-login'
import socket from 'plug-socket'
import createDebug from 'debug'

const debug = createDebug('miniplug:connect')

export default function connectPlugin (options = {}) {
  return (mp) => {
      // log in
    const loginOpts = { host: options.host, authToken: true }

    function connect (opts) {
      debug('connecting', opts.email)

      const loginPromise = Promise.resolve(
        opts.email
          ? login.user(opts.email, opts.password, loginOpts)
          : login.guest(loginOpts)
      )

      const ws = socket()
      ws.setMaxListeners(100)

      const connected = loginPromise
        .then((res) => new Promise((resolve, reject) => {
          ws.auth(res.token)
          ws.once('error', reject)

          mp.isConnected = true
          mp.emit('login')

          const me = mp.getMe()
          ws.once('ack', () => {
            resolve({ cookie: res.cookie })
            ws.removeListener('error', reject)

            me.then((user) => mp.emit('connected', user))
          })
        }))
        .catch((err) => {
          mp.emit('error', err)
          throw err
        })

      mp.ws = ws
      mp.connected = connected

      return connected
    }

    mp.connect = connect
  }
}

<|edits_diff|>
--- src/plugins/connect.js
+++ src/plugins/connect.js
@@ -3,6 +3,7 @@
 import createDebug from 'debug'

 const debug = createDebug('miniplug:connect')
+const debugWs = createDebug('miniplug:ws')

 export default function connectPlugin (options = {}) {
   return (mp) => {
<|current_version|>
import login from 'plug-login'
import socket from 'plug-socket'
import createDebug from 'debug'

const debug = createDebug('miniplug:connect')
const debugWs = createDebug('miniplug:ws')

export default function connectPlugin (options = {}) {
  return (mp) => {
      // log in
    const loginOpts = { host: options.host, authToken: true }

    function connect (opts) {
      debug('connecting', opts.email)

      const loginPromise = Promise.resolve(
        opts.email
          ? login.user(opts.email, opts.password, loginOpts)
          : login.guest(loginOpts)
      )

      const ws = socket()
      ws.setMaxListeners(100)

      const connected = loginPromise
        .then((res) => new Promise((resolve, reject) => {
          ws.auth(res.token)
          ws.once('error', reject)

          mp.isConnected = true
          mp.emit('login')

          const me = mp.getMe()
          ws.once('ack', () => {
            resolve({ cookie: res.cookie })
            ws.removeListener('error', reject)

            me.then((user) => mp.emit('connected', user))
          })
        }))
        .catch((err) => {
          mp.emit('error', err)
          throw err
        })

      mp.ws = ws
      mp.connected = connected

      return connected
    }

    mp.connect = connect
  }
}

<|next_version|>
import login from 'plug-login'
import socket from 'plug-socket'
import createDebug from 'debug'

const debug = createDebug('miniplug:connect')
const debugWs = createDebug('miniplug:ws')

export default function connectPlugin (options = {}) {
  return (mp) => {
      // log in
    const loginOpts = { host: options.host, authToken: true }

    function connect (opts) {
      debug('connecting', opts.email)

      const loginPromise = Promise.resolve(
        opts.email
          ? login.user(opts.email, opts.password, loginOpts)
          : login.guest(loginOpts)
      )

      const ws = socket()
      ws.setMaxListeners(100)
      ws.on('action', (type, payload) => {
        debugWs(type, payload)
      })

      const connected = loginPromise
        .then((res) => new Promise((resolve, reject) => {
          ws.auth(res.token)
          ws.once('error', reject)

          mp.isConnected = true
          mp.emit('login')

          const me = mp.getMe()
          ws.once('ack', () => {
            resolve({ cookie: res.cookie })
            ws.removeListener('error', reject)

            me.then((user) => mp.emit('connected', user))
          })
        }))
        .catch((err) => {
          mp.emit('error', err)
          throw err
        })

      mp.ws = ws
      mp.connected = connected

      return connected
    }

    mp.connect = connect
  }
}