<commit_before>// ==UserScript==
// @name        Fastmail Favicon Unread Count
// @author      Rob Middleton
// @license     MIT
// @description Adds a dynamic favicon to fastmail.com showing unread emails.
// @namespace   rob@middlerob.com
// @version     1
// @grant       none
// @include     https://www.fastmail.com/mail/*
// ==/UserScript==

;(function () {

  // Edit here to change excluded folders
  // To exclude sub folders just separate with a > like: "My Folder > Sub Folder"
  var excludeFolders = ["Spam", "Trash", "Promotions", "Events"]

  var totalUnread = '?'
  var pageFocused = false
  var focusedSinceNew = true
  var animateFlashesRemaining = 0
  var animateFlashOn

  var listeners = []
  var favicon = document.querySelector("link[sizes='16x16']")

  var defaultImage = new Image()
  defaultImage.onload = draw
  defaultImage.src = favicon.href

  var canvas = document.createElement('canvas')
  var ctx = canvas.getContext('2d')

  var faviconWidth = 16
  canvas.width = faviconWidth
  canvas.height = faviconWidth

  function draw () {
    var text = totalUnread
    var highlight = false

    ctx.clearRect(0, 0, faviconWidth, faviconWidth)

    if (animateFlashesRemaining > 0) {
      highlight = animateFlashOn
    }
    else {
      highlight = focusedSinceNew
    }

    if (highlight) {
      ctx.drawImage(defaultImage, 0, 0)
    }
    else {
      ctx.fillStyle = '#ff2323'
      ctx.fillRect(0, 0, faviconWidth, faviconWidth)
    }

    ctx.font = 'bold 14px Arial'
    ctx.fillStyle = 'white'
    ctx.strokeStyle = 'black'

    var textWidth = ctx.measureText(text).width
    var centered = (faviconWidth / 2) - (textWidth / 2)
    if (textWidth <= faviconWidth) {
      ctx.strokeText(text, centered, 13)
      ctx.fillText(text, centered, 13)
    }
    else {
      ctx.strokeText(text, 0, 13, faviconWidth)
      ctx.fillText(text, 0, 13, faviconWidth)
    }

    favicon.href = canvas.toDataURL('image/png')
  }

  setInterval(function () {
    if (animateFlashesRemaining > 0) {
      animateFlashOn = !(animateFlashesRemaining % 2)
      animateFlashesRemaining--
      draw()
    }
  }, 1000)

  window.addEventListener('focus', function () {
    pageFocused = true
    focusedSinceNew = true
    animateFlashesRemaining = 0
    draw()
  })
  window.addEventListener('blur', function () {
    pageFocused = false
  })

  function nameForBox (box) {
    var parent = box.get("parent")
    var name = box.get("name")
    return parent ? (parent.get("name") + " > " + name) : name
  }

  function updateTotal () {
    var unread = window.FastMail.allMailboxes.filter(function (box) {
      return excludeFolders.indexOf(nameForBox(box)) === -1
    }).reduce(function (total, current) {
      return total + current.get("unread")
    }, 0)

    if (unread > parseInt(totalUnread, 10)) {
      // new unread message
      if (!pageFocused) {
        focusedSinceNew = false
        animateFlashesRemaining = 11
      }
    }
    else if (unread === 0) {
      // for when they read all emails elsewhere
      focusedSinceNew = true
      animateFlashesRemaining = 0
    }
    totalUnread = unread.toString()
    draw()
  }

  function reBind () {
    var listener
    while (listener = listeners.pop()) {
      listener[0].removeObserverForKey(listener[1], listener[2], listener[3])
    }

    window.FastMail.allMailboxes.forEach(function (box) {
      var handler = {fn: function () {
        updateTotal()
      }}
      box.addObserverForKey("unread", handler, "fn")
      listeners.push([box, "unread", handler, "fn"])
    })
  }

  // wait for FastMail to load... not the best way
  setTimeout(function () {
    window.FastMail.allMailboxes.addObserverForRange({}, {
      fn: function () {
        reBind()
        updateTotal()
      }
    }, "fn")
    reBind()
    updateTotal()
  }, 1000)

})()
<commit_msg>Make initialization code more robust to handle fastmail loading slowly<commit_after>// ==UserScript==
// @name        Fastmail Favicon Unread Count
// @author      Rob Middleton
// @license     MIT
// @description Adds a dynamic favicon to fastmail.com showing unread emails.
// @namespace   rob@middlerob.com
// @version     1.1.0
// @grant       none
// @include     https://www.fastmail.com/mail/*
// ==/UserScript==

;(function () {

  // Edit here to change excluded folders
  // To exclude sub folders just separate with a > like: "My Folder > Sub Folder"
  var excludeFolders = ["Spam", "Trash", "Promotions", "Events"]

  var totalUnread = '?'
  var pageFocused = false
  var focusedSinceNew = true
  var animateFlashesRemaining = 0
  var animateFlashOn

  var listeners = []
  var favicon = document.querySelector("link[sizes='16x16']")

  var defaultImage = new Image()
  defaultImage.onload = draw
  defaultImage.src = favicon.href

  var canvas = document.createElement('canvas')
  var ctx = canvas.getContext('2d')

  var faviconWidth = 16
  canvas.width = faviconWidth
  canvas.height = faviconWidth

  function draw () {
    var text = totalUnread
    var highlight = false

    ctx.clearRect(0, 0, faviconWidth, faviconWidth)

    if (animateFlashesRemaining > 0) {
      highlight = animateFlashOn
    }
    else {
      highlight = focusedSinceNew
    }

    if (highlight) {
      ctx.drawImage(defaultImage, 0, 0)
    }
    else {
      ctx.fillStyle = '#ff2323'
      ctx.fillRect(0, 0, faviconWidth, faviconWidth)
    }

    ctx.font = 'bold 14px Arial'
    ctx.fillStyle = 'white'
    ctx.strokeStyle = 'black'

    var textWidth = ctx.measureText(text).width
    var centered = (faviconWidth / 2) - (textWidth / 2)
    if (textWidth <= faviconWidth) {
      ctx.strokeText(text, centered, 13)
      ctx.fillText(text, centered, 13)
    }
    else {
      ctx.strokeText(text, 0, 13, faviconWidth)
      ctx.fillText(text, 0, 13, faviconWidth)
    }

    favicon.href = canvas.toDataURL('image/png')
  }

  setInterval(function () {
    if (animateFlashesRemaining > 0) {
      animateFlashOn = !(animateFlashesRemaining % 2)
      animateFlashesRemaining--
      draw()
    }
  }, 1000)

  window.addEventListener('focus', function () {
    pageFocused = true
    focusedSinceNew = true
    animateFlashesRemaining = 0
    draw()
  })
  window.addEventListener('blur', function () {
    pageFocused = false
  })

  function nameForBox (box) {
    var parent = box.get("parent")
    var name = box.get("name")
    return parent ? (parent.get("name") + " > " + name) : name
  }

  function updateTotal () {
    var unread = window.FastMail.allMailboxes.filter(function (box) {
      return excludeFolders.indexOf(nameForBox(box)) === -1
    }).reduce(function (total, current) {
      return total + current.get("unread")
    }, 0)

    if (unread > parseInt(totalUnread, 10)) {
      // new unread message
      if (!pageFocused) {
        focusedSinceNew = false
        animateFlashesRemaining = 11
      }
    }
    else if (unread === 0) {
      // for when they read all emails elsewhere
      focusedSinceNew = true
      animateFlashesRemaining = 0
    }
    totalUnread = unread.toString()
    draw()
  }

  function reBind () {
    var listener
    while (listener = listeners.pop()) {
      listener[0].removeObserverForKey(listener[1], listener[2], listener[3])
    }

    window.FastMail.allMailboxes.forEach(function (box) {
      var handler = {fn: function () {
        updateTotal()
      }}
      box.addObserverForKey("unread", handler, "fn")
      listeners.push([box, "unread", handler, "fn"])
    })
  }

  function tryInit () {
    // wait for FastMail to load by polling
    if (window.FastMail.allMailboxes) {
      window.FastMail.allMailboxes.addObserverForRange({}, {
        fn: function () {
          reBind()
          updateTotal()
        }
      }, "fn")
      reBind()
      updateTotal()
    }
    else {
      // try again in 1s
      setTimeout(tryInit, 1000)
    }
  }

  tryInit()

})()
<|endoftext|>


<commit_before>import baseVw from './baseVw';
import loadTemplate from '../utils/loadTemplate';
import userShort from './userShort';

export default class extends baseVw {
  constructor(options = {}) {
    super(options);
    this.options = options;

    if (!options.peers) {
      throw new Error('Please provide a list of peers');
    }

    this.peers = options.peers;
    this.peersLoaded = 0;
    this.peersIterator = 3;
  }

  className() {
    return 'userPage';
  }

  events() {
    return {
      'click .js-morePeersBtn': 'loadPeers',
    };
  }

  get $peerWrapper() {
    return this._$peerWrapper ||
        (this._$peerWrapper = this.$('.js-peerWrapper'));
  }

  get $morePeers() {
    return this._$morePeers ||
        (this._$morePeers = this.$('.js-morePeers'))
  }

  loadPeers() {
    if (this.peers.length > this.peersLoaded) {
      this.peers.slice(this.peersLoaded, this.peersLoaded + this.peersIterator).forEach((peer) => {
        console.log(peer)
        const user = this.createChild(userShort, {
          guid: peer,
        });
        this.$peerWrapper.append(user.render().$el);
      });
      this.peersLoaded += this.peersIterator;
    }

    // check if next set exists
    if (this.peers.length > this.peersLoaded) {
      this.$morePeers.removeClass('hide');
    } else {
      this.$morePeers.addClass('hide');
    }
  }

  render() {
    loadTemplate('connectedPeersPage.html', (t) => {
      this.$el.html(t({
        peers: this.peers,
      }));
    });
    this._$peerWrapper = null;
    this._$morePeers = null;
    this.loadPeers();

    return this;
  }
}
<commit_msg>Fix testing number<commit_after>import baseVw from './baseVw';
import loadTemplate from '../utils/loadTemplate';
import userShort from './userShort';

export default class extends baseVw {
  constructor(options = {}) {
    super(options);
    this.options = options;

    if (!options.peers) {
      throw new Error('Please provide a list of peers');
    }

    this.peers = options.peers;
    this.peersLoaded = 0;
    this.peersIterator = 12;
  }

  className() {
    return 'userPage';
  }

  events() {
    return {
      'click .js-morePeersBtn': 'loadPeers',
    };
  }

  get $peerWrapper() {
    return this._$peerWrapper ||
        (this._$peerWrapper = this.$('.js-peerWrapper'));
  }

  get $morePeers() {
    return this._$morePeers ||
        (this._$morePeers = this.$('.js-morePeers'))
  }

  loadPeers() {
    if (this.peers.length > this.peersLoaded) {
      this.peers.slice(this.peersLoaded, this.peersLoaded + this.peersIterator).forEach((peer) => {
        console.log(peer)
        const user = this.createChild(userShort, {
          guid: peer,
        });
        this.$peerWrapper.append(user.render().$el);
      });
      this.peersLoaded += this.peersIterator;
    }

    // check if next set exists
    if (this.peers.length > this.peersLoaded) {
      this.$morePeers.removeClass('hide');
    } else {
      this.$morePeers.addClass('hide');
    }
  }

  render() {
    loadTemplate('connectedPeersPage.html', (t) => {
      this.$el.html(t({
        peers: this.peers,
      }));
    });
    this._$peerWrapper = null;
    this._$morePeers = null;
    this.loadPeers();

    return this;
  }
}
<|endoftext|>


<commit_before>import storage from './storage';
import {SVGRenderer} from 'scratch-svg-renderer';

// Contains 'font-family', but doesn't only contain 'font-family="none"'
const HAS_FONT_REGEXP = 'font-family(?!="none")';

const getCostumeUrl = (function () {
    let cachedAssetId;
    let cachedUrl;

    return function (asset) {

        if (cachedAssetId === asset.assetId) {
            return cachedUrl;
        }

        cachedAssetId = asset.assetId;

        // If the SVG refers to fonts, they must be inlined in order to display correctly in the img tag.
        // Avoid parsing the SVG when possible, since it's expensive.
        if (asset.assetType === storage.AssetType.ImageVector) {
            const svgString = asset.decodeText();
            if (svgString.match(HAS_FONT_REGEXP)) {
                const svgRenderer = new SVGRenderer();
                svgRenderer.loadString(svgString);
                const svgText = svgRenderer.toString(true /* shouldInjectFonts */);
                cachedUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;
            } else {
                cachedUrl = asset.encodeDataURI();
            }
        } else {
            cachedUrl = asset.encodeDataURI();
        }

        return cachedUrl;
    };
}());

export {
    getCostumeUrl as default,
    HAS_FONT_REGEXP
};
<commit_msg>Use fontInliner instead of entire SVGRenderer for showing costume tiles.<commit_after>import storage from './storage';
import {inlineSvgFonts} from 'scratch-svg-renderer';

// Contains 'font-family', but doesn't only contain 'font-family="none"'
const HAS_FONT_REGEXP = 'font-family(?!="none")';

const getCostumeUrl = (function () {
    let cachedAssetId;
    let cachedUrl;

    return function (asset) {

        if (cachedAssetId === asset.assetId) {
            return cachedUrl;
        }

        cachedAssetId = asset.assetId;

        // If the SVG refers to fonts, they must be inlined in order to display correctly in the img tag.
        // Avoid parsing the SVG when possible, since it's expensive.
        if (asset.assetType === storage.AssetType.ImageVector) {
            const svgString = asset.decodeText();
            if (svgString.match(HAS_FONT_REGEXP)) {
                const svgText = inlineSvgFonts(svgString);
                cachedUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;
            } else {
                cachedUrl = asset.encodeDataURI();
            }
        } else {
            cachedUrl = asset.encodeDataURI();
        }

        return cachedUrl;
    };
}());

export {
    getCostumeUrl as default,
    HAS_FONT_REGEXP
};
<|endoftext|>


<commit_before>// call, apply & bind

var person = {
	firstname: 'John',
	lastname: 'Doe',
	getFullName: function() {

		var fullname = this.firstname + ' ' + this.lastname;		// Remeber that in an object,
		return fullname;											// 'this' refers to the current object.

	}
};

var logName = function(lang1, lang2) {

	console.log('Logged: ' + this.getFullName());
	console.log('Arguments: ' + lang1 + ' ' + lang2);
	console.log('-----------------');

}; // .bind(person);

var logPersonName = logName.bind(person);

// ---------------------------------------------

// ---- bind -----

// What we are doing above is using the .bind method. What this method does is returns a new copy of
// the original function and sets up a new function object.

// It controls what the 'this' keyword refers to, rather than using the default.

// We can either make a new variable or use it at the end of the function as on line 20.

// -----------------------------------------------

// ---- call ----

// So far, we have been invoking functions with the parenthesis operator ().

// An alternative is to use the .call method of the object object.

// The benefit is that it allows us to pass what the 'this' keyword should point to as the first parementer,
// followed by any other parameters.

// The different between .bind and .call is that .call simply executes the function, it doesn't make a copy of it.

logName.call(person, 'en', 'es');

// -----------------------------------------------

// ---- apply ----

logName.apply(person, ['en', 'es']);

// The only difference between .call and .apply is that .apply expects us to pass parameters in an array.

// This can be useful in certain circumstances, like when we are working with maths.

(function(lang1, lang2) {

	console.log('Logged: ' + this.getFullName());
	console.log('Arguments: ' + lang1 + ' ' + lang2);
	console.log('--------');

}).apply(person, ['en', 'es']);

// Here we create a function on the fly and invoke it using the .apply method, set this to person and
// pass in two parameters in an array.

// -----------------------------------------------

// function borrowing

var person2 = {
	firstname: 'Jane',
	lastname: 'Doe'
};

console.log(person.getFullName.apply(person2));

// What we have done here is demonstrate function borrowing.

// We have created a new person2 object, and then we access the getFullName method of
// our original person object, but use the .apply method to 'borrow' the function and use
// it on our new object.

// -----------------------------------------------

// function currying

function multiply(a, b) {
	return a*b;
}

var multiplyByTwo = multiply.bind(this, 2);

console.log(multiplyByTwo(10));

// Function currying is a way of using .bind to make a copy of a function we have already made
// but set a default value to one or more of the parameters.<commit_msg>Revised Functional Programming<commit_after>// call, apply & bind

var person = {
	firstname: 'John',
	lastname: 'Doe',
	getFullName: function() {

		var fullname = this.firstname + ' ' + this.lastname;		// Remeber that in an object,
		return fullname;											// 'this' refers to the current object.

	}
};

var logName = function(lang1, lang2) {

	console.log('Logged: ' + this.getFullName());
	console.log('Arguments: ' + lang1 + ' ' + lang2);
	console.log('-----------------');

}.bind(person);

// var logPersonName = logName.bind(person);

//logPersonName('en', 'es');

logName('en', 'es');

// ---------------------------------------------

// ---- bind -----

// What we are doing above is using the .bind method. What this method does is returns a new copy of
// the original function and sets up a new function object.

// It controls what the 'this' keyword refers to, rather than using the default.

// We can either make a new variable or use it at the end of the function as on line 20.

// -----------------------------------------------

// ---- call ----

// So far, we have been invoking functions with the parenthesis operator ().

// An alternative is to use the .call method of the object object.

// The benefit is that it allows us to pass what the 'this' keyword should point to as the first parementer,
// followed by any other parameters.

// The different between .bind and .call is that .call simply executes the function, it doesn't make a copy of it.

logName.call(person, 'en', 'es');

// -----------------------------------------------

// ---- apply ----

logName.apply(person, ['en', 'es']);

// The only difference between .call and .apply is that .apply expects us to pass parameters in an array.

// This can be useful in certain circumstances, like when we are working with maths.

(function(lang1, lang2) {

	console.log('Logged: ' + this.getFullName());
	console.log('Arguments: ' + lang1 + ' ' + lang2);
	console.log('--------');

}).apply(person, ['en', 'es']);

// Here we create a function on the fly and invoke it using the .apply method, set this to person and
// pass in two parameters in an array.

// -----------------------------------------------

// function borrowing

var person2 = {
	firstname: 'Jane',
	lastname: 'Doe'
};

console.log(person.getFullName.apply(person2));

// What we have done here is demonstrate function borrowing.

// We have created a new person2 object, and then we access the getFullName method of
// our original person object, but use the .apply method to 'borrow' the function and use
// it on our new object.

// -----------------------------------------------

// function currying

function multiply(a, b) {
	return a*b;
}

var multiplyByTwo = multiply.bind(this, 2);

console.log(multiplyByTwo(10));

// Function currying is a way of using .bind to make a copy of a function we have already made
// but set a default value to one or more of the parameters.<|endoftext|>


<commit_before><commit_msg>Making townsfolk report you to a guard<commit_after><|endoftext|>


<commit_before>'use strict';

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

var TRANSFORM = 'transform';

function DOMRenderer (element, selector) {
    this._target = null;
    this._parent = null;
    this._path = null;
    this._children = [];
    this._root = new ElementCache(element);
    this._selector = selector;
    this._elements = {};
    this._elements[selector] = this._root;
}

DOMRenderer.prototype.getSize = function getSize () {
    return [this._root.offsetWidth, this._root.offsetHeight];
};

DOMRenderer.prototype._getSize = DOMRenderer.prototype.getSize;

DOMRenderer.prototype._assertPathLoaded = function _asserPathLoaded () {
    if (!this._path) throw new Error('path not loaded');
};

DOMRenderer.prototype._assertParentLoaded = function _assertParentLoaded () {
    if (!this._parent) throw new Error('parent not loaded');
};

DOMRenderer.prototype._assertChildrenLoaded = function _assertChildrenLoaded () {
    if (!this._children) throw new Error('children not loaded');
};

DOMRenderer.prototype.findParent = function findParent () {
    this._assertPathLoaded();

    var path = this._path;
    var parent;

    while (!parent && path.length) {
        path = path.substring(0, path.lastIndexOf('/'));
        parent = this._elements[path];
    }
    this._parent = parent;
    return parent;
};

DOMRenderer.prototype.findChildren = function findChildren () {
    this._assertPathLoaded();

    var path = this._path;
    var keys = Object.keys(this._elements);
    var i = 0;
    var len;

    this._children.length = 0;

    while (i < keys.length) {
        if (keys[i].indexOf(path) === -1 || keys[i] === path) keys.splice(i, 1);
        else i++;
    }
    var currentPath;
    var j = 0;
    for (i = 0 ; i < keys.length ; i++) {
        currentPath = keys[i];
        for (j = 0 ; j < keys.length ; j++) {
            if (i !== j && keys[j].indexOf(currentPath) !== -1) {
                keys.splice(j, 1);
                i--;
            }
        }
    }
    for (i = 0, len = keys.length ; i < len ; i++)
        this._children[i] = this._elements[keys[i]];

    return this._children;
};

DOMRenderer.prototype.findTarget = function findTarget () {
    this._target = this._elements[this._path];
    return this._target;
};

DOMRenderer.prototype.loadPath = function loadPath (path) {
    this._path = path;
    return this._path;
};

DOMRenderer.prototype.insertEl = function insertEl (tagName) {
    if (!this._target ||
         this._target.element.tagName.toLowerCase() === tagName.toLowerCase()) {

        this.findParent();
        this.findChildren();

        this._assertParentLoaded();
        this._assertChildrenLoaded();

        if (this._target) this._parent.element.removeChild(this._target.element);

        this._target = new ElementCache(document.createElement(tagName));
        this._parent.element.appendChild(this._target.element);

        for (var i = 0, len = this._children.length ; i < len ; i++) {
            this._target.element.appendChild(this._children[i].element);
        }
    }
};

DOMRenderer.prototype._assertTargetLoaded = function _assertTargetLoaded () {
    if (!this._target) throw new Error('No target loaded');
};

DOMRenderer.prototype.setProperty = function setProperty (name, value) {
    this._assertTargetLoaded();
    this._target.element.style[name] = value;
};

DOMRenderer.prototype.setSize = function setSize (width, height) {
    this._assertTargetLoaded();
    console.log(width, height);
    this._target.element.style.width = width + 'px';
    this._target.element.style.height = height + 'px';
};

DOMRenderer.prototype.setAttribute = function setAttribute (name, value) {
    this._assertTargetLoaded();
    this._target.element.setAttribute(name, value);
};

DOMRenderer.prototype.setContent = function setContent (content) {
    this._assertTargetLoaded();
    this.findChildren(this._path);

    // Temporary solution
    for (var i = 0 ; i < this._children.length ; i++)
        this._target.element.removeChild(this._children[i]);

    this._target.element.innerHTML = content;

    for (var i = 0 ; i < this._children.length ; i++)
        this._target.element.appendChild(this._children[i]);
};

DOMRenderer.prototype.setMatrix = function setMatrix (transform) {
    this._assertTargetLoaded();
    this.findParent();
    var worldTransform = this._target.worldTransform;
    for (var i = 0 ; i < 16 ; i++) worldTransform[i] = transform[i];

    invert(this._target.invertedParent, this._parent.worldTransform);
    invert(this._target.finalTransform, this._target.invertedParent, worldTransform);

    this._target.element.style[TRANSFORM] = stringifyMatrix(this._target.finalTransform);
};

DOMRenderer.prototype.addClass = function addClass (domClass) {
    this._assertTargetLoaded();
    this._target.element.classList.add(domClass);
};

function stringifyMatrix(m) {
    var r = 'matrix3d(';

    r += (m[0] < 0.000001 && m[0] > -0.000001) ? '0,' : m[0] + ',';
    r += (m[1] < 0.000001 && m[1] > -0.000001) ? '0,' : m[1] + ',';
    r += (m[2] < 0.000001 && m[2] > -0.000001) ? '0,' : m[2] + ',';
    r += (m[3] < 0.000001 && m[3] > -0.000001) ? '0,' : m[3] + ',';
    r += (m[4] < 0.000001 && m[4] > -0.000001) ? '0,' : m[4] + ',';
    r += (m[5] < 0.000001 && m[5] > -0.000001) ? '0,' : m[5] + ',';
    r += (m[6] < 0.000001 && m[6] > -0.000001) ? '0,' : m[6] + ',';
    r += (m[7] < 0.000001 && m[7] > -0.000001) ? '0,' : m[7] + ',';
    r += (m[8] < 0.000001 && m[8] > -0.000001) ? '0,' : m[8] + ',';
    r += (m[9] < 0.000001 && m[9] > -0.000001) ? '0,' : m[9] + ',';
    r += (m[10] < 0.000001 && m[10] > -0.000001) ? '0,' : m[10] + ',';
    r += (m[11] < 0.000001 && m[11] > -0.000001) ? '0,' : m[11] + ',';
    r += (m[12] < 0.000001 && m[12] > -0.000001) ? '0,' : m[12] + ',';
    r += (m[13] < 0.000001 && m[13] > -0.000001) ? '0,' : m[13] + ',';
    r += (m[14] < 0.000001 && m[14] > -0.000001) ? '0,' : m[14] + ',';

    r += m[15] + ')';
    return r;
}

function invert (out, a) {
    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],

        b00 = a00 * a11 - a01 * a10,
        b01 = a00 * a12 - a02 * a10,
        b02 = a00 * a13 - a03 * a10,
        b03 = a01 * a12 - a02 * a11,
        b04 = a01 * a13 - a03 * a11,
        b05 = a02 * a13 - a03 * a12,
        b06 = a20 * a31 - a21 * a30,
        b07 = a20 * a32 - a22 * a30,
        b08 = a20 * a33 - a23 * a30,
        b09 = a21 * a32 - a22 * a31,
        b10 = a21 * a33 - a23 * a31,
        b11 = a22 * a33 - a23 * a32,

        // Calculate the determinant
        det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;

    if (!det) {
        return null;
    }
    det = 1.0 / det;

    out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
    out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
    out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
    out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
    out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
    out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
    out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
    out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
    out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
    out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
    out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
    out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
    out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
    out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
    out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
    out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;

    return out;
}

function multiply (out, a, b) {
    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],

        b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3],
        b4 = b[4], b5 = b[5], b6 = b[6], b7 = b[7],
        b8 = b[8], b9 = b[9], b10 = b[10], b11 = b[11],
        b12 = b[12], b13 = b[13], b14 = b[14], b15 = b[15];

    out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = b4; b1 = b5; b2 = b6; b3 = b7;
    out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = b8; b1 = b9; b2 = b10; b3 = b11;
    out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = b12; b1 = b13; b2 = b14; b3 = b15;
    out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    return out;
}

module.exports = DOMRenderer;

<commit_msg>fix: Calculating size<commit_after>'use strict';

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

var TRANSFORM = 'transform';

function DOMRenderer (element, selector) {
    this._target = null;
    this._parent = null;
    this._path = null;
    this._children = [];
    this._root = new ElementCache(element);
    this._selector = selector;
    this._elements = {};
    this._elements[selector] = this._root;
}

DOMRenderer.prototype.getSize = function getSize () {
    return [this._root.element.offsetWidth, this._root.element.offsetHeight];
};

DOMRenderer.prototype._getSize = DOMRenderer.prototype.getSize;

DOMRenderer.prototype._assertPathLoaded = function _asserPathLoaded () {
    if (!this._path) throw new Error('path not loaded');
};

DOMRenderer.prototype._assertParentLoaded = function _assertParentLoaded () {
    if (!this._parent) throw new Error('parent not loaded');
};

DOMRenderer.prototype._assertChildrenLoaded = function _assertChildrenLoaded () {
    if (!this._children) throw new Error('children not loaded');
};

DOMRenderer.prototype.findParent = function findParent () {
    this._assertPathLoaded();

    var path = this._path;
    var parent;

    while (!parent && path.length) {
        path = path.substring(0, path.lastIndexOf('/'));
        parent = this._elements[path];
    }
    this._parent = parent;
    return parent;
};

DOMRenderer.prototype.findChildren = function findChildren () {
    this._assertPathLoaded();

    var path = this._path;
    var keys = Object.keys(this._elements);
    var i = 0;
    var len;

    this._children.length = 0;

    while (i < keys.length) {
        if (keys[i].indexOf(path) === -1 || keys[i] === path) keys.splice(i, 1);
        else i++;
    }
    var currentPath;
    var j = 0;
    for (i = 0 ; i < keys.length ; i++) {
        currentPath = keys[i];
        for (j = 0 ; j < keys.length ; j++) {
            if (i !== j && keys[j].indexOf(currentPath) !== -1) {
                keys.splice(j, 1);
                i--;
            }
        }
    }
    for (i = 0, len = keys.length ; i < len ; i++)
        this._children[i] = this._elements[keys[i]];

    return this._children;
};

DOMRenderer.prototype.findTarget = function findTarget () {
    this._target = this._elements[this._path];
    return this._target;
};

DOMRenderer.prototype.loadPath = function loadPath (path) {
    this._path = path;
    return this._path;
};

DOMRenderer.prototype.insertEl = function insertEl (tagName) {
    if (!this._target ||
         this._target.element.tagName.toLowerCase() === tagName.toLowerCase()) {

        this.findParent();
        this.findChildren();

        this._assertParentLoaded();
        this._assertChildrenLoaded();

        if (this._target) this._parent.element.removeChild(this._target.element);

        this._target = new ElementCache(document.createElement(tagName));
        this._parent.element.appendChild(this._target.element);
        this._elements[this._path] = this._target;

        for (var i = 0, len = this._children.length ; i < len ; i++) {
            this._target.element.appendChild(this._children[i].element);
        }
    }
};

DOMRenderer.prototype._assertTargetLoaded = function _assertTargetLoaded () {
    if (!this._target) throw new Error('No target loaded');
};

DOMRenderer.prototype.setProperty = function setProperty (name, value) {
    this._assertTargetLoaded();
    this._target.element.style[name] = value;
};

DOMRenderer.prototype.setSize = function setSize (width, height) {
    this._assertTargetLoaded();
    if (width != null && width !== true) this._target.element.style.width = width + 'px';
    if (height != null && height !== true) this._target.element.style.height = height + 'px';
};

DOMRenderer.prototype.setAttribute = function setAttribute (name, value) {
    this._assertTargetLoaded();
    this._target.element.setAttribute(name, value);
};

DOMRenderer.prototype.setContent = function setContent (content) {
    this._assertTargetLoaded();
    this.findChildren(this._path);

    // Temporary solution
    for (var i = 0 ; i < this._children.length ; i++)
        this._target.element.removeChild(this._children[i]);

    this._target.element.innerHTML = content;

    for (var i = 0 ; i < this._children.length ; i++)
        this._target.element.appendChild(this._children[i]);
};

DOMRenderer.prototype.setMatrix = function setMatrix (transform) {
    this._assertTargetLoaded();
    this.findParent();
    var worldTransform = this._target.worldTransform;
    for (var i = 0 ; i < 16 ; i++) worldTransform[i] = transform[i];

    invert(this._target.invertedParent, this._parent.worldTransform);
    invert(this._target.finalTransform, this._target.invertedParent, worldTransform);

    this._target.element.style[TRANSFORM] = stringifyMatrix(this._target.finalTransform);
};

DOMRenderer.prototype.addClass = function addClass (domClass) {
    this._assertTargetLoaded();
    this._target.element.classList.add(domClass);
};

function stringifyMatrix(m) {
    var r = 'matrix3d(';

    r += (m[0] < 0.000001 && m[0] > -0.000001) ? '0,' : m[0] + ',';
    r += (m[1] < 0.000001 && m[1] > -0.000001) ? '0,' : m[1] + ',';
    r += (m[2] < 0.000001 && m[2] > -0.000001) ? '0,' : m[2] + ',';
    r += (m[3] < 0.000001 && m[3] > -0.000001) ? '0,' : m[3] + ',';
    r += (m[4] < 0.000001 && m[4] > -0.000001) ? '0,' : m[4] + ',';
    r += (m[5] < 0.000001 && m[5] > -0.000001) ? '0,' : m[5] + ',';
    r += (m[6] < 0.000001 && m[6] > -0.000001) ? '0,' : m[6] + ',';
    r += (m[7] < 0.000001 && m[7] > -0.000001) ? '0,' : m[7] + ',';
    r += (m[8] < 0.000001 && m[8] > -0.000001) ? '0,' : m[8] + ',';
    r += (m[9] < 0.000001 && m[9] > -0.000001) ? '0,' : m[9] + ',';
    r += (m[10] < 0.000001 && m[10] > -0.000001) ? '0,' : m[10] + ',';
    r += (m[11] < 0.000001 && m[11] > -0.000001) ? '0,' : m[11] + ',';
    r += (m[12] < 0.000001 && m[12] > -0.000001) ? '0,' : m[12] + ',';
    r += (m[13] < 0.000001 && m[13] > -0.000001) ? '0,' : m[13] + ',';
    r += (m[14] < 0.000001 && m[14] > -0.000001) ? '0,' : m[14] + ',';

    r += m[15] + ')';
    return r;
}

function invert (out, a) {
    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],

        b00 = a00 * a11 - a01 * a10,
        b01 = a00 * a12 - a02 * a10,
        b02 = a00 * a13 - a03 * a10,
        b03 = a01 * a12 - a02 * a11,
        b04 = a01 * a13 - a03 * a11,
        b05 = a02 * a13 - a03 * a12,
        b06 = a20 * a31 - a21 * a30,
        b07 = a20 * a32 - a22 * a30,
        b08 = a20 * a33 - a23 * a30,
        b09 = a21 * a32 - a22 * a31,
        b10 = a21 * a33 - a23 * a31,
        b11 = a22 * a33 - a23 * a32,

        // Calculate the determinant
        det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;

    if (!det) {
        return null;
    }
    det = 1.0 / det;

    out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
    out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
    out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
    out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
    out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
    out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
    out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
    out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
    out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
    out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
    out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
    out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
    out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
    out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
    out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
    out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;

    return out;
}

function multiply (out, a, b) {
    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],

        b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3],
        b4 = b[4], b5 = b[5], b6 = b[6], b7 = b[7],
        b8 = b[8], b9 = b[9], b10 = b[10], b11 = b[11],
        b12 = b[12], b13 = b[13], b14 = b[14], b15 = b[15];

    out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = b4; b1 = b5; b2 = b6; b3 = b7;
    out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = b8; b1 = b9; b2 = b10; b3 = b11;
    out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    b0 = b12; b1 = b13; b2 = b14; b3 = b15;
    out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
    out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
    out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
    out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;

    return out;
}

module.exports = DOMRenderer;

<|endoftext|>


<commit_before>import React, { cloneElement, Children, Component, PropTypes } from 'react';
import Dock from 'react-dock';
import { POSITIONS } from './constants';
import { toggleVisibility, changeMonitor, changePosition, changeSize } from './actions';
import reducer from './reducers';
import parseKey from 'parse-key';

export default class DockMonitor extends Component {
  static update = reducer;

  static propTypes = {
    defaultPosition: PropTypes.oneOf(POSITIONS).isRequired,
    defaultIsVisible: PropTypes.bool.isRequired,
    defaultSize: PropTypes.number.isRequired,
    toggleVisibilityKey: PropTypes.string.isRequired,
    changePositionKey: PropTypes.string.isRequired,
    changeMonitorKey: PropTypes.string,
    fluid: PropTypes.bool,

    dispatch: PropTypes.func,
    monitorState: PropTypes.shape({
      position: PropTypes.oneOf(POSITIONS).isRequired,
      size: PropTypes.number.isRequired,
      isVisible: PropTypes.bool.isRequired,
      childMonitorState: PropTypes.any
    })
  };

  static defaultProps = {
    defaultIsVisible: true,
    defaultPosition: 'right',
    defaultSize: 0.3,
    fluid: true
  };

  constructor(props) {
    super(props);
    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.handleSizeChange = this.handleSizeChange.bind(this);

    const childrenCount = Children.count(props.children);
    if (childrenCount === 0) {
      console.error(
        '<DockMonitor> requires at least one monitor inside. ' +
        'Why don’t you try <LogMonitor>? You can get it at ' +
        'https://github.com/gaearon/redux-devtools-log-monitor.'
      );
    } else if (childrenCount > 1 && !props.changeMonitorKey) {
      console.error(
        'You specified multiple monitors inside <DockMonitor> ' +
        'but did not provide `changeMonitorKey` prop to change them. ' +
        'Try specifying <DockMonitor changeMonitorKey="ctrl-m" /> ' +
        'and then press Ctrl-M.'
      );
    }
  }

  componentDidMount() {
    window.addEventListener('keydown', this.handleKeyDown);
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.handleKeyDown);
  }

  matchesKey(key, event) {
    if (!key) {
      return false;
    }

    const charCode = event.keyCode || event.which;
    const char = String.fromCharCode(charCode);
    return key.name.toUpperCase() === char.toUpperCase() &&
      key.alt === event.altKey &&
      key.ctrl === event.ctrlKey &&
      key.meta === event.metaKey &&
      key.shift === event.shiftKey;
  }

  handleKeyDown(e) {
    // Ignore regular keys when focused on a field
    // and no modifiers are active.
    if ((
      !e.ctrlKey && !e.metaKey && !e.altKey
    ) && (
      e.target.tagName === 'INPUT' ||
      e.target.tagName === 'SELECT' ||
      e.target.tagName === 'TEXTAREA' ||
      e.target.isContentEditable
    )) {
      return;
    }

    const visibilityKey = parseKey(this.props.toggleVisibilityKey);
    const positionKey = parseKey(this.props.changePositionKey);

    let monitorKey;
    if (this.props.changeMonitorKey) {
      monitorKey = parseKey(this.props.changeMonitorKey);
    }

    if (this.matchesKey(visibilityKey, e)) {
      e.preventDefault();
      this.props.dispatch(toggleVisibility());
    } else if (this.matchesKey(positionKey, e)) {
      e.preventDefault();
      this.props.dispatch(changePosition());
    } else if (this.matchesKey(monitorKey, e)) {
      e.preventDefault();
      this.props.dispatch(changeMonitor());
    }
  }

  handleSizeChange(requestedSize) {
    this.props.dispatch(changeSize(requestedSize));
  }

  renderChild(child, index, otherProps) {
    const { monitorState } = this.props;
    const { childMonitorIndex, childMonitorStates } = monitorState;

    if (index !== childMonitorIndex) {
      return null;
    }

    return cloneElement(child, {
      monitorState: childMonitorStates[index],
      ...otherProps
    });
  }

  render() {
    const { monitorState, children, fluid, ...rest } = this.props;
    const { position, isVisible, size } = monitorState;

    return (
      <Dock position={position}
            isVisible={isVisible}
            size={size}
            fluid={fluid}
            onSizeChange={this.handleSizeChange}
            dimMode='none'>
        {Children.map(children, (child, index) =>
          this.renderChild(child, index, rest)
        )}
      </Dock>
    );
  }
}
<commit_msg>`defaultPosition` is not required (#39)<commit_after>import React, { cloneElement, Children, Component, PropTypes } from 'react';
import Dock from 'react-dock';
import { POSITIONS } from './constants';
import { toggleVisibility, changeMonitor, changePosition, changeSize } from './actions';
import reducer from './reducers';
import parseKey from 'parse-key';

export default class DockMonitor extends Component {
  static update = reducer;

  static propTypes = {
    defaultPosition: PropTypes.oneOf(POSITIONS),
    defaultIsVisible: PropTypes.bool.isRequired,
    defaultSize: PropTypes.number.isRequired,
    toggleVisibilityKey: PropTypes.string.isRequired,
    changePositionKey: PropTypes.string.isRequired,
    changeMonitorKey: PropTypes.string,
    fluid: PropTypes.bool,

    dispatch: PropTypes.func,
    monitorState: PropTypes.shape({
      position: PropTypes.oneOf(POSITIONS).isRequired,
      size: PropTypes.number.isRequired,
      isVisible: PropTypes.bool.isRequired,
      childMonitorState: PropTypes.any
    })
  };

  static defaultProps = {
    defaultIsVisible: true,
    defaultPosition: 'right',
    defaultSize: 0.3,
    fluid: true
  };

  constructor(props) {
    super(props);
    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.handleSizeChange = this.handleSizeChange.bind(this);

    const childrenCount = Children.count(props.children);
    if (childrenCount === 0) {
      console.error(
        '<DockMonitor> requires at least one monitor inside. ' +
        'Why don’t you try <LogMonitor>? You can get it at ' +
        'https://github.com/gaearon/redux-devtools-log-monitor.'
      );
    } else if (childrenCount > 1 && !props.changeMonitorKey) {
      console.error(
        'You specified multiple monitors inside <DockMonitor> ' +
        'but did not provide `changeMonitorKey` prop to change them. ' +
        'Try specifying <DockMonitor changeMonitorKey="ctrl-m" /> ' +
        'and then press Ctrl-M.'
      );
    }
  }

  componentDidMount() {
    window.addEventListener('keydown', this.handleKeyDown);
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.handleKeyDown);
  }

  matchesKey(key, event) {
    if (!key) {
      return false;
    }

    const charCode = event.keyCode || event.which;
    const char = String.fromCharCode(charCode);
    return key.name.toUpperCase() === char.toUpperCase() &&
      key.alt === event.altKey &&
      key.ctrl === event.ctrlKey &&
      key.meta === event.metaKey &&
      key.shift === event.shiftKey;
  }

  handleKeyDown(e) {
    // Ignore regular keys when focused on a field
    // and no modifiers are active.
    if ((
      !e.ctrlKey && !e.metaKey && !e.altKey
    ) && (
      e.target.tagName === 'INPUT' ||
      e.target.tagName === 'SELECT' ||
      e.target.tagName === 'TEXTAREA' ||
      e.target.isContentEditable
    )) {
      return;
    }

    const visibilityKey = parseKey(this.props.toggleVisibilityKey);
    const positionKey = parseKey(this.props.changePositionKey);

    let monitorKey;
    if (this.props.changeMonitorKey) {
      monitorKey = parseKey(this.props.changeMonitorKey);
    }

    if (this.matchesKey(visibilityKey, e)) {
      e.preventDefault();
      this.props.dispatch(toggleVisibility());
    } else if (this.matchesKey(positionKey, e)) {
      e.preventDefault();
      this.props.dispatch(changePosition());
    } else if (this.matchesKey(monitorKey, e)) {
      e.preventDefault();
      this.props.dispatch(changeMonitor());
    }
  }

  handleSizeChange(requestedSize) {
    this.props.dispatch(changeSize(requestedSize));
  }

  renderChild(child, index, otherProps) {
    const { monitorState } = this.props;
    const { childMonitorIndex, childMonitorStates } = monitorState;

    if (index !== childMonitorIndex) {
      return null;
    }

    return cloneElement(child, {
      monitorState: childMonitorStates[index],
      ...otherProps
    });
  }

  render() {
    const { monitorState, children, fluid, ...rest } = this.props;
    const { position, isVisible, size } = monitorState;

    return (
      <Dock position={position}
            isVisible={isVisible}
            size={size}
            fluid={fluid}
            onSizeChange={this.handleSizeChange}
            dimMode='none'>
        {Children.map(children, (child, index) =>
          this.renderChild(child, index, rest)
        )}
      </Dock>
    );
  }
}
<|endoftext|>


<commit_before>// Building a new feature that uses MutationObserver? You don't need to modify this file
// Instead of adding conditionals to this file try the example from /shared/example.js
Set.prototype.regex = function (regex) {
  for (const item of this) {
    if (regex.test(item)) return true;
  }

  return false;
};

(function poll() {
  if (ynabToolKit.pageReady === true && typeof ynabToolKit.shared.feedChanges !== 'undefined') {
    // When this is true, the feature scripts will know they can use the mutationObserver
    ynabToolKit.actOnChangeInit = false;
    ynabToolKit.onEmberViewRenderedInit = false;
    ynabToolKit.onCurrentPathChangedInit = false;

    // Set 'ynabToolKit.debugNodes = true' to print changes the mutationObserver sees
    // during page interactions and updates to the developer tools console.
    // ynabToolKit.debugNodes = true;

    ynabToolKit.actOnChange = function () {
      var _MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
      var observer = new _MutationObserver(function (mutations) {
        if (ynabToolKit.debugNodes) {
          console.log('MODIFIED NODES');
        }

        ynabToolKit.changedNodes = new Set();

        mutations.forEach(function (mutation) {
          var newNodes = mutation.target;

          var $nodes = $(newNodes); // jQuery set
          $nodes.each(function () {
            var nodeClass = $(this).attr('class');
            if (nodeClass) ynabToolKit.changedNodes.add(nodeClass.replace(/^ember-view /, ''));
          }); // each node mutation event
        }); // each mutation event

        if (ynabToolKit.debugNodes) {
          console.log(ynabToolKit.changedNodes);
          console.log('###');
        }

        // Now we are ready to feed the change digest to the
        // automatically setup feedChanges file/function
        if (ynabToolKit.changedNodes.size > 0) {
          ynabToolKit.shared.feedChanges({ changedNodes: ynabToolKit.changedNodes });
        }
      });

      // This finally says 'Watch for changes' and only needs to be called the one time
      observer.observe($('.ember-view.layout')[0], {
        subtree: true,
        childList: true,
        characterData: true,
        attributes: true,
        attributeFilter: ['class']
      });

      ynabToolKit.actOnChangeInit = true;
    };

    ynabToolKit.onEmberViewRendered = function () {
      Ember.Instrumentation.subscribe('render.view', {
        before: function before(event, timestamp, view) {
          ynabToolKit.shared.feedChanges({
            renderedViewBefore: view
          });
        },
        after: function after(event, timestamp, view, beforeParameter) {
          ynabToolKit.shared.feedChanges({
            renderedViewAfter: view,
            beforeParameter: beforeParameter
          });
        }
      });

      ynabToolKit.onEmberViewRenderedInit = true;
    };

    ynabToolKit.onCurrentPathChanged = function () {
      var routerContainer = ynabToolKit.shared.containerLookup('router:main');
      routerContainer.addObserver('currentPath', function (router) {
        ynabToolKit.shared.feedChanges({
          currentPath: router.currentPath,
          router: router
        });
      });

      ynabToolKit.onCurrentPathChangedInit = true;
    };

    // Run listeners once
    ynabToolKit.actOnChange();
    ynabToolKit.onEmberViewRendered();
    ynabToolKit.onCurrentPathChanged();
  } else {
    setTimeout(poll, 250);
  }
}());
<commit_msg>subscribing to render slowed down the app quite a bit<commit_after>// Building a new feature that uses MutationObserver? You don't need to modify this file
// Instead of adding conditionals to this file try the example from /shared/example.js
Set.prototype.regex = function (regex) {
  for (const item of this) {
    if (regex.test(item)) return true;
  }

  return false;
};

(function poll() {
  if (ynabToolKit.pageReady === true && typeof ynabToolKit.shared.feedChanges !== 'undefined') {
    // When this is true, the feature scripts will know they can use the mutationObserver
    ynabToolKit.actOnChangeInit = false;
    ynabToolKit.onEmberViewRenderedInit = false;
    ynabToolKit.onCurrentPathChangedInit = false;

    // Set 'ynabToolKit.debugNodes = true' to print changes the mutationObserver sees
    // during page interactions and updates to the developer tools console.
    // ynabToolKit.debugNodes = true;

    ynabToolKit.actOnChange = function () {
      var _MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
      var observer = new _MutationObserver(function (mutations) {
        if (ynabToolKit.debugNodes) {
          console.log('MODIFIED NODES');
        }

        ynabToolKit.changedNodes = new Set();

        mutations.forEach(function (mutation) {
          var newNodes = mutation.target;

          var $nodes = $(newNodes); // jQuery set
          $nodes.each(function () {
            var nodeClass = $(this).attr('class');
            if (nodeClass) ynabToolKit.changedNodes.add(nodeClass.replace(/^ember-view /, ''));
          }); // each node mutation event
        }); // each mutation event

        if (ynabToolKit.debugNodes) {
          console.log(ynabToolKit.changedNodes);
          console.log('###');
        }

        // Now we are ready to feed the change digest to the
        // automatically setup feedChanges file/function
        if (ynabToolKit.changedNodes.size > 0) {
          ynabToolKit.shared.feedChanges({ changedNodes: ynabToolKit.changedNodes });
        }
      });

      // This finally says 'Watch for changes' and only needs to be called the one time
      observer.observe($('.ember-view.layout')[0], {
        subtree: true,
        childList: true,
        characterData: true,
        attributes: true,
        attributeFilter: ['class']
      });

      ynabToolKit.actOnChangeInit = true;
    };

    ynabToolKit.onCurrentPathChanged = function () {
      var routerContainer = ynabToolKit.shared.containerLookup('router:main');
      routerContainer.addObserver('currentPath', function (router) {
        ynabToolKit.shared.feedChanges({
          currentPath: router.currentPath,
          router: router
        });
      });

      ynabToolKit.onCurrentPathChangedInit = true;
    };

    // Run listeners once
    ynabToolKit.actOnChange();
    ynabToolKit.onCurrentPathChanged();
  } else {
    setTimeout(poll, 250);
  }
}());
<|endoftext|>


<commit_before>const VError = require("verror");
const AsyncEventEmitter = require("../events/AsyncEventEmitter.js");
const getUniqueID = require("../../tools/encoding.js").getUniqueID;
const createCredentials = require("../credentials.js");
const credentialsToSource = require("./marshalling.js").credentialsToSource;

const COLOUR_TEST = /^#([a-f0-9]{3}|[a-f0-9]{6})$/i;

const DefaultColour = "#000000";
const DefaultOrder = 1000;
/**
 * Archive source status
 * @enum ArchiveSourceStatus
 * @memberof ArchiveSource
 * @static
 */
const Status = {
    LOCKED: "locked",
    UNLOCKED: "unlocked",
    PENDING: "pending"
};

/**
 * Rehydrate a dehydrated archive source
 * @param {String} dehydratedString A dehydrated archive source
 * @returns {ArchiveSource} The rehydrated source
 * @memberof ArchiveSource
 * @static
 */
function rehydrate(dehydratedString) {
    const { name, id, sourceCredentials, archiveCredentials, type, colour, order } = JSON.parse(dehydratedString);
    const source = new ArchiveSource(name, sourceCredentials, archiveCredentials, id);
    source.type = type;
    if (colour) {
        source._colour = colour;
    }
    if (order >= 0) {
        source.order = order;
    }
    return source;
}

/**
 * Archive source class
 * @augments AsyncEventEmitter
 */
class ArchiveSource extends AsyncEventEmitter {
    /**
     * Constructor for an archive source
     * @param {String} name The name of the source
     * @param {String} sourceCredentials Encrypted archive source credentials
     * @param {String} archiveCredentials Encrypted archive credentials
     * @param {String=} id Optional source ID (Do not pass for new source)
     */
    constructor(name, sourceCredentials, archiveCredentials, id = getUniqueID()) {
        super();
        if (createCredentials.isSecureString(sourceCredentials) !== true) {
            throw new VError("Failed constructing archive source: Source credentials not in encrypted form");
        }
        if (createCredentials.isSecureString(archiveCredentials) !== true) {
            throw new VError("Failed constructing archive source: Archive credentials not in encrypted form");
        }
        this._name = name;
        this._id = id;
        this._status = Status.LOCKED;
        this._sourceCredentials = sourceCredentials;
        this._archiveCredentials = archiveCredentials;
        this._workspace = null;
        this._colour = DefaultColour;
        this.type = "";
        this.order = DefaultOrder;
    }

    /**
     * Source colour
     * @type {String}
     * @memberof ArchiveSource
     */
    get colour() {
        return this._colour;
    }

    /**
     * @typedef ArchiveSourceDescription
     * @property {String} name - The name of the source
     * @property {String} id - The source ID
     * @property {ArchiveSourceStatus} status - Status of the source
     * @property {String} type - The source type
     * @property {String} colour - Colour for the source
     * @property {Number} order - The order of the source
     */

    /**
     * Get the source description
     * @type {ArchiveSourceDescription}
     * @memberof ArchiveSource
     * @readonly
     */
    get description() {
        return {
            name: this.name,
            id: this.id,
            status: this.status,
            type: this.type,
            colour: this.colour,
            order: this.order
        };
    }

    /**
     * Source ID
     * @type {String}
     * @memberof ArchiveSource
     * @readonly
     */
    get id() {
        return this._id;
    }

    /**
     * Source name
     * @type {String}
     * @memberof ArchiveSource
     * @readonly
     */
    get name() {
        return this._name;
    }

    /**
     * Source status
     * @type {ArchiveSourceStatus}
     * @memberof ArchiveSource
     * @readonly
     */
    get status() {
        return this._status;
    }

    /**
     * Workspace instance for the source
     * Is null when the source is locked
     * @type {Workspace|null}
     * @memberof ArchiveSource
     * @readonly
     */
    get workspace() {
        return this._workspace;
    }

    set colour(newColour) {
        if (COLOUR_TEST.test(newColour) !== true) {
            throw new VError(`Failed setting colour: Invalid format (expected hex): ${newColour}`);
        }
        this._colour = newColour;
        this.emit("sourceColourUpdated", this.description);
    }

    /**
     * Dehydrate the source for storage
     * Returns a secure string with locked (encrypted) credentials, even when the
     *  source is in the UNLOCKED state. This method does NOT store the source -
     *  this must be done separately.
     * @returns {Promise.<String>} A promise that resolves with the dehydrated
     *  source information
     * @memberof ArchiveSource
     * @throws {VError} Rejects is source in PENDING state
     */
    dehydrate() {
        if (this.status === Status.PENDING) {
            return Promise.reject(new VError(`Failed dehydrating source: Source in pending state: ${this.id}`));
        }
        return Promise.resolve()
            .then(() => {
                const payload = {
                    id: this.id,
                    name: this.name,
                    type: this.type,
                    status: Status.LOCKED,
                    colour: this.colour,
                    order: this.order
                };
                if (this.status === Status.LOCKED) {
                    payload.sourceCredentials = this._sourceCredentials;
                    payload.archiveCredentials = this._archiveCredentials;
                    return payload;
                }
                return Promise.all([
                    this._sourceCredentials.toSecureString(this._archiveCredentials.password),
                    this._archiveCredentials.toSecureString(this._archiveCredentials.password)
                ]).then(([encSourceCredentials, encArchiveCredentials]) => {
                    payload.sourceCredentials = encSourceCredentials;
                    payload.archiveCredentials = encArchiveCredentials;
                    return payload;
                });
            })
            .then(payload => JSON.stringify(payload))
            .catch(err => {
                throw new VError(err, `Failed dehydrating source: ${this.id}`);
            });
    }

    /**
     * Lock the source
     * Encrypts the credentials and performs dehydration, placing the source into
     *  a LOCKED state. No saving is performed before locking.
     * @returns {Promise.<String>} A promise that resolves with dehydrated content
     * @throws {VError} Rejects if not in unlocked state
     * @fires ArchiveSource#sourceLocked
     * @memberof ArchiveSource
     */
    lock() {
        if (this.status !== Status.UNLOCKED) {
            return Promise.reject(
                new VError(`Failed locking source: Source in invalid state (${this.status}): ${this.id}`)
            );
        }
        this._status = Status.PENDING;
        return Promise.all([
            this._sourceCredentials.toSecureString(this._archiveCredentials.password),
            this._archiveCredentials.toSecureString(this._archiveCredentials.password)
        ])
            .then(([encSourceCredentials, encArchiveCredentials]) => {
                this._status = Status.LOCKED;
                this._workspace = null;
                this._sourceCredentials = encSourceCredentials;
                this._archiveCredentials = encArchiveCredentials;
                return this.dehydrate();
            })
            .then(dehydratedContent => {
                this.emit("sourceLocked", this.description);
                return dehydratedContent;
            })
            .catch(err => {
                throw new VError(err, `Failed locking source: ${this.id}`);
            });
    }

    /**
     * Unlock the source
     * @param {String} masterPassword The master password
     * @param {Boolean=} initialiseRemote Optionally initialise the remote (replaces
     *  remote archive) (defaults to false)
     * @memberof ArchiveSource
     * @throws {VError} Rejects if not in locked state
     * @throws {VError} Rejects if not able to create the source from the encrypted
     *  credentials
     * @fires ArchiveSource#sourceUnlocked
     */
    unlock(masterPassword, initialiseRemote = false) {
        if (this.status !== Status.LOCKED) {
            return Promise.reject(
                new VError(`Failed unlocking source: Source in invalid state (${this.status}): ${this.id}`)
            );
        }
        this._status = Status.PENDING;
        return Promise.all([
            createCredentials.fromSecureString(this._sourceCredentials, masterPassword),
            createCredentials.fromSecureString(this._archiveCredentials, masterPassword)
        ])
            .then(([sourceCredentials, archiveCredentials] = []) => {
                return credentialsToSource(sourceCredentials, archiveCredentials, initialiseRemote)
                    .then(sourceInfo => {
                        const { workspace, sourceCredentials, archiveCredentials } = sourceInfo;
                        this._workspace = workspace;
                        this._sourceCredentials = sourceCredentials;
                        this._archiveCredentials = archiveCredentials;
                        this._status = Status.UNLOCKED;
                        this.type = sourceCredentials.type;
                    })
                    .catch(err => {
                        throw new VError(err, "Failed mapping credentials to a source");
                    });
            })
            .then(() => {
                this.emit("sourceUnlocked", this.description);
            })
            .catch(err => {
                this._status = Status.LOCKED;
                throw new VError(err, `Failed unlocking source: ${this.id}`);
            });
    }

    /**
     * Update the password/credentials for the archive
     * The workspace is saved after changing. Responds with dehydrated details
     * which should be SAVED.
     * @param {String} masterPassword New master password
     * @returns {Promise.<String>} A promise that resolves with the dehydrated
     *  source details
     * @memberof ArchiveSource
     * @throws {VError} Rejects if source is not in unlocked state
     */
    updateArchiveCredentials(masterPassword) {
        if (this.status !== Status.UNLOCKED) {
            return Promise.reject(
                new VError(`Failed updating archive credentials: Source is not unlocked: ${this.id}`)
            );
        }
        const credentials = createCredentials.fromPassword(masterPassword);
        // First update the credentials stored here
        this._archiveCredentials = credentials;
        // Then update the credentials in the workspace
        this.workspace.updatePrimaryCredentials(credentials);
        // Finally, dehydrate the source to save changes in the manager
        return (
            // Save the workspace to push the new password to file
            this.workspace
                .save()
                // Finally dehydrate
                .then(() => this.dehydrate())
        );
    }
}

ArchiveSource.DefaultColour = DefaultColour;
ArchiveSource.DefaultOrder = DefaultOrder;
ArchiveSource.Status = Status;

ArchiveSource.rehydrate = rehydrate;

module.exports = ArchiveSource;
<commit_msg>Add queuing to ArchiveSource processes<commit_after>const VError = require("verror");
const ChannelQueue = require("@buttercup/channel-queue");
const AsyncEventEmitter = require("../events/AsyncEventEmitter.js");
const getUniqueID = require("../../tools/encoding.js").getUniqueID;
const createCredentials = require("../credentials.js");
const credentialsToSource = require("./marshalling.js").credentialsToSource;

const COLOUR_TEST = /^#([a-f0-9]{3}|[a-f0-9]{6})$/i;

const DefaultColour = "#000000";
const DefaultOrder = 1000;
/**
 * Archive source status
 * @enum ArchiveSourceStatus
 * @memberof ArchiveSource
 * @static
 */
const Status = {
    LOCKED: "locked",
    UNLOCKED: "unlocked",
    PENDING: "pending"
};

/**
 * Rehydrate a dehydrated archive source
 * @param {String} dehydratedString A dehydrated archive source
 * @returns {ArchiveSource} The rehydrated source
 * @memberof ArchiveSource
 * @static
 */
function rehydrate(dehydratedString) {
    const { name, id, sourceCredentials, archiveCredentials, type, colour, order } = JSON.parse(dehydratedString);
    const source = new ArchiveSource(name, sourceCredentials, archiveCredentials, id);
    source.type = type;
    if (colour) {
        source._colour = colour;
    }
    if (order >= 0) {
        source.order = order;
    }
    return source;
}

/**
 * Archive source class
 * @augments AsyncEventEmitter
 */
class ArchiveSource extends AsyncEventEmitter {
    /**
     * Constructor for an archive source
     * @param {String} name The name of the source
     * @param {String} sourceCredentials Encrypted archive source credentials
     * @param {String} archiveCredentials Encrypted archive credentials
     * @param {String=} id Optional source ID (Do not pass for new source)
     */
    constructor(name, sourceCredentials, archiveCredentials, id = getUniqueID()) {
        super();
        if (createCredentials.isSecureString(sourceCredentials) !== true) {
            throw new VError("Failed constructing archive source: Source credentials not in encrypted form");
        }
        if (createCredentials.isSecureString(archiveCredentials) !== true) {
            throw new VError("Failed constructing archive source: Archive credentials not in encrypted form");
        }
        this._queue = new ChannelQueue();
        this._name = name;
        this._id = id;
        this._status = Status.LOCKED;
        this._sourceCredentials = sourceCredentials;
        this._archiveCredentials = archiveCredentials;
        this._workspace = null;
        this._colour = DefaultColour;
        this.type = "";
        this.order = DefaultOrder;
    }

    /**
     * Source colour
     * @type {String}
     * @memberof ArchiveSource
     */
    get colour() {
        return this._colour;
    }

    /**
     * @typedef ArchiveSourceDescription
     * @property {String} name - The name of the source
     * @property {String} id - The source ID
     * @property {ArchiveSourceStatus} status - Status of the source
     * @property {String} type - The source type
     * @property {String} colour - Colour for the source
     * @property {Number} order - The order of the source
     */

    /**
     * Get the source description
     * @type {ArchiveSourceDescription}
     * @memberof ArchiveSource
     * @readonly
     */
    get description() {
        return {
            name: this.name,
            id: this.id,
            status: this.status,
            type: this.type,
            colour: this.colour,
            order: this.order
        };
    }

    /**
     * Source ID
     * @type {String}
     * @memberof ArchiveSource
     * @readonly
     */
    get id() {
        return this._id;
    }

    /**
     * Source name
     * @type {String}
     * @memberof ArchiveSource
     * @readonly
     */
    get name() {
        return this._name;
    }

    /**
     * Source status
     * @type {ArchiveSourceStatus}
     * @memberof ArchiveSource
     * @readonly
     */
    get status() {
        return this._status;
    }

    /**
     * Workspace instance for the source
     * Is null when the source is locked
     * @type {Workspace|null}
     * @memberof ArchiveSource
     * @readonly
     */
    get workspace() {
        return this._workspace;
    }

    set colour(newColour) {
        if (COLOUR_TEST.test(newColour) !== true) {
            throw new VError(`Failed setting colour: Invalid format (expected hex): ${newColour}`);
        }
        this._colour = newColour;
        this.emit("sourceColourUpdated", this.description);
    }

    /**
     * Dehydrate the source for storage
     * Returns a secure string with locked (encrypted) credentials, even when the
     *  source is in the UNLOCKED state. This method does NOT store the source -
     *  this must be done separately.
     * @returns {Promise.<String>} A promise that resolves with the dehydrated
     *  source information
     * @memberof ArchiveSource
     * @throws {VError} Rejects is source in PENDING state
     */
    dehydrate() {
        if (this.status === Status.PENDING) {
            return Promise.reject(new VError(`Failed dehydrating source: Source in pending state: ${this.id}`));
        }
        return this._enqueueStateChange(() => {
            return Promise.resolve()
                .then(() => {
                    const payload = {
                        id: this.id,
                        name: this.name,
                        type: this.type,
                        status: Status.LOCKED,
                        colour: this.colour,
                        order: this.order
                    };
                    if (this.status === Status.LOCKED) {
                        payload.sourceCredentials = this._sourceCredentials;
                        payload.archiveCredentials = this._archiveCredentials;
                        return payload;
                    }
                    return Promise.all([
                        this._sourceCredentials.toSecureString(this._archiveCredentials.password),
                        this._archiveCredentials.toSecureString(this._archiveCredentials.password)
                    ]).then(([encSourceCredentials, encArchiveCredentials]) => {
                        payload.sourceCredentials = encSourceCredentials;
                        payload.archiveCredentials = encArchiveCredentials;
                        return payload;
                    });
                })
                .then(payload => JSON.stringify(payload))
                .catch(err => {
                    throw new VError(err, `Failed dehydrating source: ${this.id}`);
                });
        });
    }

    /**
     * Lock the source
     * Encrypts the credentials and performs dehydration, placing the source into
     *  a LOCKED state. No saving is performed before locking.
     * @returns {Promise.<String>} A promise that resolves with dehydrated content
     * @throws {VError} Rejects if not in unlocked state
     * @fires ArchiveSource#sourceLocked
     * @memberof ArchiveSource
     */
    lock() {
        if (this.status !== Status.UNLOCKED) {
            return Promise.reject(
                new VError(`Failed locking source: Source in invalid state (${this.status}): ${this.id}`)
            );
        }
        this._status = Status.PENDING;
        return this._enqueueStateChange(() => {
            return Promise.all([
                this._sourceCredentials.toSecureString(this._archiveCredentials.password),
                this._archiveCredentials.toSecureString(this._archiveCredentials.password)
            ])
                .then(([encSourceCredentials, encArchiveCredentials]) => {
                    this._status = Status.LOCKED;
                    this._workspace = null;
                    this._sourceCredentials = encSourceCredentials;
                    this._archiveCredentials = encArchiveCredentials;
                })
                .catch(err => {
                    throw new VError(err, `Failed locking source: ${this.id}`);
                });
        }).then(() => {
            this.emit("sourceLocked", this.description);
            return this.dehydrate();
        });
    }

    /**
     * Unlock the source
     * @param {String} masterPassword The master password
     * @param {Boolean=} initialiseRemote Optionally initialise the remote (replaces
     *  remote archive) (defaults to false)
     * @memberof ArchiveSource
     * @throws {VError} Rejects if not in locked state
     * @throws {VError} Rejects if not able to create the source from the encrypted
     *  credentials
     * @fires ArchiveSource#sourceUnlocked
     */
    unlock(masterPassword, initialiseRemote = false) {
        if (this.status !== Status.LOCKED) {
            return Promise.reject(
                new VError(`Failed unlocking source: Source in invalid state (${this.status}): ${this.id}`)
            );
        }
        this._status = Status.PENDING;
        return this._enqueueStateChange(() => {
            return Promise.all([
                createCredentials.fromSecureString(this._sourceCredentials, masterPassword),
                createCredentials.fromSecureString(this._archiveCredentials, masterPassword)
            ])
                .then(([sourceCredentials, archiveCredentials] = []) => {
                    return credentialsToSource(sourceCredentials, archiveCredentials, initialiseRemote)
                        .then(sourceInfo => {
                            const { workspace, sourceCredentials, archiveCredentials } = sourceInfo;
                            this._workspace = workspace;
                            this._sourceCredentials = sourceCredentials;
                            this._archiveCredentials = archiveCredentials;
                            this._status = Status.UNLOCKED;
                            this.type = sourceCredentials.type;
                        })
                        .catch(err => {
                            throw new VError(err, "Failed mapping credentials to a source");
                        });
                })
                .then(() => {
                    this.emit("sourceUnlocked", this.description);
                })
                .catch(err => {
                    this._status = Status.LOCKED;
                    throw new VError(err, `Failed unlocking source: ${this.id}`);
                });
        });
    }

    /**
     * Update the password/credentials for the archive
     * The workspace is saved after changing. Responds with dehydrated details
     * which should be SAVED.
     * @param {String} masterPassword New master password
     * @returns {Promise.<String>} A promise that resolves with the dehydrated
     *  source details
     * @memberof ArchiveSource
     * @throws {VError} Rejects if source is not in unlocked state
     */
    updateArchiveCredentials(masterPassword) {
        if (this.status !== Status.UNLOCKED) {
            return Promise.reject(
                new VError(`Failed updating archive credentials: Source is not unlocked: ${this.id}`)
            );
        }
        return this._enqueueStateChange(() => {
            const credentials = createCredentials.fromPassword(masterPassword);
            // First update the credentials stored here
            this._archiveCredentials = credentials;
            // Then update the credentials in the workspace
            this.workspace.updatePrimaryCredentials(credentials);
            // Finally, dehydrate the source to save changes in the manager
            return (
                // Save the workspace to push the new password to file
                this.workspace
                    .save()
                    // Finally dehydrate
                    .then(() => this.dehydrate())
            );
        });
    }

    _enqueueStateChange(cb) {
        return this._queue.channel("state").enqueue(cb);
    }
}

ArchiveSource.DefaultColour = DefaultColour;
ArchiveSource.DefaultOrder = DefaultOrder;
ArchiveSource.Status = Status;

ArchiveSource.rehydrate = rehydrate;

module.exports = ArchiveSource;
<|endoftext|>


<commit_before>'use strict';
/* global document, window */
var stateFromOptions = require('./state.js').stateFromOptions;
var Animation = require('./animation.js');
var createState = require('./state.js').createState;

var Engine = {
  runningAnimations: [],
  completedAnimations: [],
  transformProperty: 'transform',
  init() {
    var styles = window.getComputedStyle(document.documentElement, '');
    var vendorPrefix = (Array.prototype.slice
        .call(styles)
        .join('')
        .match(/-(moz|webkit|ms)-/) || styles.OLink === '' && ['', 'o']
      )[1];
    if (vendorPrefix === 'webkit')
      this.transformProperty = 'webkitTransform';

  },

  scheduleNextFrame() {
    window.requestAnimationFrame((time) => {
      this.stepAnimations(time);
    });
  },

  stepAnimations(time) {
    this.runningAnimations.forEach((runningAnimation) => {
      var element = runningAnimation[0];
      var animation = runningAnimation[1];
      this.stepAnimation(element, animation, time);
    });

    this.archiveCompletedAnimations();

    if (this.runningAnimations.length > 0) {
      this.scheduleNextFrame();
    }
  },

  stepAnimation(element, animation, time) {
    if (animation.isStopped())
      return;

    animation.tick(time);
    animation.updateElement(element);
  },

  archiveCompletedAnimations() {
    var unFinished = this.runningAnimations.filter((animation) => !animation[1].completed());
    var finished = this.runningAnimations.filter((animation) => animation[1].completed());
    var queuedAnimations = this.createQueuedAnimations(finished);
    finished = this.runningAnimations.filter((finishedAnimation) => {
      return !queuedAnimations.find((queuedAnimation) => {
        return queuedAnimation[0] !== finishedAnimation[0];
      });
    });

    Engine.runningAnimations = unFinished;
    this.completedAnimations = this.completedAnimations.filter((animation) => {
      return finished.find((finishedAnimation) => {
        return finishedAnimation[0] !== animation[0];
      });
    });
    Array.prototype.push.apply(this.completedAnimations, finished);
    Array.prototype.push.apply(this.runningAnimations, queuedAnimations);
  },

  createQueuedAnimations(finished) {
    var newAnimations = finished.filter((animation) => {
      var chainer = animation[2];
      return chainer.index < chainer.queue.length;
    }).map((animation) => {
      var element = animation[0];
      var chainer = animation[2];
      var options = chainer.queue[chainer.index];
      chainer.index++;
      return [animation[0], this.createAnimation(element, options, animation[1].endState()), chainer];
    });

    return newAnimations;
  },

  createChainer() {
    var chainer = {
      index: 0,
      queue: [],
      snabbt: function(opts) {
        this.queue.unshift(opts);
        return chainer;
      }
    };
    return chainer;
  },

  createAnimation(element, options, previousEndState) {
    var previousState = previousEndState || this.findCurrentState(element);
    var startState = stateFromOptions(options, previousState, true);
    var endState = stateFromOptions(options, null, false);

    var animation = Animation.createAnimation(startState, endState, options);
    return animation;
  },

  createAttentionAnimation(element, options) {
    var movement = stateFromOptions(options, createState({}, false));
    options.movement = movement;
    var animation = Animation.createAttentionAnimation(options);

    return animation;
  },

  initializeAnimation(element, arg2, arg3) {
    var animation;
    if (arg2 === 'attention') {
      animation = this.createAttentionAnimation(element, arg3);
    } else {
      animation = this.createAnimation(element, arg2);
    }
    var chainer = this.createChainer();

    this.runningAnimations.push([element, animation, chainer]);

    if (this.runningAnimations.length === 1) {
      this.scheduleNextFrame();
    }

    return chainer;
  },

  findCurrentState(element) {
    var match =  this.runningAnimations.find((animation) => element === animation[0]);
    if (match) {
      return match[1].getCurrentState();
    }
    match =  this.completedAnimations.find((animation) => element === animation[0]);
    if (match) {
      return match[1].endState();
    }
  }
};

module.exports = Engine;
<commit_msg>Fix chaining bug<commit_after>'use strict';
/* global document, window */
var stateFromOptions = require('./state.js').stateFromOptions;
var Animation = require('./animation.js');
var createState = require('./state.js').createState;

var Engine = {
  runningAnimations: [],
  completedAnimations: [],
  transformProperty: 'transform',
  init() {
    var styles = window.getComputedStyle(document.documentElement, '');
    var vendorPrefix = (Array.prototype.slice
        .call(styles)
        .join('')
        .match(/-(moz|webkit|ms)-/) || styles.OLink === '' && ['', 'o']
      )[1];
    if (vendorPrefix === 'webkit')
      this.transformProperty = 'webkitTransform';

  },

  scheduleNextFrame() {
    window.requestAnimationFrame((time) => {
      this.stepAnimations(time);
    });
  },

  stepAnimations(time) {
    this.runningAnimations.forEach((runningAnimation) => {
      var element = runningAnimation[0];
      var animation = runningAnimation[1];
      this.stepAnimation(element, animation, time);
    });

    this.archiveCompletedAnimations();

    if (this.runningAnimations.length > 0) {
      this.scheduleNextFrame();
    }
  },

  stepAnimation(element, animation, time) {
    if (animation.isStopped())
      return;

    animation.tick(time);
    animation.updateElement(element);
  },

  archiveCompletedAnimations() {
    var unFinished = this.runningAnimations.filter((animation) => !animation[1].completed());
    var finished = this.runningAnimations.filter((animation) => animation[1].completed());
    var queuedAnimations = this.createQueuedAnimations(finished);
    finished = this.runningAnimations.filter((finishedAnimation) => {
      return !queuedAnimations.find((queuedAnimation) => {
        return queuedAnimation[0] !== finishedAnimation[0];
      });
    });

    Engine.runningAnimations = unFinished;
    this.completedAnimations = this.completedAnimations.filter((animation) => {
      return finished.find((finishedAnimation) => {
        return finishedAnimation[0] !== animation[0];
      });
    });
    Array.prototype.push.apply(this.completedAnimations, finished);
    Array.prototype.push.apply(this.runningAnimations, queuedAnimations);
  },

  createQueuedAnimations(finished) {
    var newAnimations = finished.filter((animation) => {
      var chainer = animation[2];
      return chainer.index < chainer.queue.length;
    }).map((animation) => {
      var element = animation[0];
      var chainer = animation[2];
      var options = chainer.queue[chainer.index];
      chainer.index++;
      return [animation[0], this.createAnimation(element, options, animation[1].endState()), chainer];
    });

    return newAnimations;
  },

  createChainer() {
    var chainer = {
      index: 0,
      queue: [],
      snabbt: function(opts) {
        this.queue.push(opts);
        return chainer;
      }
    };
    return chainer;
  },

  createAnimation(element, options, previousEndState) {
    var previousState = previousEndState || this.findCurrentState(element);
    var startState = stateFromOptions(options, previousState, true);
    var endState = stateFromOptions(options, null, false);

    var animation = Animation.createAnimation(startState, endState, options);
    return animation;
  },

  createAttentionAnimation(element, options) {
    var movement = stateFromOptions(options, createState({}, false));
    options.movement = movement;
    var animation = Animation.createAttentionAnimation(options);

    return animation;
  },

  initializeAnimation(element, arg2, arg3) {
    var animation;
    if (arg2 === 'attention') {
      animation = this.createAttentionAnimation(element, arg3);
    } else {
      animation = this.createAnimation(element, arg2);
    }
    var chainer = this.createChainer();

    this.runningAnimations.push([element, animation, chainer]);

    if (this.runningAnimations.length === 1) {
      this.scheduleNextFrame();
    }

    return chainer;
  },

  findCurrentState(element) {
    var match =  this.runningAnimations.find((animation) => element === animation[0]);
    if (match) {
      return match[1].getCurrentState();
    }
    match =  this.completedAnimations.find((animation) => element === animation[0]);
    if (match) {
      return match[1].endState();
    }
  }
};

module.exports = Engine;
<|endoftext|>


<commit_before>import createErrorClass from 'create-error-class'

function setup (response, cause) {
  this.message = (response.body.data && response.body.data[0]) || cause.message
  this.status = response.body.status
  this.response = response
  this.cause = cause
}

function setupFundsPP (response, cause) {
  setup.call(this, response, cause)
  this.message = 'You don\'t have enough Plug Points to unlock this.'
}

const RequestError = createErrorClass('RequestError', setup)
const BadLoginError = createErrorClass('BadLoginError', setup)
const NotAuthorizedError = createErrorClass('NotAuthorizedError', setup)
const NotFoundError = createErrorClass('NotFoundError', setup)
const NoValidPlaylistError = createErrorClass('NoValidPlaylistError', setup)
const NotEnoughPPError = createErrorClass('NotEnoughPPError', setupFundsPP)

const statusToError = {
  requestError: RequestError,
  badLogin: BadLoginError,
  notAuthorized: NotAuthorizedError,
  notFound: NotFoundError,
  noValidPlaylist: NoValidPlaylistError
}

export const errorClasses = {
  RequestError,
  BadLoginError,
  NotAuthorizedError,
  NotFoundError,
  NoValidPlaylistError,
  NotEnoughPPError
}

export function wrapResponseError (response, cause) {
  const { status, data } = response.body
  // Special cases
  if (status === 'requestError' && data && data[0] === 'fundsPP') {
    return new NotEnoughPPError(response, cause)
  }

  const ErrorClass = statusToError[status]
  if (ErrorClass) {
    return new ErrorClass(response, cause)
  }
  setup.call(cause, response, cause)
  return cause
}
<commit_msg>Only set Error#response property if it is not yet set<commit_after>import createErrorClass from 'create-error-class'

function setup (response, cause) {
  this.message = (response.body.data && response.body.data[0]) || cause.message
  this.status = response.body.status
  if (!this.response) this.response = response
  this.cause = cause
}

function setupFundsPP (response, cause) {
  setup.call(this, response, cause)
  this.message = 'You don\'t have enough Plug Points to unlock this.'
}

const RequestError = createErrorClass('RequestError', setup)
const BadLoginError = createErrorClass('BadLoginError', setup)
const NotAuthorizedError = createErrorClass('NotAuthorizedError', setup)
const NotFoundError = createErrorClass('NotFoundError', setup)
const NoValidPlaylistError = createErrorClass('NoValidPlaylistError', setup)
const NotEnoughPPError = createErrorClass('NotEnoughPPError', setupFundsPP)

const statusToError = {
  requestError: RequestError,
  badLogin: BadLoginError,
  notAuthorized: NotAuthorizedError,
  notFound: NotFoundError,
  noValidPlaylist: NoValidPlaylistError
}

export const errorClasses = {
  RequestError,
  BadLoginError,
  NotAuthorizedError,
  NotFoundError,
  NoValidPlaylistError,
  NotEnoughPPError
}

export function wrapResponseError (response, cause) {
  const { status, data } = response.body
  // Special cases
  if (status === 'requestError' && data && data[0] === 'fundsPP') {
    return new NotEnoughPPError(response, cause)
  }

  const ErrorClass = statusToError[status]
  if (ErrorClass) {
    return new ErrorClass(response, cause)
  }
  setup.call(cause, response, cause)
  return cause
}
<|endoftext|>


<commit_before>import store2 from 'store2';
import flushPromises from 'flush-promises';

import Store, { STORAGE_KEY } from '../store';

jest.mock('store2', () => ({
  local: {
    set: jest.fn(),
    get: jest.fn(),
  },
  session: {
    set: jest.fn(),
    get: jest.fn(),
  },
}));

describe('store', () => {
  it('sensibly combines local+session storage for inital state', () => {
    store2.session.get.mockReturnValueOnce({ foo: 'bar', combined: { a: 'b' } });
    store2.local.get.mockReturnValueOnce({ foo: 'baz', another: 'value', combined: { c: 'd' } });

    const store = new Store({});
    expect(store.getInitialState()).toEqual({
      foo: 'bar',
      another: 'value',
      // We don't combine subfields from the two sources.
      combined: { a: 'b' },
    });
  });

  it('passes getState right through', () => {
    const getState = jest.fn();
    const store = new Store({ getState });

    store.getState();

    expect(getState).toHaveBeenCalled();
  });

  describe('setState', () => {
    it('sets values in React only by default', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => cb());
      const store = new Store({ setState });

      await store.setState({ foo: 'bar' });

      expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
      expect(store2.session.set).not.toHaveBeenCalled();
      expect(store2.local.set).not.toHaveBeenCalled();
    });

    it('sets values in React and sessionStorage if persistence === session', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => cb());
      const store = new Store({ setState });

      await store.setState({ foo: 'bar' }, { persistence: 'session' });

      expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
      expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' });
      expect(store2.local.set).not.toHaveBeenCalled();
    });

    it('sets values in React and sessionStorage if persistence === permanent', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => cb());
      const store = new Store({ setState });

      await store.setState({ foo: 'bar' }, { persistence: 'permanent' });

      expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
      expect(store2.session.set).not.toHaveBeenCalled();
      expect(store2.local.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' });
    });

    it('properly patches existing values', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => cb());
      store2.session.get.mockReturnValueOnce({
        foo: 'baz',
        another: 'value',
        combined: { a: 'b' },
      });
      const store = new Store({ setState });

      await store.setState({ foo: 'bar', combined: { c: 'd' } }, { persistence: 'session' });

      expect(setState).toHaveBeenCalledWith(
        { foo: 'bar', combined: { c: 'd' } },
        expect.any(Function)
      );
      expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, {
        foo: 'bar',
        another: 'value',
        combined: { c: 'd' },
      });
    });

    it('waits for react to setState', async () => {
      let cb;
      const setState = jest.fn().mockImplementation((x, inputCb) => {
        cb = inputCb;
      });
      const store = new Store({ setState });

      // NOTE: not awaiting here
      let done = false;
      store.setState({ foo: 'bar' }).then(() => {
        done = true;
      });

      await flushPromises();
      expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
      expect(done).toBe(false);

      cb();
      await flushPromises();
      expect(done).toBe(true);
    });

    it('returns react.setState result', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => cb('RESULT'));
      const store = new Store({ setState });

      const result = await store.setState({ foo: 'bar' });

      expect(result).toBe('RESULT');
    });

    it('allows a callback', async () =>
      new Promise(resolve => {
        const setState = jest.fn().mockImplementation((x, cb) => cb('RESULT'));
        const store = new Store({ setState });

        store.setState({ foo: 'bar' }, result => {
          expect(result).toBe('RESULT');
          resolve();
        });
      }));

    it('allows a patch function and persists its results', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => {
        x('OLD_STATE');
        cb();
      });
      const store = new Store({ setState });

      const patch = jest.fn().mockReturnValue({ foo: 'bar' });
      await store.setState(patch, { persistence: 'session' });

      expect(patch).toHaveBeenCalledWith('OLD_STATE');
      expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' });
    });
  });
});
<commit_msg>fixed broken tests<commit_after>import store2 from 'store2';
import flushPromises from 'flush-promises';

import Store, { STORAGE_KEY } from '../store';

jest.mock('store2', () => ({
  local: {
    set: jest.fn(),
    get: jest.fn(),
  },
  session: {
    set: jest.fn(),
    get: jest.fn(),
  },
  _: { fn: () => {} },
}));

describe('store', () => {
  it('sensibly combines local+session storage for inital state', () => {
    store2.session.get.mockReturnValueOnce({ foo: 'bar', combined: { a: 'b' } });
    store2.local.get.mockReturnValueOnce({ foo: 'baz', another: 'value', combined: { c: 'd' } });

    const store = new Store({});
    expect(store.getInitialState()).toEqual({
      foo: 'bar',
      another: 'value',
      // We don't combine subfields from the two sources.
      combined: { a: 'b' },
    });
  });

  it('passes getState right through', () => {
    const getState = jest.fn();
    const store = new Store({ getState });

    store.getState();

    expect(getState).toHaveBeenCalled();
  });

  describe('setState', () => {
    it('sets values in React only by default', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => cb());
      const store = new Store({ setState });

      await store.setState({ foo: 'bar' });

      expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
      expect(store2.session.set).not.toHaveBeenCalled();
      expect(store2.local.set).not.toHaveBeenCalled();
    });

    it('sets values in React and sessionStorage if persistence === session', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => cb());
      const store = new Store({ setState });

      await store.setState({ foo: 'bar' }, { persistence: 'session' });

      expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
      expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' });
      expect(store2.local.set).not.toHaveBeenCalled();
    });

    it('sets values in React and sessionStorage if persistence === permanent', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => cb());
      const store = new Store({ setState });

      await store.setState({ foo: 'bar' }, { persistence: 'permanent' });

      expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
      expect(store2.session.set).not.toHaveBeenCalled();
      expect(store2.local.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' });
    });

    it('properly patches existing values', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => cb());
      store2.session.get.mockReturnValueOnce({
        foo: 'baz',
        another: 'value',
        combined: { a: 'b' },
      });
      const store = new Store({ setState });

      await store.setState({ foo: 'bar', combined: { c: 'd' } }, { persistence: 'session' });

      expect(setState).toHaveBeenCalledWith(
        { foo: 'bar', combined: { c: 'd' } },
        expect.any(Function)
      );
      expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, {
        foo: 'bar',
        another: 'value',
        combined: { c: 'd' },
      });
    });

    it('waits for react to setState', async () => {
      let cb;
      const setState = jest.fn().mockImplementation((x, inputCb) => {
        cb = inputCb;
      });
      const store = new Store({ setState });

      // NOTE: not awaiting here
      let done = false;
      store.setState({ foo: 'bar' }).then(() => {
        done = true;
      });

      await flushPromises();
      expect(setState).toHaveBeenCalledWith({ foo: 'bar' }, expect.any(Function));
      expect(done).toBe(false);

      cb();
      await flushPromises();
      expect(done).toBe(true);
    });

    it('returns react.setState result', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => cb('RESULT'));
      const store = new Store({ setState });

      const result = await store.setState({ foo: 'bar' });

      expect(result).toBe('RESULT');
    });

    it('allows a callback', async () =>
      new Promise(resolve => {
        const setState = jest.fn().mockImplementation((x, cb) => cb('RESULT'));
        const store = new Store({ setState });

        store.setState({ foo: 'bar' }, result => {
          expect(result).toBe('RESULT');
          resolve();
        });
      }));

    it('allows a patch function and persists its results', async () => {
      const setState = jest.fn().mockImplementation((x, cb) => {
        x('OLD_STATE');
        cb();
      });
      const store = new Store({ setState });

      const patch = jest.fn().mockReturnValue({ foo: 'bar' });
      await store.setState(patch, { persistence: 'session' });

      expect(patch).toHaveBeenCalledWith('OLD_STATE');
      expect(store2.session.set).toHaveBeenCalledWith(STORAGE_KEY, { foo: 'bar' });
    });
  });
});
<|endoftext|>