<commit_before>import * as pickler from '../common/pickler';

const _range = (from, to) =>
  new Array(to - from).fill(0).map((_, i) => i + from);

class DSymbol {
  constructor(dim, sData, vData) {
    this._s = sData.slice();
    this._v = vData.slice();
    this._dim = dim;
    this._size = this._v.length / dim;
  }

  get dim() {
    return this._dim;
  }

  get size() {
    return this._size;
  }

  isElement(D) {
    return Number.isInteger(D) && D >= 1 && D <= this.size;
  }

  elements() {
    return _range(1, this.size + 1);
  }

  isIndex(i) {
    return Number.isInteger(i) && i >= 0 && i <= this.dim;
  }

  indices() {
    return _range(0, this.dim + 1);
  }

  s(i, D) {
    if (this.isElement(D) && this.isIndex(i))
      return this._s[i * this.size + D - 1];
  }

  v(i, j, D) {
    if (this.isElement(D) && this.isIndex(i) && this.isIndex(j)) {
      if (j == i+1)
        return this._v[i * this.size + D - 1];
      else if (j == i-1)
        return this._v[j * this.size + D - 1];
      else if (this.s(i, D) == this.s(j, D))
        return 2;
      else
        return 1;
    }
  }

  toString() {
    return stringify(this);
  }

  toJSON() {
    return stringify(this);
  }

  get __typeName() { return 'DelaneySymbol'; }
};

pickler.register(
  'DelaneySymbol',
  ({ _s, _v, _dim }) => ({ _s, _v, _dim }),
  ({ _s, _v, _dim }) => new DSymbol(_dim, _s, _v)
);

export const makeDSymbol = (dim, s, v) =>
  Object.freeze(new DSymbol(dim, s, v));

export const makeDSet = (dim, s) =>
  makeDSymbol(dim, s, new Array(dim * s.length / (dim + 1)).fill(0));

const _assert = (condition, message) => {
  if (!condition)
    throw new Error(message || 'assertion error');
};

const _assertIndex = (ds, i) =>
  _assert(ds.isIndex(i), `need integer between 0 and ${ds.dim}, got ${i}`);

const _assertElement = (ds, D) =>
  _assert(ds.isElement(D), `need integer between 1 and ${ds.size}, got ${D}`);

const _assertNonNegative = v =>
  _assert(Number.isInteger(v) && v >= 0, `need non-negative integer, got ${v}`);

export const withPairings = (ds, i, specs) => {
  _assertIndex(ds, i);

  const sNew = ds._s.slice();
  const get = D => sNew[i * ds.size + D - 1];
  const set = (D, x) => { sNew[i * ds.size + D - 1] = x; };

  const dangling = [];

  for (const [D, E] of specs) {
    _assertElement(ds, D);
    _assertElement(ds, E);

    dangling.push(get(D));
    dangling.push(get(E));

    set(D, E);
    set(E, D);
  }

  for (const D of dangling) {
    if (D && get(get(D)) != D)
      set(D, 0);
  }

  return makeDSymbol(ds.dim, sNew, ds._v);
};

export const withBranchings = (ds, i, specs) => {
  _assertIndex(ds, i);

  const vNew = ds._v.slice();
  const set = (D, x) => { vNew[i * ds.size + D - 1] = x; };

  for (const [D, v] of specs) {
    _assertElement(ds, D);
    _assertNonNegative(v);

    let E = D;
    do {
      E = ds.s(i, E) || E;
      set(E, v);
      E = ds.s(i+1, E) || E;
      set(E, v);
    }
    while (E != D);
  }

  return makeDSymbol(ds.dim, ds._s, vNew);
};

export const build = (dim, size, pairingsFn, branchingsFn) => {
  let ds = makeDSymbol(dim, new Array((dim+1) * size), new Array(dim * size));

  const ds0 = ds;
  for (let i = 0; i <= dim; ++i)
    ds = withPairings(ds, i, pairingsFn(ds0, i));

  const ds1 = ds;
  for (let i = 0; i < dim; i++)
    ds = withBranchings(ds, i, branchingsFn(ds1, i));

  return ds;
};

export const parse = str => {
  const _parseInts = str => str.trim().split(/\s+/).map(s => parseInt(s));

  const parts = str.trim().replace(/^</, '').replace(/>$/, '').split(/:/);
  if (parts[0].match(/\d+\.\d+/))
    parts.shift();

  const dims = _parseInts(parts[0]);
  const size = dims[0];
  const dim  = dims[1] || 2;

  const gluings = parts[1].split(/,/).map(_parseInts);
  const degrees = parts[2].split(/,/).map(_parseInts);

  const s = new Array((dim+1) * size);
  const v = new Array(dim * size);

  const get = (a, i, D)    => a[i * size + D - 1];
  const set = (a, i, D, x) => { a[i * size + D - 1] = x; };

  for (let i = 0; i <= dim; ++i) {
    let k = -1;
    for (let D = 1; D <= size; ++D) {
      if (!get(s, i, D)) {
        const E = gluings[i][++k];
        set(s, i, D, E);
        set(s, i, E, D);
      }
    }
  }

  for (let i = 0; i < dim; ++i) {
    let k = -1;
    for (let D = 1; D <= size; ++D) {
      if (!get(v, i, D)) {
        const m = degrees[i][++k];
        let E = D;
        let r = 0;

        do {
          E = get(s, i, E) || E;
          E = get(s, i+1, E) || E;
          ++r;
        }
        while (E != D);

        const b = m / r;

        do {
          E = get(s, i, E) || E;
          set(v, i, E, b);
          E = get(s, i+1, E) || E;
          set(v, i, E, b);
        }
        while (E != D);
      }
    }
  }

  return makeDSymbol(dim, s, v);
};

export const orbitReps1 = (ds, i) =>
  ds.elements().filter(D => (ds.s(i, D) || D) >= D);

export const orbit2 = (ds, i, j, D) => {
  const seen = new Array(ds.size + 1);
  const result = [];

  let E = D;
  do {
    for (const k of [i, j]) {
      E = ds.s(k, E) || E;
      if (!seen[E]) {
        result.push(E);
        seen[E] = true;
      }
    }
  }
  while (E != D);

  return result;
};

export const orbitReps2 = (ds, i, j) => {
  const seen = new Array(ds.size + 1);
  const result = [];

  for (const D of ds.elements()) {
    if (!seen[D]) {
      let E = D;

      do {
        E = ds.s(i, E) || E;
        seen[E] = true;
        E = ds.s(j, E) || E;
        seen[E] = true;
      }
      while (E != D);

      result.push(D);
    }
  }

  return result;
};

export const stringify = ds => {
  const sDefs = ds.indices()
    .map(i => orbitReps1(ds, i).map(D => ds.s(i, D) || 0).join(' '))
    .join(',');

  const mDefs = ds.indices()
    .filter(i => ds.isIndex(i+1))
    .map(i => orbitReps2(ds, i, i+1).map(D => m(ds, i, i+1, D) || 0).join(' '))
    .join(',');

  const n = ds.size;
  const d = ds.dim;

  return `<1.1:${n}${d == 2 ? '' : ` ${d}`}:${sDefs}:${mDefs}>`;
};

export const r = (ds, i, j, D) => {
  let k = 0;
  let E = D;

  do {
    E = ds.s(i, E) || E;
    E = ds.s(j, E) || E;
    ++k;
  }
  while (E != D);

  return k;
};

export const isElement = (ds, D) => ds.isElement(D);
export const elements = ds => ds.elements();
export const size = ds => ds.size;

export const isIndex = (ds, i) => ds.isIndex(i);
export const indices = ds => ds.indices();
export const dim = ds => ds.dim;

export const s = (ds, i, D) => ds.s(i, D);
export const v = (ds, i, j, D) => ds.v(i, j, D);
export const m = (ds, i, j, D) => ds.v(i, j, D) * r(ds, i, j, D);

export const parseSymbols = text => text
  .split('\n')
  .filter(line => !line.match(/^\s*(#.*)?$/))
  .map(parse);

if (require.main == module) {
  const ds = parse('<1.1:3:1 2 3,1 3,2 3:4 8,3>');

  console.log(stringify(ds));
  console.log(`${ds}`);
  console.log(`pickled: ${JSON.stringify(pickler.pickle(ds))}`);
  console.log(`unpickled: ${pickler.unpickle(pickler.pickle(ds))}`);

  console.log(`${withPairings(ds, 1, [[2,1]])}`);
  console.log(`${withBranchings(ds, 0, [[2,3],[1,5]])}`);
}
<commit_msg>parseSymbols: modify line filter<commit_after>import * as pickler from '../common/pickler';

const _range = (from, to) =>
  new Array(to - from).fill(0).map((_, i) => i + from);

class DSymbol {
  constructor(dim, sData, vData) {
    this._s = sData.slice();
    this._v = vData.slice();
    this._dim = dim;
    this._size = this._v.length / dim;
  }

  get dim() {
    return this._dim;
  }

  get size() {
    return this._size;
  }

  isElement(D) {
    return Number.isInteger(D) && D >= 1 && D <= this.size;
  }

  elements() {
    return _range(1, this.size + 1);
  }

  isIndex(i) {
    return Number.isInteger(i) && i >= 0 && i <= this.dim;
  }

  indices() {
    return _range(0, this.dim + 1);
  }

  s(i, D) {
    if (this.isElement(D) && this.isIndex(i))
      return this._s[i * this.size + D - 1];
  }

  v(i, j, D) {
    if (this.isElement(D) && this.isIndex(i) && this.isIndex(j)) {
      if (j == i+1)
        return this._v[i * this.size + D - 1];
      else if (j == i-1)
        return this._v[j * this.size + D - 1];
      else if (this.s(i, D) == this.s(j, D))
        return 2;
      else
        return 1;
    }
  }

  toString() {
    return stringify(this);
  }

  toJSON() {
    return stringify(this);
  }

  get __typeName() { return 'DelaneySymbol'; }
};

pickler.register(
  'DelaneySymbol',
  ({ _s, _v, _dim }) => ({ _s, _v, _dim }),
  ({ _s, _v, _dim }) => new DSymbol(_dim, _s, _v)
);

export const makeDSymbol = (dim, s, v) =>
  Object.freeze(new DSymbol(dim, s, v));

export const makeDSet = (dim, s) =>
  makeDSymbol(dim, s, new Array(dim * s.length / (dim + 1)).fill(0));

const _assert = (condition, message) => {
  if (!condition)
    throw new Error(message || 'assertion error');
};

const _assertIndex = (ds, i) =>
  _assert(ds.isIndex(i), `need integer between 0 and ${ds.dim}, got ${i}`);

const _assertElement = (ds, D) =>
  _assert(ds.isElement(D), `need integer between 1 and ${ds.size}, got ${D}`);

const _assertNonNegative = v =>
  _assert(Number.isInteger(v) && v >= 0, `need non-negative integer, got ${v}`);

export const withPairings = (ds, i, specs) => {
  _assertIndex(ds, i);

  const sNew = ds._s.slice();
  const get = D => sNew[i * ds.size + D - 1];
  const set = (D, x) => { sNew[i * ds.size + D - 1] = x; };

  const dangling = [];

  for (const [D, E] of specs) {
    _assertElement(ds, D);
    _assertElement(ds, E);

    dangling.push(get(D));
    dangling.push(get(E));

    set(D, E);
    set(E, D);
  }

  for (const D of dangling) {
    if (D && get(get(D)) != D)
      set(D, 0);
  }

  return makeDSymbol(ds.dim, sNew, ds._v);
};

export const withBranchings = (ds, i, specs) => {
  _assertIndex(ds, i);

  const vNew = ds._v.slice();
  const set = (D, x) => { vNew[i * ds.size + D - 1] = x; };

  for (const [D, v] of specs) {
    _assertElement(ds, D);
    _assertNonNegative(v);

    let E = D;
    do {
      E = ds.s(i, E) || E;
      set(E, v);
      E = ds.s(i+1, E) || E;
      set(E, v);
    }
    while (E != D);
  }

  return makeDSymbol(ds.dim, ds._s, vNew);
};

export const build = (dim, size, pairingsFn, branchingsFn) => {
  let ds = makeDSymbol(dim, new Array((dim+1) * size), new Array(dim * size));

  const ds0 = ds;
  for (let i = 0; i <= dim; ++i)
    ds = withPairings(ds, i, pairingsFn(ds0, i));

  const ds1 = ds;
  for (let i = 0; i < dim; i++)
    ds = withBranchings(ds, i, branchingsFn(ds1, i));

  return ds;
};

export const parse = str => {
  const _parseInts = str => str.trim().split(/\s+/).map(s => parseInt(s));

  const parts = str.trim().replace(/^</, '').replace(/>$/, '').split(/:/);
  if (parts[0].match(/\d+\.\d+/))
    parts.shift();

  const dims = _parseInts(parts[0]);
  const size = dims[0];
  const dim  = dims[1] || 2;

  const gluings = parts[1].split(/,/).map(_parseInts);
  const degrees = parts[2].split(/,/).map(_parseInts);

  const s = new Array((dim+1) * size);
  const v = new Array(dim * size);

  const get = (a, i, D)    => a[i * size + D - 1];
  const set = (a, i, D, x) => { a[i * size + D - 1] = x; };

  for (let i = 0; i <= dim; ++i) {
    let k = -1;
    for (let D = 1; D <= size; ++D) {
      if (!get(s, i, D)) {
        const E = gluings[i][++k];
        set(s, i, D, E);
        set(s, i, E, D);
      }
    }
  }

  for (let i = 0; i < dim; ++i) {
    let k = -1;
    for (let D = 1; D <= size; ++D) {
      if (!get(v, i, D)) {
        const m = degrees[i][++k];
        let E = D;
        let r = 0;

        do {
          E = get(s, i, E) || E;
          E = get(s, i+1, E) || E;
          ++r;
        }
        while (E != D);

        const b = m / r;

        do {
          E = get(s, i, E) || E;
          set(v, i, E, b);
          E = get(s, i+1, E) || E;
          set(v, i, E, b);
        }
        while (E != D);
      }
    }
  }

  return makeDSymbol(dim, s, v);
};

export const orbitReps1 = (ds, i) =>
  ds.elements().filter(D => (ds.s(i, D) || D) >= D);

export const orbit2 = (ds, i, j, D) => {
  const seen = new Array(ds.size + 1);
  const result = [];

  let E = D;
  do {
    for (const k of [i, j]) {
      E = ds.s(k, E) || E;
      if (!seen[E]) {
        result.push(E);
        seen[E] = true;
      }
    }
  }
  while (E != D);

  return result;
};

export const orbitReps2 = (ds, i, j) => {
  const seen = new Array(ds.size + 1);
  const result = [];

  for (const D of ds.elements()) {
    if (!seen[D]) {
      let E = D;

      do {
        E = ds.s(i, E) || E;
        seen[E] = true;
        E = ds.s(j, E) || E;
        seen[E] = true;
      }
      while (E != D);

      result.push(D);
    }
  }

  return result;
};

export const stringify = ds => {
  const sDefs = ds.indices()
    .map(i => orbitReps1(ds, i).map(D => ds.s(i, D) || 0).join(' '))
    .join(',');

  const mDefs = ds.indices()
    .filter(i => ds.isIndex(i+1))
    .map(i => orbitReps2(ds, i, i+1).map(D => m(ds, i, i+1, D) || 0).join(' '))
    .join(',');

  const n = ds.size;
  const d = ds.dim;

  return `<1.1:${n}${d == 2 ? '' : ` ${d}`}:${sDefs}:${mDefs}>`;
};

export const r = (ds, i, j, D) => {
  let k = 0;
  let E = D;

  do {
    E = ds.s(i, E) || E;
    E = ds.s(j, E) || E;
    ++k;
  }
  while (E != D);

  return k;
};

export const isElement = (ds, D) => ds.isElement(D);
export const elements = ds => ds.elements();
export const size = ds => ds.size;

export const isIndex = (ds, i) => ds.isIndex(i);
export const indices = ds => ds.indices();
export const dim = ds => ds.dim;

export const s = (ds, i, D) => ds.s(i, D);
export const v = (ds, i, j, D) => ds.v(i, j, D);
export const m = (ds, i, j, D) => ds.v(i, j, D) * r(ds, i, j, D);

export const parseSymbols = text => text
  .split('\n')
  .filter(line => line.match(/^\s*</))
  .map(parse);

if (require.main == module) {
  const ds = parse('<1.1:3:1 2 3,1 3,2 3:4 8,3>');

  console.log(stringify(ds));
  console.log(`${ds}`);
  console.log(`pickled: ${JSON.stringify(pickler.pickle(ds))}`);
  console.log(`unpickled: ${pickler.unpickle(pickler.pickle(ds))}`);

  console.log(`${withPairings(ds, 1, [[2,1]])}`);
  console.log(`${withBranchings(ds, 0, [[2,3],[1,5]])}`);
}
<|endoftext|>


<commit_before>(function($){

function parseDate(str) {
  var d = new Date(Date.parse(str));

  return d.toISOString();
}

// Gets some JSON from the url in `opts.url` then uses `opts.vars` and
// `opts.display` to add another list item in the required format.
function getJson(opts, callback) {
  $.getJSON(opts.url, function(data) {
    var vars = {};

    // For each element of `opts.vars` run through the given call path to get
    // the value. So, say we have:
    //
    //     vars: {
    //       title: ['title']
    //     }
    //
    // Which would translate to:
    //
    //    vars['title'] = data['title']
    //
    $.each(opts.vars, function(key, val) {
      vars[key] = data;
      $.each(val, function(i, meth) {
        vars[key] = vars[key][meth];
      });
    });

    // Parse the date, and return in a common format.
    vars.date = parseDate(vars.date);

    callback(vars)
  });
}

function addToList(opts, list) {
  return function(data) {
    // Now append a new list element using Mustache with `opts.display` and the
    // vars found above.
    list.append('
' + Mustache.render(opts.display, data) + '
');
  }
}

var JsonGetter = function() {
  var getter = {
    appendTo: function(list) {
      getJson(this, addToList(this, list))
    }
  }
  return getter
}

var XmlGetter = function() {
  var getter = {
    appendTo: function(list) {
      this.url = "http://query.yahooapis.com/v1/public/yql?q=" +
                 encodeURIComponent('select * from xml where url="' + this.url + '"') +
                 "&env=store://datatables.org/alltableswithkeys&format=json"

      getJson(this, addToList(this, list));
    }
  }
  return getter
};

var Maker = function(getter, defaults) {
  return function(opts) {
    var getterWithDefaults = $.extend(getter, defaults(opts));

    return $.extend(getterWithDefaults, opts)
  }
}

var Twitter = Maker(JsonGetter(), function (opts) {
  return {
    url:  "https://api.twitter.com/1/statuses/user_timeline/" + opts.user + ".json?count=1&include_rts=1&callback=?",
    vars: {
      text: [0, 'text'],
      id:   [0, 'id'],
      date: [0, 'created_at']
    },
    display: '
twitter: {{text}}
'
  }
})

var LastFm = Maker(XmlGetter(), function(opts) {
  return {
    url:  'http://ws.audioscrobbler.com/1.0/user/' + opts.user + '/recenttracks.rss?limit=1',
    vars: {
      title: ['query', 'results', 'rss', 'channel', 'item', 'title'],
      link:  ['query', 'results', 'rss', 'channel', 'item', 'link'],
      date:  ['query', 'results', 'rss', 'channel', 'item', 'pubDate']
    },
    display: '
last.fm: {{title}}
',
  }
})

var Blog = Maker(XmlGetter(), function(opts) {
  var name = opts.name || 'blog';

  return {
    url: opts.feed,
    vars: {
      title: ['query', 'results', 'rss', 'channel', 'item', 0, 'title'],
      link:  ['query', 'results', 'rss', 'channel', 'item', 0, 'link'],
      date:  ['query', 'results', 'rss', 'channel', 'item', 0, 'pubDate'],
      text:  ['query', 'results', 'rss', 'channel', 'item', 0, 'description']
    },
    display: '
' + name + ': {{title}}

{{{text}}}
'
  }
})

var Flickr = Maker(XmlGetter(), function(opts) {
  return {
    url: "http://api.flickr.com/services/feeds/photos_public.gne?id=" + opts.user + "&lang=en-us&format=rss_200",
    vars: {
      title: ['query', 'results', 'rss', 'channel', 'item', 0, 'title', 0],
      photo: ['query', 'results', 'rss', 'channel', 'item', 0, 'content', 'url'],
      date:  ['query', 'results', 'rss', 'channel', 'item', 0, 'pubDate'],
      link:  ['query', 'results', 'rss', 'channel', 'item', 0, 'link']
    },
    display: '
flickr: {{title}}
 '
  }
})

var Xml = Maker(XmlGetter(), function(opts) {
  return {}
})

var Json = Maker(JsonGetter(), function(opts) {
  return {}
})

var Providers = {
  flickr:    Flickr,
  "last.fm": LastFm,
  twitter:   Twitter,
  blog:      Blog,
  xml:       Xml,
  json:      Json,
}

$.fn.myLast = function(config) {
  var list = $(this);

  $.each(config, function(i, obj) {
    var provider = Providers[obj.type];

    if (provider == null) {
      console.log("ERROR: No provider '" + obj.type + "'");
    } else {
      provider(obj).appendTo(list);
    }
  });
};

})(jQuery);<commit_msg>Lots of improvements.<commit_after>(function($){

  function parseDate(str) {
    var d = new Date(Date.parse(str));

    return d.toISOString();
  }

  // Gets some JSON from the url in `opts.url` then uses `opts.vars` and
  // `opts.display` to add another list item in the required format.
  function getJson(opts, callback) {
    $.getJSON(opts.url, function(data) {
      var vars = {};

      if (opts.vars) {

        // For each element of `opts.vars` run through the given call path to get
        // the value. So, say we have:
        //
        //     vars: {
        //       title: ['title']
        //     }
        //
        // Which would translate to:
        //
        //    vars['title'] = data['title']
        //
        $.each(opts.vars, function(key, val) {
          vars[key] = data;
          $.each(val, function(i, meth) {
            vars[key] = vars[key][meth];
          });
        });

        if (vars.date) {
          // Parse the date, and return in a common format.
          vars.date = parseDate(vars.date);
        };

        callback(vars);

      } else {
        callback(data);
      }
    });
  }

  function addToList(opts, list) {
    return function(data) {
      var text = ''

      if (typeof opts.display == "function") {
        text = opts.display(data);
      } else {
        text = Mustache.render(opts.display, data);
      }

      // Now append a new list element using Mustache with `opts.display` and the
      // vars found above.
      list.append('
' + text + '
');
    }
  }

  function map(obj, func) {
    for (var key in obj) {
      obj[key] = func(obj[key])
    }
    return obj
  }

  var JsonGetter = function() {
    var getter = {
      appendTo: function(list) {
        getJson(this, addToList(this, list))
      }
    }
    return getter
  }

  var XmlGetter = function() {
    var getter = {
      appendTo: function(list) {
        this.url = "http://query.yahooapis.com/v1/public/yql?q=" +
                   encodeURIComponent('select * from xml where url="' + this.url + '"') +
                   "&env=store://datatables.org/alltableswithkeys&format=json"

        this.vars = map(this.vars, function(e) {
          return ['query', 'results'].concat(e);
        });

        getJson(this, addToList(this, list));
      }
    }
    return getter
  };

  var Maker = function(getter, defaults) {
    return function(opts) {
      var getterWithDefaults = $.extend(getter, defaults(opts));

      return $.extend(getterWithDefaults, opts)
    }
  }

  var Twitter = Maker(JsonGetter(), function (opts) {
    return {
      url:  "https://api.twitter.com/1/statuses/user_timeline/" + opts.user + ".json?count=1&include_rts=1&callback=?",
      vars: {
        text: [0, 'text'],
        id:   [0, 'id'],
        date: [0, 'created_at']
      },
      display: '
twitter: {{text}}
'
    }
  })

  var LastFm = Maker(XmlGetter(), function(opts) {
    return {
      url:  'http://ws.audioscrobbler.com/1.0/user/' + opts.user + '/recenttracks.rss?limit=1',
      vars: {
        title: ['rss', 'channel', 'item', 'title'],
        link:  ['rss', 'channel', 'item', 'link'],
        date:  ['rss', 'channel', 'item', 'pubDate']
      },
      display: '
last.fm: {{title}}
',
    }
  })

  var Blog = Maker(XmlGetter(), function(opts) {
    var name = opts.name || 'blog';

    return {
      url: opts.feed,
      vars: {
        title: ['rss', 'channel', 'item', 0, 'title'],
        link:  ['rss', 'channel', 'item', 0, 'link'],
        date:  ['rss', 'channel', 'item', 0, 'pubDate'],
        text:  ['rss', 'channel', 'item', 0, 'description']
      },
      display: '
' + name + ': {{title}}

{{{text}}}
'
    }
  })

  var Flickr = Maker(XmlGetter(), function(opts) {
    return {
      url: "http://api.flickr.com/services/feeds/photos_public.gne?id=" + opts.user + "&lang=en-us&format=rss_200",
      vars: {
        title: ['rss', 'channel', 'item', 0, 'title', 0],
        photo: ['rss', 'channel', 'item', 0, 'content', 'url'],
        date:  ['rss', 'channel', 'item', 0, 'pubDate'],
        link:  ['rss', 'channel', 'item', 0, 'link']
      },
      display: '
flickr: {{title}}
 '
    }
  })

  var Xml = Maker(XmlGetter(), function(opts) {
    return {}
  })

  var Json = Maker(JsonGetter(), function(opts) {
    return {}
  })

  var Providers = {
    flickr:    Flickr,
    "last.fm": LastFm,
    twitter:   Twitter,
    blog:      Blog,
    xml:       Xml,
    json:      Json,
  }

  $.fn.myLast = function(config) {
    var list = $(this);

    $.each(config, function(i, obj) {
      var provider = Providers[obj.type];

      if (provider == null) {
        console.log("ERROR: No provider '" + obj.type + "'");
      } else {
        provider(obj).appendTo(list);
      }
    });
  };

})(jQuery);<|endoftext|>


<commit_before>// Copyright (C) 2014 Makoto Mizukami

(function () {
	"use strict";

	var CONFIG = {
		serverUid : "http",
		serverGid : "http",
		listenIPv4 : true,
		listenIPv6 : true,
		servicePort : 37320,
		workersMaxNum : 64,
		workersKeepIdle : 7200,	// Unit: second
		workersTimeout : 60,	// Unit: second
		messageExpiry : 500,	// Unit: millisecond
		messageCap : 1024 * 32	// Unit: octet
	},

	Messenger = function (salt, type, payload) {
		var messageTypeRegex = /^(?:header|body|end)$/;

		this.salt = salt;
		this.type = messageTypeRegex.test(type) === true ? type : "unknown";
		this.payload = payload;
	},
	RequestHeaderMessenger = function (salt, invoking, req) {
		Messenger.call(this, salt, "header", {
			invoking : invoking,
			params : req.params
		});
	},
	ResponseHeaderMessenger = function (salt, headers) {
		Messenger.call(this, salt, "header", {
			statusCode : 200,
			headers : headers
		});
	},

	Nodebed = function () {},

	cluster = require("cluster");

	Nodebed.prototype.balancer = function () {
		var server = require("./scgi.js").createServer(),

		url = require("url"),

		idleWorker = function () {
			this.isIdle = true;
			this.stopIdling = setTimeout(this.kill.bind(this), CONFIG.workersKeepIdle * 1000);
		},

		invokeWorker = function (path) {
			var worker = null, numOfActiveWorkers = 0;

			for (let workerId in cluster.workers) {
				numOfActiveWorkers += 1;

				if (cluster.workers[workerId].workFor === path && cluster.workers[workerId].isIdle === true) {
					worker = cluster.workers[workerId];
					clearTimeout(cluster.workers[workerId].stopIdling);
					break;
				}
			}

			if (worker === null) {
				if (numOfActiveWorkers >= CONFIG.workersMaxNum) {
					return null;
				}

				worker = cluster.fork();
				worker.workFor = path;
				worker.idle = idleWorker;
			}

			worker.isIdle = false;

			return worker;
		},

		workerMessageReceptor = function (workerMes) {
			var res = this.res, worker = this.worker;

			if (workerMes.salt !== this.salt) {
				return;
			}

			switch (workerMes.type) {
				case "header":
					if (! isNaN(workerMes.payload.statusCode)) {
						res.setStatus(workerMes.payload.statusCode);
					}

					workerMes.payload.headers.forEach(function (header) {
						res.setHeader(header);
					});

					break;

				case "body":
					res.write(new Buffer(workerMes.payload));
					break;

				case "end":
					res.end();
					worker.removeListener("message", this.workerMessageReceiver);
					worker.removeListener("exit", this.workerAbendNotifier);
					worker.idle();
					break;

				default:
					break;
			}
		},

		onReqData = function (bChunk) {
			var messageContainer = new Messenger(this.salt, "body", null), p = 0;

			while (p < bChunk.length) {
				messageContainer.payload = bChunk.slice(
					p,
					p = p + CONFIG.messageCap < bChunk.length ? p + CONFIG.messageCap : bChunk.length
				);
				this.worker.send(messageContainer);
			}
		},

		onReqEnd = function () {
			this.worker.send(new Messenger(this.salt, "end"));
			this.worker.on("message", this.workerMessageReceiver);
		},

		onResClose = function () {
			this.worker.kill();
		},

		onWorkerAbend = function () {
			this.res.setStatus(500);
			this.res.end();
		},

		timeoutResponse = function () {
			this.res.setStatus(408);
			this.res.write("Timed out.");
			this.res.end();
			this.worker.kill();
		},

		responder = function (req, res) {
			var salt = Math.random(),
			requested = url.parse(req.params.SCRIPT_FILENAME.replace(/^proxy:scgi:/, "scgi:")),
			invoking = url.parse(requested.pathname).pathname,

			worker = invokeWorker(invoking),

			resources = {
				salt : salt,
				res : res,
				worker : worker
			};

			if (worker === null) {
				res.setStatus(503);
				res.write("No vacancy for your request.");
				res.end();
				return;
			}

			resources.workerMessageReceiver = workerMessageReceptor.bind(resources);
			resources.workerAbendNotifier = onWorkerAbend.bind(resources);

			worker.send(new RequestHeaderMessenger(salt, invoking, req));

			req.on("data", onReqData.bind(resources));
			req.on("end", onReqEnd.bind(resources));
			res.on("close", onResClose.bind(resources));

			worker.on("exit", resources.workerAbendNotifier);

			req.setTimeout(CONFIG.workersTimeout * 1000, timeoutResponse.bind(resources));
		};

		CONFIG.listenIPv4 && server.listen(CONFIG.servicePort, "0.0.0.0");
		CONFIG.listenIPv6 && server.listen(CONFIG.servicePort, "::");

		server.on("request", responder);
	};

	Nodebed.prototype.worker = function () {
		var built = null, request = null,

		fs = require("fs"),

		parseStr = function (str) {
			var ret = {};

			str.toString().split("&").forEach(function (term) {
				var eqSplit = term.split("="),
				fieldName = decodeURIComponent(eqSplit.shift());

				if (fieldName === "") {
					return;
				}

				ret[fieldName] = decodeURIComponent(eqSplit.join("="));
			});

			return ret;
		},

		genInstanceInterface = function (request) {
			var ret = {},

			salt = request.salt,

			responseStatus = NaN,
			responseHeaders = [],
			isHeaderSent = false,

			bCache = {
				chunks : [],
				cachedLength : 0,
				autoFlush : null
			},

			flushHeaders = function () {
				var messageContainer = new ResponseHeaderMessenger(salt, responseHeaders);

				if(! isNaN(responseStatus)) {
					messageContainer.payload.statusCode = responseStatus;
				}

				process.send(messageContainer);
				isHeaderSent = true;
			};

			ret.request = {
				params : request.header.params,
				body : Buffer.concat(request.body.chunks, request.body.length),
				formData : {}
			};

			ret.request.formData.GET = parseStr(request.header.params.QUERY_STRING || "");
			ret.request.formData.POST = parseStr(request.header.params.REQUEST_METHOD.toUpperCase() === "POST" && request.header.params.CONTENT_TYPE.toLowerCase() === "application/x-www-form-urlencoded" ? ret.request.body.toString() : "");

			ret.setStatus = function (sCode, reasonPhrase) {
				responseStatus = parseInt(sCode, 10);

				if (reasonPhrase !== undefined) {
					responseHeaders.push(["Status:", sCode.toString(), reasonPhrase].join(" "));
				}
			};

			ret.setHeader = function (expr) {
				responseHeaders.push(expr);
			};

			ret.flush = function () {
				var messageContainer = new Messenger(salt, "body", null),
				bChunk = typeof bCache.chunks[0] === "string" ? bCache.chunks.join("") : Buffer.concat(bCache.chunks, bCache.chachedLength);

				bCache.autoFlush !== null && clearTimeout(bCache.autoFlush);

				isHeaderSent === false && flushHeaders();

				for (let p = 0; p < bChunk.length;) {
					messageContainer.payload = bChunk.slice(
						p,
						p = p + CONFIG.messageCap < bChunk.length ? p + CONFIG.messageCap : bChunk.length
					);
					process.send(messageContainer);
				}

				bCache.chunks = [];
				bCache.cachedLength = 0;
				bCache.autoFlush = null;
			};

			ret.echo = function(data) {
				var toBeCached = null;

				isHeaderSent === false && flushHeaders();

				if (data === undefined || data === null || data.length === 0) {
					return;
				}

				if (Buffer.isBuffer(data) === true) {
					if (bCache.length > 0 && Buffer.isBuffer(bCache.chunks[0]) !== true) {
						ret.flush();
					}

					toBeCached = data;
				} else {
					if (bCache.length > 0 && typeof bCache.chunks[0] !== typeof "") {
						ret.flush();
					}

					toBeCached = typeof data === typeof "" ? data : data.toString();
				}

				if (bCache.cachedLength + toBeCached.length > CONFIG.messageCap) {
					ret.flush();
				}

				bCache.chunks.push(toBeCached);
				bCache.cachedLength += toBeCached.length;

				if (toBeCached.length > CONFIG.messageCap) {
					ret.flush();
				} else if (bCache.autoFlush === null) {
					bCache.autoFlush = setTimeout(ret.flush, CONFIG.messageExpiry);
				}
			};

			ret.end = function () {
				ret.flush();
				process.send(new Messenger(salt, "end"));
			};

			return ret;
		},

		respondBalancerRequest = function (request) {
			var instanceInterface = genInstanceInterface(request);

			try {
				process.chdir(require("path").dirname(fs.realpathSync(request.header.invoking)));

				if (built === null || fs.statSync(request.header.invoking).ctime.getTime() > built.builtAt.getTime()) {
					for (let cachedPath in require.cache) {
						delete require.cache[cachedPath];
					}

					built = require(request.header.invoking);
					built.builtAt = new Date();
				}

				instanceInterface.setHeader("Content-Type: text/html; charset=UTF-8");
				built.exec(instanceInterface);
			} catch (e) {
				instanceInterface.setStatus(500);
				instanceInterface.echo("Error during script execution.");
				instanceInterface.end();
				cluster.worker.kill();
			}
		},

		balancerMessageReceptor = function (messenger) {
			switch (messenger.type) {
				case "header":
					request = {
						salt : messenger.salt,
						header : messenger.payload,
						body : {
							length : 0,
							chunks : []
						}
					};
					break;

				case "body":
					if (messenger.salt === request.salt) {
						let newBodyChunk = new Buffer(messenger.payload);

						request.body.chunks.push(newBodyChunk);
						request.body.length += newBodyChunk.length;
					}
					break;

				case "end":
					respondBalancerRequest(request);
					break;
				default:
					break;
			}
		};

		process.on("message", balancerMessageReceptor);
	};

	process.setuid(CONFIG.serverUid) && process.setgid(CONFIG.serverGid);

	if (cluster.isMaster === true) {
		new Nodebed().balancer();
	} else if (cluster.isWorker === true) {
		new Nodebed().worker();
	}
})();
<commit_msg>[core] Put stack traces to log<commit_after>// Copyright (C) 2014 Makoto Mizukami

(function () {
	"use strict";

	var CONFIG = {
		serverUid : "http",
		serverGid : "http",
		listenIPv4 : true,
		listenIPv6 : true,
		servicePort : 37320,
		workersMaxNum : 64,
		workersKeepIdle : 7200,	// Unit: second
		workersTimeout : 60,	// Unit: second
		messageExpiry : 500,	// Unit: millisecond
		messageCap : 1024 * 32	// Unit: octet
	},

	Messenger = function (salt, type, payload) {
		var messageTypeRegex = /^(?:header|body|end)$/;

		this.salt = salt;
		this.type = messageTypeRegex.test(type) === true ? type : "unknown";
		this.payload = payload;
	},
	RequestHeaderMessenger = function (salt, invoking, req) {
		Messenger.call(this, salt, "header", {
			invoking : invoking,
			params : req.params
		});
	},
	ResponseHeaderMessenger = function (salt, headers) {
		Messenger.call(this, salt, "header", {
			statusCode : 200,
			headers : headers
		});
	},

	Nodebed = function () {},

	cluster = require("cluster");

	Nodebed.prototype.balancer = function () {
		var server = require("./scgi.js").createServer(),

		url = require("url"),

		idleWorker = function () {
			this.isIdle = true;
			this.stopIdling = setTimeout(this.kill.bind(this), CONFIG.workersKeepIdle * 1000);
		},

		invokeWorker = function (path) {
			var worker = null, numOfActiveWorkers = 0;

			for (let workerId in cluster.workers) {
				numOfActiveWorkers += 1;

				if (cluster.workers[workerId].workFor === path && cluster.workers[workerId].isIdle === true) {
					worker = cluster.workers[workerId];
					clearTimeout(cluster.workers[workerId].stopIdling);
					break;
				}
			}

			if (worker === null) {
				if (numOfActiveWorkers >= CONFIG.workersMaxNum) {
					return null;
				}

				worker = cluster.fork();
				worker.workFor = path;
				worker.idle = idleWorker;
			}

			worker.isIdle = false;

			return worker;
		},

		workerMessageReceptor = function (workerMes) {
			var res = this.res, worker = this.worker;

			if (workerMes.salt !== this.salt) {
				return;
			}

			switch (workerMes.type) {
				case "header":
					if (! isNaN(workerMes.payload.statusCode)) {
						res.setStatus(workerMes.payload.statusCode);
					}

					workerMes.payload.headers.forEach(function (header) {
						res.setHeader(header);
					});

					break;

				case "body":
					res.write(new Buffer(workerMes.payload));
					break;

				case "end":
					res.end();
					worker.removeListener("message", this.workerMessageReceiver);
					worker.removeListener("exit", this.workerAbendNotifier);
					worker.idle();
					break;

				default:
					break;
			}
		},

		onReqData = function (bChunk) {
			var messageContainer = new Messenger(this.salt, "body", null), p = 0;

			while (p < bChunk.length) {
				messageContainer.payload = bChunk.slice(
					p,
					p = p + CONFIG.messageCap < bChunk.length ? p + CONFIG.messageCap : bChunk.length
				);
				this.worker.send(messageContainer);
			}
		},

		onReqEnd = function () {
			this.worker.send(new Messenger(this.salt, "end"));
			this.worker.on("message", this.workerMessageReceiver);
		},

		onResClose = function () {
			this.worker.kill();
		},

		onWorkerAbend = function () {
			this.res.setStatus(500);
			this.res.end();
		},

		timeoutResponse = function () {
			this.res.setStatus(408);
			this.res.write("Timed out.");
			this.res.end();
			this.worker.kill();
		},

		responder = function (req, res) {
			var salt = Math.random(),
			requested = url.parse(req.params.SCRIPT_FILENAME.replace(/^proxy:scgi:/, "scgi:")),
			invoking = url.parse(requested.pathname).pathname,

			worker = invokeWorker(invoking),

			resources = {
				salt : salt,
				res : res,
				worker : worker
			};

			if (worker === null) {
				res.setStatus(503);
				res.write("No vacancy for your request.");
				res.end();
				return;
			}

			resources.workerMessageReceiver = workerMessageReceptor.bind(resources);
			resources.workerAbendNotifier = onWorkerAbend.bind(resources);

			worker.send(new RequestHeaderMessenger(salt, invoking, req));

			req.on("data", onReqData.bind(resources));
			req.on("end", onReqEnd.bind(resources));
			res.on("close", onResClose.bind(resources));

			worker.on("exit", resources.workerAbendNotifier);

			req.setTimeout(CONFIG.workersTimeout * 1000, timeoutResponse.bind(resources));
		};

		CONFIG.listenIPv4 && server.listen(CONFIG.servicePort, "0.0.0.0");
		CONFIG.listenIPv6 && server.listen(CONFIG.servicePort, "::");

		server.on("request", responder);
	};

	Nodebed.prototype.worker = function () {
		var built = null, request = null,

		fs = require("fs"),

		parseStr = function (str) {
			var ret = {};

			str.toString().split("&").forEach(function (term) {
				var eqSplit = term.split("="),
				fieldName = decodeURIComponent(eqSplit.shift());

				if (fieldName === "") {
					return;
				}

				ret[fieldName] = decodeURIComponent(eqSplit.join("="));
			});

			return ret;
		},

		genInstanceInterface = function (request) {
			var ret = {},

			salt = request.salt,

			responseStatus = NaN,
			responseHeaders = [],
			isHeaderSent = false,

			bCache = {
				chunks : [],
				cachedLength : 0,
				autoFlush : null
			},

			flushHeaders = function () {
				var messageContainer = new ResponseHeaderMessenger(salt, responseHeaders);

				if(! isNaN(responseStatus)) {
					messageContainer.payload.statusCode = responseStatus;
				}

				process.send(messageContainer);
				isHeaderSent = true;
			};

			ret.request = {
				params : request.header.params,
				body : Buffer.concat(request.body.chunks, request.body.length),
				formData : {}
			};

			ret.request.formData.GET = parseStr(request.header.params.QUERY_STRING || "");
			ret.request.formData.POST = parseStr(request.header.params.REQUEST_METHOD.toUpperCase() === "POST" && request.header.params.CONTENT_TYPE.toLowerCase() === "application/x-www-form-urlencoded" ? ret.request.body.toString() : "");

			ret.setStatus = function (sCode, reasonPhrase) {
				responseStatus = parseInt(sCode, 10);

				if (reasonPhrase !== undefined) {
					responseHeaders.push(["Status:", sCode.toString(), reasonPhrase].join(" "));
				}
			};

			ret.setHeader = function (expr) {
				responseHeaders.push(expr);
			};

			ret.flush = function () {
				var messageContainer = new Messenger(salt, "body", null),
				bChunk = typeof bCache.chunks[0] === "string" ? bCache.chunks.join("") : Buffer.concat(bCache.chunks, bCache.chachedLength);

				bCache.autoFlush !== null && clearTimeout(bCache.autoFlush);

				isHeaderSent === false && flushHeaders();

				for (let p = 0; p < bChunk.length;) {
					messageContainer.payload = bChunk.slice(
						p,
						p = p + CONFIG.messageCap < bChunk.length ? p + CONFIG.messageCap : bChunk.length
					);
					process.send(messageContainer);
				}

				bCache.chunks = [];
				bCache.cachedLength = 0;
				bCache.autoFlush = null;
			};

			ret.echo = function(data) {
				var toBeCached = null;

				isHeaderSent === false && flushHeaders();

				if (data === undefined || data === null || data.length === 0) {
					return;
				}

				if (Buffer.isBuffer(data) === true) {
					if (bCache.length > 0 && Buffer.isBuffer(bCache.chunks[0]) !== true) {
						ret.flush();
					}

					toBeCached = data;
				} else {
					if (bCache.length > 0 && typeof bCache.chunks[0] !== typeof "") {
						ret.flush();
					}

					toBeCached = typeof data === typeof "" ? data : data.toString();
				}

				if (bCache.cachedLength + toBeCached.length > CONFIG.messageCap) {
					ret.flush();
				}

				bCache.chunks.push(toBeCached);
				bCache.cachedLength += toBeCached.length;

				if (toBeCached.length > CONFIG.messageCap) {
					ret.flush();
				} else if (bCache.autoFlush === null) {
					bCache.autoFlush = setTimeout(ret.flush, CONFIG.messageExpiry);
				}
			};

			ret.end = function () {
				ret.flush();
				process.send(new Messenger(salt, "end"));
			};

			return ret;
		},

		respondBalancerRequest = function (request) {
			var instanceInterface = genInstanceInterface(request);

			try {
				process.chdir(require("path").dirname(fs.realpathSync(request.header.invoking)));

				if (built === null || fs.statSync(request.header.invoking).ctime.getTime() > built.builtAt.getTime()) {
					for (let cachedPath in require.cache) {
						delete require.cache[cachedPath];
					}

					built = require(request.header.invoking);
					built.builtAt = new Date();
				}

				instanceInterface.setHeader("Content-Type: text/html; charset=UTF-8");
				built.exec(instanceInterface);
			} catch (e) {
				console.log(e.stack);
				instanceInterface.setStatus(500);
				instanceInterface.echo("Error during script execution.");
				instanceInterface.end();
				cluster.worker.kill();
			}
		},

		balancerMessageReceptor = function (messenger) {
			switch (messenger.type) {
				case "header":
					request = {
						salt : messenger.salt,
						header : messenger.payload,
						body : {
							length : 0,
							chunks : []
						}
					};
					break;

				case "body":
					if (messenger.salt === request.salt) {
						let newBodyChunk = new Buffer(messenger.payload);

						request.body.chunks.push(newBodyChunk);
						request.body.length += newBodyChunk.length;
					}
					break;

				case "end":
					respondBalancerRequest(request);
					break;
				default:
					break;
			}
		};

		process.on("message", balancerMessageReceptor);
	};

	process.setuid(CONFIG.serverUid) && process.setgid(CONFIG.serverGid);

	if (cluster.isMaster === true) {
		new Nodebed().balancer();
	} else if (cluster.isWorker === true) {
		new Nodebed().worker();
	}
})();
<|endoftext|>


<commit_before>import { Util, setOptions } from 'leaflet';
import featureLayerService from '../../Services/FeatureLayerService';
import { cleanUrl, warn, setEsriAttribution } from '../../Util';
import VirtualGrid from 'leaflet-virtual-grid';
import BinarySearchIndex from 'tiny-binary-search';

export var FeatureManager = VirtualGrid.extend({
  /**
   * Options
   */

  options: {
    attribution: null,
    where: '1=1',
    fields: ['*'],
    from: false,
    to: false,
    timeField: false,
    timeFilterMode: 'server',
    simplifyFactor: 0,
    precision: 6
  },

  /**
   * Constructor
   */

  initialize: function (options) {
    VirtualGrid.prototype.initialize.call(this, options);

    options.url = cleanUrl(options.url);
    options = setOptions(this, options);

    this.service = featureLayerService(options);
    this.service.addEventParent(this);

    // use case insensitive regex to look for common fieldnames used for indexing
    if (this.options.fields[0] !== '*') {
      var oidCheck = false;
      for (var i = 0; i < this.options.fields.length; i++) {
        if (this.options.fields[i].match(/^(OBJECTID|FID|OID|ID)$/i)) {
          oidCheck = true;
        }
      }
      if (oidCheck === false) {
        warn('no known esriFieldTypeOID field detected in fields Array.  Please add an attribute field containing unique IDs to ensure the layer can be drawn correctly.');
      }
    }

    if (this.options.timeField.start && this.options.timeField.end) {
      this._startTimeIndex = new BinarySearchIndex();
      this._endTimeIndex = new BinarySearchIndex();
    } else if (this.options.timeField) {
      this._timeIndex = new BinarySearchIndex();
    }

    this._cache = {};
    this._currentSnapshot = []; // cache of what layers should be active
    this._activeRequests = 0;
  },

  /**
   * Layer Interface
   */

  onAdd: function (map) {
    // include 'Powered by Esri' in map attribution
    setEsriAttribution(map);

    this.service.metadata(function (err, metadata) {
      if (!err) {
        var supportedFormats = metadata.supportedQueryFormats;

        // Check if someone has requested that we don't use geoJSON, even if it's available
        var forceJsonFormat = false;
        if (this.service.options.isModern === false) {
          forceJsonFormat = true;
        }

        // Unless we've been told otherwise, check to see whether service can emit GeoJSON natively
        if (!forceJsonFormat && supportedFormats && supportedFormats.indexOf('geoJSON') !== -1) {
          this.service.options.isModern = true;
        }

        // add copyright text listed in service metadata
        if (!this.options.attribution && map.attributionControl && metadata.copyrightText) {
          this.options.attribution = metadata.copyrightText;
          map.attributionControl.addAttribution(this.getAttribution());
        }
      }
    }, this);

    map.on('zoomend', this._handleZoomChange, this);

    return VirtualGrid.prototype.onAdd.call(this, map);
  },

  onRemove: function (map) {
    map.off('zoomend', this._handleZoomChange, this);

    return VirtualGrid.prototype.onRemove.call(this, map);
  },

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

  /**
   * Feature Management
   */

  createCell: function (bounds, coords) {
    // dont fetch features outside the scale range defined for the layer
    if (this._visibleZoom()) {
      this._requestFeatures(bounds, coords);
    }
  },

  _requestFeatures: function (bounds, coords, callback) {
    this._activeRequests++;

    // our first active request fires loading
    if (this._activeRequests === 1) {
      this.fire('loading', {
        bounds: bounds
      }, true);
    }

    return this._buildQuery(bounds).run(function (error, featureCollection, response) {
      if (response && response.exceededTransferLimit) {
        this.fire('drawlimitexceeded');
      }

      // no error, features
      if (!error && featureCollection && featureCollection.features.length) {
        // schedule adding features until the next animation frame
        Util.requestAnimFrame(Util.bind(function () {
          this._addFeatures(featureCollection.features, coords);
          this._postProcessFeatures(bounds);
        }, this));
      }

      // no error, no features
      if (!error && featureCollection && !featureCollection.features.length) {
        this._postProcessFeatures(bounds);
      }

      if (error) {
        this._postProcessFeatures(bounds);
      }

      if (callback) {
        callback.call(this, error, featureCollection);
      }
    }, this);
  },

  _postProcessFeatures: function (bounds) {
    // deincrement the request counter now that we have processed features
    this._activeRequests--;

    // if there are no more active requests fire a load event for this view
    if (this._activeRequests <= 0) {
      this.fire('load', {
        bounds: bounds
      });
    }
  },

  _cacheKey: function (coords) {
    return coords.z + ':' + coords.x + ':' + coords.y;
  },

  _addFeatures: function (features, coords) {
    var key = this._cacheKey(coords);
    this._cache[key] = this._cache[key] || [];

    for (var i = features.length - 1; i >= 0; i--) {
      var id = features[i].id;

      if (this._currentSnapshot.indexOf(id) === -1) {
        this._currentSnapshot.push(id);
      }
      if (this._cache[key].indexOf(id) === -1) {
        this._cache[key].push(id);
      }
    }

    if (this.options.timeField) {
      this._buildTimeIndexes(features);
    }

    this.createLayers(features);
  },

  _buildQuery: function (bounds) {
    var query = this.service.query()
      .intersects(bounds)
      .where(this.options.where)
      .fields(this.options.fields)
      .precision(this.options.precision);

    if (this.options.simplifyFactor) {
      query.simplify(this._map, this.options.simplifyFactor);
    }

    if (this.options.timeFilterMode === 'server' && this.options.from && this.options.to) {
      query.between(this.options.from, this.options.to);
    }

    return query;
  },

  /**
   * Where Methods
   */

  setWhere: function (where, callback, context) {
    this.options.where = (where && where.length) ? where : '1=1';

    var oldSnapshot = [];
    var newSnapshot = [];
    var pendingRequests = 0;
    var requestError = null;
    var requestCallback = Util.bind(function (error, featureCollection) {
      if (error) {
        requestError = error;
      }

      if (featureCollection) {
        for (var i = featureCollection.features.length - 1; i >= 0; i--) {
          newSnapshot.push(featureCollection.features[i].id);
        }
      }

      pendingRequests--;

      if (pendingRequests <= 0) {
        this._currentSnapshot = newSnapshot;
        // schedule adding features for the next animation frame
        Util.requestAnimFrame(Util.bind(function () {
          this.removeLayers(oldSnapshot);
          if (zoom < this.options.maxZoom && zoom > this.options.minZoom) {
            this.addLayers(newSnapshot);
          }
          if (callback) {
            callback.call(context, requestError);
          }
        }, this));
      }
    }, this);

    for (var i = this._currentSnapshot.length - 1; i >= 0; i--) {
      oldSnapshot.push(this._currentSnapshot[i]);
    }

    for (var key in this._activeCells) {
      pendingRequests++;
      var coords = this._keyToCellCoords(key);
      var bounds = this._cellCoordsToBounds(coords);
      this._requestFeatures(bounds, key, requestCallback);
    }

    return this;
  },

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

  /**
   * Time Range Methods
   */

  getTimeRange: function () {
    return [this.options.from, this.options.to];
  },

  setTimeRange: function (from, to, callback, context) {
    var oldFrom = this.options.from;
    var oldTo = this.options.to;
    var pendingRequests = 0;
    var requestError = null;
    var requestCallback = Util.bind(function (error) {
      if (error) {
        requestError = error;
      }
      this._filterExistingFeatures(oldFrom, oldTo, from, to);

      pendingRequests--;

      if (callback && pendingRequests <= 0) {
        callback.call(context, requestError);
      }
    }, this);

    this.options.from = from;
    this.options.to = to;

    this._filterExistingFeatures(oldFrom, oldTo, from, to);

    if (this.options.timeFilterMode === 'server') {
      for (var key in this._activeCells) {
        pendingRequests++;
        var coords = this._keyToCellCoords(key);
        var bounds = this._cellCoordsToBounds(coords);
        this._requestFeatures(bounds, key, requestCallback);
      }
    }

    return this;
  },

  refresh: function () {
    for (var key in this._activeCells) {
      var coords = this._keyToCellCoords(key);
      var bounds = this._cellCoordsToBounds(coords);
      this._requestFeatures(bounds, key);
    }

    if (this.redraw) {
      this.once('load', function () {
        this.eachFeature(function (layer) {
          this._redraw(layer.feature.id);
        }, this);
      }, this);
    }
  },

  _filterExistingFeatures: function (oldFrom, oldTo, newFrom, newTo) {
    var layersToRemove = (oldFrom && oldTo) ? this._getFeaturesInTimeRange(oldFrom, oldTo) : this._currentSnapshot;
    var layersToAdd = this._getFeaturesInTimeRange(newFrom, newTo);

    if (layersToAdd.indexOf) {
      for (var i = 0; i < layersToAdd.length; i++) {
        var shouldRemoveLayer = layersToRemove.indexOf(layersToAdd[i]);
        if (shouldRemoveLayer >= 0) {
          layersToRemove.splice(shouldRemoveLayer, 1);
        }
      }
    }

    // schedule adding features until the next animation frame
    Util.requestAnimFrame(Util.bind(function () {
      this.removeLayers(layersToRemove);
      this.addLayers(layersToAdd);
    }, this));
  },

  _getFeaturesInTimeRange: function (start, end) {
    var ids = [];
    var search;

    if (this.options.timeField.start && this.options.timeField.end) {
      var startTimes = this._startTimeIndex.between(start, end);
      var endTimes = this._endTimeIndex.between(start, end);
      search = startTimes.concat(endTimes);
    } else {
      search = this._timeIndex.between(start, end);
    }

    for (var i = search.length - 1; i >= 0; i--) {
      ids.push(search[i].id);
    }

    return ids;
  },

  _buildTimeIndexes: function (geojson) {
    var i;
    var feature;
    if (this.options.timeField.start && this.options.timeField.end) {
      var startTimeEntries = [];
      var endTimeEntries = [];
      for (i = geojson.length - 1; i >= 0; i--) {
        feature = geojson[i];
        startTimeEntries.push({
          id: feature.id,
          value: new Date(feature.properties[this.options.timeField.start])
        });
        endTimeEntries.push({
          id: feature.id,
          value: new Date(feature.properties[this.options.timeField.end])
        });
      }
      this._startTimeIndex.bulkAdd(startTimeEntries);
      this._endTimeIndex.bulkAdd(endTimeEntries);
    } else {
      var timeEntries = [];
      for (i = geojson.length - 1; i >= 0; i--) {
        feature = geojson[i];
        timeEntries.push({
          id: feature.id,
          value: new Date(feature.properties[this.options.timeField])
        });
      }

      this._timeIndex.bulkAdd(timeEntries);
    }
  },

  _featureWithinTimeRange: function (feature) {
    if (!this.options.from || !this.options.to) {
      return true;
    }

    var from = +this.options.from.valueOf();
    var to = +this.options.to.valueOf();

    if (typeof this.options.timeField === 'string') {
      var date = +feature.properties[this.options.timeField];
      return (date >= from) && (date <= to);
    }

    if (this.options.timeField.start && this.options.timeField.end) {
      var startDate = +feature.properties[this.options.timeField.start];
      var endDate = +feature.properties[this.options.timeField.end];
      return ((startDate >= from) && (startDate <= to)) || ((endDate >= from) && (endDate <= to));
    }
  },

  _visibleZoom: function () {
    // check to see whether the current zoom level of the map is within the optional limit defined for the FeatureLayer
    if (!this._map) {
      return false;
    }
    var zoom = this._map.getZoom();
    if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
      return false;
    } else { return true; }
  },

  _handleZoomChange: function () {
    if (!this._visibleZoom()) {
      this.removeLayers(this._currentSnapshot);
      this._currentSnapshot = [];
    } else {
      /*
      for every cell in this._activeCells
        1. Get the cache key for the coords of the cell
        2. If this._cache[key] exists it will be an array of feature IDs.
        3. Call this.addLayers(this._cache[key]) to instruct the feature layer to add the layers back.
      */
      for (var i in this._activeCells) {
        var coords = this._activeCells[i].coords;
        var key = this._cacheKey(coords);
        if (this._cache[key]) {
          this.addLayers(this._cache[key]);
        }
      }
    }
  },

  /**
   * Service Methods
   */

  authenticate: function (token) {
    this.service.authenticate(token);
    return this;
  },

  metadata: function (callback, context) {
    this.service.metadata(callback, context);
    return this;
  },

  query: function () {
    return this.service.query();
  },

  _getMetadata: function (callback) {
    if (this._metadata) {
      var error;
      callback(error, this._metadata);
    } else {
      this.metadata(Util.bind(function (error, response) {
        this._metadata = response;
        callback(error, this._metadata);
      }, this));
    }
  },

  addFeature: function (feature, callback, context) {
    this._getMetadata(Util.bind(function (error, metadata) {
      if (error) {
        if (callback) { callback.call(this, error, null); }
        return;
      }

      this.service.addFeature(feature, Util.bind(function (error, response) {
        if (!error) {
          // assign ID from result to appropriate objectid field from service metadata
          feature.properties[metadata.objectIdField] = response.objectId;

          // we also need to update the geojson id for createLayers() to function
          feature.id = response.objectId;
          this.createLayers([feature]);
        }

        if (callback) {
          callback.call(context, error, response);
        }
      }, this));
    }, this));
  },

  updateFeature: function (feature, callback, context) {
    this.service.updateFeature(feature, function (error, response) {
      if (!error) {
        this.removeLayers([feature.id], true);
        this.createLayers([feature]);
      }

      if (callback) {
        callback.call(context, error, response);
      }
    }, this);
  },

  deleteFeature: function (id, callback, context) {
    this.service.deleteFeature(id, function (error, response) {
      if (!error && response.objectId) {
        this.removeLayers([response.objectId], true);
      }
      if (callback) {
        callback.call(context, error, response);
      }
    }, this);
  },

  deleteFeatures: function (ids, callback, context) {
    return this.service.deleteFeatures(ids, function (error, response) {
      if (!error && response.length > 0) {
        for (var i = 0; i < response.length; i++) {
          this.removeLayers([response[i].objectId], true);
        }
      }
      if (callback) {
        callback.call(context, error, response);
      }
    }, this);
  }
});
<commit_msg>Create FeatureManager.js<commit_after>import { Util, setOptions } from 'leaflet';
import featureLayerService from '../../Services/FeatureLayerService';
import { cleanUrl, warn, setEsriAttribution } from '../../Util';
import VirtualGrid from 'leaflet-virtual-grid';
import BinarySearchIndex from 'tiny-binary-search';

export var FeatureManager = VirtualGrid.extend({
  /**
   * Options
   */

  options: {
    attribution: null,
    where: '1=1',
    fields: ['*'],
    from: false,
    to: false,
    timeField: false,
    timeFilterMode: 'server',
    simplifyFactor: 0,
    precision: 6
  },

  /**
   * Constructor
   */

  initialize: function (options) {
    VirtualGrid.prototype.initialize.call(this, options);

    options.url = cleanUrl(options.url);
    options = setOptions(this, options);

    this.service = featureLayerService(options);
    this.service.addEventParent(this);

    // use case insensitive regex to look for common fieldnames used for indexing
    if (this.options.fields[0] !== '*') {
      var oidCheck = false;
      for (var i = 0; i < this.options.fields.length; i++) {
        if (this.options.fields[i].match(/^(OBJECTID|FID|OID|ID)$/i)) {
          oidCheck = true;
        }
      }
      if (oidCheck === false) {
        warn('no known esriFieldTypeOID field detected in fields Array.  Please add an attribute field containing unique IDs to ensure the layer can be drawn correctly.');
      }
    }

    if (this.options.timeField.start && this.options.timeField.end) {
      this._startTimeIndex = new BinarySearchIndex();
      this._endTimeIndex = new BinarySearchIndex();
    } else if (this.options.timeField) {
      this._timeIndex = new BinarySearchIndex();
    }

    this._cache = {};
    this._currentSnapshot = []; // cache of what layers should be active
    this._activeRequests = 0;
  },

  /**
   * Layer Interface
   */

  onAdd: function (map) {
    // include 'Powered by Esri' in map attribution
    setEsriAttribution(map);

    this.service.metadata(function (err, metadata) {
      if (!err) {
        var supportedFormats = metadata.supportedQueryFormats;

        // Check if someone has requested that we don't use geoJSON, even if it's available
        var forceJsonFormat = false;
        if (this.service.options.isModern === false) {
          forceJsonFormat = true;
        }

        // Unless we've been told otherwise, check to see whether service can emit GeoJSON natively
        if (!forceJsonFormat && supportedFormats && supportedFormats.indexOf('geoJSON') !== -1) {
          this.service.options.isModern = true;
        }

        // add copyright text listed in service metadata
        if (!this.options.attribution && map.attributionControl && metadata.copyrightText) {
          this.options.attribution = metadata.copyrightText;
          map.attributionControl.addAttribution(this.getAttribution());
        }
      }
    }, this);

    map.on('zoomend', this._handleZoomChange, this);

    return VirtualGrid.prototype.onAdd.call(this, map);
  },

  onRemove: function (map) {
    map.off('zoomend', this._handleZoomChange, this);

    return VirtualGrid.prototype.onRemove.call(this, map);
  },

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

  /**
   * Feature Management
   */

  createCell: function (bounds, coords) {
    // dont fetch features outside the scale range defined for the layer
    if (this._visibleZoom()) {
      this._requestFeatures(bounds, coords);
    }
  },

  _requestFeatures: function (bounds, coords, callback) {
    this._activeRequests++;

    // our first active request fires loading
    if (this._activeRequests === 1) {
      this.fire('loading', {
        bounds: bounds
      }, true);
    }

    return this._buildQuery(bounds).run(function (error, featureCollection, response) {
      if (response && response.exceededTransferLimit) {
        this.fire('drawlimitexceeded');
      }

      // no error, features
      if (!error && featureCollection && featureCollection.features.length) {
        // schedule adding features until the next animation frame
        Util.requestAnimFrame(Util.bind(function () {
          this._addFeatures(featureCollection.features, coords);
          this._postProcessFeatures(bounds);
        }, this));
      }

      // no error, no features
      if (!error && featureCollection && !featureCollection.features.length) {
        this._postProcessFeatures(bounds);
      }

      if (error) {
        this._postProcessFeatures(bounds);
      }

      if (callback) {
        callback.call(this, error, featureCollection);
      }
    }, this);
  },

  _postProcessFeatures: function (bounds) {
    // deincrement the request counter now that we have processed features
    this._activeRequests--;

    // if there are no more active requests fire a load event for this view
    if (this._activeRequests <= 0) {
      this.fire('load', {
        bounds: bounds
      });
    }
  },

  _cacheKey: function (coords) {
    return coords.z + ':' + coords.x + ':' + coords.y;
  },

  _addFeatures: function (features, coords) {
    var key = this._cacheKey(coords);
    this._cache[key] = this._cache[key] || [];

    for (var i = features.length - 1; i >= 0; i--) {
      var id = features[i].id;

      if (this._currentSnapshot.indexOf(id) === -1) {
        this._currentSnapshot.push(id);
      }
      if (this._cache[key].indexOf(id) === -1) {
        this._cache[key].push(id);
      }
    }

    if (this.options.timeField) {
      this._buildTimeIndexes(features);
    }

    this.createLayers(features);
  },

  _buildQuery: function (bounds) {
    var query = this.service.query()
      .intersects(bounds)
      .where(this.options.where)
      .fields(this.options.fields)
      .precision(this.options.precision);

    if (this.options.simplifyFactor) {
      query.simplify(this._map, this.options.simplifyFactor);
    }

    if (this.options.timeFilterMode === 'server' && this.options.from && this.options.to) {
      query.between(this.options.from, this.options.to);
    }

    return query;
  },

  /**
   * Where Methods
   */

  setWhere: function (where, callback, context) {
    this.options.where = (where && where.length) ? where : '1=1';

    var oldSnapshot = [];
    var newSnapshot = [];
    var pendingRequests = 0;
    var requestError = null;
    var requestCallback = Util.bind(function (error, featureCollection) {
      if (error) {
        requestError = error;
      }

      if (featureCollection) {
        for (var i = featureCollection.features.length - 1; i >= 0; i--) {
          newSnapshot.push(featureCollection.features[i].id);
        }
      }

      pendingRequests--;

      if (pendingRequests <= 0) {
        this._currentSnapshot = newSnapshot;
        // schedule adding features for the next animation frame
        Util.requestAnimFrame(Util.bind(function () {
          this.removeLayers(oldSnapshot);
		      var zoom = this._map.getZoom();
          if (zoom < this.options.maxZoom && zoom > this.options.minZoom) {
            this.addLayers(newSnapshot);
          }
          if (callback) {
            callback.call(context, requestError);
          }
        }, this));
      }
    }, this);

    for (var i = this._currentSnapshot.length - 1; i >= 0; i--) {
      oldSnapshot.push(this._currentSnapshot[i]);
    }

    for (var key in this._activeCells) {
      pendingRequests++;
      var coords = this._keyToCellCoords(key);
      var bounds = this._cellCoordsToBounds(coords);
      this._requestFeatures(bounds, key, requestCallback);
    }

    return this;
  },

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

  /**
   * Time Range Methods
   */

  getTimeRange: function () {
    return [this.options.from, this.options.to];
  },

  setTimeRange: function (from, to, callback, context) {
    var oldFrom = this.options.from;
    var oldTo = this.options.to;
    var pendingRequests = 0;
    var requestError = null;
    var requestCallback = Util.bind(function (error) {
      if (error) {
        requestError = error;
      }
      this._filterExistingFeatures(oldFrom, oldTo, from, to);

      pendingRequests--;

      if (callback && pendingRequests <= 0) {
        callback.call(context, requestError);
      }
    }, this);

    this.options.from = from;
    this.options.to = to;

    this._filterExistingFeatures(oldFrom, oldTo, from, to);

    if (this.options.timeFilterMode === 'server') {
      for (var key in this._activeCells) {
        pendingRequests++;
        var coords = this._keyToCellCoords(key);
        var bounds = this._cellCoordsToBounds(coords);
        this._requestFeatures(bounds, key, requestCallback);
      }
    }

    return this;
  },

  refresh: function () {
    for (var key in this._activeCells) {
      var coords = this._keyToCellCoords(key);
      var bounds = this._cellCoordsToBounds(coords);
      this._requestFeatures(bounds, key);
    }

    if (this.redraw) {
      this.once('load', function () {
        this.eachFeature(function (layer) {
          this._redraw(layer.feature.id);
        }, this);
      }, this);
    }
  },

  _filterExistingFeatures: function (oldFrom, oldTo, newFrom, newTo) {
    var layersToRemove = (oldFrom && oldTo) ? this._getFeaturesInTimeRange(oldFrom, oldTo) : this._currentSnapshot;
    var layersToAdd = this._getFeaturesInTimeRange(newFrom, newTo);

    if (layersToAdd.indexOf) {
      for (var i = 0; i < layersToAdd.length; i++) {
        var shouldRemoveLayer = layersToRemove.indexOf(layersToAdd[i]);
        if (shouldRemoveLayer >= 0) {
          layersToRemove.splice(shouldRemoveLayer, 1);
        }
      }
    }

    // schedule adding features until the next animation frame
    Util.requestAnimFrame(Util.bind(function () {
      this.removeLayers(layersToRemove);
      this.addLayers(layersToAdd);
    }, this));
  },

  _getFeaturesInTimeRange: function (start, end) {
    var ids = [];
    var search;

    if (this.options.timeField.start && this.options.timeField.end) {
      var startTimes = this._startTimeIndex.between(start, end);
      var endTimes = this._endTimeIndex.between(start, end);
      search = startTimes.concat(endTimes);
    } else {
      search = this._timeIndex.between(start, end);
    }

    for (var i = search.length - 1; i >= 0; i--) {
      ids.push(search[i].id);
    }

    return ids;
  },

  _buildTimeIndexes: function (geojson) {
    var i;
    var feature;
    if (this.options.timeField.start && this.options.timeField.end) {
      var startTimeEntries = [];
      var endTimeEntries = [];
      for (i = geojson.length - 1; i >= 0; i--) {
        feature = geojson[i];
        startTimeEntries.push({
          id: feature.id,
          value: new Date(feature.properties[this.options.timeField.start])
        });
        endTimeEntries.push({
          id: feature.id,
          value: new Date(feature.properties[this.options.timeField.end])
        });
      }
      this._startTimeIndex.bulkAdd(startTimeEntries);
      this._endTimeIndex.bulkAdd(endTimeEntries);
    } else {
      var timeEntries = [];
      for (i = geojson.length - 1; i >= 0; i--) {
        feature = geojson[i];
        timeEntries.push({
          id: feature.id,
          value: new Date(feature.properties[this.options.timeField])
        });
      }

      this._timeIndex.bulkAdd(timeEntries);
    }
  },

  _featureWithinTimeRange: function (feature) {
    if (!this.options.from || !this.options.to) {
      return true;
    }

    var from = +this.options.from.valueOf();
    var to = +this.options.to.valueOf();

    if (typeof this.options.timeField === 'string') {
      var date = +feature.properties[this.options.timeField];
      return (date >= from) && (date <= to);
    }

    if (this.options.timeField.start && this.options.timeField.end) {
      var startDate = +feature.properties[this.options.timeField.start];
      var endDate = +feature.properties[this.options.timeField.end];
      return ((startDate >= from) && (startDate <= to)) || ((endDate >= from) && (endDate <= to));
    }
  },

  _visibleZoom: function () {
    // check to see whether the current zoom level of the map is within the optional limit defined for the FeatureLayer
    if (!this._map) {
      return false;
    }
    var zoom = this._map.getZoom();
    if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
      return false;
    } else { return true; }
  },

  _handleZoomChange: function () {
    if (!this._visibleZoom()) {
      this.removeLayers(this._currentSnapshot);
      this._currentSnapshot = [];
    } else {
      /*
      for every cell in this._activeCells
        1. Get the cache key for the coords of the cell
        2. If this._cache[key] exists it will be an array of feature IDs.
        3. Call this.addLayers(this._cache[key]) to instruct the feature layer to add the layers back.
      */
      for (var i in this._activeCells) {
        var coords = this._activeCells[i].coords;
        var key = this._cacheKey(coords);
        if (this._cache[key]) {
          this.addLayers(this._cache[key]);
        }
      }
    }
  },

  /**
   * Service Methods
   */

  authenticate: function (token) {
    this.service.authenticate(token);
    return this;
  },

  metadata: function (callback, context) {
    this.service.metadata(callback, context);
    return this;
  },

  query: function () {
    return this.service.query();
  },

  _getMetadata: function (callback) {
    if (this._metadata) {
      var error;
      callback(error, this._metadata);
    } else {
      this.metadata(Util.bind(function (error, response) {
        this._metadata = response;
        callback(error, this._metadata);
      }, this));
    }
  },

  addFeature: function (feature, callback, context) {
    this._getMetadata(Util.bind(function (error, metadata) {
      if (error) {
        if (callback) { callback.call(this, error, null); }
        return;
      }

      this.service.addFeature(feature, Util.bind(function (error, response) {
        if (!error) {
          // assign ID from result to appropriate objectid field from service metadata
          feature.properties[metadata.objectIdField] = response.objectId;

          // we also need to update the geojson id for createLayers() to function
          feature.id = response.objectId;
          this.createLayers([feature]);
        }

        if (callback) {
          callback.call(context, error, response);
        }
      }, this));
    }, this));
  },

  updateFeature: function (feature, callback, context) {
    this.service.updateFeature(feature, function (error, response) {
      if (!error) {
        this.removeLayers([feature.id], true);
        this.createLayers([feature]);
      }

      if (callback) {
        callback.call(context, error, response);
      }
    }, this);
  },

  deleteFeature: function (id, callback, context) {
    this.service.deleteFeature(id, function (error, response) {
      if (!error && response.objectId) {
        this.removeLayers([response.objectId], true);
      }
      if (callback) {
        callback.call(context, error, response);
      }
    }, this);
  },

  deleteFeatures: function (ids, callback, context) {
    return this.service.deleteFeatures(ids, function (error, response) {
      if (!error && response.length > 0) {
        for (var i = 0; i < response.length; i++) {
          this.removeLayers([response[i].objectId], true);
        }
      }
      if (callback) {
        callback.call(context, error, response);
      }
    }, this);
  }
});
<|endoftext|>


<commit_before>// ==UserScript==
// @name         LinkAPI
// @namespace    http://tampermonkey.net/
// @version      0.7
// @description  API linking for S-NER project
// @author       Chee Yong
// @include      http://stackoverflow.com/questions/*
// @include      stackoverflow.com/questions/*
// @include      https://stackoverflow.com/questions/*
// @require      https://raw.githubusercontent.com/padolsey/findAndReplaceDOMText/master/src/findAndReplaceDOMText.js
// @require      https://code.jquery.com/jquery-2.1.4.min.js
// @require      https://code.jquery.com/ui/1.11.4/jquery-ui.min.js
// @resource     jqueryCSS https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      127.0.0.1
// @connect      128.199.217.19
// ==/UserScript==

var LinkMap = {};
var toBackEndData = {};
var myNodes = [];
var entityList = {};
var linkError = false;

/* Identify API component */
function identifyAPI() {
    var fullText = '';

    // extract discussion text, question title and code snippets
    [].forEach.call(
        document.querySelectorAll('.post-text p, #question-header .question-hyperlink, .prettyprinted code'), //include code snippet
        // document.querySelectorAll('.post-text p, #question-header .question-hyperlink'), //exclude code snippet
        function(el) {
            fullText = fullText.concat(' ').concat(el.textContent);
            myNodes.push(el);
        }
    );

    // manual mode (custom list of recognized APIs)

    var entityJSON = ['astype'];
    extractEntity(entityJSON);

    // auto mode (access NER model)
    /*
    GM_xmlhttpRequest({
        method: "POST",
        // url: "http://127.0.0.1:8000/extractentity/", //localhost
        url: "http://128.199.217.19/entity_recognition/", //external server
        data: fullText,
        headers: {
            "Content-Type": "application/json; charset=utf-8"
        },
        onload: function(response) {
            if (response) {
                try {
                    var entityJSON = JSON.parse(response.responseText);
                    extractEntity(entityJSON);
                } catch(e) {
                    console.log('Something went wrong with entity recognition :-(');
                    return;
                }
            }
        },
        onerror: function(response) {
            console.log("Cannot connect to server! (entity recognition)");

        },
        ontimeout: function(response) {
            console.log("Connection to server timed out! (entity recognition)");
        },
        onabort: function(reponse) {
            console.log("Connection to server aborted! (entity recognition)");
        }
    });
    */
}

/* Extract API in web page */
function extractEntity(entityJSON) {
    var k = 0;
    var entityIndex = [];
    var numOfEntities = entityJSON.length;

    console.log(entityJSON);

    // store list of recognized APIs in term context obj
    entityJSON.forEach(function(e) {
        entityList[k] = e;
        k++;
    });
    toBackEndData["entityList"] = entityList;

    // Inject span element to recognized APIs
    myNodes.forEach(function(e, index) {
        var startIdx = 0;
        var temp = 1;

        while (e.textContent.substr(startIdx).indexOf(entityJSON[0]) != -1 && temp) {
            temp = 0;
            findAndReplaceDOMText(e, {
                //find: RegExp('\\b' + entityJSON[0] + '\\b'),
                find: RegExp(entityJSON[0]),
                forceContext: function(el) {
                    return el.matches('.api');
                },
                replace: function(portion, match) {
                    var el = document.createElement('span');
                    el.classList.add('api');
                    el.setAttribute("id", numOfEntities-entityJSON.length);
                    el.style.backgroundColor = 'lightgreen';
                    el.innerHTML = portion.text;
                    startIdx = match.endIndex;
                    temp = 1;
                    return el;
                },
            });
            entityIndex.push(index);
            entityJSON.shift();
        }
    });

    // store list of APIs' positional index in term context obj
    toBackEndData["entityIndex"] = entityIndex;

    // trigger link API component
    entityLinking();

    // customize tooltip styles and options
    $(".api").tooltip({
        items: 'span.api',
        show: false, //show immediately
        track: false,
        content: 'Sorry, entity linking is currently not available',
        html: true,
        tooltipClass: "custom-tooltip-styling",
        position: {
            my: "center top+5",
            at: "bottom"
        },
        open: function(event, ui) {
            ui.tooltip.css("max-width", "600px");
            ui.tooltip.css("max-height", "250px");
            ui.tooltip.css("margin", "auto");
            ui.tooltip.css("font-size", "1em");
            ui.tooltip.css("padding", "5px");
            ui.tooltip.css("background-color", "#f0f0f0");
        },
        close: function(event, ui) {
            ui.tooltip.hover(function() {
                    $(this).stop(true).fadeTo(200, 1);
                },
                function() {
                    $(this).fadeOut('200', function() {
                        $(this).remove();
                    });
                });
        }
    });
}

/* Link API component */
function entityLinking() {
    var tagList = [];
    var allHrefs = [];
    var allTypes = [];
    var typeIndex = [];
    var fullTextSnippet = '';

    // store discussion text, question title and code snippets in term context obj
    [].forEach.call(
        document.querySelectorAll('.post-text p, #question-header .question-hyperlink, .prettyprinted code'),
        function(el) {
            fullTextSnippet = fullTextSnippet.concat(' ').concat(el.textContent);
        }
    );
    toBackEndData["texts"] = fullTextSnippet;

    // store question tags in term context obj
    [].forEach.call(
        document.querySelectorAll('#question .js-gps-track'),
        function(el) {
            tagList.push(el.textContent);
        }
    );
    toBackEndData["tags"] = tagList;

    // store list of hrefs in term context obj
    [].forEach.call(
        document.querySelectorAll('.post-text a,.comment-copy a'),
        function(el) {
            // allHrefs.push({'name':el.textContent, 'url':el.href});
            allHrefs.push(el.href);
        }
    );
    toBackEndData["hrefs"] = allHrefs;

    // store question title in term context obj
    var title = document.querySelector('#question-header');
    toBackEndData["title"] = title.textContent;

    // store list of types/classes in term context obj
    myNodes.forEach(function(e, index) {
        [].forEach.call(
            e.querySelectorAll('.typ'),
            function(el) {
                allTypes.push(el.textContent);
                typeIndex.push(index);
            }
        );
    });
    toBackEndData["class"] = allTypes;
    toBackEndData["classIndex"] = typeIndex;

    console.log(toBackEndData);

    // retrieve list of linked code elements
    GM_xmlhttpRequest({
        method: "POST",
        // url: "http://127.0.0.1:8000/linkentity/", //localhost
        url: "http://128.199.217.19/entity_linking/", //external server
        data: JSON.stringify(toBackEndData),
        headers: {
            "Content-Type": "application/json; charset=utf-8"
        },
        onload: UpdateTooltip,
        onerror: function(response) {
            console.log("Cannot connect to server! (entity linking)");
            linkError = true;
        },
        ontimeout: function(response) {
            console.log("Connection to server timed out! (entity linking)");
            linkError = true;
        },
        onabort: function(reponse) {
            console.log("Connection to server aborted! (entity linking)");
            linkError = true;
        }
    });
}

/* Update tooltip component */
function UpdateTooltip(response) {
    if (response) {
        try {
            linkJSON = JSON.parse(response.responseText);
            console.log(linkJSON);
        } catch(e) {
            console.log("Something went wrong with entity linking :-(");
            linkError = true;
            return;
        }
    }

    NumLink = Object.keys(linkJSON).length;

    // dynamically update tooltip content for each recognized API
    for (var i = 0; i < NumLink; i++) {
        if (linkJSON[i].length == 0) {
            contentStr = '
No reference found
';
            LinkMap[i] = contentStr;
        } else if (linkJSON[i].length == 1) {
            contentStr = "<!DOCTYPE html>";
            contentStr += "

<colgroup><col width='40'><col width='180'><col width='60'><col width='60'></colgroup>
Rank	Reference Link	Type	Library
";
            contentStr += "
" + 1 + "	" + linkJSON[i][0].name + "	" + linkJSON[i][0].type + "	" + linkJSON[i][0].lib + "	";
            contentStr += "

";
            LinkMap[i] = contentStr;
        } else {
            //sort based on score + tfidf
            linkJSON[i].sort(function(a, b) {
                return (b.score - a.score) + (b.tfidf - a.tfidf);
            });
            contentStr = "<!DOCTYPE html>";
            contentStr += "

<colgroup><col width='35'><col width='180'><col width='50'><col width='60'><col width='30'><col width='50'><col width='40'><col width='40'><col width='40'><col width='40'></colgroup>
Rank	Reference Link	Type	Library	URL	Match	Tag	Title	Class	tf-idf
";
            for (var j = 0; j < linkJSON[i].length; j++) {
                contentStr += "
" + (j + 1) + "	" + ((linkJSON[i][j].api_class) ? linkJSON[i][j].api_class : "") + '.' + linkJSON[i][j].name + "	" + linkJSON[i][j].type + "	" + linkJSON[i][j].lib + "	" + ((linkJSON[i][j].mark[0]) ? "✔" : "")  + "	" + ((linkJSON[i][j].mark[1]) ? "✔" : "") + "	" + ((linkJSON[i][j].mark[2]) ? "✔" : "") + "	" + ((linkJSON[i][j].mark[3]) ? "✔" : "") + "	" + ((linkJSON[i][j].mark[4]) ? "✔" : "") + "	" + Math.round(linkJSON[i][j].tfidf * 1000) / 1000 + "	";
            }
            contentStr += "

";
            LinkMap[i] = contentStr;
        }
    }
}

/* Triggering function when a page is loaded */
$(document).ready(function() {
    // incorporate jQuery UI CSS
    var newjqueryCSS = GM_getResourceText("jqueryCSS");
    GM_addStyle(newjqueryCSS);

    // starting of component execution
    identifyAPI();

    // Add mouseover event listener
    $(document.body).on("mouseover", ".api", function() {
        $(this).tooltip("open");
        $(this).tooltip("option", "content", function() {
            var content = LinkMap[this.id];
            if (content)
                return content;
            else if (!linkError)
                return "Loading... (Will ready in few seconds)";
            else
                return "Sorry, entity linking is currently not available";
        });
    });

    // Add mouseleave event listener
    $(document.body).on("mouseleave", ".api", function() {
        $(this).tooltip("close");
    });
});<commit_msg>Update LinkAPI userscript (v0.8)<commit_after>// ==UserScript==
// @name         LinkAPI
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  API linking for S-NER project
// @author       Chee Yong
// @include     http://stackoverflow.com/*
// @include     stackoverflow.com/*
// @include     https://stackoverflow.com/*
// @require     https://raw.githubusercontent.com/padolsey/findAndReplaceDOMText/master/src/findAndReplaceDOMText.js
// @require     https://code.jquery.com/jquery-2.1.4.min.js
// @require     https://code.jquery.com/ui/1.11.4/jquery-ui.min.js
// @resource    jqueryCSS https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css
// @grant GM_getResourceText
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect 127.0.0.1
// @connect 128.199.217.19
// ==/UserScript==

var LinkMap = {};
var toBackEndData = {};
var myNodes = [];
var entityList = {};
var linkError = false;
var model_mode = 1; // 0:CRF++, 1:CRFsuite

/* Identify API component */
function identifyAPI() {
    var toNERData = {};
    var fullText = '';

    // extract discussion text, question title and code snippets
    [].forEach.call(
        document.querySelectorAll('.post-text p, #question-header .question-hyperlink, .prettyprinted code'), //include code snippet
        // document.querySelectorAll('.post-text p, #question-header .question-hyperlink'), //exclude code snippet
        function(el) {
            fullText = fullText.concat(' ').concat(el.textContent);
            myNodes.push(el);
        }
    );

    // manual mode (custom list of recognized APIs)
    /*
    var entityJSON = ['eigvals'];
    extractEntity(entityJSON);
    */

    // auto mode (access NER model)
    toNERData['fullText'] = fullText;
    toNERData['mode'] = model_mode;
    GM_xmlhttpRequest({
        method: "POST",
        url: "http://127.0.0.1:8000/extractentity/", //localhost
        // url: "http://128.199.217.19/entity_recognition/", //external server
        data: JSON.stringify(toNERData),
        headers: {
            "Content-Type": "application/json; charset=utf-8"
        },
        onload: function(response) {
            if (response) {
                try {
                    var entityJSON = JSON.parse(response.responseText);
                    extractEntity(entityJSON);
                } catch(e) {
                    console.log(e);
                    console.log('Something went wrong with entity recognition :-(');
                    return;
                }
            }
        },
        onerror: function(response) {
            console.log("Cannot connect to server! (entity recognition)");

        },
        ontimeout: function(response) {
            console.log("Connection to server timed out! (entity recognition)");
        },
        onabort: function(reponse) {
            console.log("Connection to server aborted! (entity recognition)");
        }
    });
}

/* Extract API in web page */
function extractEntity(entityJSON) {
    var k = 0;
    var entityIndex = [];

    for (var i = entityJSON.length - 1; i >= 0; i--) {
        if (/^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]$/.test(entityJSON[i][0]) || entityJSON[i][0] == 'in') {
            entityJSON.splice(i, 1);
        }
    }
    console.log(entityJSON);

    var numOfEntities = entityJSON.length;

    // Inject span element to recognized APIs
    myNodes.forEach(function(e, index) {
        var startIdx = 0;
        var remainingText = '';
        var found = 1;
        var retry = 0;
        var ori_e = e.textContent;
        var check_next = 0;

        while (entityJSON[0] && e.textContent.substr(startIdx).indexOf(entityJSON[0][0]) != -1 && found) {
            found = 0;
            findAndReplaceDOMText(e, {
                find: RegExp(entityJSON[0][0]),
                forceContext: function(el) {
                    return el.matches('.api');
                },
                replace: function(portion, match) {
                    check_next = (ori_e.substr(match.endIndex).trim().lastIndexOf(entityJSON[0][1], 0) === 0);
                    remainingText = ori_e.substr(match.endIndex);
                    if(check_next || !remainingText) {
                        var el = document.createElement('span');
                        el.classList.add('api');
                        el.setAttribute("id", numOfEntities-entityJSON.length);
                        el.style.backgroundColor = 'lightgreen';
                        el.innerHTML = portion.text;
                        startIdx = match.endIndex;
                        retry = 0;
                        found = 1;
                        return el;
                    } else if (retry > 3) {
                        var el = document.createElement('non');
                        el.classList.add('api');
                        el.innerHTML = portion.text;
                        startIdx = match.endIndex;
                        retry = 0;
                        found = 1;
                        return el;
                    } else {
                        retry++;
                        return match[0];
                    }
                },
            });

            if(check_next || !remainingText) {
                entityIndex.push(index);
                entityList[k] = entityJSON[0][0];
                k++;
                entityJSON.shift();
            }
        }
    });

    // store list of recognized APIs in term context obj
    toBackEndData["entityList"] = entityList;

    // store list of APIs' positional index in term context obj
    toBackEndData["entityIndex"] = entityIndex;

    // trigger link API component
    entityLinking();

    // customize tooltip styles and options
    $(".api").tooltip({
        items: 'span.api',
        show: false, //show immediately
        track: false,
        content: 'Sorry, entity linking is currently not available',
        html: true,
        tooltipClass: "custom-tooltip-styling",
        position: {
            my: "center top+5",
            at: "bottom"
        },
        open: function(event, ui) {
            ui.tooltip.css("max-width", "600px");
            ui.tooltip.css("max-height", "250px");
            ui.tooltip.css("margin", "auto");
            ui.tooltip.css("font-size", "1em");
            ui.tooltip.css("padding", "5px");
            ui.tooltip.css("background-color", "#f0f0f0");
        },
        close: function(event, ui) {
            ui.tooltip.hover(function() {
                    $(this).stop(true).fadeTo(200, 1);
                },
                function() {
                    $(this).fadeOut('200', function() {
                        $(this).remove();
                    });
                });
        }
    });
}

/* Link API component */
function entityLinking() {
    var tagList = [];
    var allHrefs = [];
    var allTypes = [];
    var typeIndex = [];
    var fullTextSnippet = '';

    // store discussion text, question title and code snippets in term context obj
    [].forEach.call(
        document.querySelectorAll('.post-text p, #question-header .question-hyperlink, .prettyprinted code'),
        function(el) {
            fullTextSnippet = fullTextSnippet.concat(' ').concat(el.textContent);
        }
    );
    toBackEndData["texts"] = fullTextSnippet;

    // store question tags in term context obj
    [].forEach.call(
        document.querySelectorAll('#question .js-gps-track'),
        function(el) {
            tagList.push(el.textContent);
        }
    );
    toBackEndData["tags"] = tagList;

    // store list of hrefs in term context obj
    [].forEach.call(
        document.querySelectorAll('.post-text a,.comment-copy a'),
        function(el) {
            allHrefs.push(el.href);
        }
    );
    toBackEndData["hrefs"] = allHrefs;

    // store question title in term context obj
    var title = document.querySelector('#question-header');
    toBackEndData["title"] = title.textContent;

    // store list of types/classes in term context obj
    myNodes.forEach(function(e, index) {
        [].forEach.call(
            e.querySelectorAll('.typ'),
            function(el) {
                allTypes.push(el.textContent);
                typeIndex.push(index);
            }
        );
    });
    toBackEndData["class"] = allTypes;
    toBackEndData["classIndex"] = typeIndex;

    console.log(toBackEndData);

    // retrieve list of linked code elements
    GM_xmlhttpRequest({
        method: "POST",
        url: "http://127.0.0.1:8000/linkentity/", //localhost
        // url: "http://128.199.217.19/entity_linking/", //external server
        data: JSON.stringify(toBackEndData),
        headers: {
            "Content-Type": "application/json; charset=utf-8"
        },
        onload: UpdateTooltip,
        onerror: function(response) {
            console.log("Cannot connect to server! (entity linking)");
            linkError = true;
        },
        ontimeout: function(response) {
            console.log("Connection to server timed out! (entity linking)");
            linkError = true;
        },
        onabort: function(reponse) {
            console.log("Connection to server aborted! (entity linking)");
            linkError = true;
        }
    });
}

/* Update tooltip component */
function UpdateTooltip(response) {
    if (response) {
        try {
            linkJSON = JSON.parse(response.responseText);
            console.log(linkJSON);
        } catch(e) {
            console.log(e);
            console.log("Something went wrong with entity linking :-(");
            linkError = true;
            return;
        }
    }

    NumLink = Object.keys(linkJSON).length;

    // dynamically update tooltip content for each recognized API
    for (var i = 0; i < NumLink; i++) {
        if (linkJSON[i].length == 0) {
            contentStr = '
No reference found
';
            LinkMap[i] = contentStr;
        } else if (linkJSON[i].length == 1) {
            contentStr = "<!DOCTYPE html>";
            contentStr += "

<colgroup><col width='40'><col width='200'><col width='60'><col width='60'></colgroup>
Rank	Reference Link	Type	Library
";
            contentStr += "
" + 1 + "	" + linkJSON[i][0].name + "	" + linkJSON[i][0].type + "	" + linkJSON[i][0].lib + "	";
            contentStr += "

";
            LinkMap[i] = contentStr;
        } else {
            //sort based on score + tfidf
            linkJSON[i].sort(function(a, b) {
                return (b.score - a.score) + (b.tfidf - a.tfidf);
            });
            contentStr = "<!DOCTYPE html>";
            contentStr += "

<colgroup><col width='35'><col width='200'><col width='50'><col width='60'><col width='30'><col width='50'><col width='40'><col width='30'><col width='40'><col width='40'></colgroup>
Rank	Reference Link	Type	Library	URL	Match	Tag	Title	Class	tf-idf
";
            for (var j = 0; j < linkJSON[i].length; j++) {
                contentStr += "
" + (j + 1) + "	" + ((linkJSON[i][j].api_class != 'none') ? linkJSON[i][j].api_class + "." : "") + linkJSON[i][j].name + "	" + linkJSON[i][j].type + "	" + linkJSON[i][j].lib + "	" + ((linkJSON[i][j].mark[0]) ? "✔" : "")  + "	" + ((linkJSON[i][j].mark[1]) ? "✔" : "") + "	" + ((linkJSON[i][j].mark[2]) ? "✔" : "") + "	" + ((linkJSON[i][j].mark[3]) ? "✔" : "") + "	" + ((linkJSON[i][j].mark[4]) ? "✔" : "") + "	" + Math.round(linkJSON[i][j].tfidf * 1000) / 1000 + "	";
            }
            contentStr += "

";
            LinkMap[i] = contentStr;
        }
    }
}

/* Triggering function when a page is loaded */
$(document).ready(function() {
    // incorporate jQuery UI CSS
    var newjqueryCSS = GM_getResourceText("jqueryCSS");
    GM_addStyle(newjqueryCSS);

    // starting of component execution
    identifyAPI();

    // Add mouseover event listener
    $(document.body).on("mouseover", ".api", function() {
        $(this).tooltip("open");
        $(this).tooltip("option", "content", function() {
            var content = LinkMap[this.id];
            if (content)
                return content;
            else if (!linkError)
                return "Loading... (Will ready in few seconds)";
            else
                return "Sorry, entity linking is currently not available";
        });
    });

    // Add mouseleave event listener
    $(document.body).on("mouseleave", ".api", function() {
        $(this).tooltip("close");
    });
});<|endoftext|>


<commit_before>import React from "react";
import {classNames, hyperscript as h} from "neutrine-utils";
import {omit}, from "kofi";

import "siimple/scss/elements/_spinner.scss";

//Spinner class
export default class Spinner extends React.Component {
    render() {
        let props = omit(this.props, ["children", "className", "color", "size"]);
        let classList = ["siimple-spinner"];
        //Check the color attribute
        if(typeof this.props.color === "string") {
            classList.push("siimple-spinner--" + this.props.color);
        }
        //Check the size attribute
        if (typeof this.props.size === "string") {
            classList.push("siimple-spinner--" + this.props.size);
        }
        props.className = classNames(classList, this.props.className);
        //Return the spinner element
        return h("div", props, null);
    }
}

//Spinner default props
Spinner.defaultProps = {
    color: "blue",
    size: null
};

<commit_msg>Fixed bug<commit_after>import React from "react";
import {classNames, hyperscript as h} from "neutrine-utils";
import {omit} from "kofi";

import "siimple/scss/elements/_spinner.scss";

//Spinner class
export default class Spinner extends React.Component {
    render() {
        let props = omit(this.props, ["children", "className", "color", "size"]);
        let classList = ["siimple-spinner"];
        //Check the color attribute
        if(typeof this.props.color === "string") {
            classList.push("siimple-spinner--" + this.props.color);
        }
        //Check the size attribute
        if (typeof this.props.size === "string") {
            classList.push("siimple-spinner--" + this.props.size);
        }
        props.className = classNames(classList, this.props.className);
        //Return the spinner element
        return h("div", props, null);
    }
}

//Spinner default props
Spinner.defaultProps = {
    color: "blue",
    size: null
};

<|endoftext|>