<commit_before>const should = require('should');
const DynamicRedirectManager = require('../');

const urlJoin = (...parts) => {
    let url = parts.join('/');
    return url.replace(/(^|[^:])\/\/+/g, '$1/');
};

describe('DynamicRedirectManager', function () {
    let headers;
    let status;
    let location;
    let req;
    let res;

    beforeEach(function () {
        headers = null;
        status = null;
        location = null;

        req = {
            method: 'GET'
        };

        res = {
            set(_headers) {
                headers = _headers;
            },
            redirect(_status, _location) {
                status = _status;
                location = _location;
            }
        };
    });

    describe('no subdirectory configuration', function () {
        let manager;

        beforeEach(function () {
            manager = new DynamicRedirectManager({
                permanentMaxAge: 100,
                getSubdirectoryURL: (pathname) => {
                    return urlJoin('', pathname);
                }
            });
        });

        it('Prioritizes the query params of the redirect', function () {
            manager.addRedirect('/test-params', '/result?q=abc', {
                permanent: true
            });

            req.url = '/test-params/?q=123&lang=js';

            manager.handleRequest(req, res, function next() {
                should.fail(true, false, 'next should NOT have been called');
            });

            should.equal(headers['Cache-Control'], 'public, max-age=100');
            should.equal(status, 301);
            should.equal(location, '/result?q=abc&lang=js');
        });

        it('Allows redirects to be removed', function () {
            const id = manager.addRedirect('/test-params', '/result?q=abc', {permanent: true});
            manager.removeRedirect(id);

            req.url = '/test-params/?q=123&lang=js';

            manager.handleRequest(req, res, function next() {
                should.ok(true, 'next should have been called');
            });

            should.equal(headers, null);
            should.equal(status, null);
            should.equal(location, null);
        });

        it('The routing works when passed an invalid regexp for the from parameter', function () {
            const from = '/invalid_regex/(/size/[a-zA-Z0-9_-.]*/[a-zA-Z0-9_-.]*/[0-9]*/[0-9]*/)([a-zA-Z0-9_-.]*)';
            const to = '/';

            manager.addRedirect(from , to, {
                permanent: false
            });

            req.url = '/test-params/';

            manager.handleRequest(req, res, function next() {
                should.ok(true, 'next should have been called');
            });

            should.equal(headers, null);
            should.equal(status, null);
            should.equal(location, null);
        });

        describe('Substitution regex redirects', function () {
            it('Works with substitution redirect case and no trailing slash', function (){
                const from = '^/post/[0-9]+/([a-z0-9\\-]+)';
                const to = '/$1';

                manager.addRedirect(from , to);

                req.url = '/post/10/a-nice-blog-post';

                manager.handleRequest(req, res, function next() {
                    should.fail(true, 'next should NOT have been called');
                });

                // NOTE: max-age is "0" because it's not a permanent redirect
                should.equal(headers['Cache-Control'], 'public, max-age=0');
                should.equal(status, 302);
                should.equal(location, '/a-nice-blog-post');
            });

            it('Works with substitution redirect case and a trailing slash', function (){
                const from = '^/post/[0-9]+/([a-z0-9\\-]+)';
                const to = '/$1';

                manager.addRedirect(from , to);

                req.url = '/post/10/a-nice-blog-post/';

                manager.handleRequest(req, res, function next() {
                    should.fail(true, 'next should NOT have been called');
                });

                // NOTE: max-age is "0" because it's not a permanent redirect
                should.equal(headers['Cache-Control'], 'public, max-age=0');
                should.equal(status, 302);
                should.equal(location, '/a-nice-blog-post');
            });

            it('Redirects keeping the query params for substitution regexp', function (){
                const from = '^/post/[0-9]+/([a-z0-9\\-]+)';
                const to = '/$1';

                manager.addRedirect(from , to);

                req.url = '/post/10/a-nice-blog-post?a=b';

                manager.handleRequest(req, res, function next() {
                    should.fail(true, 'next should NOT have been called');
                });

                // NOTE: max-age is "0" because it's not a permanent redirect
                should.equal(headers['Cache-Control'], 'public, max-age=0');
                should.equal(status, 302);
                should.equal(location, '/a-nice-blog-post?a=b');
            });

            it('Redirects keeping the query params', function (){
                const from = '^\\/topic\\/';
                const to = '/';

                manager.addRedirect(from , to);

                req.url = '/topic?something=good';

                manager.handleRequest(req, res, function next() {
                    should.fail(true, 'next should NOT have been called');
                });

                // NOTE: max-age is "0" because it's not a permanent redirect
                should.equal(headers['Cache-Control'], 'public, max-age=0');
                should.equal(status, 302);
                should.equal(location, '/?something=good');
            });
        });
    });
});
<commit_msg>Added case sensitivity coverage to DynamiRedirectsManager suites<commit_after>const should = require('should');
const DynamicRedirectManager = require('../');

const urlJoin = (...parts) => {
    let url = parts.join('/');
    return url.replace(/(^|[^:])\/\/+/g, '$1/');
};

describe('DynamicRedirectManager', function () {
    let headers;
    let status;
    let location;
    let req;
    let res;

    beforeEach(function () {
        headers = null;
        status = null;
        location = null;

        req = {
            method: 'GET'
        };

        res = {
            set(_headers) {
                headers = _headers;
            },
            redirect(_status, _location) {
                status = _status;
                location = _location;
            }
        };
    });

    describe('no subdirectory configuration', function () {
        let manager;

        beforeEach(function () {
            manager = new DynamicRedirectManager({
                permanentMaxAge: 100,
                getSubdirectoryURL: (pathname) => {
                    return urlJoin('', pathname);
                }
            });
        });

        it('Prioritizes the query params of the redirect', function () {
            manager.addRedirect('/test-params', '/result?q=abc', {
                permanent: true
            });

            req.url = '/test-params/?q=123&lang=js';

            manager.handleRequest(req, res, function next() {
                should.fail(true, false, 'next should NOT have been called');
            });

            should.equal(headers['Cache-Control'], 'public, max-age=100');
            should.equal(status, 301);
            should.equal(location, '/result?q=abc&lang=js');
        });

        it('Allows redirects to be removed', function () {
            const id = manager.addRedirect('/test-params', '/result?q=abc', {permanent: true});
            manager.removeRedirect(id);

            req.url = '/test-params/?q=123&lang=js';

            manager.handleRequest(req, res, function next() {
                should.ok(true, 'next should have been called');
            });

            should.equal(headers, null);
            should.equal(status, null);
            should.equal(location, null);
        });

        it('The routing works when passed an invalid regexp for the from parameter', function () {
            const from = '/invalid_regex/(/size/[a-zA-Z0-9_-.]*/[a-zA-Z0-9_-.]*/[0-9]*/[0-9]*/)([a-zA-Z0-9_-.]*)';
            const to = '/';

            manager.addRedirect(from , to, {
                permanent: false
            });

            req.url = '/test-params/';

            manager.handleRequest(req, res, function next() {
                should.ok(true, 'next should have been called');
            });

            should.equal(headers, null);
            should.equal(status, null);
            should.equal(location, null);
        });

        describe('Substitution regex redirects', function () {
            it('Works with substitution redirect case and no trailing slash', function (){
                const from = '^/post/[0-9]+/([a-z0-9\\-]+)';
                const to = '/$1';

                manager.addRedirect(from , to);

                req.url = '/post/10/a-nice-blog-post';

                manager.handleRequest(req, res, function next() {
                    should.fail(true, 'next should NOT have been called');
                });

                // NOTE: max-age is "0" because it's not a permanent redirect
                should.equal(headers['Cache-Control'], 'public, max-age=0');
                should.equal(status, 302);
                should.equal(location, '/a-nice-blog-post');
            });

            it('Works with substitution redirect case and a trailing slash', function (){
                const from = '^/post/[0-9]+/([a-z0-9\\-]+)';
                const to = '/$1';

                manager.addRedirect(from , to);

                req.url = '/post/10/a-nice-blog-post/';

                manager.handleRequest(req, res, function next() {
                    should.fail(true, 'next should NOT have been called');
                });

                // NOTE: max-age is "0" because it's not a permanent redirect
                should.equal(headers['Cache-Control'], 'public, max-age=0');
                should.equal(status, 302);
                should.equal(location, '/a-nice-blog-post');
            });

            it('Redirects keeping the query params for substitution regexp', function (){
                const from = '^/post/[0-9]+/([a-z0-9\\-]+)';
                const to = '/$1';

                manager.addRedirect(from , to);

                req.url = '/post/10/a-nice-blog-post?a=b';

                manager.handleRequest(req, res, function next() {
                    should.fail(true, 'next should NOT have been called');
                });

                // NOTE: max-age is "0" because it's not a permanent redirect
                should.equal(headers['Cache-Control'], 'public, max-age=0');
                should.equal(status, 302);
                should.equal(location, '/a-nice-blog-post?a=b');
            });

            it('Redirects keeping the query params', function (){
                const from = '^\\/topic\\/';
                const to = '/';

                manager.addRedirect(from , to);

                req.url = '/topic?something=good';

                manager.handleRequest(req, res, function next() {
                    should.fail(true, 'next should NOT have been called');
                });

                // NOTE: max-age is "0" because it's not a permanent redirect
                should.equal(headers['Cache-Control'], 'public, max-age=0');
                should.equal(status, 302);
                should.equal(location, '/?something=good');
            });
        });

        describe('Case sensitivity', function () {
            it('with case insensitive', function () {
                const from = '/^\\/case-insensitive/i';
                const to = '/redirected-insensitive';

                manager.addRedirect(from , to);

                req.url = '/CaSe-InSeNsItIvE';

                manager.handleRequest(req, res, function next() {
                    should.fail(true, 'next should NOT have been called');
                });

                // NOTE: max-age is "0" because it's not a permanent redirect
                should.equal(headers['Cache-Control'], 'public, max-age=0');
                should.equal(status, 302);
                should.equal(location, '/redirected-insensitive');
            });

            it('with case sensitive', function () {
                const from = '^\\/Case-Sensitive';
                const to = '/redirected-sensitive';

                manager.addRedirect(from , to);

                req.url = '/Case-Sensitive';

                manager.handleRequest(req, res, function next() {
                    should.fail(true, 'next should NOT have been called');
                });

                // NOTE: max-age is "0" because it's not a permanent redirect
                should.equal(headers['Cache-Control'], 'public, max-age=0');
                should.equal(status, 302);
                should.equal(location, '/redirected-sensitive');
            });

            it('defaults to case sensitive', function () {
                const from = '^\\/Default-Sensitive';
                const to = '/redirected-default';

                manager.addRedirect(from , to);

                req.url = '/Default-Sensitive';

                manager.handleRequest(req, res, function next() {
                    should.fail(true, 'next should NOT have been called');
                });

                should.equal(headers['Cache-Control'], 'public, max-age=0');
                should.equal(status, 302);
                should.equal(location, '/redirected-default');
            });

            it('should not redirect with case sensitive', function () {
                const from = '^\\/Case-Sensitive';
                const to = '/redirected-insensitive';

                manager.addRedirect(from , to);

                req.url = '/casE-sensitivE';

                manager.handleRequest(req, res, function next() {
                    should.ok(true, 'next should have been called');
                });

                should.equal(headers, null);
                should.equal(status, null);
                should.equal(location, null);
            });

            it('should not redirect with default case sensitive', function () {
                const from = '^\\/Default-Sensitive';
                const to = '/redirected-default';

                manager.addRedirect(from , to);

                req.url = '/defaulT-sensitivE';

                manager.handleRequest(req, res, function next() {
                    should.ok(true, 'next should have been called');
                });

                should.equal(headers, null);
                should.equal(status, null);
                should.equal(location, null);
            });
        });
    });
});
<|endoftext|>


<commit_before>(function() {
  'use strict';

  /**
   * Specify controller for badgeFrontend.layout module.
   *
   * @namespace Controllers
   */
  angular
    .module('badgeFrontend.layout')
    .controller('HeaderController', HeaderController);

  //////////

  /**
   * @desc      Controller implementation.
   * @namespace Layout
   * @memberOf  Controllers
   * @ngInject
   *
   * @param {ui.router.state.$state}  $state
   * @constructor
   */
  function HeaderController($state) {
    var vm = this;

    /**
     * Method to make logout action.
     *
     * @param {Event} $event
     */
    vm.logout = function logout($event) {
      $event.preventDefault();
      $event.stopPropagation();
    };
  }
})();
<commit_msg>Fixed user data on header controller.<commit_after>(function() {
  'use strict';

  /**
   * Specify controller for badgeFrontend.layout module.
   *
   * @namespace Controllers
   */
  angular
    .module('badgeFrontend.layout')
    .controller('HeaderController', HeaderController);

  //////////

  /**
   * @desc      Controller implementation.
   * @namespace Layout
   * @memberOf  Controllers
   * @ngInject
   *
   * @param {*}                     $scope
   * @param {*}                     $localStorage
   * @param {Services.AuthService}  AuthService
   * @param {Services.UserService}  UserService
   * @constructor
   */
  function HeaderController(
    $scope, $localStorage,
    AuthService, UserService
  ) {
    var vm = this;

    // Functions
    vm.logout = logout;

    //////////

    /**
     * Method to make logout action.
     *
     * @param {Event} $event
     */
    function logout($event) {
      $event.preventDefault();
      $event.stopPropagation();

      AuthService.logout();
    }

    $scope.$watch(function() {
      return angular.toJson($localStorage);
    }, function() {
      vm.user = UserService.user();
    });
  }
})();
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     542
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v543 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     543
// ==/UserScript==
<|endoftext|>


<commit_before>/*!
 * The Final Countdown for jQuery v2.0.2 (http://hilios.github.io/jQuery.countdown/)
 * Copyright (c) 2013 Edson Hilios
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){"use strict";function b(a){if(a instanceof Date)return a;if(String(a).match(g))return String(a).match(/^[0-9]*$/)&&(a=Number(a)),new Date(a);throw new Error("Couldn't cast `"+a+"` to a date object.")}function c(a){return function(b){var c=b.match(/%(-|!)?[A-Z]{1}(:[^;]+;)?/gi);if(c)for(var e=0,f=c.length;f>e;++e){var g=c[e].match(/%(-|!)?([a-zA-Z]{1})(:[^;]+;)?/),i=new RegExp(g[0]),j=g[1]||"",k=g[3]||"",l=null;g=g[2],h.hasOwnProperty(g)&&(l=h[g],l=Number(a[l])),null!==l&&("!"===j&&(l=d(k,l)),""===j&&10>l&&(l="0"+l.toString()),b=b.replace(i,l.toString()))}return b=b.replace(/%%/,"%")}}function d(a,b){var c="s",d="";return a&&(a=a.replace(/(:|;|\s)/gi,"").split(/\,/),1===a.length?c=a[0]:(d=a[0],c=a[1])),1===Math.abs(b)?d:c}var e=100,f=[],g=[];g.push(/^[0-9]*$/.source),g.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/.source),g.push(/[0-9]{4}(\/[0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/.source),g=new RegExp(g.join("|"));var h={Y:"years",m:"months",w:"weeks",d:"days",D:"totalDays",H:"hours",M:"minutes",S:"seconds"},i=function(b,c,d){this.el=b,this.$el=a(b),this.interval=null,this.offset={},this.setFinalDate(c),this.instanceNumber=f.length,f.push(this),this.$el.data("countdown-instance",this.instanceNumber),d&&(this.$el.on("update.countdown",d),this.$el.on("stoped.countdown",d),this.$el.on("finish.countdown",d)),this.start()};a.extend(i.prototype,{start:function(){if(null!==this.interval)throw new Error("Countdown is already running!");var a=this;this.update(),this.interval=setInterval(function(){a.update.call(a)},e)},stop:function(){clearInterval(this.interval),this.interval=null,this.dispatchEvent("stoped")},pause:function(){this.stop.call(this)},resume:function(){this.start.call(this)},remove:function(){this.stop(),delete f[this.instanceNumber]},setFinalDate:function(a){this.finalDate=b(a)},update:function(){return 0===this.$el.closest("html").length?(this.remove(),void 0):(this.totalSecsLeft=this.finalDate.valueOf()-(new Date).valueOf(),this.totalSecsLeft=Math.ceil(this.totalSecsLeft/1e3),this.totalSecsLeft=this.totalSecsLeft<0?0:this.totalSecsLeft,this.offset={seconds:this.totalSecsLeft%60,minutes:Math.floor(this.totalSecsLeft/60)%60,hours:Math.floor(this.totalSecsLeft/60/60)%24,days:Math.floor(this.totalSecsLeft/60/60/24)%7,totalDays:Math.floor(this.totalSecsLeft/60/60/24),weeks:Math.floor(this.totalSecsLeft/60/60/24/7),months:Math.floor(this.totalSecsLeft/60/60/24/30),years:Math.floor(this.totalSecsLeft/60/60/24/365)},0===this.totalSecsLeft?(this.stop(),this.dispatchEvent("finish")):this.dispatchEvent("update"),void 0)},dispatchEvent:function(b){var d=a.Event(b+".countdown");d.finalDate=this.finalDate,d.offset=a.extend({},this.offset),d.strftime=c(this.offset),this.$el.trigger(d)}}),a.fn.countdown=function(){var b=Array.prototype.slice.call(arguments,0);return this.each(function(){var c=a(this).data("countdown-instance");if(void 0!==c){var d=f[c],e=b[0];i.prototype.hasOwnProperty(e)?d[e].apply(d,b.slice(1)):null===String(e).match(/^[$A-Z_][0-9A-Z_$]*$/i)?d.setFinalDate.call(d,e):a.error("Method %s does not exist on jQuery.countdown".replace(/\%s/gi,e))}else new i(this,b[0],b[1])})}});<commit_msg>Delete jquery.countdown.min.js<commit_after><|endoftext|>


<commit_before>var inpaintingController = inpaintingController || {};

let canvas, context;
let tool;
let tools = {};
let inpaintingControls = [];
let background;
let nDots;

inpaintingController.init = async (index, blobSrc, canvasElement) => {
  canvas = canvasElement;
  context = canvas.getContext('2d');
  if (ddl.inputs[index].max_dots) nDots = ddl.inputs[index].max_dots;
  if (!inpaintingControls[index]) {
    tool = new tools[ddl.inputs[index].control];
    inpaintingControls[index] = tool;
  } else {
    tool = inpaintingControls[index];
  }

  $(canvas).mousedown(canvas_event);
  $(canvas).mousemove(canvas_event);
  $(canvas).mouseup(canvas_event);
  $(canvas).mouseout(canvas_event);

  $('#color-picker').change(setStyle);
  $('#size-selector').change(setStyle);
  $("#closeFigure").change(tool.draw);
  addEraseEvent();

  background = await loadImage(blobSrc);
  canvas.height = background.height;
  canvas.width = background.width;
  setStyle();
  tool.draw();
}

addEraseEvent = () => {
  $('#erase-btn').click(function () {
    if ($(this).hasClass('activated')) {
      $(this).removeClass('activated');
      context.globalCompositeOperation = 'source-over';
    } else {
      $(this).addClass('activated');
      context.globalCompositeOperation = 'destination-out';
    }
  });
}

canvas_event = (ev) => {
  if (ev.layerX || ev.layerX == 0) { // Firefox
    ev._x = ev.layerX;
    ev._y = ev.layerY;
  } else if (ev.offsetX || ev.offsetX == 0) { // Chrome Opera
    ev._x = ev.offsetX;
    ev._y = ev.offsetY;
  }

  // Call the event handler of the tool.
  var tool_handler = tool[ev.type];
  if (tool_handler) {
    tool_handler(ev);
  }
}

function loadImage(src) {
  return new Promise((resolve, reject) => {
    var img = new Image();
    img.onload = () => resolve(img);
    img.src = src;
  });
}

function setStyle() {
  context.strokeStyle = $('#color-picker').val();
  context.lineWidth = $('#size-selector').val();
  context.lineCap = "round";
  context.lineJoin = "round";
  context.save();
}

function resetCanvas() {
  context.beginPath();
  // Use the identity matrix while clearing the canvas
  context.setTransform(1, 0, 0, 1, 0, 0);
  context.clearRect(0, 0, canvas.width, canvas.height);
}

tools.mask = function () {
  let tool = this;
  this.started = false;
  this.lastX = 0;
  this.lastY = 0;
  let state = [$('#editor-blob-left')[0].toDataURL()];
  let idx = 0;

  this.mousedown = (ev) => {
    if (state.length > idx + 1) state.splice(idx + 1);
    tool.started = true;
    tool.drawStroke(ev._x, ev._y, false);
  }

  this.mousemove = (ev) => {
    if (tool.started) {
      tool.drawStroke(ev._x, ev._y, true);
    }
  }

  this.mouseup = (ev) => {
    if (tool.started) {
      tool.mousemove(ev);
      tool.started = false;
      tool.push();
    }
  }

  this.mouseout = (ev) => {
    if (tool.started) {
      tool.mousemove(ev);
      tool.started = false;
      tool.push();
    }
  }

  this.drawStroke = (x, y, started) => {
    if (started) {
      context.beginPath();
      context.moveTo(tool.lastX, tool.lastY);
      context.lineTo(x, y);
      context.stroke();
    }
    tool.lastX = x;
    tool.lastY = y;
  }

  this.push = () => {
    idx++;
    state.push($('#editor-blob-left')[0].toDataURL());
  }

  this.undo = () => {
    if (idx > 0) {
      idx--;
      tool.draw();
    }
  }

  this.redo = () => {
    if (state[idx + 1]) {
      idx++;
      tool.draw();
    }
  }

  this.draw = () => {
    var canvasPic = new Image();
    canvasPic.src = state[idx];
    canvasPic.onload = function() {
      resetCanvas();
      context.drawImage(canvasPic, 0, 0);
    }
  }

  this.getData = () => {
    let dataURL = canvas.toDataURL();
    return dataURLtoBlob(dataURL);
  }

  this.clear = ev => {
    resetCanvas();
    tool.push();
  }
};

tools.lines = function () {
  var tool = this;
  let currentState = [];
  let state = [currentState.map(x => ([...x]))];
  let idx = 0;

  this.mousedown = (ev) => {
    if (state.length > idx + 1) {
      state.splice(idx + 1);
    }
    currentState.push([ev._x, ev._y]);
    if (currentState.length > nDots) {
      currentState.shift();
    }
    state.push(currentState.map(x => ([...x])));
    idx++;
    tool.draw();
  }

  this.undo = () => {
    if (idx > 0) {
      idx--;
      tool.draw();
    }
  }

  this.redo = () => {
    if (state[idx + 1]) {
      idx++;
      tool.draw();
    }
  }

  this.draw = () => {
    if (state[idx]) currentState = state[idx].map(x => ([...x]));
    resetCanvas();
    context.beginPath();
    for (const point of currentState) {
      context.lineTo(point[0], point[1]);
    }
    if ($("#closeFigure").prop('checked') && currentState[0]) {
      var firstPoint = currentState[0];
      context.lineTo(firstPoint[0], firstPoint[1]);
    }
    context.stroke();
  }

  this.clear = ev => {
    if (state.length > idx + 1) {
      state.splice(idx + 1);
    }
    state.push([]);
    idx++;
    currentState = state[idx].map(x => ([...x]));
    resetCanvas();
  }

  this.getData = index => {
    return state.slice(state.length - nDots, state.length);
  }

  this.push = () => {}
  this.mousemove = ev => {}
  this.mouseup = ev => {}
  this.mouseout = ev => {}
};

tools.dots = function () {
  var tool = this;
  let currentState = [];
  let state = [currentState.map(x => ([...x]))];
  let idx = 0;

  this.mousedown = (ev) => {
    if (state.length > idx + 1) {
      state.splice(idx + 1);
    }
    currentState.push([ev._x, ev._y]);
    if (currentState.length > nDots) {
      currentState.shift();
    }
    state.push(currentState.map(x => ([...x])));
    idx++;
    tool.draw();
  }

  this.undo = () => {
    if (idx > 0) {
      idx--;
      tool.draw();
    }
  }

  this.redo = () => {
    if (state[idx + 1]) {
      idx++;
      tool.draw();
    }
  }

  this.draw = () => {
    if (state[idx]) currentState = state[idx].map(x => ([...x]));
    resetCanvas();
    context.beginPath();
    for (const point of currentState) {
      context.moveTo(point[0], point[1]);
      context.lineTo(point[0], point[1]);
    }
    context.stroke();
  }

  this.clear = ev => {
    if (state.length > idx + 1) {
      state.splice(idx + 1);
    }
    state.push([]);
    idx++;
    currentState = state[idx].map(x => ([...x]));
    resetCanvas();
  }

  this.getData = index => {
    return currentState.slice(currentState.length - nDots, currentState.length);
  }

  this.push = ev => {}
  this.mousemove = ev => {}
  this.mouseup = ev => {}
  this.mouseout = ev => {}
};

function dataURLtoBlob(dataURI) {
  // convert base64/URLEncoded data component to raw binary data held in a string
  let byteString;
  if (dataURI.split(',')[0].indexOf('base64') >= 0)
    // Decode base-64 encoded string
    byteString = atob(dataURI.split(',')[1]);

  // separate out the mime component
  let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

  // write the bytes of the string to a typed array
  let dataArray = new Uint8Array(byteString.length);
  for (var i = 0; i < byteString.length; i++) {
    dataArray[i] = byteString.charCodeAt(i);
  }

  return new Blob([dataArray], { type: mimeString });
}
<commit_msg>[ClientApp/inpainting] refactor inpainting data sent when running<commit_after>var inpaintingController = inpaintingController || {};

let canvas, context;
let tool;
let tools = {};
let inpaintingControls = [];
let background;
let nDots;

inpaintingController.init = async (index, blobSrc, canvasElement) => {
  canvas = canvasElement;
  context = canvas.getContext('2d');
  if (ddl.inputs[index].max_dots) nDots = ddl.inputs[index].max_dots;
  if (!inpaintingControls[index]) {
    tool = new tools[ddl.inputs[index].control];
    inpaintingControls[index] = tool;
  } else {
    tool = inpaintingControls[index];
  }

  $(canvas).mousedown(canvas_event);
  $(canvas).mousemove(canvas_event);
  $(canvas).mouseup(canvas_event);
  $(canvas).mouseout(canvas_event);

  $('#color-picker').change(setStyle);
  $('#size-selector').change(setStyle);
  $("#closeFigure").change(tool.draw);
  addEraseEvent();

  background = await loadImage(blobSrc);
  canvas.height = background.height;
  canvas.width = background.width;
  setStyle();
  tool.draw();
}

addEraseEvent = () => {
  $('#erase-btn').click(function () {
    if ($(this).hasClass('activated')) {
      $(this).removeClass('activated');
      context.globalCompositeOperation = 'source-over';
    } else {
      $(this).addClass('activated');
      context.globalCompositeOperation = 'destination-out';
    }
  });
}

canvas_event = (ev) => {
  if (ev.layerX || ev.layerX == 0) { // Firefox
    ev._x = ev.layerX;
    ev._y = ev.layerY;
  } else if (ev.offsetX || ev.offsetX == 0) { // Chrome Opera
    ev._x = ev.offsetX;
    ev._y = ev.offsetY;
  }

  // Call the event handler of the tool.
  var tool_handler = tool[ev.type];
  if (tool_handler) {
    tool_handler(ev);
  }
}

function loadImage(src) {
  return new Promise((resolve, reject) => {
    var img = new Image();
    img.onload = () => resolve(img);
    img.src = src;
  });
}

function setStyle() {
  context.strokeStyle = $('#color-picker').val();
  context.lineWidth = $('#size-selector').val();
  context.lineCap = "round";
  context.lineJoin = "round";
  context.save();
}

function resetCanvas() {
  context.beginPath();
  // Use the identity matrix while clearing the canvas
  context.setTransform(1, 0, 0, 1, 0, 0);
  context.clearRect(0, 0, canvas.width, canvas.height);
}

tools.mask = function () {
  let tool = this;
  this.started = false;
  this.lastX = 0;
  this.lastY = 0;
  let state = [$('#editor-blob-left')[0].toDataURL()];
  let idx = 0;

  this.mousedown = (ev) => {
    if (state.length > idx + 1) state.splice(idx + 1);
    tool.started = true;
    tool.drawStroke(ev._x, ev._y, false);
  }

  this.mousemove = (ev) => {
    if (tool.started) {
      tool.drawStroke(ev._x, ev._y, true);
    }
  }

  this.mouseup = (ev) => {
    if (tool.started) {
      tool.mousemove(ev);
      tool.started = false;
      tool.push();
    }
  }

  this.mouseout = (ev) => {
    if (tool.started) {
      tool.mousemove(ev);
      tool.started = false;
      tool.push();
    }
  }

  this.drawStroke = (x, y, started) => {
    if (started) {
      context.beginPath();
      context.moveTo(tool.lastX, tool.lastY);
      context.lineTo(x, y);
      context.stroke();
    }
    tool.lastX = x;
    tool.lastY = y;
  }

  this.push = () => {
    idx++;
    state.push($('#editor-blob-left')[0].toDataURL());
  }

  this.undo = () => {
    if (idx > 0) {
      idx--;
      tool.draw();
    }
  }

  this.redo = () => {
    if (state[idx + 1]) {
      idx++;
      tool.draw();
    }
  }

  this.draw = () => {
    var canvasPic = new Image();
    canvasPic.src = state[idx];
    canvasPic.onload = function() {
      resetCanvas();
      context.drawImage(canvasPic, 0, 0);
    }
  }

  this.getData = () => {
    return dataURLtoBlob(state[idx]);
  }

  this.clear = ev => {
    resetCanvas();
    tool.push();
  }
};

tools.lines = function () {
  var tool = this;
  let currentState = [];
  let state = [currentState.map(x => ([...x]))];
  let idx = 0;

  this.mousedown = (ev) => {
    if (state.length > idx + 1) {
      state.splice(idx + 1);
    }
    currentState.push([ev._x, ev._y]);
    if (currentState.length > nDots) {
      currentState.shift();
    }
    state.push(currentState.map(x => ([...x])));
    idx++;
    tool.draw();
  }

  this.undo = () => {
    if (idx > 0) {
      idx--;
      tool.draw();
    }
  }

  this.redo = () => {
    if (state[idx + 1]) {
      idx++;
      tool.draw();
    }
  }

  this.draw = () => {
    if (state[idx]) currentState = state[idx].map(x => ([...x]));
    resetCanvas();
    context.beginPath();
    for (const point of currentState) {
      context.lineTo(point[0], point[1]);
    }
    if ($("#closeFigure").prop('checked') && currentState[0]) {
      var firstPoint = currentState[0];
      context.lineTo(firstPoint[0], firstPoint[1]);
    }
    context.stroke();
  }

  this.clear = ev => {
    if (state.length > idx + 1) {
      state.splice(idx + 1);
    }
    state.push([]);
    idx++;
    currentState = state[idx].map(x => ([...x]));
    resetCanvas();
  }

  this.getData = index => {
    return state[idx];
  }

  this.push = () => {}
  this.mousemove = ev => {}
  this.mouseup = ev => {}
  this.mouseout = ev => {}
};

tools.dots = function () {
  var tool = this;
  let currentState = [];
  let state = [currentState.map(x => ([...x]))];
  let idx = 0;

  this.mousedown = (ev) => {
    if (state.length > idx + 1) {
      state.splice(idx + 1);
    }
    currentState.push([ev._x, ev._y]);
    if (currentState.length > nDots) {
      currentState.shift();
    }
    state.push(currentState.map(x => ([...x])));
    idx++;
    tool.draw();
  }

  this.undo = () => {
    if (idx > 0) {
      idx--;
      tool.draw();
    }
  }

  this.redo = () => {
    if (state[idx + 1]) {
      idx++;
      tool.draw();
    }
  }

  this.draw = () => {
    if (state[idx]) currentState = state[idx].map(x => ([...x]));
    resetCanvas();
    context.beginPath();
    for (const point of currentState) {
      context.moveTo(point[0], point[1]);
      context.lineTo(point[0], point[1]);
    }
    context.stroke();
  }

  this.clear = ev => {
    if (state.length > idx + 1) {
      state.splice(idx + 1);
    }
    state.push([]);
    idx++;
    currentState = state[idx].map(x => ([...x]));
    resetCanvas();
  }

  this.getData = index => {
    return state[idx];
  }

  this.push = ev => {}
  this.mousemove = ev => {}
  this.mouseup = ev => {}
  this.mouseout = ev => {}
};

function dataURLtoBlob(dataURI) {
  // convert base64/URLEncoded data component to raw binary data held in a string
  let byteString;
  if (dataURI.split(',')[0].indexOf('base64') >= 0)
    // Decode base-64 encoded string
    byteString = atob(dataURI.split(',')[1]);

  // separate out the mime component
  let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

  // write the bytes of the string to a typed array
  let dataArray = new Uint8Array(byteString.length);
  for (var i = 0; i < byteString.length; i++) {
    dataArray[i] = byteString.charCodeAt(i);
  }

  return new Blob([dataArray], { type: mimeString });
}
<|endoftext|>


<commit_before>import Action from './action'
import Emitter from './emitter'
import defaultUpdateStrategy from './default-update-strategy'
import { merge } from './utils'
import { BIRTH, START } from './lifecycle'

const DEFAULTS = {
  maxHistory: 1,
  batch: false,
  updater: defaultUpdateStrategy
}

/**
 * @fileoverview All Microcosms have a history. This history keeps
 * track of outstanding actions, working with a Microcosm to determine
 * the next application state as actions move through different
 * states.
 */
class History extends Emitter {
  constructor(config) {
    super()

    let options = merge(DEFAULTS, config)

    this.size = 0
    this.limit = Math.max(1, options.maxHistory)

    this.updater = options.updater(options)

    // Track whether a release is pending. This prevents .wait() from getting
    // stuck in limbo
    this.releasing = false

    this.release = () => this.closeRelease()

    this.begin()
  }

  /**
   * Set the head of the tree to a target action. This has the effect
   * of controlling time in a Microcosm's history.
   * @param {Action} action The new head of the tree
   */
  checkout(action) {
    let sharedRoot = this.sharedRoot(action) || this.head

    this.head = action || this.head

    // Each action has a "next" property that tells the history how to
    //  move forward. Update that path back to the sharedRoot:
    let cursor = this.head
    while (cursor != sharedRoot) {
      let parent = cursor.parent
      parent.next = cursor

      cursor = parent
    }

    this.setSize()

    this.reconcile(sharedRoot)

    return this
  }

  /**
   * Toggle actions in bulk, then reconcile from the first action
   * @param {Action[]} - A list of actions to toggle
   */
  toggle(actions) {
    let list = [].concat(actions)

    list.forEach(action => action.toggle('silently'))

    this.reconcile(list[0])
  }

  /**
   * Convert the active branch of history into an array.
   */
  toArray() {
    return this.map(n => n)
  }

  /**
   * Map over the active branch.
   * @param {Function} fn
   * @param {*} scope
   */
  map(fn, scope) {
    let size = this.size
    let items = Array(size)
    let action = this.head

    while (size--) {
      items[size] = fn.call(scope, action)
      action = action.parent
    }

    return items
  }

  /**
   * Return a promise that represents the resolution of all actions in
   * the current branch.
   * @returns {Promise}
   */
  wait() {
    let actions = this.toArray()

    return new Promise((resolve, reject) => {
      const checkStatus = () => {
        let done = actions.every(action => action.complete)
        let errors = actions.filter(action => action.is('reject'))

        if (done) {
          this.off('release', checkStatus)

          if (errors.length) {
            reject(errors[0].payload)
          } else {
            resolve()
          }
        }
      }

      if (this.releasing === false) {
        checkStatus()
      }

      this.on('release', checkStatus)
    })
  }

  /**
   * Chain off of wait(). Provides a promise interface
   * @returns {Promise}
   */
  then(pass, fail) {
    return this.wait().then(pass, fail)
  }

  /**
   * Setup the head and root action for a history. This effectively
   * starts or restarts history.
   */
  begin() {
    this.head = this.root = null
    this.append(START, 'resolve')
  }

  /**
   * Append a new action to the end of history
   * @param {Function|string} command
   * @param {string} [status]
   */
  append(command, status) {
    let action = new Action(command, status)

    if (this.size > 0) {
      this.head.lead(action)
    } else {
      // Always have a parent node, no matter what
      let birth = new Action(BIRTH, 'resolve')
      birth.adopt(action)

      this.root = action
    }

    this.head = action
    this.size += 1

    this._emit('append', action)

    action.on('change', this.reconcile, this)

    return this.head
  }

  /**
   * Remove an action from history, connecting adjacent actions
   * together to bridge the gap.
   * @param {Action} action - Action to remove from history
   */
  remove(action) {
    if (action.isDisconnected()) {
      return
    }

    // cache linking references and activeness
    let parent = action.parent
    let next = action.next
    let wasActive = this.isActive(action)

    this.clean(action)

    // if there are no more actions left, we're done
    if (this.size <= 0) {
      this.begin()
      return
    }

    // reset head/root references if necessary
    if (action === this.head) {
      next = this.head = parent
    } else if (action === this.root) {
      this.root = next
    }

    // reconcile history if action was in active history branch
    if (wasActive && !action.disabled) {
      this.reconcile(next)
    }
  }

  /**
   * The actual clean up operation that purges an action from both
   * history, and removes all snapshots within tracking repos.
   * @param {Action} action - Action to clean up
   */
  clean(action) {
    this.size -= 1

    this._emit('remove', action)

    action.remove()
  }

  /**
   * Starting with a given action, emit events such that repos can
   * dispatch actions to domains in a consistent order to build a new
   * state. This is how Microcosm updates state.
   * @param {Action} action
   */
  reconcile(action) {
    console.assert(this.head, 'History should always have a head node')
    console.assert(action, 'History should never reconcile ' + action)

    let focus = action

    while (focus) {
      this._emit('update', focus)

      if (focus === this.head) {
        break
      } else {
        focus = focus.next
      }
    }

    this.archive()

    this._emit('reconcile', action)

    this.queueRelease()
  }

  /**
   * Batch releases by "queuing" an update. See `closeRelease`.
   */
  queueRelease() {
    if (this.releasing === false) {
      this.releasing = true
      this.updater(this.release)
    }
  }

  /**
   * Complete a release by emitting the "release" event. This function
   * is called by the updater for the given history. If batching is
   * enabled, it will be asynchronous.
   */
  closeRelease() {
    this.releasing = false
    this._emit('release')
  }

  /**
   * Instead of holding on to actions forever, Microcosm initiates an
   * archival process at the end of every reconciliation. If the
   * active branch of history is greater than the `limit` property,
   * signal that the action should be removed.
   */
  archive() {
    let size = this.size
    let root = this.root

    while (size > this.limit && root.complete) {
      size -= 1
      this._emit('remove', root.parent)
      root = root.next
    }

    root.prune()

    this.root = root
    this.size = size
  }

  /**
   * Update the size of the tree by bubbling up from the head to the
   * root.
   */
  setSize() {
    let action = this.head
    let size = 1

    while (action !== this.root) {
      action = action.parent
      size += 1
    }

    this.size = size
  }

  /**
   * Determine if provided action is within active history branch
   * @param {Action} action
   */
  isActive(action) {
    let cursor = action
    while (cursor) {
      if (cursor === this.head) {
        return true
      }
      cursor = cursor.next
    }

    return false
  }

  sharedRoot(action) {
    let cursor = action

    while (cursor) {
      if (this.isActive(cursor)) {
        return cursor
      }
      cursor = cursor.parent
    }
  }
}

export default History
<commit_msg>Provide method documentation for History::sharedRoot<commit_after>import Action from './action'
import Emitter from './emitter'
import defaultUpdateStrategy from './default-update-strategy'
import { merge } from './utils'
import { BIRTH, START } from './lifecycle'

const DEFAULTS = {
  maxHistory: 1,
  batch: false,
  updater: defaultUpdateStrategy
}

/**
 * @fileoverview All Microcosms have a history. This history keeps
 * track of outstanding actions, working with a Microcosm to determine
 * the next application state as actions move through different
 * states.
 */
class History extends Emitter {
  constructor(config) {
    super()

    let options = merge(DEFAULTS, config)

    this.size = 0
    this.limit = Math.max(1, options.maxHistory)

    this.updater = options.updater(options)

    // Track whether a release is pending. This prevents .wait() from getting
    // stuck in limbo
    this.releasing = false

    this.release = () => this.closeRelease()

    this.begin()
  }

  /**
   * Set the head of the tree to a target action. This has the effect
   * of controlling time in a Microcosm's history.
   * @param {Action} action The new head of the tree
   */
  checkout(action) {
    let sharedRoot = this.sharedRoot(action) || this.head

    this.head = action || this.head

    // Each action has a "next" property that tells the history how to
    //  move forward. Update that path back to the sharedRoot:
    let cursor = this.head
    while (cursor != sharedRoot) {
      let parent = cursor.parent
      parent.next = cursor

      cursor = parent
    }

    this.setSize()

    this.reconcile(sharedRoot)

    return this
  }

  /**
   * Toggle actions in bulk, then reconcile from the first action
   * @param {Action[]} - A list of actions to toggle
   */
  toggle(actions) {
    let list = [].concat(actions)

    list.forEach(action => action.toggle('silently'))

    this.reconcile(list[0])
  }

  /**
   * Convert the active branch of history into an array.
   */
  toArray() {
    return this.map(n => n)
  }

  /**
   * Map over the active branch.
   * @param {Function} fn
   * @param {*} scope
   */
  map(fn, scope) {
    let size = this.size
    let items = Array(size)
    let action = this.head

    while (size--) {
      items[size] = fn.call(scope, action)
      action = action.parent
    }

    return items
  }

  /**
   * Return a promise that represents the resolution of all actions in
   * the current branch.
   * @returns {Promise}
   */
  wait() {
    let actions = this.toArray()

    return new Promise((resolve, reject) => {
      const checkStatus = () => {
        let done = actions.every(action => action.complete)
        let errors = actions.filter(action => action.is('reject'))

        if (done) {
          this.off('release', checkStatus)

          if (errors.length) {
            reject(errors[0].payload)
          } else {
            resolve()
          }
        }
      }

      if (this.releasing === false) {
        checkStatus()
      }

      this.on('release', checkStatus)
    })
  }

  /**
   * Chain off of wait(). Provides a promise interface
   * @returns {Promise}
   */
  then(pass, fail) {
    return this.wait().then(pass, fail)
  }

  /**
   * Setup the head and root action for a history. This effectively
   * starts or restarts history.
   */
  begin() {
    this.head = this.root = null
    this.append(START, 'resolve')
  }

  /**
   * Append a new action to the end of history
   * @param {Function|string} command
   * @param {string} [status]
   */
  append(command, status) {
    let action = new Action(command, status)

    if (this.size > 0) {
      this.head.lead(action)
    } else {
      // Always have a parent node, no matter what
      let birth = new Action(BIRTH, 'resolve')
      birth.adopt(action)

      this.root = action
    }

    this.head = action
    this.size += 1

    this._emit('append', action)

    action.on('change', this.reconcile, this)

    return this.head
  }

  /**
   * Remove an action from history, connecting adjacent actions
   * together to bridge the gap.
   * @param {Action} action - Action to remove from history
   */
  remove(action) {
    if (action.isDisconnected()) {
      return
    }

    // cache linking references and activeness
    let parent = action.parent
    let next = action.next
    let wasActive = this.isActive(action)

    this.clean(action)

    // if there are no more actions left, we're done
    if (this.size <= 0) {
      this.begin()
      return
    }

    // reset head/root references if necessary
    if (action === this.head) {
      next = this.head = parent
    } else if (action === this.root) {
      this.root = next
    }

    // reconcile history if action was in active history branch
    if (wasActive && !action.disabled) {
      this.reconcile(next)
    }
  }

  /**
   * The actual clean up operation that purges an action from both
   * history, and removes all snapshots within tracking repos.
   * @param {Action} action - Action to clean up
   */
  clean(action) {
    this.size -= 1

    this._emit('remove', action)

    action.remove()
  }

  /**
   * Starting with a given action, emit events such that repos can
   * dispatch actions to domains in a consistent order to build a new
   * state. This is how Microcosm updates state.
   * @param {Action} action
   */
  reconcile(action) {
    console.assert(this.head, 'History should always have a head node')
    console.assert(action, 'History should never reconcile ' + action)

    let focus = action

    while (focus) {
      this._emit('update', focus)

      if (focus === this.head) {
        break
      } else {
        focus = focus.next
      }
    }

    this.archive()

    this._emit('reconcile', action)

    this.queueRelease()
  }

  /**
   * Batch releases by "queuing" an update. See `closeRelease`.
   */
  queueRelease() {
    if (this.releasing === false) {
      this.releasing = true
      this.updater(this.release)
    }
  }

  /**
   * Complete a release by emitting the "release" event. This function
   * is called by the updater for the given history. If batching is
   * enabled, it will be asynchronous.
   */
  closeRelease() {
    this.releasing = false
    this._emit('release')
  }

  /**
   * Instead of holding on to actions forever, Microcosm initiates an
   * archival process at the end of every reconciliation. If the
   * active branch of history is greater than the `limit` property,
   * signal that the action should be removed.
   */
  archive() {
    let size = this.size
    let root = this.root

    while (size > this.limit && root.complete) {
      size -= 1
      this._emit('remove', root.parent)
      root = root.next
    }

    root.prune()

    this.root = root
    this.size = size
  }

  /**
   * Update the size of the tree by bubbling up from the head to the
   * root.
   */
  setSize() {
    let action = this.head
    let size = 1

    while (action !== this.root) {
      action = action.parent
      size += 1
    }

    this.size = size
  }

  /**
   * Determine if provided action is within active history branch
   * @param {Action} action
   */
  isActive(action) {
    let cursor = action
    while (cursor) {
      if (cursor === this.head) {
        return true
      }
      cursor = cursor.next
    }

    return false
  }

  /**
   * Starting with the provided action, navigate up the parent chain
   * until you find an action which is active. That action is the shared
   * root between the provided action and the current head.
   * @param {Action} action
   */
  sharedRoot(action) {
    let cursor = action

    while (cursor) {
      if (this.isActive(cursor)) {
        return cursor
      }
      cursor = cursor.parent
    }
  }
}

export default History
<|endoftext|>


<commit_before>UserQuestions = new Mongo.Collection('userQuestions');

var Schemas = {};

UniverseSchema = new SimpleSchema({
	universeId: {
		type: String,
		label: 'Univers ID'
	},
	matchingPower: {
		type: Number,
		decimal: true,
		label: 'Matching power or the universe against the question or choice'
	}
});

WorkshopSchema = new SimpleSchema({
	workshopId: {
		type: String,
		label: 'Workshop ID'
	},
	matchingPower: {
		type: Number,
		decimal: true,
		label: 'Matching power or the workshop against the question or choice'
	}
});

ChoiceSchema = new SimpleSchema({
	choiceId: {
		type: String,
		label: 'Choice ID'
	},
	label: {
		type: String,
		label: 'tradID via i18n'
	},
	pictureSource: {
		type: String,
		label: 'Url or image path',
		optional: true
	},
	universesLinked: {
		type: [UniverseSchema],
		label: 'List of the linked universes to the choice'
	},
	workshopsLinked: {
		type: [WorkshopSchema],
		label: 'List of the linked workshops to the choice'
	}
});

ResultSchema = new SimpleSchema({
	universeId: {
		type: String,
		label: 'Univers ID',
		optional: true
	},
	workshopId: {
		type: String,
		label: 'Workshop ID',
		optional: true
	},
	result: {
		type: Number,
		decimal: true,
		label: 'Result for an univer of a workshop'
	}
});

Schemas.UserQuestions = new SimpleSchema({
	questionId: {
		type: String,
		label: 'Question ID'
	},
	userId: {
		type: String,
		label: 'User Id'
	},
	answered: {
		type: Boolean,
		label: 'Does the user has answered the question ?'
	},
	level: {
		type: Number,
		label: 'Question level'
	},
	version: {
		type: Number,
		label: 'Question version'
	},
	choices: {
		type: [ChoiceSchema],
		label: 'Choices list'
	},
	choiceSelected: {
		type: String,
		label: 'User choice',
		optional: true
	},
	deprecated: {
		type: Boolean,
		label: 'Is the question deprecated or not'
	},
	universesLinked: {
		type: [UniverseSchema],
		label: 'List of the linked universe to the question'
	},
	workshopsLinked: {
		type: [WorkshopSchema],
		label: 'List of the linked workshops to the question'
	},
	answerDate: {
		type: Date,
		label: 'Answer date',
		optional: true
	},
	displayType: {
		type: String,
		label: 'Question display',
		allowedValues: ['text', 'picture']
	},
	title: {
		type: String,
		label: 'Question title'
	},
	result: {
		type: [ResultSchema],
		label: 'Question result  via Choice x Question univers & workshop matching power',
		optional: true
	}
});

UserQuestions.attachSchema(Schemas.UserQuestions);

Meteor.methods({
	insertQuestion(data) {
		check(data, Object);
		check(data.title, String);
		check(data.level, Number);
		check(data.displayType, String);
		check(data.version, Number);
		check(data.deprecated, Boolean);
		check(data.choices, Array);
		check(data.universesLinked, Array);
		check(data.workshopsLinked, Array);
		check(data.answered, Boolean);
		check(data.userId, String);
		check(data.questionId, String);
		return UserQuestions.insert(data);
	},
	answerQuestion(data) {
		check(data, Object);
		check(data.userQuestionId, String);
		check(data.choiceSelected, String);
		return UserQuestions.update({ _id: data.userQuestionId }, {
			$set: {
				choiceSelected: data.choiceSelected,
				answered: true,
				answerDate: new Date()
			}
		});
	}
});
<commit_msg>WIP function to calculate the result of a question<commit_after>UserQuestions = new Mongo.Collection('userQuestions');

var Schemas = {};

UniverseSchema = new SimpleSchema({
	universeId: {
		type: String,
		label: 'Univers ID'
	},
	matchingPower: {
		type: Number,
		decimal: true,
		label: 'Matching power or the universe against the question or choice'
	}
});

WorkshopSchema = new SimpleSchema({
	workshopId: {
		type: String,
		label: 'Workshop ID'
	},
	matchingPower: {
		type: Number,
		decimal: true,
		label: 'Matching power or the workshop against the question or choice'
	}
});

ChoiceSchema = new SimpleSchema({
	choiceId: {
		type: String,
		label: 'Choice ID'
	},
	label: {
		type: String,
		label: 'tradID via i18n'
	},
	pictureSource: {
		type: String,
		label: 'Url or image path',
		optional: true
	},
	universesLinked: {
		type: [UniverseSchema],
		label: 'List of the linked universes to the choice'
	},
	workshopsLinked: {
		type: [WorkshopSchema],
		label: 'List of the linked workshops to the choice'
	}
});

ResultSchema = new SimpleSchema({
	universeId: {
		type: String,
		label: 'Univers ID',
		optional: true
	},
	workshopId: {
		type: String,
		label: 'Workshop ID',
		optional: true
	},
	result: {
		type: Number,
		decimal: true,
		label: 'Result for an univer of a workshop'
	}
});

Schemas.UserQuestions = new SimpleSchema({
	questionId: {
		type: String,
		label: 'Question ID'
	},
	userId: {
		type: String,
		label: 'User Id'
	},
	answered: {
		type: Boolean,
		label: 'Does the user has answered the question ?'
	},
	level: {
		type: Number,
		label: 'Question level'
	},
	version: {
		type: Number,
		label: 'Question version'
	},
	choices: {
		type: [ChoiceSchema],
		label: 'Choices list'
	},
	choiceSelected: {
		type: String,
		label: 'User choice',
		optional: true
	},
	deprecated: {
		type: Boolean,
		label: 'Is the question deprecated or not'
	},
	universesLinked: {
		type: [UniverseSchema],
		label: 'List of the linked universe to the question'
	},
	workshopsLinked: {
		type: [WorkshopSchema],
		label: 'List of the linked workshops to the question'
	},
	answerDate: {
		type: Date,
		label: 'Answer date',
		optional: true
	},
	displayType: {
		type: String,
		label: 'Question display',
		allowedValues: ['text', 'picture']
	},
	title: {
		type: String,
		label: 'Question title'
	},
	result: {
		type: [ResultSchema],
		label: 'Question result  via Choice x Question univers & workshop matching power',
		optional: true
	}
});

UserQuestions.attachSchema(Schemas.UserQuestions);

Meteor.methods({
	insertQuestion(data) {
		check(data, Object);
		check(data.title, String);
		check(data.level, Number);
		check(data.displayType, String);
		check(data.version, Number);
		check(data.deprecated, Boolean);
		check(data.choices, Array);
		check(data.universesLinked, Array);
		check(data.workshopsLinked, Array);
		check(data.answered, Boolean);
		check(data.userId, String);
		check(data.questionId, String);
		return UserQuestions.insert(data);
	},
	answerQuestion(data) {
		check(data, Object);
		check(data.userQuestionId, String);
		check(data.choiceSelected, String);
		return UserQuestions.update({ _id: data.userQuestionId }, {
			$set: {
				choiceSelected: data.choiceSelected,
				answered: true,
				answerDate: new Date()
			}
		});
	},
	calcQuestionResult(data) {
		check(data, Object);
		check(data.userQuestionId, String);
		check(data.choiceSelected, String);
		let questionData = UserQuestions.findOne({ _id: userQuestionId });
		let ind = lodash.findIndex(questionData.choices, ['choiceId', data.choiceSelected]);
		return;
	}
});
<|endoftext|>


<commit_before>const t = require('@babel/types')
const babelTemplate = require('@babel/template').default

const buildDynamicImport = babelTemplate(`var IMPORT_NAME = ()=>import(IMPORT_SOURCE)`, {
  preserveComments: true,
  plugins: [
    'dynamicImport'
  ]
})
// var test = ()=>import(/* webpackChunkName: "components/test" */'../../components/test')
function getDynamicImport (name, source, chunkName) {
  const stringLiteral = t.stringLiteral(source)
  const dynamicImportComment = {
    type: 'CommentBlock',
    value: `webpackChunkName: "${chunkName}"`
  }
  stringLiteral.leadingComments = [dynamicImportComment]
  return buildDynamicImport({
    IMPORT_NAME: t.identifier(name),
    IMPORT_SOURCE: stringLiteral
  })
}

module.exports = function ({
  types: t
}) {
  return {
    visitor: {
      ImportDeclaration (path, state) {
        const dynamicImport = state.opts.dynamicImports[path.node.source.value]
        if (dynamicImport) {
          path.insertBefore(
            getDynamicImport(
              path.node.specifiers[0].local.name,
              dynamicImport.source,
              dynamicImport.chunkName
            )
          )
          path.remove()
        }
      }
    }
  }
}
<commit_msg>fix(cli): compatible with @vue/cli-plugin-babel@4.2.3  #1394<commit_after>const t = require('@babel/types')
const babelTemplate = require('@babel/template').default

// const buildDynamicImport = babelTemplate(`var IMPORT_NAME = ()=>import(IMPORT_SOURCE)`, {
//   preserveComments: true,
//   plugins: [
//     'dynamicImport'
//   ]
// })
// 已废弃，@vue/cli-plugin-babel@4 增加了 dynamic import 转换
// var test = ()=>import(/* webpackChunkName: "components/test" */'../../components/test')
// function getDynamicImport (name, source, chunkName) {
//   const stringLiteral = t.stringLiteral(source)
//   const dynamicImportComment = {
//     type: 'CommentBlock',
//     value: `webpackChunkName: "${chunkName}"`
//   }
//   stringLiteral.leadingComments = [dynamicImportComment]
//   return buildDynamicImport({
//     IMPORT_NAME: t.identifier(name),
//     IMPORT_SOURCE: stringLiteral
//   })
// }
// var test = function(resolve) {require.ensure([], () => resolve(require('../../components/test')),'components/test')}
const buildRequireEnsure = babelTemplate(
  `var IMPORT_NAME = function(){require.ensure([],()=>resolve(require(IMPORT_SOURCE)),CHUNK_NAME)}`
)

function getRequireEnsure (name, source, chunkName) {
  return buildRequireEnsure({
    IMPORT_NAME: t.identifier(name),
    IMPORT_SOURCE: t.stringLiteral(source),
    CHUNK_NAME: t.stringLiteral(chunkName)
  })
}

module.exports = function ({
  types: t
}) {
  return {
    visitor: {
      ImportDeclaration (path, state) {
        const dynamicImport = state.opts.dynamicImports[path.node.source.value]
        if (dynamicImport) {
          path.insertBefore(
            getRequireEnsure(
              path.node.specifiers[0].local.name,
              dynamicImport.source,
              dynamicImport.chunkName
            )
          )
          path.remove()
        }
      }
    }
  }
}
<|endoftext|>


<commit_before>//= require /bower_components/d3/d3.min.js
//= require /d3/chartLabelUtil
//= require /d3/trafficLightDataProvider
//= require /d3/chartColorProvider
//= require_self
//= require /chartComponents/utility
//= require /chartComponents/common
//= require /chartComponents/chartHeader
//= require /chartComponents/chartBarScore
//= require /chartComponents/chartBars
//= require /chartComponents/chartMultiLegend.js

"use strict";
var OpenSpeedMonitor = OpenSpeedMonitor || {};
OpenSpeedMonitor.ChartModules = OpenSpeedMonitor.ChartModules || {};

OpenSpeedMonitor.ChartModules.PageComparisonChart = (function (chartIdentifier) {

    var svg = d3.select(chartIdentifier);
    var transitionDuration = OpenSpeedMonitor.ChartComponents.common.transitionDuration;
    var data = OpenSpeedMonitor.ChartModules.PageComparisonData(svg);
    var chartBarsComponents = {};
    var chartBarScoreComponent = OpenSpeedMonitor.ChartComponents.ChartBarScore();
    var chartLegendComponent = OpenSpeedMonitor.ChartComponents.ChartMultiLegend();
    var chartHeaderComponent = OpenSpeedMonitor.ChartComponents.ChartHeader();
    var highlightedIds = "";
    var anyHighlighted = false;
    var selectedIds = "";
    var anySelected = false;

    chartLegendComponent.on("select", function (selectEvent) {
        selectedIds = selectEvent.ids;
        anySelected = selectEvent.anySelected;
        render()
    });

    chartLegendComponent.on("highlight", function (highlightEvent) {
        highlightedIds = highlightEvent.ids;
        anyHighlighted = highlightEvent.anyHighlighted;
        render()
    });

    var setData = function (inputData) {
        data.setData(inputData);
        chartHeaderComponent.setData(data.getDataForHeader());
        chartBarScoreComponent.setData(data.getDataForBarScore());
        chartLegendComponent.setData(data.getDataForLegend());
        setBarChartData();
        registerBarEvents();
    };

    var setBarChartData = function () {
        var componentsToRender = [];
        var firstPage = OpenSpeedMonitor.ChartComponents.ChartBars();
        firstPage.setData(data.getDataForBars(0));
        firstPage.setOpacitiyFunction(determineBarOpacity);
        var secondPage = OpenSpeedMonitor.ChartComponents.ChartBars();
        secondPage.setData(data.getDataForBars(1));
        secondPage.setOpacitiyFunction(determineBarOpacity);
        componentsToRender.push(secondPage);
        componentsToRender.push(firstPage);
        chartBarsComponents = componentsToRender;
    };

    var registerBarEvents = function () {
        chartBarsComponents.forEach(function (barGroup) {
            barGroup.on("click", function (bar) {
                chartLegendComponent.clickEntry([bar.id]);
            });
            barGroup.on("mouseover", function (bar) {
                chartLegendComponent.mouseOverEntry([bar.id])
            });
            barGroup.on("mouseout", function (bar) {
                chartLegendComponent.mouseOutEntry([bar.id])
            });
        });
    };

    var determineBarOpacity = function (d) {
        var opacity = 1;
        if(anyHighlighted){
            opacity = 0.2;
            if(highlightedIds.indexOf(d.id) > -1) opacity = 1;
        }
        if(anySelected){
            opacity = 0.2;
            if(selectedIds.indexOf(d.id) > -1) opacity = 1;
        }
        return opacity
    };

    var render = function () {
        var shouldShowScore = data.hasLoadTimes();
        var componentMargin = OpenSpeedMonitor.ChartComponents.common.ComponentMargin;
        var headerHeight = OpenSpeedMonitor.ChartComponents.ChartHeader.Height + componentMargin;
        var barScorePosY = data.getChartBarsHeight() + componentMargin;
        var barScoreHeight = shouldShowScore ? OpenSpeedMonitor.ChartComponents.common.barBand + componentMargin : 0;
        var legendPosY = barScorePosY + barScoreHeight;
        var legendHeight = chartLegendComponent.estimateHeight(svg) + componentMargin;
        var chartHeight = headerHeight + data.getChartBarsHeight() + barScoreHeight + legendHeight + 20;

        svg.transition()
            .duration(transitionDuration);

        var svgName = chartIdentifier.substr(1);
        document.getElementById(svgName).setAttribute("height",chartHeight);

        var contentGroup = svg.selectAll(".bars-content-group").data([1]);
        contentGroup.enter()
            .append("g")
            .classed("bars-content-group", true);

        contentGroup.attr("transform",
            "translate(" + (data.getChartSideLabelsWidth() + componentMargin) + ", " + headerHeight + ")");

        renderHeader(svg);
        renderBars(contentGroup);
        renderBarScore(contentGroup, shouldShowScore, barScorePosY);
        renderLegend(contentGroup, legendPosY);
    };

    var renderLegend = function (svg, posY) {
        var legend = svg.selectAll(".chart-legend-group").data([chartLegendComponent]);
        legend.exit()
            .remove();
        legend.enter()
            .append("g")
            .attr("class", "chart-legend-group")
            .attr("transform", "translate(0, " + posY + ")");
        legend.call(chartLegendComponent.render)
            .transition()
            .duration(transitionDuration)
            .attr("transform", "translate(0, " + posY + ")");
    };

    var renderBars = function (contentGroup) {
        var chartBarsGroup = contentGroup.selectAll(".chart-bar-group").data([1]);
        chartBarsGroup.enter()
            .append("g")
            .attr("class", "chart-bar-group");
        var chartBars = chartBarsGroup.selectAll(".chart-bars").data(chartBarsComponents);
        chartBars.exit()
            .transition()
            .duration(transitionDuration)
            .attr("transform", "translate(0, 0)")
            .style("opacity", 0)
            .remove();
        chartBars.enter()
            .append("g")
            .attr("class", "chart-bars");
        chartBars
            .each(function (chartBarsComponent) {
                chartBarsComponent.render(d3.select(this));
            });
    };

    var renderHeader = function (svg) {
        var header = svg.selectAll(".header-group").data([chartHeaderComponent]);
        header.exit()
            .remove();
        header.enter()
            .append("g")
            .classed("header-group", true);
        header.call(chartHeaderComponent.render);
    };
    var renderBarScore = function (svg, shouldShowScore, posY) {
        var barScore = svg.selectAll(".chart-score-group").data([chartBarScoreComponent]);
        barScore.exit()
            .remove();
        barScore.enter()
            .append("g")
            .attr("class", "chart-score-group")
            .attr("transform", "translate(0, " + posY + ")");
        barScore
            .call(chartBarScoreComponent.render)
            .transition()
            .style("opacity", shouldShowScore ? 1 : 0)
            .duration(transitionDuration)
            .attr("transform", "translate(0, " + posY + ")");
    };

    return {
        setData: setData,
        render: render
    };

});
<commit_msg>[IT-2138] workaround, switched push order of components so the subsequent page is on top and visible<commit_after>//= require /bower_components/d3/d3.min.js
//= require /d3/chartLabelUtil
//= require /d3/trafficLightDataProvider
//= require /d3/chartColorProvider
//= require_self
//= require /chartComponents/utility
//= require /chartComponents/common
//= require /chartComponents/chartHeader
//= require /chartComponents/chartBarScore
//= require /chartComponents/chartBars
//= require /chartComponents/chartMultiLegend.js

"use strict";
var OpenSpeedMonitor = OpenSpeedMonitor || {};
OpenSpeedMonitor.ChartModules = OpenSpeedMonitor.ChartModules || {};

OpenSpeedMonitor.ChartModules.PageComparisonChart = (function (chartIdentifier) {

    var svg = d3.select(chartIdentifier);
    var transitionDuration = OpenSpeedMonitor.ChartComponents.common.transitionDuration;
    var data = OpenSpeedMonitor.ChartModules.PageComparisonData(svg);
    var chartBarsComponents = {};
    var chartBarScoreComponent = OpenSpeedMonitor.ChartComponents.ChartBarScore();
    var chartLegendComponent = OpenSpeedMonitor.ChartComponents.ChartMultiLegend();
    var chartHeaderComponent = OpenSpeedMonitor.ChartComponents.ChartHeader();
    var highlightedIds = "";
    var anyHighlighted = false;
    var selectedIds = "";
    var anySelected = false;

    chartLegendComponent.on("select", function (selectEvent) {
        selectedIds = selectEvent.ids;
        anySelected = selectEvent.anySelected;
        render()
    });

    chartLegendComponent.on("highlight", function (highlightEvent) {
        highlightedIds = highlightEvent.ids;
        anyHighlighted = highlightEvent.anyHighlighted;
        render()
    });

    var setData = function (inputData) {
        data.setData(inputData);
        chartHeaderComponent.setData(data.getDataForHeader());
        chartBarScoreComponent.setData(data.getDataForBarScore());
        chartLegendComponent.setData(data.getDataForLegend());
        setBarChartData();
        registerBarEvents();
    };

    var setBarChartData = function () {
        var componentsToRender = [];
        var firstPage = OpenSpeedMonitor.ChartComponents.ChartBars();
        firstPage.setData(data.getDataForBars(0));
        firstPage.setOpacitiyFunction(determineBarOpacity);
        var secondPage = OpenSpeedMonitor.ChartComponents.ChartBars();
        secondPage.setData(data.getDataForBars(1));
        secondPage.setOpacitiyFunction(determineBarOpacity);
        componentsToRender.push(firstPage);
        componentsToRender.push(secondPage);
        chartBarsComponents = componentsToRender;
    };

    var registerBarEvents = function () {
        chartBarsComponents.forEach(function (barGroup) {
            barGroup.on("click", function (bar) {
                chartLegendComponent.clickEntry([bar.id]);
            });
            barGroup.on("mouseover", function (bar) {
                chartLegendComponent.mouseOverEntry([bar.id])
            });
            barGroup.on("mouseout", function (bar) {
                chartLegendComponent.mouseOutEntry([bar.id])
            });
        });
    };

    var determineBarOpacity = function (d) {
        var opacity = 1;
        if(anyHighlighted){
            opacity = 0.2;
            if(highlightedIds.indexOf(d.id) > -1) opacity = 1;
        }
        if(anySelected){
            opacity = 0.2;
            if(selectedIds.indexOf(d.id) > -1) opacity = 1;
        }
        return opacity
    };

    var render = function () {
        var shouldShowScore = data.hasLoadTimes();
        var componentMargin = OpenSpeedMonitor.ChartComponents.common.ComponentMargin;
        var headerHeight = OpenSpeedMonitor.ChartComponents.ChartHeader.Height + componentMargin;
        var barScorePosY = data.getChartBarsHeight() + componentMargin;
        var barScoreHeight = shouldShowScore ? OpenSpeedMonitor.ChartComponents.common.barBand + componentMargin : 0;
        var legendPosY = barScorePosY + barScoreHeight;
        var legendHeight = chartLegendComponent.estimateHeight(svg) + componentMargin;
        var chartHeight = headerHeight + data.getChartBarsHeight() + barScoreHeight + legendHeight + 20;

        svg.transition()
            .duration(transitionDuration);

        var svgName = chartIdentifier.substr(1);
        document.getElementById(svgName).setAttribute("height",chartHeight);

        var contentGroup = svg.selectAll(".bars-content-group").data([1]);
        contentGroup.enter()
            .append("g")
            .classed("bars-content-group", true);

        contentGroup.attr("transform",
            "translate(" + (data.getChartSideLabelsWidth() + componentMargin) + ", " + headerHeight + ")");

        renderHeader(svg);
        renderBars(contentGroup);
        renderBarScore(contentGroup, shouldShowScore, barScorePosY);
        renderLegend(contentGroup, legendPosY);
    };

    var renderLegend = function (svg, posY) {
        var legend = svg.selectAll(".chart-legend-group").data([chartLegendComponent]);
        legend.exit()
            .remove();
        legend.enter()
            .append("g")
            .attr("class", "chart-legend-group")
            .attr("transform", "translate(0, " + posY + ")");
        legend.call(chartLegendComponent.render)
            .transition()
            .duration(transitionDuration)
            .attr("transform", "translate(0, " + posY + ")");
    };

    var renderBars = function (contentGroup) {
        var chartBarsGroup = contentGroup.selectAll(".chart-bar-group").data([1]);
        chartBarsGroup.enter()
            .append("g")
            .attr("class", "chart-bar-group");
        var chartBars = chartBarsGroup.selectAll(".chart-bars").data(chartBarsComponents);
        chartBars.exit()
            .transition()
            .duration(transitionDuration)
            .attr("transform", "translate(0, 0)")
            .style("opacity", 0)
            .remove();
        chartBars.enter()
            .append("g")
            .attr("class", "chart-bars");
        chartBars
            .each(function (chartBarsComponent) {
                chartBarsComponent.render(d3.select(this));
            });
    };

    var renderHeader = function (svg) {
        var header = svg.selectAll(".header-group").data([chartHeaderComponent]);
        header.exit()
            .remove();
        header.enter()
            .append("g")
            .classed("header-group", true);
        header.call(chartHeaderComponent.render);
    };
    var renderBarScore = function (svg, shouldShowScore, posY) {
        var barScore = svg.selectAll(".chart-score-group").data([chartBarScoreComponent]);
        barScore.exit()
            .remove();
        barScore.enter()
            .append("g")
            .attr("class", "chart-score-group")
            .attr("transform", "translate(0, " + posY + ")");
        barScore
            .call(chartBarScoreComponent.render)
            .transition()
            .style("opacity", shouldShowScore ? 1 : 0)
            .duration(transitionDuration)
            .attr("transform", "translate(0, " + posY + ")");
    };

    return {
        setData: setData,
        render: render
    };

});
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     407
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v408 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     408
// ==/UserScript==
<|endoftext|>