<commit_before>import {getTrackableArray} from './types/trackableArray';
import {getTrackableObject} from './types/trackableObject';
import {addHiddenFinalProp, isUntrackable, isPlainObject, isCollection, isArray} from './utils';
import {markedUntrackable} from './core/untracked';

const trackerProp = '__$tracker';

export function getTracker(instance) {
    if(isUntrackable(instance) || markedUntrackable(instance) || isCollection(instance))
        return;

    if(instance[trackerProp])
        return instance[trackerProp];

    const tracker = new Tracker(instance);
    addHiddenFinalProp(instance, trackerProp, tracker);

    return tracker;
}

export function isTracking(target) {
    return target[trackerProp] != null;
}

/**
 * Refactored to not use ES6 class, to reduce bundle size
 * */
function Tracker(target) {
    this.values = {};
    this.callbacks = [];
    this.shallowCallbacks = [];
    this.subscriptions = {};
    this.propsScopes = {};
    this.target = target;
    this.waitingForActionsToEnd = false;
    this.rerenderCallbackSetted = false;
}

Tracker.prototype._setValue = function(prop, value, shouldReport = true) {
    const currValue = this.getValue(prop);
    const isSame = currValue === value;

    if(!isSame) {
        const {
            tracker: valueTracker,
            value: newValue
        } = this._makeTrackable(value);

        if(newValue)
            value = newValue;

        this.stopListen(prop);

        if(valueTracker) {
            // don't subscribe for changes for scope === 'ref'
            if(this.propsScopes[prop] == null) {
                this.subscriptions[prop] = valueTracker.onChange(() => this.reportChange(prop));
            } else if(this.propsScopes[prop] == 'shallow') {
                this.subscriptions[prop] = valueTracker.onShallowChange(() => this.reportChange(prop));
            }
        }
    }

    this.values[prop] = value;

    if(shouldReport && !isSame)
        this.reportChange();
};

Tracker.prototype._makeTrackable = function(target) {
    if(isUntrackable(target) || markedUntrackable(target)) {
        //TODO may be change to return target?
        return {
            value: target
        };
    }

    if(isCollection(target)) {
        console.warn('Map/Set/WeakMap/WeakSet are not currently supported for state tracking');
        return {
            value: target
        };
    }

    if(isTracking(target)) {
        return {
            tracker: getTracker(target),
            value: target
        };
    }

    if(isPlainObject(target)) {
        const trackableObject = getTrackableObject(target);
        return {
            tracker: getTracker(trackableObject),
            value: trackableObject
        };
    }

    if(isArray(target)) {
        const trackableArray = getTrackableArray(target);
        return {
            tracker: getTracker(trackableArray),
            value: trackableArray
        };
    }

    return {
        value: target
    };
};

Tracker.prototype.setValue = function(prop, value) {
    this._setValue(prop, value);
};

Tracker.prototype.setValueSilently = function(prop, value) {
    this._setValue(prop, value, false);
};

Tracker.prototype.initValue = function(prop, value) {
    this._setValue(prop, value, false);
};

Tracker.prototype.deleteValue = function(prop) {
    this.stopListen(prop);
    delete this.values[prop];
};

Tracker.prototype.stopListen = function(prop) {
    this.subscriptions[prop] && this.subscriptions[prop]();
    delete this.subscriptions[prop];
};

Tracker.prototype.getValue = function(prop) {
    return this.values[prop];
};

Tracker.prototype.onChange = function(callback, isRerenderCallback) {
    if(isRerenderCallback)
        this.rerenderCallbackSetted = true;

    return this._addCallback(this.callbacks, callback);
};

Tracker.prototype.onShallowChange = function(callback) {
    return this._addCallback(this.shallowCallbacks, callback);
};

Tracker.prototype._addCallback = function(cbArr, callback) {
    cbArr.push(callback);

    return () => {
        const index = cbArr.indexOf(callback);
        if(index > -1) {
            cbArr.splice(index, 1);
        }
    };
};

Tracker.prototype.isRerenderCallbackSetted = function() {
    return this.rerenderCallbackSetted;
};

Tracker.prototype.isWaitingForActionsToEnd = function() {
    return this.waitingForActionsToEnd;
};

Tracker.prototype.setWaitingForActionsToEnd = function(value) {
    this.waitingForActionsToEnd = value;
};

Tracker.prototype.reportChange = function(changedProp) {
    // TODO change report algorithm so changes on objects located multiple times inside target would result in one change event
    if(changedProp && Object.getOwnPropertyDescriptor(this.target, changedProp) == null) {
        // property was deleted
        this.stopListen(changedProp);
        return;
    }

    this.callbacks.forEach(callback => callback());

    // if changedProp is not specified, object changed shallowly
    if(changedProp == null)
        this.shallowCallbacks.forEach(callback => callback());

};

Tracker.prototype.setPropScope = function(prop, scope) {
    this.propsScopes[prop] = scope;
};

/*

class Tracker {

    constructor(target) {
        this.values = {};
        this.callbacks = [];
        this.subscriptions = {};
        this.target = target;
        this.waitingForActionsToEnd = false;
        this.rerenderCallbackSetted = false;
    }

    // private

    _setValue(prop, value, shouldReport = true) {
        const currValue = this.getValue(prop);
        const isSame = currValue === value;

        if(!isSame) {
            const {
                tracker: valueTracker,
                value: newValue
            } = this._makeTrackable(value);

            if(newValue)
                value = newValue;

            this.stopListen(prop);

            if(valueTracker)
                this.subscriptions[prop] = valueTracker.onChange(() => this.reportChange(prop));
        }

        this.values[prop] = value;

        if(shouldReport && !isSame)
            this.reportChange();
    }

    _makeTrackable(target) {
        if(isUntrackable(target) || markedUntrackable(target)) {
            //TODO may be change to return target?
            return {
                value: target
            };
        }

        if(isTracking(target)) {
            return {
                tracker: getTracker(target),
                value: target
            };
        }

        if(isPlainObject(target)) {
            const trackableObject = getTrackableObject(target);
            return {
                tracker: getTracker(trackableObject),
                value: trackableObject
            };
        }

        if(isArray(target)) {
            const trackableArray = getTrackableArray(target);
            return {
                tracker: getTracker(trackableArray),
                value: trackableArray
            };
        }

        return {
            value: target
        };
    }

    // end private

    setValue(prop, value) {
        this._setValue(prop, value);
    }

    setValueSilently(prop, value) {
        this._setValue(prop, value, false);
    }

    initValue(prop, value) {
        this._setValue(prop, value, false);
    }

    deleteValue(prop) {
        this.stopListen(prop);
        delete this.values[prop];
    }

    stopListen(prop) {
        this.subscriptions[prop] && this.subscriptions[prop]();
        delete this.subscriptions[prop];
    }

    getValue(prop) {
        return this.values[prop];
    }

    onChange(callback, isRerenderCallback) {
        this.callbacks.push(callback);

        if(isRerenderCallback)
            this.rerenderCallbackSetted = true;

        return () => {
            const index = this.callbacks.indexOf(callback);
            if(index > -1) {
                this.callbacks.splice(index, 1);
            }
        };
    }

    isRerenderCallbackSetted() {
        return this.rerenderCallbackSetted;
    }

    isWaitingForActionsToEnd() {
        return this.waitingForActionsToEnd;
    }

    setWaitingForActionsToEnd(value) {
        this.waitingForActionsToEnd = value;
    }

    reportChange(changedProp) {
        // TODO change report algorithm so changes on objects located multiple times inside target would result in one change event
        if(changedProp && Object.getOwnPropertyDescriptor(this.target, changedProp) == null) {
            // property was deleted
            this.stopListen(changedProp);
            return;
        }

        this.callbacks.forEach(callback => callback());
    }

}*/
<commit_msg>remove comments<commit_after>import {getTrackableArray} from './types/trackableArray';
import {getTrackableObject} from './types/trackableObject';
import {addHiddenFinalProp, isUntrackable, isPlainObject, isCollection, isArray} from './utils';
import {markedUntrackable} from './core/untracked';

const trackerProp = '__$tracker';

export function getTracker(instance) {
    if(isUntrackable(instance) || markedUntrackable(instance) || isCollection(instance))
        return;

    if(instance[trackerProp])
        return instance[trackerProp];

    const tracker = new Tracker(instance);
    addHiddenFinalProp(instance, trackerProp, tracker);

    return tracker;
}

export function isTracking(target) {
    return target[trackerProp] != null;
}

/**
 * Refactored to not use ES6 class, to reduce bundle size
 * */
function Tracker(target) {
    this.values = {};
    this.callbacks = [];
    this.shallowCallbacks = [];
    this.subscriptions = {};
    this.propsScopes = {};
    this.target = target;
    this.waitingForActionsToEnd = false;
    this.rerenderCallbackSetted = false;
}

Tracker.prototype._setValue = function(prop, value, shouldReport = true) {
    const currValue = this.getValue(prop);
    const isSame = currValue === value;

    if(!isSame) {
        const {
            tracker: valueTracker,
            value: newValue
        } = this._makeTrackable(value);

        if(newValue)
            value = newValue;

        this.stopListen(prop);

        if(valueTracker) {
            // don't subscribe for changes for scope === 'ref'
            if(this.propsScopes[prop] == null) {
                this.subscriptions[prop] = valueTracker.onChange(() => this.reportChange(prop));
            } else if(this.propsScopes[prop] == 'shallow') {
                this.subscriptions[prop] = valueTracker.onShallowChange(() => this.reportChange(prop));
            }
        }
    }

    this.values[prop] = value;

    if(shouldReport && !isSame)
        this.reportChange();
};

Tracker.prototype._makeTrackable = function(target) {
    if(isUntrackable(target) || markedUntrackable(target)) {
        //TODO may be change to return target?
        return {
            value: target
        };
    }

    if(isCollection(target)) {
        console.warn('Map/Set/WeakMap/WeakSet are not currently supported for state tracking');
        return {
            value: target
        };
    }

    if(isTracking(target)) {
        return {
            tracker: getTracker(target),
            value: target
        };
    }

    if(isPlainObject(target)) {
        const trackableObject = getTrackableObject(target);
        return {
            tracker: getTracker(trackableObject),
            value: trackableObject
        };
    }

    if(isArray(target)) {
        const trackableArray = getTrackableArray(target);
        return {
            tracker: getTracker(trackableArray),
            value: trackableArray
        };
    }

    return {
        value: target
    };
};

Tracker.prototype.setValue = function(prop, value) {
    this._setValue(prop, value);
};

Tracker.prototype.setValueSilently = function(prop, value) {
    this._setValue(prop, value, false);
};

Tracker.prototype.initValue = function(prop, value) {
    this._setValue(prop, value, false);
};

Tracker.prototype.deleteValue = function(prop) {
    this.stopListen(prop);
    delete this.values[prop];
};

Tracker.prototype.stopListen = function(prop) {
    this.subscriptions[prop] && this.subscriptions[prop]();
    delete this.subscriptions[prop];
};

Tracker.prototype.getValue = function(prop) {
    return this.values[prop];
};

Tracker.prototype.onChange = function(callback, isRerenderCallback) {
    if(isRerenderCallback)
        this.rerenderCallbackSetted = true;

    return this._addCallback(this.callbacks, callback);
};

Tracker.prototype.onShallowChange = function(callback) {
    return this._addCallback(this.shallowCallbacks, callback);
};

Tracker.prototype._addCallback = function(cbArr, callback) {
    cbArr.push(callback);

    return () => {
        const index = cbArr.indexOf(callback);
        if(index > -1) {
            cbArr.splice(index, 1);
        }
    };
};

Tracker.prototype.isRerenderCallbackSetted = function() {
    return this.rerenderCallbackSetted;
};

Tracker.prototype.isWaitingForActionsToEnd = function() {
    return this.waitingForActionsToEnd;
};

Tracker.prototype.setWaitingForActionsToEnd = function(value) {
    this.waitingForActionsToEnd = value;
};

Tracker.prototype.reportChange = function(changedProp) {
    // TODO change report algorithm so changes on objects located multiple times inside target would result in one change event
    if(changedProp && Object.getOwnPropertyDescriptor(this.target, changedProp) == null) {
        // property was deleted
        this.stopListen(changedProp);
        return;
    }

    this.callbacks.forEach(callback => callback());

    // if changedProp is not specified, object changed shallowly
    if(changedProp == null)
        this.shallowCallbacks.forEach(callback => callback());

};

Tracker.prototype.setPropScope = function(prop, scope) {
    this.propsScopes[prop] = scope;
};<|endoftext|>


<commit_before>export default function(config) {
  function Treemap(config) {
    var vm = this;
    vm._config = config ? config : {};
    vm._config._padding    = 3;
    vm._config._colorScale = d3.scaleOrdinal(d3.schemeCategory20c);
    vm._config._format     = d3.format(",.1f");
    vm._config._labels     = true;
    vm._data   = [];
    vm._scales = {};
    vm._axes   = {};
  }

  //-------------------------------
  //User config functions
  Treemap.prototype.end = function(){
    var vm = this;
    return vm._chart;
  }

  Treemap.prototype.size = function(col){
    var vm = this;
    vm._config._size = col;
    return vm;
  }

  Treemap.prototype.colorScale = function(arrayOfColors){
    var vm = this;
    vm._config._colorScale = d3.scaleOrdinal(arrayOfColors);
    return vm;
  }

  Treemap.prototype.padding = function(padding){
    var vm = this;
    vm._config._padding = padding;
    return vm;
  }

  Treemap.prototype.nestBy = function(keys) {
    var vm = this;
    if(Array.isArray(keys)) {
      if(keys.length == 0)
        throw "Error: nestBy() array is empty";
      vm._config._keys = keys;
    } else if(typeof keys === 'string' || keys instanceof String) {
      vm._config._keys = [keys];
    } else {
      if(keys == undefined || keys == null)
        throw "Error: nestBy() expects column names to deaggregate data";
      vm._config._keys = [keys.toString()];
      console.warning("nestBy() expected name of columns. Argument will be forced to string version .toString()");
    }
    vm._config._labelName = vm._config._keys[vm._config._keys.length - 1]; //label will be last key
    return vm;
  }

  Treemap.prototype.format = function(format){
    var vm = this;
    if (typeof format == 'function' || format instanceof Function)
      vm._config._format = format;
    else
      vm._config._format = d3.format(format);
    return vm;
  }

  Treemap.prototype.labels = function(bool) {
    var vm = this;
    vm._config._labels = Boolean(bool);
    return vm;
  };

  //-------------------------------
  //Triggered by the chart.js;
  Treemap.prototype.chart = function(chart){
    var vm = this;
    vm._chart = chart;
    return vm;
  }

  Treemap.prototype.scales = function(scales){
    var vm = this;
    return vm;
  }

  Treemap.prototype.axes = function(axes){
    var vm = this;
    return vm;
  }

  Treemap.prototype.domains = function(){
    var vm = this;
    return vm;
  }

  Treemap.prototype.isValidStructure = function(datum){
    var vm = this;
    if((typeof datum.name === 'string' || datum.name instanceof String) && Array.isArray(datum.children)) {
      var res = true;
      datum.children.forEach(function(child) {
        res = res && vm.isValidStructure(child);
      });
      return res;
    } else if((typeof datum.name === 'string' || datum.name instanceof String) && Number(datum[vm._config._size]) == datum[vm._config._size]) {
      return true;
    } else {
      return false;
    }
  }

  Treemap.prototype.formatNestedData = function(data) {
    var vm = this;
    if(data.key) {
      data.name = data.key;
      delete data.key;
    } else {
      if(!Array.isArray(data.values)) {
        data.name = data[vm._config._labelName];
      }
    }
    if(Array.isArray(data.values)) {
      var children = [];
      data.values.forEach(function(v){
        children.push(vm.formatNestedData(v))
      });
      data.children = children;
      delete data.values;
    }
    if(!data[vm._config._size] && data.value){
      data[vm._config._size] = data.value;
    }
    return data;
  }

  Treemap.prototype.data = function(data){
    var vm = this;
    // Validate structure like [{name: '', children: [{},{}]}]
    if(data){
      if(Array.isArray(data) && data.length > 0) {
        if(!vm.isValidStructure(data[0])) {
          data.forEach(function(d){
            d[vm._config._size] = +d[vm._config._size];
          });
          try {
            if(!vm._config._keys)
              throw "nestBy() in layer was not configured";
            var nested = 'd3.nest()';
            for (var i = 0; i < vm._config._keys.length; i++) {
              nested += '.key(function(d){ return d.'+ vm._config._keys[i] + '; })';
            }
            nested += '.rollup(function(leaves) { return d3.sum(leaves, function(d) {return d.' + vm._config._size + ';})})'
            nested += '.entries(data)';

            var nestedData = eval(nested);
            // TODO: improve way to get nested multiple keys
            var aux = {};
            aux.key = 'data';
            aux.values = _.cloneDeep(nestedData); // WARN: Lodash dependency
            data = vm.formatNestedData(aux);
          } catch(err){
            console.error(err);
          }
        }
      } else {
        if(!vm.isValidStructure(data)) {
          try {
            if(!data.key)
              throw "Property 'key' not found";
            if(data[vm._config._size] !== Number(data[vm._config._size]))
              throw  "Value used for treemap rect size is not a number";
            data = vm.formatNestedData(data);
          } catch(err){
            console.error(err);
          }
        }
      }
    }
    vm._data = data;
    return vm;
  }

  Treemap.prototype.draw = function(){
    var vm = this;

    var treemap = d3.treemap()
        .tile(d3.treemapResquarify)
        .size([vm._chart._width, vm._chart._height])
        .round(true)
        .paddingInner(vm._config._padding);

    var root = d3.hierarchy(vm._data)
        .eachBefore(function(d) { d.data.id = (d.parent ? d.parent.data.id + "." : "") + d.data.name; })
        .sum(function(d){return d[vm._config._size];})
        .sort(function(a, b) { return b.height - a.height || b.value - a.value; });

    treemap(root);

    var cell = vm._chart._svg.selectAll("g")
      .data(root.leaves())
      .enter().append("g")
        .attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; });

    cell.append("rect")
        .attr("id", function(d) { return d.data.id; })
        .attr("width", function(d) { return d.x1 - d.x0; })
        .attr("height", function(d) { return d.y1 - d.y0; })
        .attr("fill", function(d) { return vm._config._colorScale(d.data.id); });

    cell.append("clipPath")
        .attr("id", function(d) { return "clip-" + d.data.id; })
      .append("use")
        .attr("xlink:href", function(d) { return "#" + d.data.id; });

    if(vm._config._labels) {
      var text = cell.append("text")
          .attr("clip-path", function(d) { return "url(#clip-" + d.data.id + ")"; })
      text.append("tspan")
          .attr('class','capitalize')
          .attr("x", 8)
          .attr("y", function(d, i) { return 25; })
          .text(function(d) {
            var arr = d.data.id.replace('data.','').split('.');
            return arr.length > 1 ? arr.slice(arr.length - 2, arr.length).join(' / ') : arr[arr.length - 1].toString();
          });
      text.append("tspan")
          .attr('class','capitalize')
          .attr("x", 8)
          .attr("y", function(d, i) { return 45; })
          .text(function(d) {
            return vm._config._format(d.value);
          });
    }

    cell.append("title")
        .text(function(d) { return d.data.name + "\n" + vm._config._format(d.value); });

    return vm;
  }
  return new Treemap(config);
}
<commit_msg>Tweaky d3-tips.<commit_after>export default function(config) {
  function Treemap(config) {
    var vm = this;
    vm._config = config ? config : {};
    vm._config._padding    = 3;
    vm._config._colorScale = d3.scaleOrdinal(d3.schemeCategory20c);
    vm._config._format     = d3.format(",.1f");
    vm._config._labels     = true;
    vm._config.tip = function(d) { return d.data.name + "\n" + vm._config._format(d.value); };
    vm._data   = [];
    vm._scales = {};
    vm._axes   = {};
    vm._tip = d3.tip()
                .attr('class', 'd3-tip tip-treemap')
                //.rootElement(document.getElementById(vm._config.bindTo))
                .direction('n')
                .offset(function(d){ return [d3.select(vm._config.bindTo).node().offsetParent.offsetTop - 5735,0];})
                .html(vm._config.tip);
  }

  //-------------------------------
  //User config functions
  Treemap.prototype.end = function(){
    var vm = this;
    return vm._chart;
  }

  Treemap.prototype.size = function(col){
    var vm = this;
    vm._config._size = col;
    return vm;
  }

  Treemap.prototype.colorScale = function(arrayOfColors){
    var vm = this;
    vm._config._colorScale = d3.scaleOrdinal(arrayOfColors);
    return vm;
  }

  Treemap.prototype.padding = function(padding){
    var vm = this;
    vm._config._padding = padding;
    return vm;
  }

  Treemap.prototype.nestBy = function(keys) {
    var vm = this;
    if(Array.isArray(keys)) {
      if(keys.length == 0)
        throw "Error: nestBy() array is empty";
      vm._config._keys = keys;
    } else if(typeof keys === 'string' || keys instanceof String) {
      vm._config._keys = [keys];
    } else {
      if(keys == undefined || keys == null)
        throw "Error: nestBy() expects column names to deaggregate data";
      vm._config._keys = [keys.toString()];
      console.warning("nestBy() expected name of columns. Argument will be forced to string version .toString()");
    }
    vm._config._labelName = vm._config._keys[vm._config._keys.length - 1]; //label will be last key
    return vm;
  }

  Treemap.prototype.format = function(format){
    var vm = this;
    if (typeof format == 'function' || format instanceof Function)
      vm._config._format = format;
    else
      vm._config._format = d3.format(format);
    return vm;
  }

  Treemap.prototype.labels = function(bool) {
    var vm = this;
    vm._config._labels = Boolean(bool);
    return vm;
  };

  Treemap.prototype.tip = function(tip){
    var vm = this;
    vm._config.tip = tip;
    vm._tip.html(vm._config.tip);
    return vm;
  }

  //-------------------------------
  //Triggered by the chart.js;
  Treemap.prototype.chart = function(chart){
    var vm = this;
    vm._chart = chart;
    return vm;
  }

  Treemap.prototype.scales = function(scales){
    var vm = this;
    return vm;
  }

  Treemap.prototype.axes = function(axes){
    var vm = this;
    return vm;
  }

  Treemap.prototype.domains = function(){
    var vm = this;
    return vm;
  }

  Treemap.prototype.isValidStructure = function(datum){
    var vm = this;
    if((typeof datum.name === 'string' || datum.name instanceof String) && Array.isArray(datum.children)) {
      var res = true;
      datum.children.forEach(function(child) {
        res = res && vm.isValidStructure(child);
      });
      return res;
    } else if((typeof datum.name === 'string' || datum.name instanceof String) && Number(datum[vm._config._size]) == datum[vm._config._size]) {
      return true;
    } else {
      return false;
    }
  }

  Treemap.prototype.formatNestedData = function(data) {
    var vm = this;
    if(data.key) {
      data.name = data.key;
      delete data.key;
    } else {
      if(!Array.isArray(data.values)) {
        data.name = data[vm._config._labelName];
      }
    }
    if(Array.isArray(data.values)) {
      var children = [];
      data.values.forEach(function(v){
        children.push(vm.formatNestedData(v))
      });
      data.children = children;
      delete data.values;
    }
    if(!data[vm._config._size] && data.value){
      data[vm._config._size] = data.value;
    }
    return data;
  }

  Treemap.prototype.data = function(data){
    var vm = this;
    // Validate structure like [{name: '', children: [{},{}]}]
    if(data){
      if(Array.isArray(data) && data.length > 0) {
        if(!vm.isValidStructure(data[0])) {
          data.forEach(function(d){
            d[vm._config._size] = +d[vm._config._size];
          });
          try {
            if(!vm._config._keys)
              throw "nestBy() in layer was not configured";
            var nested = 'd3.nest()';
            for (var i = 0; i < vm._config._keys.length; i++) {
              nested += '.key(function(d){ return d.'+ vm._config._keys[i] + '; })';
            }
            nested += '.rollup(function(leaves) { return d3.sum(leaves, function(d) {return d.' + vm._config._size + ';})})'
            nested += '.entries(data)';

            var nestedData = eval(nested);
            // TODO: improve way to get nested multiple keys
            var aux = {};
            aux.key = 'data';
            aux.values = _.cloneDeep(nestedData); // WARN: Lodash dependency
            data = vm.formatNestedData(aux);
          } catch(err){
            console.error(err);
          }
        }
      } else {
        if(!vm.isValidStructure(data)) {
          try {
            if(!data.key)
              throw "Property 'key' not found";
            if(data[vm._config._size] !== Number(data[vm._config._size]))
              throw  "Value used for treemap rect size is not a number";
            data = vm.formatNestedData(data);
          } catch(err){
            console.error(err);
          }
        }
      }
    }
    vm._data = data;
    return vm;
  }

  Treemap.prototype.draw = function(){
    var vm = this;
    vm._chart._svg.call(vm._tip);

    var treemap = d3.treemap()
        .tile(d3.treemapResquarify)
        .size([vm._chart._width, vm._chart._height])
        .round(true)
        .paddingInner(vm._config._padding);

    var root = d3.hierarchy(vm._data)
        .eachBefore(function(d) { d.data.id = (d.parent ? d.parent.data.id + "." : "") + d.data.name; })
        .sum(function(d){return d[vm._config._size];})
        .sort(function(a, b) { return b.height - a.height || b.value - a.value; });

    treemap(root);

    var cell = vm._chart._svg.selectAll("g")
      .data(root.leaves())
      .enter().append("g")
        .attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; });

    var rect = cell.append("rect")
        .attr("id", function(d) { return d.data.id; })
        .attr("width", function(d) { return d.x1 - d.x0; })
        .attr("height", function(d) { return d.y1 - d.y0; })
        .attr("fill", function(d) { return vm._config._colorScale(d.data.id); });

    cell.append("clipPath")
        .attr("id", function(d) { return "clip-" + d.data.id; })
      .append("use")
        .attr("xlink:href", function(d) { return "#" + d.data.id; });

    if(vm._config._labels) {
      var text = cell.append("text")
          .attr("clip-path", function(d) { return "url(#clip-" + d.data.id + ")"; })
      text.append("tspan")
          .attr('class','capitalize')
          .attr("x", 8)
          .attr("y", function(d, i) { return 25; })
          .text(function(d) {
            var arr = d.data.id.replace('data.','').split('.');
            return arr.length > 1 ? arr.slice(arr.length - 2, arr.length).join(' / ') : arr[arr.length - 1].toString();
          });
      text.append("tspan")
          .attr('class','capitalize')
          .attr("x", 8)
          .attr("y", function(d, i) { return 45; })
          .text(function(d) {
            return vm._config._format(d.value);
          });
    }

    /*
    cell.append("title")
        .text(function(d) { return d.data.name + "\n" + vm._config._format(d.value); });
    */

    rect.on('mouseover', function(d,i){
          /*if(vm._config.data.mouseover){
            vm._config.data.mouseover.call(vm, d,i);
          }*/
          vm._tip.show(d, d3.select(this).node());
        })
        .on('mouseout', function(d,i){
          /*if(vm._config.data.mouseout){
            vm._config.data.mouseout.call(this, d,i);
          }*/
          vm._tip.hide(d, d3.select(this).node());
        })

    return vm;
  }
  return new Treemap(config);
}
<|endoftext|>


<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

var View = this.View = Base.extend({
	/** @lends View# */

	// DOCS: View: there is alot left to document
	// TODO: Add bounds parameter that defines position within canvas?
	// Find a good name for these bounds, since #bounds is already the artboard
	// bounds of the visible area.
	/**
	 * Creates a view object
	 * @param {Canvas} canvas
	 * @constructs View
	 *
	 * @class The View object..
	 */
	initialize: function(canvas) {
		// Associate this view with the active paper scope.
		this._scope = paper;
		// Push it onto project.views and set index:
		this._index = this._scope.views.push(this) - 1;
		// Handle canvas argument
		var size;
		if (canvas && canvas instanceof HTMLCanvasElement) {
			this._canvas = canvas;
			var offset = DomElement.getOffset(canvas);
			// If the canvas has the resize attribute, resize the it to fill the
			// window and resize it again whenever the user resizes the window.
			if (canvas.attributes.resize) {
				size = DomElement.getWindowSize().subtract(offset);
				canvas.width = size.width;
				canvas.height = size.height;
				var that = this;
				DomEvent.add(window, {
					resize: function(event) {
						// Only get canvas offset if it's not invisible, as
						// otherwise the offset would be wrong.
						if (!DomElement.isInvisible(canvas))
							offset = DomElement.getOffset(canvas);
						// Set the size now, which internally calls onResize
						that.setViewSize(
								DomElement.getWindowSize().subtract(offset));
						// If there's a _onFrameCallback, call it staight away,
						// but without requesting another animation frame.
						if (that._onFrameCallback) {
							that._onFrameCallback(0, true);
						} else {
							that.draw(true);
						}
					}
				});
			} else {
				size = DomElement.isInvisible(canvas)
					? Size.create(parseInt(canvas.getAttribute('width')),
							parseInt(canvas.getAttribute('height')))
					: DomElement.getSize(canvas);
			}
			// TODO: Test this on IE:
			if (canvas.attributes.stats) {
				this._stats = new Stats();
				// Align top-left to the canvas
				var element = this._stats.domElement,
					style = element.style;
				style.position = 'absolute';
				style.left = offset.x + 'px';
				style.top = offset.y + 'px';
				document.body.appendChild(element);
			}
		} else {
			// 2nd argument onwards could be view size, otherwise use default:
			size = Size.read(arguments, 1);
			if (size.isZero())
				size = new Size(1024, 768);
			this._canvas = CanvasProvider.getCanvas(size);
		}
		this._viewBounds = LinkedRectangle.create(this, 'setViewBounds',
				0, 0, size.width, size.height);
		this._context = this._canvas.getContext('2d');
		this._matrix = new Matrix();
		this._zoom = 1;
		this._events = this._createEvents();
		DomEvent.add(this._canvas, this._events);
		// Make sure the first view is focused for keyboard input straight away
		if (!View.focused)
			View.focused = this;
	},

	getCanvas: function() {
		return this._canvas;
	},

	/**
	 * The bounds of the view, i.e. the bounds of the part of the project which
	 * is visible in the window.
	 *
	 * @type Rectangle
	 * @bean
	 */
	getViewBounds: function() {
		return this._viewBounds;
	},

	setViewBounds: function(bounds) {
		bounds = Rectangle.read(arguments);
		var size = bounds.getSize(),
			delta = size.subtract(this._viewBounds.getSize());
		// TODO: Take into acount bounds.x/y and decide on what grounds to
		// change canvas size. Also, if x/y is not 0, do we need to add that
		// to transform, or is that up to the user?
		this._canvas.width = size.width;
		this._canvas.height = size.height;
		// Call onResize handler on any size change
		if (this.onResize) {
			this.onResize({
				size: size,
				delta: delta
			});
		}
		// Force recalculation
		this._bounds = null;
	},

	/**
	 * @type Size
	 * @bean
	 */
	getViewSize: function() {
		return this._viewBounds.getSize();
	},

	setViewSize: function(size) {
		this._viewBounds.setSize.apply(this._viewBounds, arguments);
	},

	/**
	 * @type Rectangle
	 * @bean
	 */
	getBounds: function() {
		if (!this._bounds)
			this._bounds = this._matrix._transformBounds(this._viewBounds);
		return this._bounds;
	},

	/**
	 * @type Size
	 * @bean
	 */
	getSize: function() {
		return this.getBounds().getSize();
	},

	/**
	 * @type Point
	 * @bean
	 */
	getCenter: function() {
		return this.getBounds().getCenter();
	},

	setCenter: function(center) {
		this.scrollBy(Point.read(arguments).subtract(this.getCenter()));
	},

	/**
	 * @type Number
	 * @bean
	 */
	getZoom: function() {
		return this._zoom;
	},

	setZoom: function(zoom) {
		// TODO: Clamp the view between 1/32 and 64, just like Illustrator?
		this._transform(new Matrix().scale(zoom / this._zoom, this.getCenter()));
		this._zoom = zoom;
	},

	/**
	 * @param {Point} point
	 */
	scrollBy: function(point) {
		this._transform(new Matrix().translate(Point.read(arguments).negate()));
	},

	_transform: function(matrix, flags) {
		this._matrix.preConcatenate(matrix);
		// Force recalculation of these values next time they are requested.
		this._bounds = null;
		this._inverse = null;
	},

	draw: function(checkRedraw) {
		if (checkRedraw && !this._redrawNeeded)
			return false;
		if (this._stats)
			this._stats.update();
		// Initial tests conclude that clearing the canvas using clearRect
		// is always faster than setting canvas.width = canvas.width
		// http://jsperf.com/clearrect-vs-setting-width/7
		var ctx = this._context,
			bounds = this._viewBounds;
		ctx.clearRect(bounds._x, bounds._y,
				// TODO: +1... what if we have multiple views in one canvas?
				bounds._width + 1, bounds._height + 1);

		ctx.save();
		this._matrix.applyToContext(ctx);
		// Just draw the active project for now
		this._scope.project.draw(ctx);
		ctx.restore();
		// Update _redrawNotified in PaperScope as soon as one view was drawn
		this._redrawNeeded = false;
		this._scope._redrawNotified = false;
		return true;
	},

	activate: function() {
		this._scope.view = this;
	},

	remove: function() {
		var res = Base.splice(this._scope.views, null, this._index, 1);
		// Uninstall event handlers again for this view.
		DomEvent.remove(this._canvas, this._events);
		// Clearing _onFrame makes the frame handler stop automatically.
		this._scope = this._canvas = this._events = this._onFrame = null;
		return !!res.length;
	},

	// TODO: getInvalidBounds
	// TODO: invalidate(rect)
	// TODO: style: artwork / preview / raster / opaque / ink
	// TODO: getShowGrid
	// TODO: getMousePoint
	// TODO: artworkToView(rect)

	// TODO: Consider naming these projectToView, viewToProject
	artworkToView: function(point) {
		return this._matrix._transformPoint(Point.read(arguments));
	},

	viewToArtwork: function(point) {
		return this._getInverse()._transformPoint(Point.read(arguments));
	},

	_getInverse: function() {
		if (!this._inverse)
			this._inverse = this._matrix.createInverse();
		return this._inverse;
	},

	/**
	 * {@grouptitle Event Handlers}
	 * Handler function to be called on each frame of an animation.
	 * The function receives an event object which contains information about
	 * the frame event:
	 *
	 * {@code event.count}: the number of times the frame event was fired.
	 * {@code event.time}: the total amount of time passed since the first frame
	 * event in seconds.
	 * {@code event.delta}: the time passed in seconds since the last frame
	 * event.
	 *
	 * @example {@paperscript}
	 * // Creating an animation:
	 *
	 * // Create a rectangle shaped path with its top left point at:
	 * // {x: 50, y: 25} and a size of {width: 50, height: 50}
	 * var path = new Path.Rectangle(new Point(50, 25), new Size(50, 50));
	 * path.fillColor = 'black';
	 *
	 * function onFrame(event) {
	 * 	// Every frame, rotate the path by 3 degrees:
	 * 	path.rotate(3);
	 * }
	 *
	 * @type function
	 * @bean
	 */
	getOnFrame: function() {
		return this._onFrame;
	},

	setOnFrame: function(onFrame) {
		this._onFrame = onFrame;
		if (!onFrame) {
			delete this._onFrameCallback;
			return;
		}
		var that = this,
			requested = false,
			before,
			time = 0,
			count = 0;
		this._onFrameCallback = function(param, dontRequest) {
			requested = false;
			if (!that._onFrame)
				return;
			// Set the global paper object to the current scope
			paper = that._scope;
			// Request next frame already
			requested = true;
			if (!dontRequest) {
				DomEvent.requestAnimationFrame(that._onFrameCallback,
						that._canvas);
			}
			var now = Date.now() / 1000,
			 	delta = before ? now - before : 0;
			that._onFrame({
				delta: delta, // Time elapsed since last redraw in seconds
				time: time += delta, // Time since first call of frame() in seconds
				count: count++
			});
			before = now;
			// Automatically draw view on each frame.
			that.draw(true);
		};
		// Call the onFrame handler straight away, initializing the sequence
		// of onFrame calls.
		if (!requested)
			this._onFrameCallback();
	},

	/**
	 * Handler function that is called whenever a view is resized.
	 *
	 * @example
	 * // Repositioning items when a view is resized:
	 *
	 * // Create a circle shaped path in the center of the view:
	 * var path = new Path.Circle(view.bounds.center, 30);
	 * path.fillColor = 'red';
	 *
	 * function onResize(event) {
	 * 	// Whenever the view is resized, move the path to its center:
	 * 	path.position = view.center;
	 * }
	 *
	 * @type function
	 */
	onResize: null,

	_createEvents: function() {
		var that = this,
			tool,
			timer,
			curPoint,
			dragging = false;

		function viewToArtwork(event) {
			return that.viewToArtwork(DomEvent.getOffset(event));
		}

		function mousedown(event) {
			// Tell the Key class which view should receive keyboard input.
			View.focused = that;
			if (!(tool = that._scope.tool))
				return;
			curPoint = viewToArtwork(event);
			if (tool.onHandleEvent('mousedown', curPoint, event))
				that.draw(true);
			if (tool.eventInterval != null)
				timer = setInterval(mousemove, tool.eventInterval);
			dragging = true;
		}

		function mousemove(event) {
			if (!(tool = that._scope.tool))
				return;
			// If the event was triggered by a touch screen device, prevent the
			// default behaviour, as it will otherwise scroll the page:
			if (event && event.targetTouches)
				DomEvent.preventDefault(event);
			var point = event && viewToArtwork(event);
			var onlyMove = !!(!tool.onMouseDrag && tool.onMouseMove);
			if (dragging && !onlyMove) {
				curPoint = point || curPoint;
				if (curPoint && tool.onHandleEvent('mousedrag', curPoint, event))
					that.draw(true);
			// PORT: If there is only an onMouseMove handler, also call it when
			// the user is dragging:
			} else if (!dragging || onlyMove) {
				if (tool.onHandleEvent('mousemove', point, event))
					that.draw(true);
			}
		}

		function mouseup(event) {
			if (!dragging)
				return;
			dragging = false;
			curPoint = null;
			if (tool) {
				if (tool.eventInterval != null)
					timer = clearInterval(timer);
				if (tool.onHandleEvent('mouseup', viewToArtwork(event), event))
					that.draw(true);
			}
		}

		return {
			mousedown: mousedown,
			mousemove: mousemove,
			mouseup: mouseup,
			touchstart: mousedown,
			touchmove: mousemove,
			touchend: mouseup
		};
	}
});
<commit_msg>Fix _redrawNotified mechanism.<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

var View = this.View = Base.extend({
	/** @lends View# */

	// DOCS: View: there is alot left to document
	// TODO: Add bounds parameter that defines position within canvas?
	// Find a good name for these bounds, since #bounds is already the artboard
	// bounds of the visible area.
	/**
	 * Creates a view object
	 * @param {Canvas} canvas
	 * @constructs View
	 *
	 * @class The View object..
	 */
	initialize: function(canvas) {
		// Associate this view with the active paper scope.
		this._scope = paper;
		// Push it onto project.views and set index:
		this._index = this._scope.views.push(this) - 1;
		// Handle canvas argument
		var size;
		if (canvas && canvas instanceof HTMLCanvasElement) {
			this._canvas = canvas;
			var offset = DomElement.getOffset(canvas);
			// If the canvas has the resize attribute, resize the it to fill the
			// window and resize it again whenever the user resizes the window.
			if (canvas.attributes.resize) {
				size = DomElement.getWindowSize().subtract(offset);
				canvas.width = size.width;
				canvas.height = size.height;
				var that = this;
				DomEvent.add(window, {
					resize: function(event) {
						// Only get canvas offset if it's not invisible, as
						// otherwise the offset would be wrong.
						if (!DomElement.isInvisible(canvas))
							offset = DomElement.getOffset(canvas);
						// Set the size now, which internally calls onResize
						that.setViewSize(
								DomElement.getWindowSize().subtract(offset));
						// If there's a _onFrameCallback, call it staight away,
						// but without requesting another animation frame.
						if (that._onFrameCallback) {
							that._onFrameCallback(0, true);
						} else {
							that.draw(true);
						}
					}
				});
			} else {
				size = DomElement.isInvisible(canvas)
					? Size.create(parseInt(canvas.getAttribute('width')),
							parseInt(canvas.getAttribute('height')))
					: DomElement.getSize(canvas);
			}
			// TODO: Test this on IE:
			if (canvas.attributes.stats) {
				this._stats = new Stats();
				// Align top-left to the canvas
				var element = this._stats.domElement,
					style = element.style;
				style.position = 'absolute';
				style.left = offset.x + 'px';
				style.top = offset.y + 'px';
				document.body.appendChild(element);
			}
		} else {
			// 2nd argument onwards could be view size, otherwise use default:
			size = Size.read(arguments, 1);
			if (size.isZero())
				size = new Size(1024, 768);
			this._canvas = CanvasProvider.getCanvas(size);
		}
		this._viewBounds = LinkedRectangle.create(this, 'setViewBounds',
				0, 0, size.width, size.height);
		this._context = this._canvas.getContext('2d');
		this._matrix = new Matrix();
		this._zoom = 1;
		this._events = this._createEvents();
		DomEvent.add(this._canvas, this._events);
		// Make sure the first view is focused for keyboard input straight away
		if (!View.focused)
			View.focused = this;
		// As soon as a new view is added we need to mark the redraw as not
		// motified, so the next call loops through all the views again.
		this._scope._redrawNotified = false;
	},

	getCanvas: function() {
		return this._canvas;
	},

	/**
	 * The bounds of the view, i.e. the bounds of the part of the project which
	 * is visible in the window.
	 *
	 * @type Rectangle
	 * @bean
	 */
	getViewBounds: function() {
		return this._viewBounds;
	},

	setViewBounds: function(bounds) {
		bounds = Rectangle.read(arguments);
		var size = bounds.getSize(),
			delta = size.subtract(this._viewBounds.getSize());
		// TODO: Take into acount bounds.x/y and decide on what grounds to
		// change canvas size. Also, if x/y is not 0, do we need to add that
		// to transform, or is that up to the user?
		this._canvas.width = size.width;
		this._canvas.height = size.height;
		// Call onResize handler on any size change
		if (this.onResize) {
			this.onResize({
				size: size,
				delta: delta
			});
		}
		// Force recalculation
		this._bounds = null;
	},

	/**
	 * @type Size
	 * @bean
	 */
	getViewSize: function() {
		return this._viewBounds.getSize();
	},

	setViewSize: function(size) {
		this._viewBounds.setSize.apply(this._viewBounds, arguments);
	},

	/**
	 * @type Rectangle
	 * @bean
	 */
	getBounds: function() {
		if (!this._bounds)
			this._bounds = this._matrix._transformBounds(this._viewBounds);
		return this._bounds;
	},

	/**
	 * @type Size
	 * @bean
	 */
	getSize: function() {
		return this.getBounds().getSize();
	},

	/**
	 * @type Point
	 * @bean
	 */
	getCenter: function() {
		return this.getBounds().getCenter();
	},

	setCenter: function(center) {
		this.scrollBy(Point.read(arguments).subtract(this.getCenter()));
	},

	/**
	 * @type Number
	 * @bean
	 */
	getZoom: function() {
		return this._zoom;
	},

	setZoom: function(zoom) {
		// TODO: Clamp the view between 1/32 and 64, just like Illustrator?
		this._transform(new Matrix().scale(zoom / this._zoom, this.getCenter()));
		this._zoom = zoom;
	},

	/**
	 * @param {Point} point
	 */
	scrollBy: function(point) {
		this._transform(new Matrix().translate(Point.read(arguments).negate()));
	},

	_transform: function(matrix, flags) {
		this._matrix.preConcatenate(matrix);
		// Force recalculation of these values next time they are requested.
		this._bounds = null;
		this._inverse = null;
	},

	draw: function(checkRedraw) {
		if (checkRedraw && !this._redrawNeeded)
			return false;
		if (this._stats)
			this._stats.update();
		// Initial tests conclude that clearing the canvas using clearRect
		// is always faster than setting canvas.width = canvas.width
		// http://jsperf.com/clearrect-vs-setting-width/7
		var ctx = this._context,
			bounds = this._viewBounds;
		ctx.clearRect(bounds._x, bounds._y,
				// TODO: +1... what if we have multiple views in one canvas?
				bounds._width + 1, bounds._height + 1);

		ctx.save();
		this._matrix.applyToContext(ctx);
		// Just draw the active project for now
		this._scope.project.draw(ctx);
		ctx.restore();
		if (this._redrawNeeded) {
			this._redrawNeeded = false;
			// Update _redrawNotified in PaperScope as soon as a view was drawn
			this._scope._redrawNotified = false;
		}
		return true;
	},

	activate: function() {
		this._scope.view = this;
	},

	remove: function() {
		var res = Base.splice(this._scope.views, null, this._index, 1);
		// Uninstall event handlers again for this view.
		DomEvent.remove(this._canvas, this._events);
		// Clearing _onFrame makes the frame handler stop automatically.
		this._scope = this._canvas = this._events = this._onFrame = null;
		return !!res.length;
	},

	// TODO: getInvalidBounds
	// TODO: invalidate(rect)
	// TODO: style: artwork / preview / raster / opaque / ink
	// TODO: getShowGrid
	// TODO: getMousePoint
	// TODO: artworkToView(rect)

	// TODO: Consider naming these projectToView, viewToProject
	artworkToView: function(point) {
		return this._matrix._transformPoint(Point.read(arguments));
	},

	viewToArtwork: function(point) {
		return this._getInverse()._transformPoint(Point.read(arguments));
	},

	_getInverse: function() {
		if (!this._inverse)
			this._inverse = this._matrix.createInverse();
		return this._inverse;
	},

	/**
	 * {@grouptitle Event Handlers}
	 * Handler function to be called on each frame of an animation.
	 * The function receives an event object which contains information about
	 * the frame event:
	 *
	 * {@code event.count}: the number of times the frame event was fired.
	 * {@code event.time}: the total amount of time passed since the first frame
	 * event in seconds.
	 * {@code event.delta}: the time passed in seconds since the last frame
	 * event.
	 *
	 * @example {@paperscript}
	 * // Creating an animation:
	 *
	 * // Create a rectangle shaped path with its top left point at:
	 * // {x: 50, y: 25} and a size of {width: 50, height: 50}
	 * var path = new Path.Rectangle(new Point(50, 25), new Size(50, 50));
	 * path.fillColor = 'black';
	 *
	 * function onFrame(event) {
	 * 	// Every frame, rotate the path by 3 degrees:
	 * 	path.rotate(3);
	 * }
	 *
	 * @type function
	 * @bean
	 */
	getOnFrame: function() {
		return this._onFrame;
	},

	setOnFrame: function(onFrame) {
		this._onFrame = onFrame;
		if (!onFrame) {
			delete this._onFrameCallback;
			return;
		}
		var that = this,
			requested = false,
			before,
			time = 0,
			count = 0;
		this._onFrameCallback = function(param, dontRequest) {
			requested = false;
			if (!that._onFrame)
				return;
			// Set the global paper object to the current scope
			paper = that._scope;
			// Request next frame already
			requested = true;
			if (!dontRequest) {
				DomEvent.requestAnimationFrame(that._onFrameCallback,
						that._canvas);
			}
			var now = Date.now() / 1000,
			 	delta = before ? now - before : 0;
			that._onFrame({
				delta: delta, // Time elapsed since last redraw in seconds
				time: time += delta, // Time since first call of frame() in seconds
				count: count++
			});
			before = now;
			// Automatically draw view on each frame.
			that.draw(true);
		};
		// Call the onFrame handler straight away, initializing the sequence
		// of onFrame calls.
		if (!requested)
			this._onFrameCallback();
	},

	/**
	 * Handler function that is called whenever a view is resized.
	 *
	 * @example
	 * // Repositioning items when a view is resized:
	 *
	 * // Create a circle shaped path in the center of the view:
	 * var path = new Path.Circle(view.bounds.center, 30);
	 * path.fillColor = 'red';
	 *
	 * function onResize(event) {
	 * 	// Whenever the view is resized, move the path to its center:
	 * 	path.position = view.center;
	 * }
	 *
	 * @type function
	 */
	onResize: null,

	_createEvents: function() {
		var that = this,
			tool,
			timer,
			curPoint,
			dragging = false;

		function viewToArtwork(event) {
			return that.viewToArtwork(DomEvent.getOffset(event));
		}

		function mousedown(event) {
			// Tell the Key class which view should receive keyboard input.
			View.focused = that;
			if (!(tool = that._scope.tool))
				return;
			curPoint = viewToArtwork(event);
			if (tool.onHandleEvent('mousedown', curPoint, event))
				that.draw(true);
			if (tool.eventInterval != null)
				timer = setInterval(mousemove, tool.eventInterval);
			dragging = true;
		}

		function mousemove(event) {
			if (!(tool = that._scope.tool))
				return;
			// If the event was triggered by a touch screen device, prevent the
			// default behaviour, as it will otherwise scroll the page:
			if (event && event.targetTouches)
				DomEvent.preventDefault(event);
			var point = event && viewToArtwork(event);
			var onlyMove = !!(!tool.onMouseDrag && tool.onMouseMove);
			if (dragging && !onlyMove) {
				curPoint = point || curPoint;
				if (curPoint && tool.onHandleEvent('mousedrag', curPoint, event))
					that.draw(true);
			// PORT: If there is only an onMouseMove handler, also call it when
			// the user is dragging:
			} else if (!dragging || onlyMove) {
				if (tool.onHandleEvent('mousemove', point, event))
					that.draw(true);
			}
		}

		function mouseup(event) {
			if (!dragging)
				return;
			dragging = false;
			curPoint = null;
			if (tool) {
				if (tool.eventInterval != null)
					timer = clearInterval(timer);
				if (tool.onHandleEvent('mouseup', viewToArtwork(event), event))
					that.draw(true);
			}
		}

		return {
			mousedown: mousedown,
			mousemove: mousemove,
			mouseup: mouseup,
			touchstart: mousedown,
			touchmove: mousemove,
			touchend: mouseup
		};
	}
});
<|endoftext|>


<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

var View = this.View = Base.extend({
	beans: true,

	// TODO: Add bounds parameter that defines position within canvas?
	// Find a good name for these bounds, since #bounds is already the artboard
	// bounds of the visible area.
	initialize: function(canvas) {
		// Associate this view with the active paper scope.
		this._scope = paper;
		// Push it onto project.views and set index:
		this._index = this._scope.views.push(this) - 1;
		// Handle canvas argument
		var size;
		if (canvas && canvas instanceof HTMLCanvasElement) {
			this._canvas = canvas;
			var offset = DomElement.getOffset(canvas);
			// If the canvas has the resize attribute, resize the it to fill the
			// window and resize it again whenever the user resizes the window.
			if (canvas.attributes.resize) {
				size = DomElement.getWindowSize().subtract(offset);
				canvas.width = size.width;
				canvas.height = size.height;
				var that = this;
				DomEvent.add(window, {
					resize: function(event) {
						// Only get canvas offset if it's not invisible (size is
						// 0, 0), as otherwise the offset would be wrong.
						if (!DomElement.getSize(canvas).equals([0, 0]))
							offset = DomElement.getOffset(canvas);
						// Set the size now, which internally calls onResize
						that.setViewSize(
								DomElement.getWindowSize().subtract(offset));
						// If there's a _onFrameCallback, call it staight away,
						// but without requesting another animation frame.
						if (that._onFrameCallback) {
							that._onFrameCallback(0, true);
						} else {
							that.draw();
						}
					}
				});
			} else {
				size = Size.create(canvas.offsetWidth, canvas.offsetHeight);
			}
			// TODO: Test this on IE:
			if (canvas.attributes.stats) {
				this._stats = new Stats();
				// Align top-left to the canvas
				var element = this._stats.domElement,
					style = element.style;
				style.position = 'absolute';
				style.left = offset.x + 'px';
				style.top = offset.y + 'px';
				document.body.appendChild(element);
			}
		} else {
			// 2nd argument onwards could be view size, otherwise use default:
			size = Size.read(arguments, 1);
			if (size.isZero())
				size = new Size(1024, 768);
			this._canvas = CanvasProvider.getCanvas(size);
		}
		this._viewBounds = LinkedRectangle.create(this, 'setViewBounds',
				0, 0, size.width, size.height);
		this._context = this._canvas.getContext('2d');
		this._matrix = new Matrix();
		this._zoom = 1;
		this._events = this._createEvents();
		DomEvent.add(this._canvas, this._events);
		// Make sure the first view is focused for keyboard input straight away
		if (!View.focused)
			View.focused = this;
	},

	getViewBounds: function() {
		return this._viewBounds;
	},

	setViewBounds: function(bounds) {
		bounds = Rectangle.read(arguments);
		var size = bounds.getSize(),
			delta = size.subtract(this._viewBounds.getSize());
		// TODO: Take into acount bounds.x/y and decide on what grounds to
		// change canvas size. Also, if x/y is not 0, do we need to add that
		// to transform, or is that up to the user?
		this._canvas.width = size.width;
		this._canvas.height = size.height;
		// Call onResize handler on any size change
		if (this.onResize) {
			this.onResize({
				size: size,
				delta: delta
			});
		}
		// Force recalculation
		this._bounds = null;
	},

	getViewSize: function() {
		return this._viewBounds.getSize();
	},

	setViewSize: function(size) {
		this._viewBounds.setSize.apply(this._viewBounds, arguments);
	},

	getBounds: function() {
		if (!this._bounds)
			this._bounds = this._matrix._transformBounds(this._viewBounds);
		return this._bounds;
	},

	getSize: function() {
		return this.getBounds().getSize();
	},

	getCenter: function() {
		return this.getBounds().getCenter();
	},

	setCenter: function(center) {
		this.scrollBy(Point.read(arguments).subtract(this.getCenter()));
	},

	getZoom: function() {
		return this._zoom;
	},

	setZoom: function(zoom) {
		// TODO: Clamp the view between 1/32 and 64, just like Illustrator?
		this.transform(new Matrix().scale(zoom / this._zoom, this.getCenter()));
		this._zoom = zoom;
	},

	scrollBy: function(point) {
		this.transform(new Matrix().translate(Point.read(arguments).negate()));
	},

	draw: function() {
		if (this._stats)
			this._stats.update();
		// Initial tests conclude that clearing the canvas using clearRect
		// is always faster than setting canvas.width = canvas.width
		// http://jsperf.com/clearrect-vs-setting-width/7
		var ctx =this._context,
			bounds = this._viewBounds;
		ctx.clearRect(bounds._x, bounds._y,
				// TODO: +1... what if we have multiple views in one canvas?
				bounds._width + 1, bounds._height + 1);

		ctx.save();
		this._matrix.applyToContext(ctx);
		// Just draw the active project for now
		this._scope.project.draw(ctx);
		ctx.restore();
	},

	activate: function() {
		this._scope.view = this;
	},

	remove: function() {
		var res = Base.splice(this._scope.views, null, this._index, 1);
		// Uninstall event handlers again for this view.
		DomEvent.remove(this._canvas, this._events);
		// Clearing _onFrame makes the frame handler stop automatically.
		this._scope = this._canvas = this._events = this._onFrame = null;
		return !!res.length;
	},

	transform: function(matrix, flags) {
		this._matrix.preConcatenate(matrix);
		// Force recalculation of these values next time they are requested.
		this._bounds = null;
		this._inverse = null;
	},

	_getInverse: function() {
		if (!this._inverse)
			this._inverse = this._matrix.createInverse();
		return this._inverse;
	},

	// TODO: getInvalidBounds
	// TODO: invalidate(rect)
	// TODO: style: artwork / preview / raster / opaque / ink
	// TODO: getShowGrid
	// TODO: getMousePoint
	// TODO: artworkToView(rect)
	artworkToView: function(point) {
		return this._matrix._transformPoint(Point.read(arguments));
	},

	viewToArtwork: function(point) {
		return this._getInverse()._transformPoint(Point.read(arguments));
	},

	/**
	 * Handler to be called whenever a view gets resized.
	 */
	onResize: null,

	/**
	 * Handler to be called on each frame of an animation.
	 */
	getOnFrame: function() {
		return this._onFrame;
	},

	setOnFrame: function(onFrame) {
		this._onFrame = onFrame;
		if (!onFrame) {
			delete this._onFrameCallback;
			return;
		}
		var that = this,
			requested = false,
			before,
			time = 0,
			count = 0;
		this._onFrameCallback = function(param, dontRequest) {
			requested = false;
			if (!that._onFrame)
				return;
			// Set the global paper object to the current scope
			paper = that._scope;
			// Request next frame already
			requested = true;
			if (!dontRequest) {
				DomEvent.requestAnimationFrame(that._onFrameCallback,
						that._canvas);
			}
			var now = Date.now() / 1000,
			 	delta = before ? now - before : 0;
			that._onFrame({
				delta: delta, // Time elapsed since last redraw in seconds
				time: time += delta, // Time since first call of frame() in seconds
				count: count++
			});
			before = now;
			// Automatically draw view on each frame.
			that.draw();
		};
		// Call the onFrame handler straight away, initializing the sequence
		// of onFrame calls.
		if (!requested)
			this._onFrameCallback();
	},

	_createEvents: function() {
		var that = this,
			tool,
			timer,
			curPoint,
			dragging = false;

		function viewToArtwork(event) {
			return that.viewToArtwork(DomEvent.getOffset(event));
		}

		function mousedown(event) {
			// Tell the Key class which view should receive keyboard input.
			View.focused = that;
			if (!(tool = that._scope.tool))
				return;
			curPoint = viewToArtwork(event);
			tool.onHandleEvent('mousedown', curPoint, event);
			if (tool.onMouseDown)
				that.draw();
			if (tool.eventInterval != null)
				timer = setInterval(mousemove, tool.eventInterval);
			dragging = true;
		}

		function mousemove(event) {
			if (!(tool = that._scope.tool))
				return;
			// If the event was triggered by a touch screen device, prevent the
			// default behaviour, as it will otherwise scroll the page:
			if (event && event.targetTouches)
				DomEvent.preventDefault(event);
			var point = event && viewToArtwork(event);
			var onlyMove = !!(!tool.onMouseDrag && tool.onMouseMove);
			if (dragging && !onlyMove) {
				curPoint = point || curPoint;
				if (curPoint)
					tool.onHandleEvent('mousedrag', curPoint, event);
				if (tool.onMouseDrag)
					that.draw();
			// PORT: If there is only an onMouseMove handler, also call it when
			// the user is dragging:
			} else if (!dragging || onlyMove) {
				tool.onHandleEvent('mousemove', point, event);
				if (tool.onMouseMove)
					that.draw();
			}
		}

		function mouseup(event) {
			if (!dragging)
				return;
			dragging = false;
			curPoint = null;
			if (tool) {
				if (tool.eventInterval != null)
					timer = clearInterval(timer);
				tool.onHandleEvent('mouseup', viewToArtwork(event), event);
				if (tool.onMouseUp)
					that.draw();
			}
		}

		return {
			mousedown: mousedown,
			mousemove: mousemove,
			mouseup: mouseup,
			touchstart: mousedown,
			touchmove: mousemove,
			touchend: mouseup
		};
	}
});
<commit_msg>Add comment about potential renaming of artworkToView -> projectToView?<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

var View = this.View = Base.extend({
	beans: true,

	// TODO: Add bounds parameter that defines position within canvas?
	// Find a good name for these bounds, since #bounds is already the artboard
	// bounds of the visible area.
	initialize: function(canvas) {
		// Associate this view with the active paper scope.
		this._scope = paper;
		// Push it onto project.views and set index:
		this._index = this._scope.views.push(this) - 1;
		// Handle canvas argument
		var size;
		if (canvas && canvas instanceof HTMLCanvasElement) {
			this._canvas = canvas;
			var offset = DomElement.getOffset(canvas);
			// If the canvas has the resize attribute, resize the it to fill the
			// window and resize it again whenever the user resizes the window.
			if (canvas.attributes.resize) {
				size = DomElement.getWindowSize().subtract(offset);
				canvas.width = size.width;
				canvas.height = size.height;
				var that = this;
				DomEvent.add(window, {
					resize: function(event) {
						// Only get canvas offset if it's not invisible (size is
						// 0, 0), as otherwise the offset would be wrong.
						if (!DomElement.getSize(canvas).equals([0, 0]))
							offset = DomElement.getOffset(canvas);
						// Set the size now, which internally calls onResize
						that.setViewSize(
								DomElement.getWindowSize().subtract(offset));
						// If there's a _onFrameCallback, call it staight away,
						// but without requesting another animation frame.
						if (that._onFrameCallback) {
							that._onFrameCallback(0, true);
						} else {
							that.draw();
						}
					}
				});
			} else {
				size = Size.create(canvas.offsetWidth, canvas.offsetHeight);
			}
			// TODO: Test this on IE:
			if (canvas.attributes.stats) {
				this._stats = new Stats();
				// Align top-left to the canvas
				var element = this._stats.domElement,
					style = element.style;
				style.position = 'absolute';
				style.left = offset.x + 'px';
				style.top = offset.y + 'px';
				document.body.appendChild(element);
			}
		} else {
			// 2nd argument onwards could be view size, otherwise use default:
			size = Size.read(arguments, 1);
			if (size.isZero())
				size = new Size(1024, 768);
			this._canvas = CanvasProvider.getCanvas(size);
		}
		this._viewBounds = LinkedRectangle.create(this, 'setViewBounds',
				0, 0, size.width, size.height);
		this._context = this._canvas.getContext('2d');
		this._matrix = new Matrix();
		this._zoom = 1;
		this._events = this._createEvents();
		DomEvent.add(this._canvas, this._events);
		// Make sure the first view is focused for keyboard input straight away
		if (!View.focused)
			View.focused = this;
	},

	getViewBounds: function() {
		return this._viewBounds;
	},

	setViewBounds: function(bounds) {
		bounds = Rectangle.read(arguments);
		var size = bounds.getSize(),
			delta = size.subtract(this._viewBounds.getSize());
		// TODO: Take into acount bounds.x/y and decide on what grounds to
		// change canvas size. Also, if x/y is not 0, do we need to add that
		// to transform, or is that up to the user?
		this._canvas.width = size.width;
		this._canvas.height = size.height;
		// Call onResize handler on any size change
		if (this.onResize) {
			this.onResize({
				size: size,
				delta: delta
			});
		}
		// Force recalculation
		this._bounds = null;
	},

	getViewSize: function() {
		return this._viewBounds.getSize();
	},

	setViewSize: function(size) {
		this._viewBounds.setSize.apply(this._viewBounds, arguments);
	},

	getBounds: function() {
		if (!this._bounds)
			this._bounds = this._matrix._transformBounds(this._viewBounds);
		return this._bounds;
	},

	getSize: function() {
		return this.getBounds().getSize();
	},

	getCenter: function() {
		return this.getBounds().getCenter();
	},

	setCenter: function(center) {
		this.scrollBy(Point.read(arguments).subtract(this.getCenter()));
	},

	getZoom: function() {
		return this._zoom;
	},

	setZoom: function(zoom) {
		// TODO: Clamp the view between 1/32 and 64, just like Illustrator?
		this.transform(new Matrix().scale(zoom / this._zoom, this.getCenter()));
		this._zoom = zoom;
	},

	scrollBy: function(point) {
		this.transform(new Matrix().translate(Point.read(arguments).negate()));
	},

	draw: function() {
		if (this._stats)
			this._stats.update();
		// Initial tests conclude that clearing the canvas using clearRect
		// is always faster than setting canvas.width = canvas.width
		// http://jsperf.com/clearrect-vs-setting-width/7
		var ctx =this._context,
			bounds = this._viewBounds;
		ctx.clearRect(bounds._x, bounds._y,
				// TODO: +1... what if we have multiple views in one canvas?
				bounds._width + 1, bounds._height + 1);

		ctx.save();
		this._matrix.applyToContext(ctx);
		// Just draw the active project for now
		this._scope.project.draw(ctx);
		ctx.restore();
	},

	activate: function() {
		this._scope.view = this;
	},

	remove: function() {
		var res = Base.splice(this._scope.views, null, this._index, 1);
		// Uninstall event handlers again for this view.
		DomEvent.remove(this._canvas, this._events);
		// Clearing _onFrame makes the frame handler stop automatically.
		this._scope = this._canvas = this._events = this._onFrame = null;
		return !!res.length;
	},

	transform: function(matrix, flags) {
		this._matrix.preConcatenate(matrix);
		// Force recalculation of these values next time they are requested.
		this._bounds = null;
		this._inverse = null;
	},

	_getInverse: function() {
		if (!this._inverse)
			this._inverse = this._matrix.createInverse();
		return this._inverse;
	},

	// TODO: getInvalidBounds
	// TODO: invalidate(rect)
	// TODO: style: artwork / preview / raster / opaque / ink
	// TODO: getShowGrid
	// TODO: getMousePoint
	// TODO: artworkToView(rect)

	// TODO: Consider naming these projectToView, viewToProject
	artworkToView: function(point) {
		return this._matrix._transformPoint(Point.read(arguments));
	},

	viewToArtwork: function(point) {
		return this._getInverse()._transformPoint(Point.read(arguments));
	},

	/**
	 * Handler to be called whenever a view gets resized.
	 */
	onResize: null,

	/**
	 * Handler to be called on each frame of an animation.
	 */
	getOnFrame: function() {
		return this._onFrame;
	},

	setOnFrame: function(onFrame) {
		this._onFrame = onFrame;
		if (!onFrame) {
			delete this._onFrameCallback;
			return;
		}
		var that = this,
			requested = false,
			before,
			time = 0,
			count = 0;
		this._onFrameCallback = function(param, dontRequest) {
			requested = false;
			if (!that._onFrame)
				return;
			// Set the global paper object to the current scope
			paper = that._scope;
			// Request next frame already
			requested = true;
			if (!dontRequest) {
				DomEvent.requestAnimationFrame(that._onFrameCallback,
						that._canvas);
			}
			var now = Date.now() / 1000,
			 	delta = before ? now - before : 0;
			that._onFrame({
				delta: delta, // Time elapsed since last redraw in seconds
				time: time += delta, // Time since first call of frame() in seconds
				count: count++
			});
			before = now;
			// Automatically draw view on each frame.
			that.draw();
		};
		// Call the onFrame handler straight away, initializing the sequence
		// of onFrame calls.
		if (!requested)
			this._onFrameCallback();
	},

	_createEvents: function() {
		var that = this,
			tool,
			timer,
			curPoint,
			dragging = false;

		function viewToArtwork(event) {
			return that.viewToArtwork(DomEvent.getOffset(event));
		}

		function mousedown(event) {
			// Tell the Key class which view should receive keyboard input.
			View.focused = that;
			if (!(tool = that._scope.tool))
				return;
			curPoint = viewToArtwork(event);
			tool.onHandleEvent('mousedown', curPoint, event);
			if (tool.onMouseDown)
				that.draw();
			if (tool.eventInterval != null)
				timer = setInterval(mousemove, tool.eventInterval);
			dragging = true;
		}

		function mousemove(event) {
			if (!(tool = that._scope.tool))
				return;
			// If the event was triggered by a touch screen device, prevent the
			// default behaviour, as it will otherwise scroll the page:
			if (event && event.targetTouches)
				DomEvent.preventDefault(event);
			var point = event && viewToArtwork(event);
			var onlyMove = !!(!tool.onMouseDrag && tool.onMouseMove);
			if (dragging && !onlyMove) {
				curPoint = point || curPoint;
				if (curPoint)
					tool.onHandleEvent('mousedrag', curPoint, event);
				if (tool.onMouseDrag)
					that.draw();
			// PORT: If there is only an onMouseMove handler, also call it when
			// the user is dragging:
			} else if (!dragging || onlyMove) {
				tool.onHandleEvent('mousemove', point, event);
				if (tool.onMouseMove)
					that.draw();
			}
		}

		function mouseup(event) {
			if (!dragging)
				return;
			dragging = false;
			curPoint = null;
			if (tool) {
				if (tool.eventInterval != null)
					timer = clearInterval(timer);
				tool.onHandleEvent('mouseup', viewToArtwork(event), event);
				if (tool.onMouseUp)
					that.draw();
			}
		}

		return {
			mousedown: mousedown,
			mousemove: mousemove,
			mouseup: mouseup,
			touchstart: mousedown,
			touchmove: mousemove,
			touchend: mouseup
		};
	}
});
<|endoftext|>


<commit_before>var wallabyWebpack = require('wallaby-webpack');
var webpackConfig = require('./config/webpack.config.dev.js');
var wallabyPostprocessor = wallabyWebpack(webpackConfig);

module.exports = function (wallaby) {
    return {
        files: [
            { pattern: 'app/**/*.ts', load: false, instrument: false },
            { pattern: 'app/**/*.spec.ts', ignore: true },
            //{ pattern: 'app/**/*.ts', load: false },
            { pattern: 'app/**/*.html', load: false }
        ],

        tests: [
            { pattern: 'app/**/*.spec.ts', load: false }
        ],

        postprocessor: wallabyPostprocessor,
        compilers: {
            'app/**/*.js': wallaby.compilers.typeScript({
                "target": "es5",
                "module": "commonjs",
                "allowJs": true,
                "suppressOutputPathCheck": true
            })
        },
        testFramework: 'mocha',
        setup: function () {
            // required to trigger test loading
            window.__moduleBundler.loadTests();
        }
    };
};<commit_msg>DEV | Progress with wallaby<commit_after>var wallabyWebpack = require('wallaby-webpack');
//var webpackConfig = require('./config/webpack.test.node.js');
//var wallabyPostprocessor = wallabyWebpack(webpackConfig);

var wallabyPostprocessor = wallabyWebpack({
    entryPatterns: [
        'config/mocha-test-shim.node.js',
        'src/**/*spec.js'
    ],

    module: {
        loaders: [
            // if you use templateUrl in your components and want to inline your templates uncomment the below line
            { test: /\.js$/, loader: 'angular2-template-loader', exclude: /node_modules/ },

            // if importing .scss files in your component styleUrls uncomment the following line
            // { test: /\.scss$/, loaders: ['raw-loader', 'sass-loader'] },
            { test: /\.css$/, loader: 'raw-loader' },
            { test: /\.json$/, loader: 'json-loader' },
            { test: /\.html$/, loader: 'raw-loader' }
        ]
    }
});

module.exports = function (wallaby) {
    return {
        files: [
            { pattern: 'config/mocha-test-shim.node.js', load: false },
            { pattern: 'app/**/*.ts', load: false },
            { pattern: 'app/**/*.css', load: false },
            { pattern: 'app/**/*.less', load: false },
            { pattern: 'app/**/*.scss', load: false },
            { pattern: 'app/**/*.sass', load: false },
            { pattern: 'app/**/*.html', load: false },
            { pattern: 'app/**/*spec.ts', ignore: true },
        ],

        tests: [
            { pattern: 'app/**/*spec.ts', load: false },
        ],
        env: {
            type: 'node'
        },

        postprocessor: wallabyPostprocessor,
        // compilers: {
        //     'app/**/*.ts': wallaby.compilers.typeScript({
        //         "target": "es5",
        //         "module": "commonjs",
        //         "allowJs": true,
        //         "suppressOutputPathCheck": true
        //     })
        // },
        testFramework: 'mocha',
        setup: function () {
            // required to trigger test loading
            //window.__moduleBundler.loadTests();
            const jsdom = require('jsdom').jsdom
            //const sinon = require('sinon')
            const chai = require('chai')
            const expect = chai.expect
            const $ = require('jquery')
            //const chaiJquery = require('chai-jquery')
            //require('babel-polyfill')

            // from mocha-jsdom https://github.com/rstacruz/mocha-jsdom/blob/master/index.js#L80
            const propagateToGlobal = (window) => {
                for (var key in window) {
                    if (!window.hasOwnProperty(key)) continue
                    if (key in global) continue

                    global[key] = window[key]
                }
            }

            // setup the simplest document possible
            const doc = jsdom('')
            const win = doc.defaultView
            global.document = doc
            global.window = win
            //global.sinon = sinon
            global.expect = expect
            global.chai = chai
            global.$ = $(win)

            propagateToGlobal(win)
            global.window.____isjsdom = true
        },
        debug: true
    };
};<|endoftext|>


<commit_before>/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define, $, brackets */

/**
* Brackets Link File Watcher.
*/
define(function (require, exports, module) {
    'use strict';

    var EditorManager   = brackets.getModule("editor/EditorManager"),
        LanguageManager = brackets.getModule("language/LanguageManager"),
        FileUtils       = brackets.getModule("file/FileUtils"),
        ProjectManager  = brackets.getModule("project/ProjectManager");

    // Regexps
    var REGEXPS = {
        'javascript':   /<script\b[^>]*>([\s\S]*?)<\/script>/g,
        'image':        /<img\b[^>]*>([\s\S]*?)>{0}/g,
        'css':          /<link\b[^>]*>([\s\S]*?)>{0}/g,
        'php':          /include\b[^>]*>([\s\S]*?);{0}/g,
        'audio':        /<audio\b[^>]*>([\s\S]*?)<\/audio>/g,
        'video':        /<video\b[^>]*>([\s\S]*?)<\/video>/g
    };

    var VIDEO_EXTENSIONS = ['mp4', 'ogg', 'ogv', 'webm'];

    // Project files stores
    var _watchedFiles = [],
        _watchedFilesCache = [];

    // Watcher functions
    var _intervalFunc,
        _projectChangeFunc;

    function _compareFiles() {
        var removedFiles = [];
        _watchedFilesCache.forEach(function (file) {
            if ($.inArray(file, _watchedFiles) === -1) {
                removedFiles.push(file.fullPath);
            }
        });
        return removedFiles;
    }

    function _removeLinks(relPath, docText, fileLang) {
        if (fileLang === "unknown" || fileLang === "binary") {
            var fileExt = FileUtils.getFileExtension(relPath);
            if ($.inArray(fileExt, VIDEO_EXTENSIONS) > -1) { fileLang = "video"; }
        }
        if (fileLang === "svg") { fileLang = "image"; }

        var links = docText.match(REGEXPS[fileLang]);
        links.forEach(function (link) {
            if (link.indexOf(relPath) !== -1) {
                docText = docText.replace(link, "");
            }
        });
        return docText;
    }

    function start(findRelativePathFunc) {
        ProjectManager.getAllFiles().done(function (files) {
            _watchedFilesCache = files.slice(0);

            _intervalFunc = setInterval(function () {
                ProjectManager.getAllFiles().done(function (files) {
                    _watchedFiles = files.slice(0);
                    var removedFiles = _compareFiles();
                    if (removedFiles.length > 0) {
                        removedFiles.forEach(function (filePath) {
                            var editor = EditorManager. getActiveEditor();
                            if (editor) {
                                var fileLang = LanguageManager.getLanguageForPath(filePath).getId();
                                var relPath = findRelativePathFunc(filePath, editor.getFile().fullPath);
                                var docText = editor.document.getText();
                                var newText = _removeLinks(relPath, docText, fileLang);
                                editor.document.replaceRange(
                                    newText,
                                    {line: 0, ch: 0},
                                    {line: 9999, ch: 9999}
                                );
                            }
                        });
                    }
                    _watchedFilesCache = files.slice(0);
                });
            }, 3000);

            _projectChangeFunc = function () {
                ProjectManager.getAllFiles().done(function (files) {
                    _watchedFilesCache = files.slice(0);
                });
            }
            ProjectManager.on("projectOpen", _projectChangeFunc);
        });
    }

    function stop() {
        clearInterval(_intervalFunc);
        ProjectManager.off("projectOpen", _projectChangeFunc);
    }

    module.exports = {
        start: start,
        stop: stop
    };
});
<commit_msg>Fix for null regexp match return that causes error in forEach.<commit_after>/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define, $, brackets */

/**
* Brackets Link File Watcher.
*/
define(function (require, exports, module) {
    'use strict';

    var EditorManager   = brackets.getModule("editor/EditorManager"),
        LanguageManager = brackets.getModule("language/LanguageManager"),
        FileUtils       = brackets.getModule("file/FileUtils"),
        ProjectManager  = brackets.getModule("project/ProjectManager");

    // Regexps
    var REGEXPS = {
        'javascript':   /<script\b[^>]*>([\s\S]*?)<\/script>/g,
        'image':        /<img\b[^>]*>([\s\S]*?)>{0}/g,
        'css':          /<link\b[^>]*>([\s\S]*?)>{0}/g,
        'php':          /include\b[^>]*>([\s\S]*?);{0}/g,
        'audio':        /<audio\b[^>]*>([\s\S]*?)<\/audio>/g,
        'video':        /<video\b[^>]*>([\s\S]*?)<\/video>/g
    };

    var VIDEO_EXTENSIONS = ['mp4', 'ogg', 'ogv', 'webm'];

    // Project files stores
    var _watchedFiles = [],
        _watchedFilesCache = [];

    // Watcher functions
    var _intervalFunc,
        _projectChangeFunc;

    function _compareFiles() {
        var removedFiles = [];
        _watchedFilesCache.forEach(function (file) {
            if ($.inArray(file, _watchedFiles) === -1) {
                removedFiles.push(file.fullPath);
            }
        });
        return removedFiles;
    }

    function _removeLinks(relPath, docText, fileLang) {
        if (fileLang === "unknown" || fileLang === "binary") {
            var fileExt = FileUtils.getFileExtension(relPath);
            if ($.inArray(fileExt, VIDEO_EXTENSIONS) > -1) { fileLang = "video"; }
        }
        if (fileLang === "svg") { fileLang = "image"; }

        var links = docText.match(REGEXPS[fileLang]);
        if (links && links.length > 0) {
            links.forEach(function (link) {
                if (link.indexOf(relPath) !== -1) {
                    docText = docText.replace(link, "");
                }
            });
        }
        return docText;
    }

    function start(findRelativePathFunc) {
        ProjectManager.getAllFiles().done(function (files) {
            _watchedFilesCache = files.slice(0);

            _intervalFunc = setInterval(function () {
                ProjectManager.getAllFiles().done(function (files) {
                    _watchedFiles = files.slice(0);
                    var removedFiles = _compareFiles();
                    if (removedFiles.length > 0) {
                        removedFiles.forEach(function (filePath) {
                            var editor = EditorManager. getActiveEditor();
                            if (editor) {
                                var fileLang = LanguageManager.getLanguageForPath(filePath).getId();
                                var relPath = findRelativePathFunc(filePath, editor.getFile().fullPath);
                                var docText = editor.document.getText();
                                var newText = _removeLinks(relPath, docText, fileLang);
                                editor.document.replaceRange(
                                    newText,
                                    {line: 0, ch: 0},
                                    {line: 9999, ch: 9999}
                                );
                            }
                        });
                    }
                    _watchedFilesCache = files.slice(0);
                });
            }, 2000);

            _projectChangeFunc = function () {
                ProjectManager.getAllFiles().done(function (files) {
                    _watchedFilesCache = files.slice(0);
                });
            }
            ProjectManager.on("projectOpen", _projectChangeFunc);
        });
    }

    function stop() {
        clearInterval(_intervalFunc);
        ProjectManager.off("projectOpen", _projectChangeFunc);
    }

    module.exports = {
        start: start,
        stop: stop
    };
});
<|endoftext|>