<commit_before>
"use strict";

let isDateTime = new RegExp(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/);
let isDate = new RegExp(/^(\d{4})-(\d{2})-(\d{2})/);
let mapper = {
    Jan: '01',
    Feb: '02',
    Mar: '03',
    Apr: '04',
    May: '05',
    Jun: '06',
    Jul: '07',
    Aug: '08',
    Sep: '09',
    Oct: '10',
    Nov: '11',
    Dec: '12'
};

function map(month) {
    return mapper[month];
};

/**
 * A data object with dirty checking and optional virtual members or other
 * additional logic. Implementors should generally extend this class, but this
 * is not required.
 */
export default class Model {

    /**
     * Class constructor.
     */
    constructor() {
        /**
         * The "initial" state for this model. Used to check for pristineness.
         * This is automatically set when $load is called.
         */
        this.$initial = undefined;
        /**
         * Internal data storage for this model.
         */
        this.$data = {};
        /**
         * Set to true if the model is scheduled for deletion.
         */
        this.$deleted = false;
    }

    /**
     * Static method to create a new Model of this type, optionally specifying
     * default data and a class to derive from.
     *
     * @param object data Hash of key/value pairs of data.
     * @param class derivedClass Optional class to actually use when
     *  instantiating (defaults to the current class).
     * @return mixed A new model of the desired class.
     */
    static $create(data = {}, derivedClass = undefined) {
        derivedClass = derivedClass || Model;
        let instance = new derivedClass();
        instance.$load(data);
        instance.$initial = undefined;
        return instance;
    }

    /**
     * Load data into this model instance.
     *
     * @param object data Hash of key/value pairs of data.
     * @return self
     */
    $load(data = {}) {
        this.$initial = data;
        for (let key in data) {
            let checkDate = isDateTime.exec(data[key]);
            if (!checkDate) {
                checkDate = isDate.exec(data[key]);
            }
            if (checkDate) {
                checkDate.shift();
                checkDate[1]--; // Javascript months are offset by 0 for reasons only Brendan Eich will know...
                data[key] = new Date(...checkDate);
            }
            var props = {enumerable: true, configurable: true};
            if (!this.hasOwnProperty(key)) {
                props.get = () => {
                    return this.$data[key];
                };
                props.set = value => {
                    this.$data[key] = value;
                };
            }
            Object.defineProperty(this, key, props);
            this.$data[key] = data[key];
        }
        return this;
    }

    /**
     * Virtual property to check if this model is "new".
     *
     * @return boolean True if new, false if existing.
     */
    get $new() {
        return this.$initial === undefined;
    }

    /**
     * Virtual property to check if this model is "dirty".
     *
     * @return boolean True if dirty, false if pristine.
     */
    get $dirty() {
        if (this.$deleted || this.$new) {
            return true;
        }
        for (let key in this) {
            if (key.substring(0, 1) == '$') {
                continue;
            }
            if (key in this.$data && this.$data[key] != this.$initial[key]) {
                return true;
            } else if (typeof this[key] == 'object' && this[key] != null) {
                if ('map' in this[key]) {
                    let dirty = false;
                    this[key].map(elem => {
                        if (typeof elem == 'object' && elem.$dirty || elem.$deleted) {
                            dirty = true;
                        }
                    });
                    if (dirty) {
                        return true;
                    }
                } else if (this[key].$dirty || this[key].$deleted) {
                    return true;
                }
            }
        }
        return false;
    }

};

<commit_msg>even better dirty checking<commit_after>
"use strict";

let isDateTime = new RegExp(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/);
let isDate = new RegExp(/^(\d{4})-(\d{2})-(\d{2})/);
let mapper = {
    Jan: '01',
    Feb: '02',
    Mar: '03',
    Apr: '04',
    May: '05',
    Jun: '06',
    Jul: '07',
    Aug: '08',
    Sep: '09',
    Oct: '10',
    Nov: '11',
    Dec: '12'
};

function map(month) {
    return mapper[month];
};

/**
 * A data object with dirty checking and optional virtual members or other
 * additional logic. Implementors should generally extend this class, but this
 * is not required.
 */
export default class Model {

    /**
     * Class constructor.
     */
    constructor() {
        /**
         * The "initial" state for this model. Used to check for pristineness.
         * This is automatically set when $load is called.
         */
        this.$initial = undefined;
        /**
         * Internal data storage for this model.
         */
        this.$data = {};
        /**
         * Set to true if the model is scheduled for deletion.
         */
        this.$deleted = false;
    }

    /**
     * Static method to create a new Model of this type, optionally specifying
     * default data and a class to derive from.
     *
     * @param object data Hash of key/value pairs of data.
     * @param class derivedClass Optional class to actually use when
     *  instantiating (defaults to the current class).
     * @return mixed A new model of the desired class.
     */
    static $create(data = {}, derivedClass = undefined) {
        derivedClass = derivedClass || Model;
        let instance = new derivedClass();
        instance.$load(data);
        instance.$initial = undefined;
        return instance;
    }

    /**
     * Load data into this model instance.
     *
     * @param object data Hash of key/value pairs of data.
     * @return self
     */
    $load(data = {}) {
        this.$initial = data;
        for (let key in data) {
            let checkDate = isDateTime.exec(data[key]);
            if (!checkDate) {
                checkDate = isDate.exec(data[key]);
            }
            if (checkDate) {
                checkDate.shift();
                checkDate[1]--; // Javascript months are offset by 0 for reasons only Brendan Eich will know...
                data[key] = new Date(...checkDate);
            }
            var props = {enumerable: true, configurable: true};
            if (!this.hasOwnProperty(key)) {
                props.get = () => {
                    return this.$data[key];
                };
                props.set = value => {
                    this.$data[key] = value;
                };
            }
            Object.defineProperty(this, key, props);
            this.$data[key] = data[key];
        }
        return this;
    }

    /**
     * Virtual property to check if this model is "new".
     *
     * @return boolean True if new, false if existing.
     */
    get $new() {
        return this.$initial === undefined;
    }

    /**
     * Virtual property to check if this model is "dirty".
     *
     * @return boolean True if dirty, false if pristine.
     */
    get $dirty() {
        if (this.$deleted || this.$new) {
            return true;
        }
        for (let key in this) {
            if (key.substring(0, 1) == '$') {
                continue;
            }
            if (!(key in this.$data) || (key in this.$data && this.$data[key] != this.$initial[key])) {
                return true;
            } else if (typeof this[key] == 'object' && this[key] != null) {
                if ('map' in this[key]) {
                    let dirty = false;
                    this[key].map(elem => {
                        if (typeof elem == 'object' && elem.$dirty || elem.$deleted) {
                            dirty = true;
                        }
                    });
                    if (dirty) {
                        return true;
                    }
                } else if (this[key].$dirty || this[key].$deleted) {
                    return true;
                }
            }
        }
        return false;
    }

};

<|endoftext|>


<commit_before>(this, function () {
  var pi2 = Math.PI * 2;
  /**
   * Bubble Chart implementation using {@link d3js.org|d3js}
   *
   * @class BubbleChart
   * @example
   *  - [test-bubble-chart](../test/test-bubble-chart.html)
   *
   * @param {settings} settings - Settings of bubble chart
   */
  d3.svg.BubbleChart = function (settings) {
    var self = this;
    var defaultViewBoxSize = settings.size;
    var defaultInnerRadius = settings.size / 3;
    var defaultOuterRadius = settings.size / 2;
    var defaultRadiusMin = settings.size / 10;
    self.options = {};
    $.extend(self.options, {
      plugins: [],
      container: ".bubbleChart",
      viewBoxSize: defaultViewBoxSize,
      innerRadius: defaultInnerRadius,
      outerRadius: defaultOuterRadius,
      radiusMin: defaultRadiusMin,
      intersectDelta: 0,
      transitDuration: 1000,
      centralClickMethod: function () {
        alert("sup");
      },
    }, settings);

    $.extend(self.options, {
      radiusMax: (self.options.outerRadius - self.options.innerRadius) / 2,
      intersectInc: self.options.intersectDelta
    }, settings);

    self.setup();
    self.registerClickEvent(self.getNodes());
    self.moveToCentral(d3.select(".node"));
  };

  $.extend(d3.svg.BubbleChart.prototype, {
    getTransition: function() {
      return this.transition;
    },

    getClickedNode: function () {
      return this.clickedNode;
    },

    getCentralNode: function () {
      return this.centralNode;
    },

    getOptions: function () {
      return this.options;
    },

    randomCirclesPositions: function (delta) {
      var self = this;
      var circles = [];
      var interval = 0;
      var options = self.options;
      while (circles.length < self.items.length && ++interval < self.intervalMax) {
        var val = self.values[circles.length];
        var rad = Math.max((val * options.radiusMax) / self.valueMax, options.radiusMin);
        var dist = self.innerRadius + rad + Math.random() * (self.outerRadius - self.innerRadius - rad * 2);
        var angle = Math.random() * pi2;
        var cx = self.centralPoint + dist * Math.cos(angle);
        var cy = self.centralPoint + dist * Math.sin(angle);

        var hit = false;
        $.each(circles, function (i, circle) {
          var dx = circle.cx - cx;
          var dy = circle.cy - cy;
          var r = circle.r + rad;
          if (dx * dx + dy * dy < Math.pow(r - delta, 2)) {
            hit = true;
            return false;
          }
        });
        if (!hit) {
          circles.push({cx: cx, cy: cy, r: rad, item: self.items[circles.length]});
        }
      }
      if (circles.length < self.items.length) {
        if (delta === options.radiusMin) {
          throw {
            message: "Not enough space for all bubble. Please change the options.",
            options: options
          }
        }
        return self.randomCirclesPositions(delta + options.intersectInc);
      }
      return circles.shuffle();
    },

    getValues: function () {
      var values = [];
      var self = this;
      $.each(self.items, function (i, item) {values.push(self.options.data.eval(item));});
      return values;
    },

    setup: function () {
      var self = this;
      var options = self.options;
      self.swapped = false;
      self.innerRadius = options.innerRadius;
      self.outerRadius = options.outerRadius;
      self.centralPoint = options.size / 2;
      self.intervalMax = options.size * options.size;
      self.items = options.data.items;
      self.centralClickMethod = options.centralClickMethod;
      self.values = self.getValues();
      self.valueMax = self.values.max();
      self.svg = d3.select(options.container).append("svg")
        .attr({preserveAspectRatio: "xMidYMid", width: options.size, height: options.size, class: "bubbleChart"})
        .attr("viewBox", function (d) {return ["0 0", options.viewBoxSize, options.viewBoxSize].join(" ")});
      self.circlePositions = self.randomCirclesPositions(options.intersectDelta);

      var node = self.svg.selectAll(".node")
        .data(self.circlePositions)
        .enter().append("g")
        .attr("class", function (d) {return ["node", options.data.classed(d.item)].join(" ");});

      var fnColor = d3.scale.category20();
      node.append("circle")
        .attr({r: function (d) {return d.r;}, cx: function (d) {return d.cx;}, cy: function (d) {return d.cy;}})
        .style("fill", function (d) {
          return options.data.color !== undefined ? options.data.color(d.item) : fnColor(d.item.text);
        })
        .attr("opacity", "0.8");
      node.sort(function (a, b) {return options.data.eval(b.item) - options.data.eval(a.item);});

      self.transition = {};

      if (options.supportResponsive) {
        $(window).resize(function() {
          var width = $(options.container).width();
          self.svg.attr("width", width);
          self.svg.attr("height", width);
        });
        $(window).resize();
      }

      //lines plugin
      var node = self.getNodes();
      $.each(options.linesFormat, function (i, f) {
        node.append("text")
          .classed(f.classed)
          .style(f.style)
          .attr(f.attr)
          .text(function (d) {return d.item[f.textField];});
      });

    },

    getCirclePositions: function () {
      return this.circlePositions;
    },

    moveToCentral: function (node) {
      var self = this;
      var toCentralPoint = d3.svg.transform()
        .translate(function (d) {
          var cx = node.select('circle').attr("cx");
          var dx = self.centralPoint - d.cx;
          var dy = self.centralPoint - d.cy;
          return [dx, dy];
        });
      self.centralNode = node;
      self.transition.centralNode = node.classed({active: true})
        .transition().duration(self.options.transitDuration);
      self.transition.centralNode.attr('transform', toCentralPoint)
        .select("circle")
        .attr('r', function (d) {return self.options.innerRadius;});

      //central-click
      self.transition.centralNode.each("end", function() {
        node.append("text").classed({"central-click": true})
          .attr(self.options.centralClick.attr)
          .style(self.options.centralClick.style)
          .attr("x", function (d) {return d.cx;})
          .attr("y", function (d) {return d.cy;})
          .html(self.options.centralClick.label)
          .style("opacity", 0).transition().duration(self.getOptions().transitDuration / 2).style("opacity", "0.8");
      });

      //lines plugin
      $.each(self.options.centralFormat, function (i, f) {
        var tNode = d3.select(node.selectAll("text")[0][i]);
        tNode.transition().duration(self.getOptions().transitDuration)
          .style(f.style)
          .attr(f.attr);
        f.classed !== undefined && tNode.classed(f.classed);
        f.textField !== undefined && tNode.text(function (d) {return d.item[f.textField];});
      });

    },

    moveToReflection: function (node) {
      var self = this;
      var toReflectionPoint = d3.svg.transform()
        .translate(function (d) {
          var dx = 2 * (self.centralPoint - d.cx);
          var dy = 2 * (self.centralPoint - d.cy);
          return [dx, dy];
        });

      node.transition()
        .duration(self.options.transitDuration)
        .delay(function (d, i) {return i * 10;})
        .attr('transform', self.swapped ? "" : toReflectionPoint)
        .select("circle")
        .attr('r', function (d) {return d.r;});
    },

    reset: function (node) {
      var self = this;
      node.classed({active: false});

      //central-click
      node.select("text.central-click").remove();

      //lines plugin
      $.each(self.options.linesFormat, function (i, f) {
        var tNode = d3.select(node.selectAll("text")[0][i]);
        tNode.classed(f.classed).text(function (d) {return d.item[f.textField];})
          .transition().duration(self.getOptions().transitDuration)
          .style(f.style)
          .attr(f.attr);
      });

    },

    handleClick: function (node) {
      //FIXME
      this.clickedNode = d3.select(node);

      var label = this.clickedNode.selectAll("text.text")[0][0].innerHTML;
      if (this.clickedNode.selectAll("text.central-click")[0].length === 1) {
        this.centralClickMethod(node, label);
      }
      else {
        this.reset(this.centralNode);
        this.moveToCentral(this.clickedNode);
        this.moveToReflection(this.svg.selectAll(".node:not(.active)"), this.swapped);
        this.swapped = !this.swapped;
      }
    },

    registerClickEvent: function (node) {
      var self = this;
      node.style("cursor", "pointer").on("click", function (d) {
        self.handleClick(this);
      });
    },

    getNodes: function () {
      return this.svg.selectAll(".node");
    },

    get: function () {
      return this.svg;
    }
  });

  return d3.svg.BubbleChart;
})();
/**
 * Settings of bubble chart
 *
 * @typedef {object} settings
 *
 * @param {(object[]|string[])} plugins - Array of plugin [microplugin](https://github.com/brianreavis/microplugin.js|microplugin)
 * @param {string} [container=".bubbleChart"] - Jquery selector which will contain the chart
 * @param {number} size - Size of the chart, in pixel
 * @param {number} [viewBoxSize=size] - Size of the viewport of the chart, in pixel [ViewBoxAttribute](http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute)
 * @param {number} [innerRadius=size/3] - Radius of the Inner Circle, in pixel
 * @param {number} [outerRadius=size/2] - Radius of the Outer Circle, in pixel
 * @param {number} [radiusMin=size/10] - Minimum radius, in pixel
 * @param {number} [radiusMax=(outerRadius  innerRadius)/2] - Maximum radius, in pixel
 * @param {number} [intersectDelta=0] - Intersection between circles, in pixel
 * @param {number} [intersectInc=intersectDelta] - Increment of settings.intersectDelta, in pixel
 * @param {number} [transitDuration=1000] - Duration of transition when do animations, in mili-seconds
 * @param {data} data - Data information
 */

/**
 * Data information
 *
 * @typedef {object} data
 * @param {object[]} items - Array of items
 ex:
 * ```js
 * data.items = [{number: 179, label: "something"}, {number: 220, label: "everything"}]
 * ```
 * @param {function} eval - Function should return a number used to evaluate an item
 ex:
 * ```js
 * data.eval = function(d){
 *   return d.number;
 * }
 * ```
 * @param {function} [color=d3.scale.category20] - Function should return a string used to fill bubbles
ex:
 * ```js
 * data.color = function(d){
 *   return "white";
 * }
 * ```
 */
<commit_msg>Revert "allow html for central click label"<commit_after>(this, function () {
  var pi2 = Math.PI * 2;
  /**
   * Bubble Chart implementation using {@link d3js.org|d3js}
   *
   * @class BubbleChart
   * @example
   *  - [test-bubble-chart](../test/test-bubble-chart.html)
   *
   * @param {settings} settings - Settings of bubble chart
   */
  d3.svg.BubbleChart = function (settings) {
    var self = this;
    var defaultViewBoxSize = settings.size;
    var defaultInnerRadius = settings.size / 3;
    var defaultOuterRadius = settings.size / 2;
    var defaultRadiusMin = settings.size / 10;
    self.options = {};
    $.extend(self.options, {
      plugins: [],
      container: ".bubbleChart",
      viewBoxSize: defaultViewBoxSize,
      innerRadius: defaultInnerRadius,
      outerRadius: defaultOuterRadius,
      radiusMin: defaultRadiusMin,
      intersectDelta: 0,
      transitDuration: 1000,
      centralClickMethod: function () {
        alert("sup");
      },
    }, settings);

    $.extend(self.options, {
      radiusMax: (self.options.outerRadius - self.options.innerRadius) / 2,
      intersectInc: self.options.intersectDelta
    }, settings);

    self.setup();
    self.registerClickEvent(self.getNodes());
    self.moveToCentral(d3.select(".node"));
  };

  $.extend(d3.svg.BubbleChart.prototype, {
    getTransition: function() {
      return this.transition;
    },

    getClickedNode: function () {
      return this.clickedNode;
    },

    getCentralNode: function () {
      return this.centralNode;
    },

    getOptions: function () {
      return this.options;
    },

    randomCirclesPositions: function (delta) {
      var self = this;
      var circles = [];
      var interval = 0;
      var options = self.options;
      while (circles.length < self.items.length && ++interval < self.intervalMax) {
        var val = self.values[circles.length];
        var rad = Math.max((val * options.radiusMax) / self.valueMax, options.radiusMin);
        var dist = self.innerRadius + rad + Math.random() * (self.outerRadius - self.innerRadius - rad * 2);
        var angle = Math.random() * pi2;
        var cx = self.centralPoint + dist * Math.cos(angle);
        var cy = self.centralPoint + dist * Math.sin(angle);

        var hit = false;
        $.each(circles, function (i, circle) {
          var dx = circle.cx - cx;
          var dy = circle.cy - cy;
          var r = circle.r + rad;
          if (dx * dx + dy * dy < Math.pow(r - delta, 2)) {
            hit = true;
            return false;
          }
        });
        if (!hit) {
          circles.push({cx: cx, cy: cy, r: rad, item: self.items[circles.length]});
        }
      }
      if (circles.length < self.items.length) {
        if (delta === options.radiusMin) {
          throw {
            message: "Not enough space for all bubble. Please change the options.",
            options: options
          }
        }
        return self.randomCirclesPositions(delta + options.intersectInc);
      }
      return circles.shuffle();
    },

    getValues: function () {
      var values = [];
      var self = this;
      $.each(self.items, function (i, item) {values.push(self.options.data.eval(item));});
      return values;
    },

    setup: function () {
      var self = this;
      var options = self.options;
      self.swapped = false;
      self.innerRadius = options.innerRadius;
      self.outerRadius = options.outerRadius;
      self.centralPoint = options.size / 2;
      self.intervalMax = options.size * options.size;
      self.items = options.data.items;
      self.centralClickMethod = options.centralClickMethod;
      self.values = self.getValues();
      self.valueMax = self.values.max();
      self.svg = d3.select(options.container).append("svg")
        .attr({preserveAspectRatio: "xMidYMid", width: options.size, height: options.size, class: "bubbleChart"})
        .attr("viewBox", function (d) {return ["0 0", options.viewBoxSize, options.viewBoxSize].join(" ")});
      self.circlePositions = self.randomCirclesPositions(options.intersectDelta);

      var node = self.svg.selectAll(".node")
        .data(self.circlePositions)
        .enter().append("g")
        .attr("class", function (d) {return ["node", options.data.classed(d.item)].join(" ");});

      var fnColor = d3.scale.category20();
      node.append("circle")
        .attr({r: function (d) {return d.r;}, cx: function (d) {return d.cx;}, cy: function (d) {return d.cy;}})
        .style("fill", function (d) {
          return options.data.color !== undefined ? options.data.color(d.item) : fnColor(d.item.text);
        })
        .attr("opacity", "0.8");
      node.sort(function (a, b) {return options.data.eval(b.item) - options.data.eval(a.item);});

      self.transition = {};

      if (options.supportResponsive) {
        $(window).resize(function() {
          var width = $(options.container).width();
          self.svg.attr("width", width);
          self.svg.attr("height", width);
        });
        $(window).resize();
      }

      //lines plugin
      var node = self.getNodes();
      $.each(options.linesFormat, function (i, f) {
        node.append("text")
          .classed(f.classed)
          .style(f.style)
          .attr(f.attr)
          .text(function (d) {return d.item[f.textField];});
      });

    },

    getCirclePositions: function () {
      return this.circlePositions;
    },

    moveToCentral: function (node) {
      var self = this;
      var toCentralPoint = d3.svg.transform()
        .translate(function (d) {
          var cx = node.select('circle').attr("cx");
          var dx = self.centralPoint - d.cx;
          var dy = self.centralPoint - d.cy;
          return [dx, dy];
        });
      self.centralNode = node;
      self.transition.centralNode = node.classed({active: true})
        .transition().duration(self.options.transitDuration);
      self.transition.centralNode.attr('transform', toCentralPoint)
        .select("circle")
        .attr('r', function (d) {return self.options.innerRadius;});

      //central-click
      self.transition.centralNode.each("end", function() {
        node.append("text").classed({"central-click": true})
          .attr(self.options.centralClick.attr)
          .style(self.options.centralClick.style)
          .attr("x", function (d) {return d.cx;})
          .attr("y", function (d) {return d.cy;})
          .text(self.options.centralClick.text)
          .style("opacity", 0).transition().duration(self.getOptions().transitDuration / 2).style("opacity", "0.8");
      });

      //lines plugin
      $.each(self.options.centralFormat, function (i, f) {
        var tNode = d3.select(node.selectAll("text")[0][i]);
        tNode.transition().duration(self.getOptions().transitDuration)
          .style(f.style)
          .attr(f.attr);
        f.classed !== undefined && tNode.classed(f.classed);
        f.textField !== undefined && tNode.text(function (d) {return d.item[f.textField];});
      });

    },

    moveToReflection: function (node) {
      var self = this;
      var toReflectionPoint = d3.svg.transform()
        .translate(function (d) {
          var dx = 2 * (self.centralPoint - d.cx);
          var dy = 2 * (self.centralPoint - d.cy);
          return [dx, dy];
        });

      node.transition()
        .duration(self.options.transitDuration)
        .delay(function (d, i) {return i * 10;})
        .attr('transform', self.swapped ? "" : toReflectionPoint)
        .select("circle")
        .attr('r', function (d) {return d.r;});
    },

    reset: function (node) {
      var self = this;
      node.classed({active: false});

      //central-click
      node.select("text.central-click").remove();

      //lines plugin
      $.each(self.options.linesFormat, function (i, f) {
        var tNode = d3.select(node.selectAll("text")[0][i]);
        tNode.classed(f.classed).text(function (d) {return d.item[f.textField];})
          .transition().duration(self.getOptions().transitDuration)
          .style(f.style)
          .attr(f.attr);
      });

    },

    handleClick: function (node) {
      //FIXME
      this.clickedNode = d3.select(node);

      var label = this.clickedNode.selectAll("text.text")[0][0].innerHTML;
      if (this.clickedNode.selectAll("text.central-click")[0].length === 1) {
        this.centralClickMethod(node, label);
      }
      else {
        this.reset(this.centralNode);
        this.moveToCentral(this.clickedNode);
        this.moveToReflection(this.svg.selectAll(".node:not(.active)"), this.swapped);
        this.swapped = !this.swapped;
      }
    },

    registerClickEvent: function (node) {
      var self = this;
      node.style("cursor", "pointer").on("click", function (d) {
        self.handleClick(this);
      });
    },

    getNodes: function () {
      return this.svg.selectAll(".node");
    },

    get: function () {
      return this.svg;
    }
  });

  return d3.svg.BubbleChart;
})();
/**
 * Settings of bubble chart
 *
 * @typedef {object} settings
 *
 * @param {(object[]|string[])} plugins - Array of plugin [microplugin](https://github.com/brianreavis/microplugin.js|microplugin)
 * @param {string} [container=".bubbleChart"] - Jquery selector which will contain the chart
 * @param {number} size - Size of the chart, in pixel
 * @param {number} [viewBoxSize=size] - Size of the viewport of the chart, in pixel [ViewBoxAttribute](http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute)
 * @param {number} [innerRadius=size/3] - Radius of the Inner Circle, in pixel
 * @param {number} [outerRadius=size/2] - Radius of the Outer Circle, in pixel
 * @param {number} [radiusMin=size/10] - Minimum radius, in pixel
 * @param {number} [radiusMax=(outerRadius  innerRadius)/2] - Maximum radius, in pixel
 * @param {number} [intersectDelta=0] - Intersection between circles, in pixel
 * @param {number} [intersectInc=intersectDelta] - Increment of settings.intersectDelta, in pixel
 * @param {number} [transitDuration=1000] - Duration of transition when do animations, in mili-seconds
 * @param {data} data - Data information
 */

/**
 * Data information
 *
 * @typedef {object} data
 * @param {object[]} items - Array of items
 ex:
 * ```js
 * data.items = [{number: 179, label: "something"}, {number: 220, label: "everything"}]
 * ```
 * @param {function} eval - Function should return a number used to evaluate an item
 ex:
 * ```js
 * data.eval = function(d){
 *   return d.number;
 * }
 * ```
 * @param {function} [color=d3.scale.category20] - Function should return a string used to fill bubbles
ex:
 * ```js
 * data.color = function(d){
 *   return "white";
 * }
 * ```
 */
<|endoftext|>


<commit_before>import { VectorKeyframeTrack } from './tracks/VectorKeyframeTrack';
import { QuaternionKeyframeTrack } from './tracks/QuaternionKeyframeTrack';
import { NumberKeyframeTrack } from './tracks/NumberKeyframeTrack';
import { AnimationUtils } from './AnimationUtils';
import { KeyframeTrack } from './KeyframeTrack';
import { _Math } from '../math/Math';

/**
 *
 * Reusable set of Tracks that represent an animation.
 *
 * @author Ben Houston / http://clara.io/
 * @author David Sarno / http://lighthaus.us/
 */

function AnimationClip( name, duration, tracks ) {

	this.name = name;
	this.tracks = tracks;
	this.duration = ( duration !== undefined ) ? duration : -1;

	this.uuid = _Math.generateUUID();

	// this means it should figure out its duration by scanning the tracks
	if ( this.duration < 0 ) {

		this.resetDuration();

	}

	this.optimize();

}

AnimationClip.prototype = {

	constructor: AnimationClip,

	resetDuration: function() {

		var tracks = this.tracks,
			duration = 0;

		for ( var i = 0, n = tracks.length; i !== n; ++ i ) {

			var track = this.tracks[ i ];

			duration = Math.max( duration, track.times[ track.times.length - 1 ] );

		}

		this.duration = duration;

	},

	trim: function() {

		for ( var i = 0; i < this.tracks.length; i ++ ) {

			this.tracks[ i ].trim( 0, this.duration );

		}

		return this;

	},

	optimize: function() {

		for ( var i = 0; i < this.tracks.length; i ++ ) {

			this.tracks[ i ].optimize();

		}

		return this;

	}

};

// Static methods:

Object.assign( AnimationClip, {

	parse: function( json ) {

		var tracks = [],
			jsonTracks = json.tracks,
			frameTime = 1.0 / ( json.fps || 1.0 );

		for ( var i = 0, n = jsonTracks.length; i !== n; ++ i ) {

			tracks.push( KeyframeTrack.parse( jsonTracks[ i ] ).scale( frameTime ) );

		}

		return new AnimationClip( json.name, json.duration, tracks );

	},

	toJSON: function( clip ) {

		var tracks = [],
			clipTracks = clip.tracks;

		var json = {

			'name': clip.name,
			'duration': clip.duration,
			'tracks': tracks

		};

		for ( var i = 0, n = clipTracks.length; i !== n; ++ i ) {

			tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );

		}

		return json;

	},

	CreateFromMorphTargetSequence: function( name, morphTargetSequence, fps, noLoop ) {

		var numMorphTargets = morphTargetSequence.length;
		var tracks = [];

		for ( var i = 0; i < numMorphTargets; i ++ ) {

			var times = [];
			var values = [];

			times.push(
					( i + numMorphTargets - 1 ) % numMorphTargets,
					i,
					( i + 1 ) % numMorphTargets );

			values.push( 0, 1, 0 );

			var order = AnimationUtils.getKeyframeOrder( times );
			times = AnimationUtils.sortedArray( times, 1, order );
			values = AnimationUtils.sortedArray( values, 1, order );

			// if there is a key at the first frame, duplicate it as the
			// last frame as well for perfect loop.
			if ( ! noLoop && times[ 0 ] === 0 ) {

				times.push( numMorphTargets );
				values.push( values[ 0 ] );

			}

			tracks.push(
					new NumberKeyframeTrack(
						'.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',
						times, values
					).scale( 1.0 / fps ) );
		}

		return new AnimationClip( name, -1, tracks );

	},

	findByName: function( objectOrClipArray, name ) {

		var clipArray = objectOrClipArray;

		if ( ! Array.isArray( objectOrClipArray ) ) {

			var o = objectOrClipArray;
			clipArray = o.geometry && o.geometry.animations || o.animations;

		}

		for ( var i = 0; i < clipArray.length; i ++ ) {

			if ( clipArray[ i ].name === name ) {

				return clipArray[ i ];

			}
		}

		return null;

	},

	CreateClipsFromMorphTargetSequences: function( morphTargets, fps, noLoop ) {

		var animationToMorphTargets = {};

		// tested with https://regex101.com/ on trick sequences
		// such flamingo_flyA_003, flamingo_run1_003, crdeath0059
		var pattern = /^([\w-]*?)([\d]+)$/;

		// sort morph target names into animation groups based
		// patterns like Walk_001, Walk_002, Run_001, Run_002
		for ( var i = 0, il = morphTargets.length; i < il; i ++ ) {

			var morphTarget = morphTargets[ i ];
			var parts = morphTarget.name.match( pattern );

			if ( parts && parts.length > 1 ) {

				var name = parts[ 1 ];

				var animationMorphTargets = animationToMorphTargets[ name ];
				if ( ! animationMorphTargets ) {

					animationToMorphTargets[ name ] = animationMorphTargets = [];

				}

				animationMorphTargets.push( morphTarget );

			}

		}

		var clips = [];

		for ( var name in animationToMorphTargets ) {

			clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );

		}

		return clips;

	},

	// parse the animation.hierarchy format
	parseAnimation: function( animation, bones ) {

		if ( ! animation ) {

			console.error( "  no animation in JSONLoader data" );
			return null;

		}

		var addNonemptyTrack = function(
				trackType, trackName, animationKeys, propertyName, destTracks ) {

			// only return track if there are actually keys.
			if ( animationKeys.length !== 0 ) {

				var times = [];
				var values = [];

				AnimationUtils.flattenJSON(
						animationKeys, times, values, propertyName );

				// empty keys are filtered out, so check again
				if ( times.length !== 0 ) {

					destTracks.push( new trackType( trackName, times, values ) );

				}

			}

		};

		var tracks = [];

		var clipName = animation.name || 'default';
		// automatic length determination in AnimationClip.
		var duration = animation.length || -1;
		var fps = animation.fps || 30;

		var hierarchyTracks = animation.hierarchy || [];

		for ( var h = 0; h < hierarchyTracks.length; h ++ ) {

			var animationKeys = hierarchyTracks[ h ].keys;

			// skip empty tracks
			if ( ! animationKeys || animationKeys.length === 0 ) continue;

			// process morph targets in a way exactly compatible
			// with AnimationHandler.init( animation )
			if ( animationKeys[0].morphTargets ) {

				// figure out all morph targets used in this track
				var morphTargetNames = {};
				for ( var k = 0; k < animationKeys.length; k ++ ) {

					if ( animationKeys[k].morphTargets ) {

						for ( var m = 0; m < animationKeys[k].morphTargets.length; m ++ ) {

							morphTargetNames[ animationKeys[k].morphTargets[m] ] = -1;
						}

					}

				}

				// create a track for each morph target with all zero
				// morphTargetInfluences except for the keys in which
				// the morphTarget is named.
				for ( var morphTargetName in morphTargetNames ) {

					var times = [];
					var values = [];

					for ( var m = 0; m !== animationKeys[k].morphTargets.length; ++ m ) {

						var animationKey = animationKeys[k];

						times.push( animationKey.time );
						values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );

					}

					tracks.push( new NumberKeyframeTrack('.morphTargetInfluence[' + morphTargetName + ']', times, values ) );

				}

				duration = morphTargetNames.length * ( fps || 1.0 );

			} else {
				// ...assume skeletal animation

				var boneName = '.bones[' + bones[ h ].name + ']';

				addNonemptyTrack(
						VectorKeyframeTrack, boneName + '.position',
						animationKeys, 'pos', tracks );

				addNonemptyTrack(
						QuaternionKeyframeTrack, boneName + '.quaternion',
						animationKeys, 'rot', tracks );

				addNonemptyTrack(
						VectorKeyframeTrack, boneName + '.scale',
						animationKeys, 'scl', tracks );

			}

		}

		if ( tracks.length === 0 ) {

			return null;

		}

		var clip = new AnimationClip( clipName, duration, tracks );

		return clip;

	}

} );

export { AnimationClip };
<commit_msg>Protect AnimationClip against the closure circular reference bug<commit_after>import { VectorKeyframeTrack } from './tracks/VectorKeyframeTrack';
import { QuaternionKeyframeTrack } from './tracks/QuaternionKeyframeTrack';
import { NumberKeyframeTrack } from './tracks/NumberKeyframeTrack';
import { AnimationUtils } from './AnimationUtils';
import { KeyframeTrack } from './KeyframeTrack';
import { _Math } from '../math/Math';

/**
 *
 * Reusable set of Tracks that represent an animation.
 *
 * @author Ben Houston / http://clara.io/
 * @author David Sarno / http://lighthaus.us/
 */

function AnimationClip( name, duration, tracks ) {

	this.name = name;
	this.tracks = tracks;
	this.duration = ( duration !== undefined ) ? duration : -1;

	this.uuid = _Math.generateUUID();

	// this means it should figure out its duration by scanning the tracks
	if ( this.duration < 0 ) {

		this.resetDuration();

	}

	this.optimize();

}

Object.assign( AnimationClip, {

	parse: function( json ) {

		var tracks = [],
			jsonTracks = json.tracks,
			frameTime = 1.0 / ( json.fps || 1.0 );

		for ( var i = 0, n = jsonTracks.length; i !== n; ++ i ) {

			tracks.push( KeyframeTrack.parse( jsonTracks[ i ] ).scale( frameTime ) );

		}

		return new AnimationClip( json.name, json.duration, tracks );

	},

	toJSON: function( clip ) {

		var tracks = [],
			clipTracks = clip.tracks;

		var json = {

			'name': clip.name,
			'duration': clip.duration,
			'tracks': tracks

		};

		for ( var i = 0, n = clipTracks.length; i !== n; ++ i ) {

			tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );

		}

		return json;

	},

	CreateFromMorphTargetSequence: function( name, morphTargetSequence, fps, noLoop ) {

		var numMorphTargets = morphTargetSequence.length;
		var tracks = [];

		for ( var i = 0; i < numMorphTargets; i ++ ) {

			var times = [];
			var values = [];

			times.push(
					( i + numMorphTargets - 1 ) % numMorphTargets,
					i,
					( i + 1 ) % numMorphTargets );

			values.push( 0, 1, 0 );

			var order = AnimationUtils.getKeyframeOrder( times );
			times = AnimationUtils.sortedArray( times, 1, order );
			values = AnimationUtils.sortedArray( values, 1, order );

			// if there is a key at the first frame, duplicate it as the
			// last frame as well for perfect loop.
			if ( ! noLoop && times[ 0 ] === 0 ) {

				times.push( numMorphTargets );
				values.push( values[ 0 ] );

			}

			tracks.push(
					new NumberKeyframeTrack(
						'.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',
						times, values
					).scale( 1.0 / fps ) );
		}

		return new AnimationClip( name, -1, tracks );

	},

	findByName: function( objectOrClipArray, name ) {

		var clipArray = objectOrClipArray;

		if ( ! Array.isArray( objectOrClipArray ) ) {

			var o = objectOrClipArray;
			clipArray = o.geometry && o.geometry.animations || o.animations;

		}

		for ( var i = 0; i < clipArray.length; i ++ ) {

			if ( clipArray[ i ].name === name ) {

				return clipArray[ i ];

			}
		}

		return null;

	},

	CreateClipsFromMorphTargetSequences: function( morphTargets, fps, noLoop ) {

		var animationToMorphTargets = {};

		// tested with https://regex101.com/ on trick sequences
		// such flamingo_flyA_003, flamingo_run1_003, crdeath0059
		var pattern = /^([\w-]*?)([\d]+)$/;

		// sort morph target names into animation groups based
		// patterns like Walk_001, Walk_002, Run_001, Run_002
		for ( var i = 0, il = morphTargets.length; i < il; i ++ ) {

			var morphTarget = morphTargets[ i ];
			var parts = morphTarget.name.match( pattern );

			if ( parts && parts.length > 1 ) {

				var name = parts[ 1 ];

				var animationMorphTargets = animationToMorphTargets[ name ];
				if ( ! animationMorphTargets ) {

					animationToMorphTargets[ name ] = animationMorphTargets = [];

				}

				animationMorphTargets.push( morphTarget );

			}

		}

		var clips = [];

		for ( var name in animationToMorphTargets ) {

			clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );

		}

		return clips;

	},

	// parse the animation.hierarchy format
	parseAnimation: function( animation, bones ) {

		if ( ! animation ) {

			console.error( "  no animation in JSONLoader data" );
			return null;

		}

		var addNonemptyTrack = function(
				trackType, trackName, animationKeys, propertyName, destTracks ) {

			// only return track if there are actually keys.
			if ( animationKeys.length !== 0 ) {

				var times = [];
				var values = [];

				AnimationUtils.flattenJSON(
						animationKeys, times, values, propertyName );

				// empty keys are filtered out, so check again
				if ( times.length !== 0 ) {

					destTracks.push( new trackType( trackName, times, values ) );

				}

			}

		};

		var tracks = [];

		var clipName = animation.name || 'default';
		// automatic length determination in AnimationClip.
		var duration = animation.length || -1;
		var fps = animation.fps || 30;

		var hierarchyTracks = animation.hierarchy || [];

		for ( var h = 0; h < hierarchyTracks.length; h ++ ) {

			var animationKeys = hierarchyTracks[ h ].keys;

			// skip empty tracks
			if ( ! animationKeys || animationKeys.length === 0 ) continue;

			// process morph targets in a way exactly compatible
			// with AnimationHandler.init( animation )
			if ( animationKeys[0].morphTargets ) {

				// figure out all morph targets used in this track
				var morphTargetNames = {};
				for ( var k = 0; k < animationKeys.length; k ++ ) {

					if ( animationKeys[k].morphTargets ) {

						for ( var m = 0; m < animationKeys[k].morphTargets.length; m ++ ) {

							morphTargetNames[ animationKeys[k].morphTargets[m] ] = -1;
						}

					}

				}

				// create a track for each morph target with all zero
				// morphTargetInfluences except for the keys in which
				// the morphTarget is named.
				for ( var morphTargetName in morphTargetNames ) {

					var times = [];
					var values = [];

					for ( var m = 0; m !== animationKeys[k].morphTargets.length; ++ m ) {

						var animationKey = animationKeys[k];

						times.push( animationKey.time );
						values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );

					}

					tracks.push( new NumberKeyframeTrack('.morphTargetInfluence[' + morphTargetName + ']', times, values ) );

				}

				duration = morphTargetNames.length * ( fps || 1.0 );

			} else {
				// ...assume skeletal animation

				var boneName = '.bones[' + bones[ h ].name + ']';

				addNonemptyTrack(
						VectorKeyframeTrack, boneName + '.position',
						animationKeys, 'pos', tracks );

				addNonemptyTrack(
						QuaternionKeyframeTrack, boneName + '.quaternion',
						animationKeys, 'rot', tracks );

				addNonemptyTrack(
						VectorKeyframeTrack, boneName + '.scale',
						animationKeys, 'scl', tracks );

			}

		}

		if ( tracks.length === 0 ) {

			return null;

		}

		var clip = new AnimationClip( clipName, duration, tracks );

		return clip;

	}

} );

Object.assign( AnimationClip.prototype, {

	constructor: AnimationClip,

	resetDuration: function() {

		var tracks = this.tracks,
			duration = 0;

		for ( var i = 0, n = tracks.length; i !== n; ++ i ) {

			var track = this.tracks[ i ];

			duration = Math.max( duration, track.times[ track.times.length - 1 ] );

		}

		this.duration = duration;

	},

	trim: function() {

		for ( var i = 0; i < this.tracks.length; i ++ ) {

			this.tracks[ i ].trim( 0, this.duration );

		}

		return this;

	},

	optimize: function() {

		for ( var i = 0; i < this.tracks.length; i ++ ) {

			this.tracks[ i ].optimize();

		}

		return this;

	}

} );

export { AnimationClip };
<|endoftext|>


<commit_before>(function() {
  /**
   * Interface: cachinglayer
   *
   * This module defines functions that are mixed into remoteStorage.local when
   * it is instantiated (currently one of indexeddb.js, localstorage.js, or
   * inmemorystorage.js).
   *
   * All remoteStorage.local implementations should therefore implement
   * this.getNodes, this.setNodes, and this.forAllNodes. The rest is blended in
   * here to create a GPD (get/put/delete) interface which the BaseClient can
   * talk to.
   */

  function isFolder(path) {
    return path.substr(-1) === '/';
  }

  function isDocument(path) {
    return path.substr(-1) !== '/';
  }

  function deepClone(obj) {
    if (obj === undefined) {
      return undefined;
    } else {
      return JSON.parse(JSON.stringify(obj));
    }
  }

  function equal(obj1, obj2) {
    return JSON.stringify(obj1) === JSON.stringify(obj2);
  }

  function getLatest(node) {
    if (typeof(node) !== 'object' || typeof(node.path) !== 'string') {
      return;
    }
    if (isFolder(node.path)) {
      if (node.local && node.local.itemsMap) {
        return node.local;
      }
      if (node.common && node.common.itemsMap) {
        return node.common;
      }
    } else {
      if (node.local && node.local.body && node.local.contentType) {
        return node.local;
      }
      if (node.common && node.common.body && node.common.contentType) {
        return node.common;
      }
      // Migration code! Once all apps use at least this version of the lib, we
      // can publish clean-up code that migrates over any old-format data, and
      // stop supporting it. For now, new apps will support data in both
      // formats, thanks to this:
      if (node.body && node.contentType) {
        return {
          body: node.body,
          contentType: node.contentType
        };
      }
    }
  }

  function isOutdated(node, maxAge) {
    return !node || !node.timestamp ||
           ((new Date().getTime()) - node.timestamp > maxAge);
  }

  function pathsFromRoot(path) {
    var paths = [path];
    var parts = path.replace(/\/$/, '').split('/');

    while (parts.length > 1) {
      parts.pop();
      paths.push(parts.join('/')+'/');
    }
    return paths;
  }

  function makeNode(path, timestamp) {
    var node = { path: path, common: { timestamp: timestamp } };

    if (isFolder(path)) {
      node.common.itemsMap = {};
    }
    return node;
  }

  function updateFolderNodeWithItemName(node, itemName) {
    if (!node.common) {
      node.common = {
        timestamp: now,
        itemsMap: {}
      };
    }
    if (!node.common.itemsMap) {
      node.common.itemsMap = {};
    }
    if (!node.local) {
      node.local = deepClone(node.common);
    }
    if (!node.local.itemsMap) {
      node.local.itemsMap = node.common.itemsMap;
    }
    node.local.itemsMap[itemName] = true;

    return node;
  }

  var methods = {

    get: function(path, maxAge) {
      var promise = promising();

      this.getNodes([path]).then(function(objs) {
        var node = getLatest(objs[path]);
        if ((typeof(maxAge) === 'number') && isOutdated(node, maxAge)) {
          remoteStorage.sync.queueGetRequest(path, promise);
        }

        if (node) {
          promise.fulfill(200, node.body || node.itemsMap, node.contentType);
        } else {
          promise.fulfill(404);
        }
      }.bind(this), function(err) {
        promise.reject(err);
      }.bind(this));

      return promise;
    },

    put: function(path, body, contentType) {
      var paths = pathsFromRoot(path);
      var now = new Date().getTime();

      return this._updateNodes(paths, function(nodes) {
        try {
          for (var i=0; i<paths.length; i++) {
            var path = paths[i];
            var node = nodes[path];
            var previous;

            if (!node) {
              nodes[path] = node = makeNode(path, now);
            }

            // Document
            if (i === 0) {
              previous = getLatest(node);
              node.local = {
                body:                body,
                contentType:         contentType,
                timestamp:           now,
                previousBody:        (previous ? previous.body : undefined),
                previousContentType: (previous ? previous.contentType : undefined),
              };
            }
            // Folder
            else {
              var itemName = paths[i-1].substring(path.length);
              node = updateFolderNodeWithItemName(node, itemName);
            }
          }
          return nodes;
        } catch(e) {
          RemoteStorage.log('Error during PUT', nodes, i, e);
          throw e;
        }
      });
    },

    delete: function(path) {
      var pathNodes = pathsFromRoot(path);
      return this._updateNodes(pathNodes, function(objs) {
        var i, now = new Date().getTime();
        for (i=0; i<pathNodes.length; i++) {
          if (!objs[pathNodes[i]]) {
            throw new Error('cannot delete a non-existing node; retrieve its parent folder first; missing node: '+pathNodes[i]);
          }
          if(i === 0) {
            //delete the document itself
            objs[path].local = {
              body: false,
              timestamp: now
            };
          } else {
            //remove it from all parents
            itemName = pathNodes[i-1].substring(pathNodes[i].length);
            if (!objs[pathNodes[i]].local) {
              objs[pathNodes[i]].local = deepClone(objs[pathNodes[i]].common);
            }
            delete objs[pathNodes[i]].local.itemsMap[itemName];
            if (Object.getOwnPropertyNames(objs[pathNodes[i]].local.itemsMap).length) {
              //this folder still has other items, don't remove any further ancestors
              break;
            }
          }
        }
        return objs;
      });
    },

    flush: function(path) {
      return this._getAllDescendentPaths(path).then(function(paths) {
        return this.getNodes(paths);
      }.bind(this)).then(function(objs) {
        var i;
        for (i in objs) {
          if (objs[i] && objs[i].common && objs[i].local) {
            this._emit('change', {
              path: objs[i].path,
              origin: 'local',
              oldValue: (objs[i].local.body === false ? undefined : objs[i].local.body),
              newValue: (objs[i].common.body === false ? undefined : objs[i].common.body)
            });
          }
          objs[i] = undefined;
        }
        return this.setNodes(objs);
      }.bind(this));
    },

    fireInitial: function() {
      this.forAllNodes(function(node) {
        var latest;
        if (isDocument(node.path)) {
          latest = getLatest(node);
          if (latest) {
            this._emit('change', {
              path: node.path,
              origin: 'local',
              oldValue: undefined,
              oldContentType: undefined,
              newValue: latest.body,
              newContentType: latest.contentType
            });
          }
        }
      }.bind(this));
    },

    /**
     * Method: onDiff
     *
     * Set an event handler for ?
     */
    onDiff: function(diffHandler) {
      this.diffHandler = diffHandler;
    },

    migrate: function(node) {
      if (typeof(node) === 'object' && !node.common) {
        node.common = {};
        if (typeof(node.path) === 'string') {
          if (node.path.substr(-1) === '/' && typeof(node.body) === 'object') {
            node.common.itemsMap = node.body;
          }
        } else {
          //save legacy content of document node as local version
          if (!node.local) {
            node.local = {};
          }
          node.local.body = node.body;
          node.local.contentType = node.contentType;
        }
      }
      return node;
    },

    _updateNodes: function(paths, cb) {
      var self = this;

      return this.getNodes(paths).then(function(nodes) {
        var existingNodes = deepClone(nodes);
        var changeEvents = [];

        nodes = cb(nodes);

        for (var path in nodes) {
          if (equal(nodes[path], existingNodes[path])) {
            delete nodes[path];
          }
          else if(isDocument(path)) {
            changeEvents.push({
              path:           path,
              origin:         'window',
              oldValue:       nodes[path].local.previousBody,
              newValue:       nodes[path].local.body,
              oldContentType: nodes[path].local.previousContentType,
              newContentType: nodes[path].local.contentType
            });
            delete nodes[path].local.previousBody;
            delete nodes[path].local.previousContentType;
          }
        }

        return self.setNodes(nodes).then(function() {
          self._emitChangeEvents(changeEvents);
          return 200;
        });
      },
      function(err) {
        throw(err);
      });
    },

    _emitChangeEvents: function(events) {
      for (var i=0; i<events.length; i++) {
        this._emit('change', events[i]);
        if (this.diffHandler) {
          this.diffHandler(events[i].path);
        }
      }
    },

    _getAllDescendentPaths: function(path) {
      if (isFolder(path)) {
        return this.getNodes([path]).then(function(objs) {
          var i, pending=0, allPaths = [path], latest = getLatest(objs[path]), promise = promising();
          for (i in latest.itemsMap) {
            pending++;
            var subPromise = this._getAllDescendentPaths(path+i);
            subPromise.then(function(paths) {
              var j;
              pending--;
              for (j=0; j<paths.length; j++) {
                allPaths.push(paths[j]);
              }
              if (pending === 0) {
                promise.fulfill(allPaths);
              }
            });
          }
          return promise;
        }.bind(this));
      } else {
        return promising().fulfill([path]);
      }
    },

    _getInternals: function() {
      return {
        isFolder: isFolder,
        isDocument: isDocument,
        deepClone: deepClone,
        equal: equal,
        getLatest: getLatest,
        pathsFromRoot: pathsFromRoot,
        makeNode: makeNode
      };
    }
  };

  /**
   * Function: cachingLayer
   *
   * Mixes common caching layer functionality into an object.
   *
   * The first parameter is always the object to be extended.
   *
   * Example:
   *   (start code)
   *   var MyConstructor = function() {
   *     cachingLayer(this);
   *   };
   *   (end code)
   */
  RemoteStorage.cachingLayer = function(object) {
    for (var key in methods) {
      object[key] = methods[key];
    }
  };
})();
<commit_msg>Refactor cachinglayer delete method<commit_after>(function() {
  /**
   * Interface: cachinglayer
   *
   * This module defines functions that are mixed into remoteStorage.local when
   * it is instantiated (currently one of indexeddb.js, localstorage.js, or
   * inmemorystorage.js).
   *
   * All remoteStorage.local implementations should therefore implement
   * this.getNodes, this.setNodes, and this.forAllNodes. The rest is blended in
   * here to create a GPD (get/put/delete) interface which the BaseClient can
   * talk to.
   */

  function isFolder(path) {
    return path.substr(-1) === '/';
  }

  function isDocument(path) {
    return path.substr(-1) !== '/';
  }

  function deepClone(obj) {
    if (obj === undefined) {
      return undefined;
    } else {
      return JSON.parse(JSON.stringify(obj));
    }
  }

  function equal(obj1, obj2) {
    return JSON.stringify(obj1) === JSON.stringify(obj2);
  }

  function getLatest(node) {
    if (typeof(node) !== 'object' || typeof(node.path) !== 'string') {
      return;
    }
    if (isFolder(node.path)) {
      if (node.local && node.local.itemsMap) {
        return node.local;
      }
      if (node.common && node.common.itemsMap) {
        return node.common;
      }
    } else {
      if (node.local && node.local.body && node.local.contentType) {
        return node.local;
      }
      if (node.common && node.common.body && node.common.contentType) {
        return node.common;
      }
      // Migration code! Once all apps use at least this version of the lib, we
      // can publish clean-up code that migrates over any old-format data, and
      // stop supporting it. For now, new apps will support data in both
      // formats, thanks to this:
      if (node.body && node.contentType) {
        return {
          body: node.body,
          contentType: node.contentType
        };
      }
    }
  }

  function isOutdated(node, maxAge) {
    return !node || !node.timestamp ||
           ((new Date().getTime()) - node.timestamp > maxAge);
  }

  function pathsFromRoot(path) {
    var paths = [path];
    var parts = path.replace(/\/$/, '').split('/');

    while (parts.length > 1) {
      parts.pop();
      paths.push(parts.join('/')+'/');
    }
    return paths;
  }

  function makeNode(path, timestamp) {
    var node = { path: path, common: { timestamp: timestamp } };

    if (isFolder(path)) {
      node.common.itemsMap = {};
    }
    return node;
  }

  function updateFolderNodeWithItemName(node, itemName) {
    if (!node.common) {
      node.common = {
        timestamp: now,
        itemsMap: {}
      };
    }
    if (!node.common.itemsMap) {
      node.common.itemsMap = {};
    }
    if (!node.local) {
      node.local = deepClone(node.common);
    }
    if (!node.local.itemsMap) {
      node.local.itemsMap = node.common.itemsMap;
    }
    node.local.itemsMap[itemName] = true;

    return node;
  }

  var methods = {

    get: function(path, maxAge) {
      var promise = promising();

      this.getNodes([path]).then(function(objs) {
        var node = getLatest(objs[path]);
        if ((typeof(maxAge) === 'number') && isOutdated(node, maxAge)) {
          remoteStorage.sync.queueGetRequest(path, promise);
        }

        if (node) {
          promise.fulfill(200, node.body || node.itemsMap, node.contentType);
        } else {
          promise.fulfill(404);
        }
      }.bind(this), function(err) {
        promise.reject(err);
      }.bind(this));

      return promise;
    },

    put: function(path, body, contentType) {
      var paths = pathsFromRoot(path);
      var now = new Date().getTime();

      return this._updateNodes(paths, function(nodes) {
        try {
          for (var i=0; i<paths.length; i++) {
            var path = paths[i];
            var node = nodes[path];
            var previous;

            if (!node) {
              nodes[path] = node = makeNode(path, now);
            }

            // Document
            if (i === 0) {
              previous = getLatest(node);
              node.local = {
                body:                body,
                contentType:         contentType,
                timestamp:           now,
                previousBody:        (previous ? previous.body : undefined),
                previousContentType: (previous ? previous.contentType : undefined),
              };
            }
            // Folder
            else {
              var itemName = paths[i-1].substring(path.length);
              node = updateFolderNodeWithItemName(node, itemName);
            }
          }
          return nodes;
        } catch(e) {
          RemoteStorage.log('Error during PUT', nodes, i, e);
          throw e;
        }
      });
    },

    delete: function(path) {
      var paths = pathsFromRoot(path);

      return this._updateNodes(paths, function(nodes) {
        var now = new Date().getTime();

        for (var i=0; i<paths.length; i++) {
          var path = paths[i];
          var node = nodes[path];

          if (!node) {
            throw new Error('Cannot delete non-existing node '+path);
          }

          // Document
          if (i === 0) {
            // TODO should body better be undefined?
            node.local = { body: false, timestamp: now };
          }
          // Folder
          else {
            if (!node.local) {
              node.local = deepClone(node.common);
            }
            var itemName = paths[i-1].substring(path.length);
            delete node.local.itemsMap[itemName];

            if (Object.getOwnPropertyNames(node.local.itemsMap).length > 0) {
              // This folder still contains other items, don't remove any further ancestors
              break;
            }
          }
        }
        return nodes;
      });
    },

    flush: function(path) {
      return this._getAllDescendentPaths(path).then(function(paths) {
        return this.getNodes(paths);
      }.bind(this)).then(function(objs) {
        var i;
        for (i in objs) {
          if (objs[i] && objs[i].common && objs[i].local) {
            this._emit('change', {
              path: objs[i].path,
              origin: 'local',
              oldValue: (objs[i].local.body === false ? undefined : objs[i].local.body),
              newValue: (objs[i].common.body === false ? undefined : objs[i].common.body)
            });
          }
          objs[i] = undefined;
        }
        return this.setNodes(objs);
      }.bind(this));
    },

    fireInitial: function() {
      this.forAllNodes(function(node) {
        var latest;
        if (isDocument(node.path)) {
          latest = getLatest(node);
          if (latest) {
            this._emit('change', {
              path: node.path,
              origin: 'local',
              oldValue: undefined,
              oldContentType: undefined,
              newValue: latest.body,
              newContentType: latest.contentType
            });
          }
        }
      }.bind(this));
    },

    /**
     * Method: onDiff
     *
     * Set an event handler for ?
     */
    onDiff: function(diffHandler) {
      this.diffHandler = diffHandler;
    },

    migrate: function(node) {
      if (typeof(node) === 'object' && !node.common) {
        node.common = {};
        if (typeof(node.path) === 'string') {
          if (node.path.substr(-1) === '/' && typeof(node.body) === 'object') {
            node.common.itemsMap = node.body;
          }
        } else {
          //save legacy content of document node as local version
          if (!node.local) {
            node.local = {};
          }
          node.local.body = node.body;
          node.local.contentType = node.contentType;
        }
      }
      return node;
    },

    _updateNodes: function(paths, cb) {
      var self = this;

      return this.getNodes(paths).then(function(nodes) {
        var existingNodes = deepClone(nodes);
        var changeEvents = [];

        nodes = cb(nodes);

        for (var path in nodes) {
          if (equal(nodes[path], existingNodes[path])) {
            delete nodes[path];
          }
          else if(isDocument(path)) {
            changeEvents.push({
              path:           path,
              origin:         'window',
              oldValue:       nodes[path].local.previousBody,
              newValue:       nodes[path].local.body,
              oldContentType: nodes[path].local.previousContentType,
              newContentType: nodes[path].local.contentType
            });
            delete nodes[path].local.previousBody;
            delete nodes[path].local.previousContentType;
          }
        }

        return self.setNodes(nodes).then(function() {
          self._emitChangeEvents(changeEvents);
          return 200;
        });
      },
      function(err) {
        throw(err);
      });
    },

    _emitChangeEvents: function(events) {
      for (var i=0; i<events.length; i++) {
        this._emit('change', events[i]);
        if (this.diffHandler) {
          this.diffHandler(events[i].path);
        }
      }
    },

    _getAllDescendentPaths: function(path) {
      if (isFolder(path)) {
        return this.getNodes([path]).then(function(objs) {
          var i, pending=0, allPaths = [path], latest = getLatest(objs[path]), promise = promising();
          for (i in latest.itemsMap) {
            pending++;
            var subPromise = this._getAllDescendentPaths(path+i);
            subPromise.then(function(paths) {
              var j;
              pending--;
              for (j=0; j<paths.length; j++) {
                allPaths.push(paths[j]);
              }
              if (pending === 0) {
                promise.fulfill(allPaths);
              }
            });
          }
          return promise;
        }.bind(this));
      } else {
        return promising().fulfill([path]);
      }
    },

    _getInternals: function() {
      return {
        isFolder: isFolder,
        isDocument: isDocument,
        deepClone: deepClone,
        equal: equal,
        getLatest: getLatest,
        pathsFromRoot: pathsFromRoot,
        makeNode: makeNode
      };
    }
  };

  /**
   * Function: cachingLayer
   *
   * Mixes common caching layer functionality into an object.
   *
   * The first parameter is always the object to be extended.
   *
   * Example:
   *   (start code)
   *   var MyConstructor = function() {
   *     cachingLayer(this);
   *   };
   *   (end code)
   */
  RemoteStorage.cachingLayer = function(object) {
    for (var key in methods) {
      object[key] = methods[key];
    }
  };
})();
<|endoftext|>


<commit_before>export const WORDS = [
  "red",
  "blue",
  "green",
  "brown",
  "black",
  "yellow",
  "purple",
  "orange",
  "silver",
  "gold",
  "bronze",
  "cat",
  "dog",
  "cow",
  "rabbit",
  "turtle",
  "pig",
  "horse",
  "whale",
  "shark",
  "fish",
  "bird",
  "bat",
  "lamb",
  "snake",
  "lizard",
  "frog",
  "giraffe",
  "elephant",
  "dinosaur",
  "chicken",
  "penguin",
  "eagle",
  "hawk",
  "aligator",
  "spider",
  "fly",
  "bee",
  "worm",
  "dragon",
  "unicorn",
  "tarantula",
  "steak",
  "pork",
  "beef",
  "milk",
  "honey",
  "sugar",
  "flour",
  "cereal",
  "bread",
  "pasta",
  "pizza",
  "burrito",
  "taco",
  "shrimp",
  "squid",
  "sushi",
  "noodles",
  "china",
  "japan",
  "russia",
  "europe",
  "america",
  "australia",
  "ireland",
  "africa",
  "india",
  "canada",
  "mexico",
  "brazil",
  "superman",
  "batman",
  "spiderman",
  "wonderwoman",
  "hulk",
  "iron man",
  "thor",
  "god",
  "jesus",
  "bible",
  "pray",
  "church",
  "camp",
  "jeep",
  "dirt",
  "water",
  "rain",
  "cloud",
  "sun",
  "star",
  "moon",
  "mars",
  "saturn",
  "comet",
  "asteroid",
  "earth",
  "world",
  "horseshoes",
  "cabin",
  "tent",
  "sleeping bag",
  "stove",
  "oven",
  "fridge",
  "couch",
  "house",
  "chimney",
  "fireplace",
  "car",
  "motorcyle",
  "bus",
  "tank",
  "bike",
  "wagon",
  "scooter",
  "firetruck",
  "police",
  "ambulance",
  "airplane",
  "helicopter",
  "jet",
  "mouse",
  "rat",
  "disneyland",
  "legos",
  "mountain",
  "cliff",
  "sand",
  "beach",
  "wave",
  "starfish",
  "river",
  "lake",
  "volcano",
  "ocean",
  "grass",
  "road",
  "slide",
  "pool",
  "football",
  "soccer",
  "basketball",
  "baseball",
  "wrestling ",
  "golf",
  "bowling",
  "darts",
  "cards",
  "poker",
  "blackjack",
  "casino",
  "vegas",
  "new york",
  "san francisco",
  "paris",
  "chicago",
  "miami",
  "detroit",
  "dallas",
  "los angelas",
  "hawaii",
  "california",
  "alaska",
  "texas",
  "florida",
  "colorado",
  "arizona",
  "yosemite",
  "waterfall",
  "mayor",
  "captain",
  "president",
  "chief",
  "indian",
  "cowboy",
  "frankenstein",
  "dracula",
  "halloween",
  "easter",
  "christmas",
  "4th of July",
  "fireworks",
  "mummy",
  "werewolf",
  "clown",
  "circus",
  "puppet",
  "computer",
  "apple",
  "banana",
  "cherry",
  "strawberry",
  "potato",
  "tomato ",
  "corn",
  "ketchup",
  "mustard",
  "soda",
  "beer",
  "wine",
  "whiskey",
  "vodka",
  "tequila",
  "koolade",
  "trump",
  "clinton",
  "obama",
  "bush",
  "tree",
  "throne",
  "toilet",
  "shower",
  "sword",
  "shield",
  "gun",
  "knife",
  "fork",
  "spoon",
  "jedi",
  "harry potter",
  "darth vader",
  "jail",
  "store",
  "school",
  "college",
  "freeway",
  "airport",
  "work",
  "mickey mouse",
  "donald duck",
  "pokemon",
  "lifegaurd",
  "hospital",
  "taxes",
  "money",
  "coin",
  "wallet",
  "purse",
  "pants",
  "shoes",
  "socks",
  "shirt",
  "jacket",
  "hat",
  "glasses",
  "sunglasses",
  "gloves",
  "jewelry",
  "coffee",
  "starbucks",
  "mcdonalds",
  "latte",
  "rock",
  "glass",
  "iron",
  "wood",
  "cotton",
  "metal",
  "window",
  "door",
  "wall",
  "ceiling",
  "neighbor",
  "cobra",
  "pitbull",
  "bar",
  "towel",
  "soup",
  "soap",
  "math",
  "history",
  "chemistry",
  "lunch",
  "dinner",
  "breakfast",
  "eggs",
  "bacon",
  "pancake",
  "sandwich",
  "peanut butter",
  "snowboard",
  "skateboard",
  "surf",
  "tsunami",
  "earthquake",
  "tornado",
  "hurricane",
  "storm",
  "flood",
  "hollywood",
  "movie",
  "tv",
  "music",
  "band",
  "concert",
  "cartoon",
  "smurf",
  "snoopy",
  "peanut",
  "cracker",
  "chip",
  "facebook",
  "iphone",
  "text",
  "asia",
  "space",
  "astronaut",
  "detective",
  "king",
  "queen",
  "prince",
  "devil",
  "angel",
  "princess",
  "prince",
  "dwarf",
  "soldier",
  "robot",
  "satellite",
  "helmet",
  "goggles",
  "ink",
  "pencil",
  "transformer",
  "toys",
  "baby",
  "child",
  "parent",
  "adult",
  "brother",
  "sister",
  "mom",
  "dad",
  "babysitter",
  "mailman",
  "grass",
  "gas",
  "steam",
  "crane",
  "tractor",
  "truck",
  "palm",
  "hand",
  "foot",
  "stomach",
  "neck",
  "eyes",
  "nose",
  "mouth",
  "ears",
  "head",
  "butt",
  "web",
  "nest",
  "crib",
  "day",
  "night",
  "knight",
  "morning",
  "bottle",
  "plastic",
  "mug",
  "marshmellow",
  "ranger",
  "fire",
  "log",
  "key",
  "fence",
  "rope",
  "garage",
  "thumb",
  "engine",
  "suit",
  "cross",
  "force",
  "theater",
  "ice cream",
  "cookie",
  "cake",
  "mail",
  "light",
  "bell",
  "watch",
  "clock",
  "piano",
  "guitar",
  "stereo",
  "drums",
  "santa",
  "bunny",
  "pumpkin",
  "witch",
  "broom",
  "vacuum",
  "carpet",
  "hoop",
  "hole",
  "ramp",
  "swing",
  "merry go round",
  "park",
  "stick",
  "game",
  "turkey",
  "thanksgiving",
  "vacation",
  "mirror",
  "magic",
  "magician",
  "priest",
  "dance",
  "opera",
  "oprah",
  "ballet",
  "rock n roll",
  "book",
  "novel",
  "comic book",
  "diamond",
  "screen",
  "play",
  "hood",
  "ship",
  "lock",
  "cage",
  "contract",
  "paper",
  "van",
  "nurse",
  "doctor",
  "patient",
  "stock",
  "room",
  "kitchen",
  "yard",
  "chalk",
  "actor",
  "director",
  "wheelchair",
  "medicine",
  "shot",
  "cast",
  "island",
  "country",
  "planet",
  "blanket",
  "suitcase",
  "luggage",
  "bandage",
  "scarf",
  "makeup",
  "cigar",
  "cigarette",
  "drugs",
  "boxing",
  "trunks",
  "camera",
  "telescope",
  "microscope",
  "pie",
  "buffalo",
  "square",
  "triangle",
  "circle",
  "madonna",
  "teddy bear",
  "bear",
  "war",
  "pillow",
  "elvis",
  "michael jordan",
  "beatles",
  "friends",
  "simpsons",
  "JFK",
  "sinatra",
  "kermit",
  "bug bunny",
  "pixar",
  "E.T.",
  "alien",
  "babe ruth",
  "MTV",
  "fox",
  "politics",
  "disease",
  "pickle",
  "spaceship",
  "blimp",
  "portland",
  "hunter",
  "lion",
  "tiger",
  "zebra",
  "safari",
  "france",
  "london",
  "washingston",
  "white house",
  "lincoln",
  "puppy",
  "kitten",
  "zoo",
  "aquarium",
  "shelter",
  "forest",
  "oz",
  "galaxy",
  "jupiter",
  "pluto",
  "hose",
  "rope",
  "air conditioner",
  "library",
  "gym",
  "workout",
  "palm",
  "fortune",
  "lottery",
  "picasso",
  "mona lisa",
  "art",
  "museum",
  "skyscraper",
  "subway",
  "beethoven",
  "C3PO",
  "garfield",
  "popeye",
  "flintstone",
  "garden",
  "flower",
  "rose",
  "beast",
  "mermaid",
  "shipwreck",
  "light bulb",
  "DVD",
  "CD",
  "teacher",
  "student",
  "sports",
  "ring",
  "necklace",
  "skirt",
  "monster",
  "nightmare",
  "dream",
  "imagination",
  "balloon",
  "string",
  "parachute",
  "caveman",
  "army",
  "navy",
  "airforce",
  "marine",
  "thumb",
  "snow",
  "ant",
  "freckle",
  "keys",
  "remote control",
  "GPS",
  "printer"
];<commit_msg>Adding more words<commit_after>export const WORDS = [
  "red",
  "blue",
  "green",
  "brown",
  "black",
  "yellow",
  "purple",
  "orange",
  "silver",
  "gold",
  "bronze",
  "cat",
  "dog",
  "cow",
  "rabbit",
  "turtle",
  "pig",
  "horse",
  "whale",
  "shark",
  "fish",
  "bird",
  "bat",
  "lamb",
  "snake",
  "lizard",
  "frog",
  "giraffe",
  "elephant",
  "dinosaur",
  "chicken",
  "penguin",
  "eagle",
  "hawk",
  "aligator",
  "spider",
  "fly",
  "bee",
  "worm",
  "dragon",
  "unicorn",
  "tarantula",
  "steak",
  "pork",
  "beef",
  "milk",
  "honey",
  "sugar",
  "flour",
  "cereal",
  "bread",
  "pasta",
  "pizza",
  "burrito",
  "taco",
  "shrimp",
  "squid",
  "sushi",
  "noodles",
  "china",
  "japan",
  "russia",
  "europe",
  "america",
  "australia",
  "ireland",
  "africa",
  "india",
  "canada",
  "mexico",
  "brazil",
  "superman",
  "batman",
  "spiderman",
  "wonderwoman",
  "hulk",
  "iron man",
  "thor",
  "god",
  "jesus",
  "bible",
  "pray",
  "church",
  "camp",
  "jeep",
  "dirt",
  "water",
  "rain",
  "cloud",
  "sun",
  "star",
  "moon",
  "mars",
  "saturn",
  "comet",
  "asteroid",
  "earth",
  "world",
  "horseshoes",
  "cabin",
  "tent",
  "sleeping bag",
  "stove",
  "oven",
  "fridge",
  "couch",
  "house",
  "chimney",
  "fireplace",
  "car",
  "motorcyle",
  "bus",
  "tank",
  "bike",
  "wagon",
  "scooter",
  "firetruck",
  "police",
  "ambulance",
  "airplane",
  "helicopter",
  "jet",
  "mouse",
  "rat",
  "disneyland",
  "legos",
  "mountain",
  "cliff",
  "sand",
  "beach",
  "wave",
  "starfish",
  "river",
  "lake",
  "volcano",
  "ocean",
  "grass",
  "road",
  "slide",
  "pool",
  "football",
  "soccer",
  "basketball",
  "baseball",
  "wrestling",
  "golf",
  "bowling",
  "darts",
  "cards",
  "poker",
  "blackjack",
  "casino",
  "vegas",
  "new york",
  "san francisco",
  "paris",
  "chicago",
  "miami",
  "detroit",
  "dallas",
  "los angelas",
  "hawaii",
  "california",
  "alaska",
  "texas",
  "florida",
  "colorado",
  "arizona",
  "yosemite",
  "waterfall",
  "mayor",
  "captain",
  "president",
  "chief",
  "indian",
  "cowboy",
  "frankenstein",
  "dracula",
  "halloween",
  "easter",
  "christmas",
  "4th of July",
  "fireworks",
  "mummy",
  "werewolf",
  "clown",
  "circus",
  "puppet",
  "computer",
  "apple",
  "banana",
  "cherry",
  "strawberry",
  "potato",
  "tomato",
  "corn",
  "ketchup",
  "mustard",
  "soda",
  "beer",
  "wine",
  "whiskey",
  "vodka",
  "tequila",
  "koolade",
  "trump",
  "clinton",
  "obama",
  "bush",
  "tree",
  "throne",
  "toilet",
  "shower",
  "sword",
  "shield",
  "gun",
  "knife",
  "fork",
  "spoon",
  "jedi",
  "harry potter",
  "darth vader",
  "jail",
  "store",
  "school",
  "college",
  "freeway",
  "airport",
  "work",
  "mickey mouse",
  "donald duck",
  "pokemon",
  "lifegaurd",
  "hospital",
  "taxes",
  "money",
  "coin",
  "wallet",
  "purse",
  "pants",
  "shoes",
  "socks",
  "shirt",
  "jacket",
  "hat",
  "glasses",
  "sunglasses",
  "gloves",
  "jewelry",
  "coffee",
  "starbucks",
  "mcdonalds",
  "latte",
  "rock",
  "glass",
  "iron",
  "wood",
  "cotton",
  "metal",
  "window",
  "door",
  "wall",
  "ceiling",
  "neighbor",
  "cobra",
  "pitbull",
  "bar",
  "towel",
  "soup",
  "soap",
  "math",
  "history",
  "chemistry",
  "lunch",
  "dinner",
  "breakfast",
  "eggs",
  "bacon",
  "pancake",
  "sandwich",
  "peanut butter",
  "snowboard",
  "skateboard",
  "surf",
  "tsunami",
  "earthquake",
  "tornado",
  "hurricane",
  "storm",
  "flood",
  "hollywood",
  "movie",
  "tv",
  "music",
  "band",
  "concert",
  "cartoon",
  "smurf",
  "snoopy",
  "peanut",
  "cracker",
  "chip",
  "facebook",
  "iphone",
  "text",
  "asia",
  "space",
  "astronaut",
  "detective",
  "king",
  "queen",
  "prince",
  "devil",
  "angel",
  "princess",
  "prince",
  "dwarf",
  "soldier",
  "robot",
  "satellite",
  "helmet",
  "goggles",
  "ink",
  "pencil",
  "transformer",
  "toys",
  "baby",
  "child",
  "parent",
  "adult",
  "brother",
  "sister",
  "mom",
  "dad",
  "babysitter",
  "mailman",
  "grass",
  "gas",
  "steam",
  "crane",
  "tractor",
  "truck",
  "palm",
  "hand",
  "foot",
  "stomach",
  "neck",
  "eyes",
  "nose",
  "mouth",
  "ears",
  "head",
  "butt",
  "web",
  "nest",
  "crib",
  "day",
  "night",
  "knight",
  "morning",
  "bottle",
  "plastic",
  "mug",
  "marshmellow",
  "ranger",
  "fire",
  "log",
  "key",
  "fence",
  "rope",
  "garage",
  "thumb",
  "engine",
  "suit",
  "cross",
  "force",
  "theater",
  "ice cream",
  "cookie",
  "cake",
  "mail",
  "light",
  "bell",
  "watch",
  "clock",
  "piano",
  "guitar",
  "stereo",
  "drums",
  "santa",
  "bunny",
  "pumpkin",
  "witch",
  "broom",
  "vacuum",
  "carpet",
  "hoop",
  "hole",
  "ramp",
  "swing",
  "merry go round",
  "park",
  "stick",
  "game",
  "turkey",
  "thanksgiving",
  "vacation",
  "mirror",
  "magic",
  "magician",
  "priest",
  "dance",
  "opera",
  "oprah",
  "ballet",
  "rock n roll",
  "book",
  "novel",
  "comic book",
  "diamond",
  "screen",
  "play",
  "hood",
  "ship",
  "lock",
  "cage",
  "contract",
  "paper",
  "van",
  "nurse",
  "doctor",
  "patient",
  "stock",
  "room",
  "kitchen",
  "yard",
  "chalk",
  "actor",
  "director",
  "wheelchair",
  "medicine",
  "shot",
  "cast",
  "island",
  "country",
  "planet",
  "blanket",
  "suitcase",
  "luggage",
  "bandage",
  "scarf",
  "makeup",
  "cigar",
  "cigarette",
  "drugs",
  "boxing",
  "trunks",
  "camera",
  "telescope",
  "microscope",
  "pie",
  "buffalo",
  "square",
  "triangle",
  "circle",
  "madonna",
  "teddy bear",
  "bear",
  "war",
  "pillow",
  "elvis",
  "michael jordan",
  "beatles",
  "friends",
  "simpsons",
  "JFK",
  "sinatra",
  "kermit",
  "bug bunny",
  "pixar",
  "E.T.",
  "alien",
  "babe ruth",
  "MTV",
  "fox",
  "politics",
  "disease",
  "pickle",
  "spaceship",
  "blimp",
  "portland",
  "hunter",
  "lion",
  "tiger",
  "zebra",
  "safari",
  "france",
  "london",
  "washingston",
  "white house",
  "lincoln",
  "puppy",
  "kitten",
  "zoo",
  "aquarium",
  "shelter",
  "forest",
  "oz",
  "galaxy",
  "jupiter",
  "pluto",
  "hose",
  "rope",
  "air conditioner",
  "library",
  "gym",
  "workout",
  "palm",
  "fortune",
  "lottery",
  "picasso",
  "mona lisa",
  "art",
  "museum",
  "skyscraper",
  "subway",
  "beethoven",
  "C3PO",
  "garfield",
  "popeye",
  "flintstone",
  "garden",
  "flower",
  "rose",
  "beast",
  "mermaid",
  "shipwreck",
  "light bulb",
  "DVD",
  "CD",
  "teacher",
  "student",
  "sports",
  "ring",
  "necklace",
  "skirt",
  "monster",
  "nightmare",
  "dream",
  "imagination",
  "balloon",
  "string",
  "parachute",
  "caveman",
  "army",
  "navy",
  "airforce",
  "marine",
  "thumb",
  "snow",
  "ant",
  "freckle",
  "keys",
  "remote control",
  "GPS",
  "printer",
  "woodstock",
  "tinman",
  "scarecrow",
  "corkscrew",
  "candle",
  "flashlight",
  "match",
  "tokyo",
  "paraside",
  "bermuda",
  "village",
  "poodle",
  "twins",
  "darth vader",
  "yoda",
  "pretzel",
  "investigation",
  "classroom",
  "tie",
  "resume",
  "plate",
  "dishes",
  "bucket",
  "desert",
  "palm tree",
  "stone",
  "castle",
  "bridge",
  "torch",
  "weapon",
  "underwear",
  "bra",
  "mime",
  "time machine",
  "crystal ball",
  "jetpack",
  "prisoner",
  "leader",
  "sunglasses",
  "microphone",
  "bolt",
  "lightning",
  "thunder",
  "street",
  "mailbox",
  "wheel",
  "chain",
  "seat",
  "seattle",
  "pacific",
  "phoenix",
  "milk",
  "lemonade",
  "juice",
  "tiger woods",
  "muhammad ali",
  "joker",
  "wig",
  "adam sandler",
  "will ferrel",
  "keyboard",
  "zombie",
  "fairy",
  "rainbow",
  "leprechaun",
  "valentine",
  "heart",
  "brain",
  "stomach",
  "kidney",
  "tongue",
  "skeleton",
  "elf",
  "hobbit",
  "giant",
  "beans",
  "willy wonka",
  "oompa loompa",
  "milky way",
  "scuba diver",
  "atlantis",
  "egypt",
  "rome",
  "italy",
];<|endoftext|>


<commit_before>export const initialState = {
    user: {
        isAuthenticated: false,
        email: '',
        userType: '',
        isAdmin: false
    },
    global: {
        workingOn: {}
    },
    rootPath: "/florence",
    teams: {
        active: {},
        all: [],
        allIDsAndNames: [],
        users: []
    },
    datasets: {
        all: [],
        jobs: []
    },
    collections: {
        all: [],
        active: null,
        toDelete: {}
    },
    notifications: [],
    preview: {
        selectedPage: null
    }
};<commit_msg>Update workingOn initial state so it matches empty state<commit_after>export const initialState = {
    user: {
        isAuthenticated: false,
        email: '',
        userType: '',
        isAdmin: false
    },
    global: {
        workingOn: null
    },
    rootPath: "/florence",
    teams: {
        active: {},
        all: [],
        allIDsAndNames: [],
        users: []
    },
    datasets: {
        all: [],
        jobs: []
    },
    collections: {
        all: [],
        active: null,
        toDelete: {}
    },
    notifications: [],
    preview: {
        selectedPage: null
    }
};<|endoftext|>


<commit_before>'use strict';

var React     = require('react'),
    TokenCell = require('./TokenCell'),

    Tokenizer;

const KEYS = {
  TAB: 9,
  COMMA: 188,
  ENTER: 13
};

const SEPERATORS = [KEYS.TAB, KEYS.COMMA, KEYS.ENTER];

function _findTextNode(nodes) {
  var i;

  for (i = 0; i < nodes.length; i++) {
    if (nodes[i].nodeType === 3) {
      return nodes[i];
    }
  }

  return null;
}

Tokenizer = React.createClass({

  propTypes: {
    tokens: React.PropTypes.array,
    tokenize: React.PropTypes.func
  },

  getDefaultProps: function() {
    return { tokens: [] };
  },

  componentDidMount: function() {
    this.getDOMNode().focus();
  },

  render: function() {
    var items = this.props.tokens.map(function(token, index) {
      return <TokenCell key={index} textContent={token} />;
    });

    return (

{items}

    );
  },

  _handleKeyDown: function(evt) {
    var node     = this.getDOMNode(),
        children = node.childNodes,

        textNode, textContent;

    if (SEPERATORS.indexOf(evt.which) !== -1) {
      evt.preventDefault();

      if (children.length > 0) {
        textNode = _findTextNode(children);

        if (textNode && textNode.textContent.trim()) {
          node.removeChild(textNode);
          this.props.tokenize(textNode.textContent.trim());
        }
      }
    }
  }

});

module.exports = Tokenizer;
<commit_msg>Set caret at end when new token is created<commit_after>'use strict';

var React     = require('react'),
    TokenCell = require('./TokenCell'),

    Tokenizer;

const KEYS = {
  TAB: 9,
  COMMA: 188,
  ENTER: 13
};

const SEPERATORS = [KEYS.TAB, KEYS.COMMA, KEYS.ENTER];

function _findTextNode(nodes) {
  var i;

  for (i = 0; i < nodes.length; i++) {
    if (nodes[i].nodeType === 3) {
      return nodes[i];
    }
  }

  return null;
}

function _setCaretAtEnd(node) {
  var range, selection;

  range = document.createRange();
  range.selectNodeContents(node);
  range.collapse(false);

  selection = window.getSelection();
  selection.removeAllRanges();
  selection.addRange(range);
}

Tokenizer = React.createClass({

  propTypes: {
    tokens: React.PropTypes.array,
    tokenize: React.PropTypes.func
  },

  getDefaultProps: function() {
    return { tokens: [] };
  },

  componentDidMount: function() {
    this.getDOMNode().focus();
  },

  componentDidUpdate: function() {
    var node     = this.getDOMNode(),
        children = node.childNodes,
        lastNode = children[children.length - 1];

    if (this.props.tokens.length > 0) {
      node.innerHTML += ' ';

      _setCaretAtEnd(node);
    }
  },

  render: function() {
    var items = this.props.tokens.map(function(token, index) {
      return <TokenCell key={index} textContent={token} />;
    });

    return (

{items}

    );
  },

  _handleKeyDown: function(evt) {
    var node     = this.getDOMNode(),
        children = node.childNodes,

        textNode, textContent;

    if (SEPERATORS.indexOf(evt.which) !== -1) {
      evt.preventDefault();

      if (children.length > 0) {
        textNode = _findTextNode(children);

        if (textNode && textNode.textContent.trim()) {
          node.removeChild(textNode);
          this.props.tokenize(textNode.textContent.trim());
        }
      }
    }
  }

});

module.exports = Tokenizer;
<|endoftext|>