<commit_before>/**
 * JSON Feed middleware.
 *
 * This middleware generates a feed in JSON Feed format, which can be used to
 * syndicate content to users and other websites.
 *
 * JSON Feed is similar to RSS or Atom, but serialized to JSON rather than XML.
 * It was developed by [Brent Simmons](http://inessential.com/2017/05/17/json_feed)
 * and [Manton Reece](http://www.manton.org/2017/05/json-feed.html).
 *
 * Whether or not a JSON serialization is necessary for web syndication is
 * debatable, and as Dave Winer [notes](http://scripting.com/2017/05/20.html#a010552),
 * this is probably not the most relevant point to focus on.  That being said,
 * if a JSON-based format allows syndication to gain further adoption, that has
 * benefits.
 *
 * Due to the fact that there are multiple, incompatible JSON-based feed
 * formats without a [specific](https://github.com/brentsimmons/JSONFeed/issues/22)
 * MIME type registered in [mime-db](https://github.com/jshttp/mime-db), there
 * is ambiguity about what file extension to use.  The recommendation by the
 * developers of Kerouac.js is to use `json`, which is the type used by the
 * WordPress [plugin](https://wordpress.org/plugins/jsonfeed/) which generates
 * JSON feeds.
 *
 * References:
 *   - [JSON Feed Version 1](https://jsonfeed.org/version/1)
 *   - [JSONFeed](https://github.com/brentsimmons/JSONFeed)
 *   - https://news.ycombinator.com/item?id=14360729
 */
exports = module.exports = function() {
  var linkto = require('../../utils').linkto;

  return function feed(page, next) {
    var site = page.site
      , pages, post, item, val, i, len;

    var json = {};
    json.version = 'https://jsonfeed.org/version/1';

    val = site.get('title');
    if (val) {
      json.title = val;
    }
    val = site.get('description');
    if (val) {
      json.description = val;
    }

    pages = site.pages.filter(function(p) {
      return p.index == true;
    });
    if (pages.length) {
      json.home_page_url = linkto(pages[0], page);
    }

    json.feed_url = linkto(page, page);

    // TODO: Get the blog index and set as `link` element

    pages = site.pages.filter(function(p) {
      // Filter the set of pages to just those that are blog posts, as indicated
      // by the `post` property.
      return (p.meta && p.meta.post == true);
    });

    json.items = [];
    for (i = 0, len = pages.length; i < len; i++) {
      post = pages[i];

      item = {};
      item.id = post.canonicalURL || post.absoluteURL || post.url;
      if (post.locals.title) { item.title = post.locals.title; }
      if (post.locals.tags) { item.tags = post.locals.tags; }

      item.url = linkto(post, page);
      if (post.locals.publishedAt) { item.date_published = post.locals.publishedAt.toISOString().substring(0,19)+'Z'; }
      if (post.locals.modifiedAt) { item.date_modified = post.locals.modifiedAt.toISOString().substring(0,19)+'Z'; }

      // TODO: convert content to text, stripping links, headings, etc
      item.content_text = post.content;
      // TODO: content_html

      json.items.push(item);
    };

    page.write(JSON.stringify(json, null, 2));
    page.end();
  };
};

exports['@require'] = [];
<commit_msg>Clean up JSON feed.<commit_after>/**
 * JSON Feed middleware.
 *
 * This middleware generates a feed in JSON Feed format, which can be used to
 * syndicate content to users and other websites.
 *
 * JSON Feed is similar to RSS or Atom, but serialized to JSON rather than XML.
 * It was developed by [Brent Simmons](http://inessential.com/2017/05/17/json_feed)
 * and [Manton Reece](http://www.manton.org/2017/05/json-feed.html).
 *
 * Whether or not a JSON serialization is necessary for web syndication is
 * debatable, and as Dave Winer [notes](http://scripting.com/2017/05/20.html#a010552),
 * this is probably not the most relevant point to focus on.  That being said,
 * if a JSON-based format allows syndication to gain further adoption, that has
 * benefits.
 *
 * Due to the fact that there are multiple, incompatible JSON-based feed
 * formats without a [specific](https://github.com/brentsimmons/JSONFeed/issues/22)
 * MIME type registered in [mime-db](https://github.com/jshttp/mime-db), there
 * is ambiguity about what file extension to use.  The recommendation by the
 * developers of Kerouac.js is to use `json`, which is the type used by the
 * WordPress [plugin](https://wordpress.org/plugins/jsonfeed/) which generates
 * JSON feeds.
 *
 * References:
 *   - [JSON Feed Version 1](https://jsonfeed.org/version/1)
 *   - [JSONFeed](https://github.com/brentsimmons/JSONFeed)
 *   - https://news.ycombinator.com/item?id=14360729
 */
exports = module.exports = function() {
  var linkto = require('../../utils').linkto;

  return function jsonFeed(feed, next) {
    var site = feed.site
      , home, posts, post, item, val, i, len;

    home = site.pages.filter(function(p) {
      return (p.meta && p.meta.home == true);
    });
    if (home.length) { home = home[0]; }
    else { home = undefined; }

    posts = site.pages.filter(function(p) {
      // Filter the set of pages to just those that are blog posts.
      return (p.meta && p.meta.post == true);
    });

    var json = {};
    json.version = 'https://jsonfeed.org/version/1';

    val = site.get('title');
    if (val) {
      json.title = val;
    }
    val = site.get('description');
    if (val) {
      json.description = val;
    }

    if (home) {
      json.home_page_url = linkto(home, feed);
    }

    json.feed_url = linkto(feed, feed);

    // TODO: Get the blog index and set as `link` element

    json.items = [];
    for (i = 0, len = posts.length; i < len; i++) {
      post = posts[i];

      item = {};
      item.id = post.canonicalURL || post.absoluteURL || post.url;
      if (post.locals.title) { item.title = post.locals.title; }
      if (post.locals.tags) { item.tags = post.locals.tags; }

      item.url = linkto(post, feed);
      if (post.locals.publishedAt) { item.date_published = post.locals.publishedAt.toISOString().substring(0,19)+'Z'; }
      if (post.locals.modifiedAt) { item.date_modified = post.locals.modifiedAt.toISOString().substring(0,19)+'Z'; }

      // TODO: convert content to text, stripping links, headings, etc
      item.content_text = post.content;
      // TODO: content_html

      json.items.push(item);
    };

    feed.write(JSON.stringify(json, null, 2));
    feed.end();
  };
};

exports['@require'] = [];
<|endoftext|>


<commit_before>var fs = require('fs');
var crypto = require('crypto');
var Step = require('step');
var path = require('path');
var url = require('url');
var mapnik = require('mapnik');
var qs = require('querystring');
var Pool = require('generic-pool').Pool;
var LockingCache = require('./lockingcache');
var sm = new (require('sphericalmercator'));

var cache = {};

// Increase number of threads to 1.5x the number of logical CPUs.
var threads = Math.ceil(Math.max(4, require('os').cpus().length * 1.5));
require('eio').setMinParallel(threads);

exports = module.exports = MapnikSource;

require('util').inherits(MapnikSource, require('events').EventEmitter);
function MapnikSource(uri, callback) {
    uri = this._normalizeURI(uri);
    var key = url.format(uri);

    if (uri.protocol && uri.protocol !== 'mapnik:') {
        throw new Error('Only the mapnik protocol is supported');
    }

    if (!cache[key]) {
        cache[key] = this;
        this._open(uri);
    }

    var source = cache[key];
    source.setMaxListeners(0);
    if (!source.open) {
        source.once('open', function(err, source) {
            if (err) cache[key] = false;
            callback(err, source);
        });
    } else {
        callback(null, source);
    }
    return undefined;
}

MapnikSource.prototype._normalizeURI = function(uri) {
    if (typeof uri === 'string') uri = url.parse(uri, true);
    if (uri.hostname === '.' || uri.hostname == '..') {
        uri.pathname = uri.hostname + uri.pathname;
        delete uri.hostname;
        delete uri.host;
    }
    uri.pathname = path.resolve(uri.pathname);
    uri.query = uri.query || {};
    if (typeof uri.query === 'string') uri.query = qs.parse(uri.query);
    if (!uri.query.cache) uri.query.cache = '/tmp/';
    if (!uri.query.base) uri.query.base = '';
    if (!uri.query.metatile) uri.query.metatile = 2;
    if (!uri.query.resolution) uri.query.resolution = 4;
    if (!uri.query.bufferSize) uri.query.bufferSize = 128;
    return uri;
};

// Finds all XML files in the filepath and returns their tilesource URI.
MapnikSource.list = function(filepath, callback) {
    filepath = path.resolve(filepath);
    fs.readdir(filepath, function(err, files) {
        if (err) return callback(err);
        for (var result = {}, i = 0; i < files.length; i++) {
            var name = files[i].match(/^([\w-]+)\.xml$/);
            if (name) result[name[1]] = 'mapnik://' + path.join(filepath, name[0]);
        }
        return callback(null, result);
    });
};

// Finds an XML file with the given ID in the filepath and returns a
// tilesource URI.
MapnikSource.findID = function(filepath, id, callback) {
    filepath = path.resolve(filepath);
    var file = path.join(filepath, id + '.xml');
    fs.stat(file, function(err, stats) {
        if (err) return callback(err);
        else return callback(null, 'mapnik://' + file);
    });
};

MapnikSource.prototype._open = function(uri) {
    var source = this;
    function error(err) {
        process.nextTick(function() {
            source.emit('open', err);
        });
    }

    this._stats = {
        render: 0,          // # of times a render is requested from mapnik
        total: 0,           // # of tiles returned from source
        encoded: 0,         // # of tiles encoded
        solid: 0,           // # of tiles isSolid
        solidPainted: 0     // # of tiles isSolid && painted
    };
    this._cache = uri.query.cache;
    this._base = uri.query.base;
    uri.query.metatile = +uri.query.metatile;
    uri.query.resolution = +uri.query.resolution;
    uri.query.bufferSize = +uri.query.bufferSize;
    this._uri = uri;

    // Public API to announce how we're metatiling.
    this.metatile = uri.query.metatile;
    this.bufferSize = uri.query.bufferSize;

    // Initialize this map. This wraps `localize()` and calls `create()`
    // afterwards to actually create a new Mapnik map object.
    Step(function() {
        source._loadXML(this);
    }, function(err, xml, mml) {
        if (err) return error(err);
        source._xml = xml;
        source._mml = mml;
        source._createMetatileCache();
        source._createPool(xml, this);
    }, function(err) {
        if (err) return error(err);
        source._populateInfo(this);
    }, function(err) {
        if (err) return error(err);

        source.open = true;
        source.emit('open', null, source);
    });
};

MapnikSource.prototype._close = function() {
    if (this._cache) this._cache.clear();
    // Note: this doesn't clear timeouts in node-pool.
    // See https://github.com/coopernurse/node-pool/issues/17
    if (this._pool) {
        this._pool.drain();
        this._pool.destroyAllNow();
    }
};

MapnikSource.registerProtocols = function(tilelive) {
    tilelive.protocols['mapnik:'] = MapnikSource;
};

// Loads the XML file from the specified path. Calls `callback` when the mapfile
// can be expected to be in `mapfile`. If this isn't successful, `callback` gets
// an error as its first argument.
MapnikSource.prototype._loadXML = function(callback) {
    var source = this;
    var xml, mml;
    Step(function() {
        if (source._uri.xml) {
            // This is a string-based map file. Pass it on literally.
            source._base = path.resolve(path.dirname(source._uri.pathname));
            this(null, source._uri.xml);
        } else {
            // This is a file-based map file. Load it from the file.
            source._base = path.resolve(path.dirname(source._uri.pathname));
            fs.readFile(path.resolve(source._uri.pathname), 'utf8', this);
        }
    }, function(err, data) {
        if (err) return callback(err);
        xml = data;

        if (source._uri.mml) {
            // This is an MML object. Pass it on literally.
            this(null, source._uri.mml);
        } else {
            var p = path.resolve(source._uri.pathname);
            var filepath = path.join(
                path.dirname(p),
                path.basename(p, path.extname(p))) + '.mml';
            fs.readFile(filepath, 'utf8', this);
        }
    }, function(err, data) {
        // Only care about the error if it's something
        // different from "file not found".
        if (err && err.code !== 'ENOENT') return callback(err);

        if (data) {
            // An accompanying MML file exists.
            if (typeof data === 'string') {
                try {
                    mml = JSON.parse(data);
                } catch (err) {
                    return callback(err);
                }
            } else {
                mml = data;
            }
        }

        callback(null, xml, mml);
    });
};

// Create a new mapnik map object at `this.mapnik`. Requires that the mapfile
// be localized with `this.localize()`. This can be called in repetition because
// it won't recreate `this.mapnik`.
MapnikSource.prototype._createPool = function(xml, callback) {
    var source = this;
    if (!this._pool) this._pool = Pool({
        create: function(callback) {
            var map = new mapnik.Map(256, 256);
            map.bufferSize = source._uri.query.bufferSize;
            map.fromString(xml, {
                strict: false,
                base: source._base + '/'
            }, function(err, map) {
                if (err) return callback(err);
                // Wrap zoomAll() with a try/catch and don't bail on error.
                // Certain layers/projections seem to cause an error here that
                // need not be fatal.
                try {
                    map.zoomAll();
                } catch(e) {};
                callback(null, map);
            });
        },
        destroy: function(map) {
            map.clear();
            delete map;
        },
        // @TODO: need a smarter way to scale this. More
        // maps in pool seems better for PostGIS.
        max: require('os').cpus().length
    });
    callback(null);
};

MapnikSource.prototype._populateInfo = function(callback) {
    var source = this;
    var info = {
        name: path.basename(this._uri.pathname, path.extname(this._uri.pathname)),

        // We can't determine a sensible minzoom/maxzoom
        minzoom: 0,
        maxzoom: 22
    };

    info.id = info.name;

    if (this._mml && this._mml.center) info.center = this._mml.center;
    if (this._mml && this._mml.bounds) info.bounds = this._mml.bounds;
    if (info.bounds && info.center) {
        source._info = info;
        return callback(null);
    }

    this._pool.acquire(function(err, map) {
        if (err) return callback(err);
        info.bounds = sm.convert(map.extent);
        source._pool.release(map);

        // sm.convert may sometimes return -180.00000000000003 or crap like that.
        if (info.bounds[0] < -180) info.bounds[0] = -180;
        if (info.bounds[1] < -90) info.bounds[1] = -90;
        if (info.bounds[2] > 180) info.bounds[2] = 180;
        if (info.bounds[3] > 90) info.bounds[3] = 90;

        if (!info.center || info.center.length !== 3) {
            info.center = [
                (info.bounds[2] - info.bounds[0]) / 2 + info.bounds[0],
                (info.bounds[3] - info.bounds[1]) / 2 + info.bounds[1],
                2
            ];
        }

        // This is currently a synchronous operation.
        source._info = info;
        callback(null);
    });
};

// Creates a locking cache that generates tiles. When requesting the same tile
// multiple times, they'll be grouped to one request.
MapnikSource.prototype._createMetatileCache = function() {
    var source = this;
    this._tileCache = new LockingCache(function(key) {
        var cache = this;
        var coords = key.split(',');
        var keys = source._renderMetatile({
            metatile: source._uri.query.metatile,
            format: coords[0],
            z: +coords[1],
            x: +coords[2],
            y: +coords[3]
        }, function(err, tiles) {
            if (err) {
                // Push error objects to all entries that were supposed to
                // be generated.
                keys.forEach(function(key) {
                    cache.put(key, err);
                });
            } else {
                // Put all the generated tiles into the locking cache.
                for (var key in tiles) {
                    cache.put(key, null, tiles[key].image, tiles[key].headers);
                }
            }
        });

        // Return a list of all the tile coordinates that are being rendered
        // as part of this metatile.
        return keys;
    });
};

// Render handler for a given tile request.
MapnikSource.prototype.getTile = function(z, x, y, callback) {
    z = +z; x = +x; y = +y;
    if (isNaN(z) || isNaN(x) || isNaN(y)) {
        return callback(new Error('Invalid coordinates: %d/%d/%d', z, x, y));
    }

    var max = (1 << z);
    if (x >= max || x < 0 || y >= max || y < 0) {
        return callback(new Error('Coordinates out of range: %d/%d/%d', z, x, y));
    }

    var format = (this._mml && this._mml.format) || 'png';
    var key = [format, z, x, y].join(',');
    this._tileCache.get(key, function(err, tile, headers) {
        if (err) return callback(err);
        callback(null, tile, headers);
    });
};

MapnikSource.prototype.getGrid = function(z, x, y, callback) {
    z = +z; x = +x; y = +y;
    if (isNaN(z) || isNaN(x) || isNaN(y)) {
        return callback(new Error('Invalid coordinates: %d/%d/%d', z, x, y));
    }

    var max = (1 << z);
    if (x >= max || x < 0 || y >= max || y < 0) {
        return callback(new Error('Coordinates out of range: %d/%d/%d', z, x, y));
    } else if (!this._mml || !this._mml.interactivity) {
        return callback(new Error('Tileset has no interactivity'));
    } else if (!mapnik.supports.grid) {
        return callback(new Error('Mapnik is missing grid support'));
    }

    var key = ['utf', z, x, y].join(',');
    this._tileCache.get(key, function(err, grid, headers) {
        if (err) return callback(err);
        callback(null, grid, headers);
    });
};

MapnikSource.prototype.getInfo = function(callback) {
    if (this._info) callback(null, this._info);
    else callback(new Error('Info is unavailable'));
};

// Add other functions.
require('./render');
<commit_msg>allow serializing the state<commit_after>var fs = require('fs');
var crypto = require('crypto');
var Step = require('step');
var path = require('path');
var url = require('url');
var mapnik = require('mapnik');
var qs = require('querystring');
var Pool = require('generic-pool').Pool;
var LockingCache = require('./lockingcache');
var sm = new (require('sphericalmercator'));

var cache = {};

// Increase number of threads to 1.5x the number of logical CPUs.
var threads = Math.ceil(Math.max(4, require('os').cpus().length * 1.5));
require('eio').setMinParallel(threads);

exports = module.exports = MapnikSource;

require('util').inherits(MapnikSource, require('events').EventEmitter);
function MapnikSource(uri, callback) {
    uri = this._normalizeURI(uri);
    var key = url.format(uri);

    if (uri.protocol && uri.protocol !== 'mapnik:') {
        throw new Error('Only the mapnik protocol is supported');
    }

    if (!cache[key]) {
        cache[key] = this;
        this._open(uri);
    }

    var source = cache[key];
    source.setMaxListeners(0);
    if (!source.open) {
        source.once('open', function(err, source) {
            if (err) cache[key] = false;
            callback(err, source);
        });
    } else {
        callback(null, source);
    }
    return undefined;
}

MapnikSource.prototype.toJSON = function() {
    return url.format(this._uri);
};

MapnikSource.prototype._normalizeURI = function(uri) {
    if (typeof uri === 'string') uri = url.parse(uri, true);
    if (uri.hostname === '.' || uri.hostname == '..') {
        uri.pathname = uri.hostname + uri.pathname;
        delete uri.hostname;
        delete uri.host;
    }
    uri.pathname = path.resolve(uri.pathname);
    uri.query = uri.query || {};
    if (typeof uri.query === 'string') uri.query = qs.parse(uri.query);
    if (!uri.query.cache) uri.query.cache = '/tmp/';
    if (!uri.query.base) uri.query.base = '';
    if (!uri.query.metatile) uri.query.metatile = 2;
    if (!uri.query.resolution) uri.query.resolution = 4;
    if (!uri.query.bufferSize) uri.query.bufferSize = 128;
    return uri;
};

// Finds all XML files in the filepath and returns their tilesource URI.
MapnikSource.list = function(filepath, callback) {
    filepath = path.resolve(filepath);
    fs.readdir(filepath, function(err, files) {
        if (err) return callback(err);
        for (var result = {}, i = 0; i < files.length; i++) {
            var name = files[i].match(/^([\w-]+)\.xml$/);
            if (name) result[name[1]] = 'mapnik://' + path.join(filepath, name[0]);
        }
        return callback(null, result);
    });
};

// Finds an XML file with the given ID in the filepath and returns a
// tilesource URI.
MapnikSource.findID = function(filepath, id, callback) {
    filepath = path.resolve(filepath);
    var file = path.join(filepath, id + '.xml');
    fs.stat(file, function(err, stats) {
        if (err) return callback(err);
        else return callback(null, 'mapnik://' + file);
    });
};

MapnikSource.prototype._open = function(uri) {
    var source = this;
    function error(err) {
        process.nextTick(function() {
            source.emit('open', err);
        });
    }

    this._stats = {
        render: 0,          // # of times a render is requested from mapnik
        total: 0,           // # of tiles returned from source
        encoded: 0,         // # of tiles encoded
        solid: 0,           // # of tiles isSolid
        solidPainted: 0     // # of tiles isSolid && painted
    };
    this._cache = uri.query.cache;
    this._base = uri.query.base;
    uri.query.metatile = +uri.query.metatile;
    uri.query.resolution = +uri.query.resolution;
    uri.query.bufferSize = +uri.query.bufferSize;
    this._uri = uri;

    // Public API to announce how we're metatiling.
    this.metatile = uri.query.metatile;
    this.bufferSize = uri.query.bufferSize;

    // Initialize this map. This wraps `localize()` and calls `create()`
    // afterwards to actually create a new Mapnik map object.
    Step(function() {
        source._loadXML(this);
    }, function(err, xml, mml) {
        if (err) return error(err);
        source._xml = xml;
        source._mml = mml;
        source._createMetatileCache();
        source._createPool(xml, this);
    }, function(err) {
        if (err) return error(err);
        source._populateInfo(this);
    }, function(err) {
        if (err) return error(err);

        source.open = true;
        source.emit('open', null, source);
    });
};

MapnikSource.prototype._close = function() {
    if (this._cache) this._cache.clear();
    // Note: this doesn't clear timeouts in node-pool.
    // See https://github.com/coopernurse/node-pool/issues/17
    if (this._pool) {
        this._pool.drain();
        this._pool.destroyAllNow();
    }
};

MapnikSource.registerProtocols = function(tilelive) {
    tilelive.protocols['mapnik:'] = MapnikSource;
};

// Loads the XML file from the specified path. Calls `callback` when the mapfile
// can be expected to be in `mapfile`. If this isn't successful, `callback` gets
// an error as its first argument.
MapnikSource.prototype._loadXML = function(callback) {
    var source = this;
    var xml, mml;
    Step(function() {
        if (source._uri.xml) {
            // This is a string-based map file. Pass it on literally.
            source._base = path.resolve(path.dirname(source._uri.pathname));
            this(null, source._uri.xml);
        } else {
            // This is a file-based map file. Load it from the file.
            source._base = path.resolve(path.dirname(source._uri.pathname));
            fs.readFile(path.resolve(source._uri.pathname), 'utf8', this);
        }
    }, function(err, data) {
        if (err) return callback(err);
        xml = data;

        if (source._uri.mml) {
            // This is an MML object. Pass it on literally.
            this(null, source._uri.mml);
        } else {
            var p = path.resolve(source._uri.pathname);
            var filepath = path.join(
                path.dirname(p),
                path.basename(p, path.extname(p))) + '.mml';
            fs.readFile(filepath, 'utf8', this);
        }
    }, function(err, data) {
        // Only care about the error if it's something
        // different from "file not found".
        if (err && err.code !== 'ENOENT') return callback(err);

        if (data) {
            // An accompanying MML file exists.
            if (typeof data === 'string') {
                try {
                    mml = JSON.parse(data);
                } catch (err) {
                    return callback(err);
                }
            } else {
                mml = data;
            }
        }

        callback(null, xml, mml);
    });
};

// Create a new mapnik map object at `this.mapnik`. Requires that the mapfile
// be localized with `this.localize()`. This can be called in repetition because
// it won't recreate `this.mapnik`.
MapnikSource.prototype._createPool = function(xml, callback) {
    var source = this;
    if (!this._pool) this._pool = Pool({
        create: function(callback) {
            var map = new mapnik.Map(256, 256);
            map.bufferSize = source._uri.query.bufferSize;
            map.fromString(xml, {
                strict: false,
                base: source._base + '/'
            }, function(err, map) {
                if (err) return callback(err);
                // Wrap zoomAll() with a try/catch and don't bail on error.
                // Certain layers/projections seem to cause an error here that
                // need not be fatal.
                try {
                    map.zoomAll();
                } catch(e) {};
                callback(null, map);
            });
        },
        destroy: function(map) {
            map.clear();
            delete map;
        },
        // @TODO: need a smarter way to scale this. More
        // maps in pool seems better for PostGIS.
        max: require('os').cpus().length
    });
    callback(null);
};

MapnikSource.prototype._populateInfo = function(callback) {
    var source = this;
    var info = {
        name: path.basename(this._uri.pathname, path.extname(this._uri.pathname)),

        // We can't determine a sensible minzoom/maxzoom
        minzoom: 0,
        maxzoom: 22
    };

    info.id = info.name;

    if (this._mml && this._mml.center) info.center = this._mml.center;
    if (this._mml && this._mml.bounds) info.bounds = this._mml.bounds;
    if (info.bounds && info.center) {
        source._info = info;
        return callback(null);
    }

    this._pool.acquire(function(err, map) {
        if (err) return callback(err);
        info.bounds = sm.convert(map.extent);
        source._pool.release(map);

        // sm.convert may sometimes return -180.00000000000003 or crap like that.
        if (info.bounds[0] < -180) info.bounds[0] = -180;
        if (info.bounds[1] < -90) info.bounds[1] = -90;
        if (info.bounds[2] > 180) info.bounds[2] = 180;
        if (info.bounds[3] > 90) info.bounds[3] = 90;

        if (!info.center || info.center.length !== 3) {
            info.center = [
                (info.bounds[2] - info.bounds[0]) / 2 + info.bounds[0],
                (info.bounds[3] - info.bounds[1]) / 2 + info.bounds[1],
                2
            ];
        }

        // This is currently a synchronous operation.
        source._info = info;
        callback(null);
    });
};

// Creates a locking cache that generates tiles. When requesting the same tile
// multiple times, they'll be grouped to one request.
MapnikSource.prototype._createMetatileCache = function() {
    var source = this;
    this._tileCache = new LockingCache(function(key) {
        var cache = this;
        var coords = key.split(',');
        var keys = source._renderMetatile({
            metatile: source._uri.query.metatile,
            format: coords[0],
            z: +coords[1],
            x: +coords[2],
            y: +coords[3]
        }, function(err, tiles) {
            if (err) {
                // Push error objects to all entries that were supposed to
                // be generated.
                keys.forEach(function(key) {
                    cache.put(key, err);
                });
            } else {
                // Put all the generated tiles into the locking cache.
                for (var key in tiles) {
                    cache.put(key, null, tiles[key].image, tiles[key].headers);
                }
            }
        });

        // Return a list of all the tile coordinates that are being rendered
        // as part of this metatile.
        return keys;
    });
};

// Render handler for a given tile request.
MapnikSource.prototype.getTile = function(z, x, y, callback) {
    z = +z; x = +x; y = +y;
    if (isNaN(z) || isNaN(x) || isNaN(y)) {
        return callback(new Error('Invalid coordinates: %d/%d/%d', z, x, y));
    }

    var max = (1 << z);
    if (x >= max || x < 0 || y >= max || y < 0) {
        return callback(new Error('Coordinates out of range: %d/%d/%d', z, x, y));
    }

    var format = (this._mml && this._mml.format) || 'png';
    var key = [format, z, x, y].join(',');
    this._tileCache.get(key, function(err, tile, headers) {
        if (err) return callback(err);
        callback(null, tile, headers);
    });
};

MapnikSource.prototype.getGrid = function(z, x, y, callback) {
    z = +z; x = +x; y = +y;
    if (isNaN(z) || isNaN(x) || isNaN(y)) {
        return callback(new Error('Invalid coordinates: %d/%d/%d', z, x, y));
    }

    var max = (1 << z);
    if (x >= max || x < 0 || y >= max || y < 0) {
        return callback(new Error('Coordinates out of range: %d/%d/%d', z, x, y));
    } else if (!this._mml || !this._mml.interactivity) {
        return callback(new Error('Tileset has no interactivity'));
    } else if (!mapnik.supports.grid) {
        return callback(new Error('Mapnik is missing grid support'));
    }

    var key = ['utf', z, x, y].join(',');
    this._tileCache.get(key, function(err, grid, headers) {
        if (err) return callback(err);
        callback(null, grid, headers);
    });
};

MapnikSource.prototype.getInfo = function(callback) {
    if (this._info) callback(null, this._info);
    else callback(new Error('Info is unavailable'));
};

// Add other functions.
require('./render');
<|endoftext|>


<commit_before>//
// system-level requires
//

var exec = require('child_process').exec;
var path = require('path');
var envify = require('envify/custom');
var rimraf = require('rimraf');

//
// gulp-specific tools
//

var gulp = require('gulp');
var concat = require('gulp-concat');
var vsource = require('vinyl-source-stream');
var vtransform = require('vinyl-transform');
var jshint = require('gulp-jshint');
var template = require('gulp-template');
var livereload = require('gulp-livereload');
var gutil = require('gulp-util');
var uglify = require('gulp-uglify');
var jsxtransform = require('gulp-react');

//
// testing/packaging
//

var karma = require('karma');
var browserify = require('browserify');
var pkg = require('./package.json');

//
// config for the web server used to serve examples
//

var SERVERPORT = 8080;
var SOURCEGLOB = './src/**/*.js';
var EXAMPLESGLOB = './examples/**/*.js';

//
// final built output goes into build/<OUTPUTFILE>.js
//

var OUTPUTFILE = 'react-pixi';

var banner = ['/**',
              ' * <%= pkg.name %>',
              ' * @version <%= pkg.version %>',
              ' * @license <%= pkg.license %>',
              ' */',
              ''].join('\n');

var browserlist = ['Firefox'];
var karmaconfiguration = {
    browsers: browserlist,
    files: ['bower_components/lodash/dist/lodash.min.js',
            'bower_components/pixi.js/bin/pixi.dev.js',
            'build/react-pixi.js',
            'vendor/phantomjs-shims.js', // need a shim to work with the ancient version of Webkit used in PhantomJS
            'node_modules/resemblejs/resemble.js',
            'test/createTestFixtureMountPoint.js',
            'test/pixels/pixelTests.js',
            'test/basics/*.js',
            'test/components/*.js',
            {pattern:'test/pixels/*.png',included:false, served:true} // for render tests
           ],
    frameworks:['jasmine'],
    singleRun:true
};

function errorHandler(err) {
  gutil.log(err);
  this.emit('end'); // so that gulp knows the task is done
}

gulp.task('help', function() {
  console.log('Possible tasks:');
  console.log('"default" - compile react-pixi into build/react-pixi.js');
  console.log('"test" - run tests in test directory');
  console.log('"dist" - compile react-pixi into dist/ directory for distribution');
  console.log('"dist-clojars" - generate files in dist-clojars/ which can be deployed via leiningen');
  console.log('"watch" - watch react-pixi source files and rebuild');
  console.log('"livereload" - compile and launch web server/reload server');
  console.log('"pixelrefs" - generate reference images for render-specific tests');
});

gulp.task('lint', function() {
  return gulp.src([SOURCEGLOB,EXAMPLESGLOB])
    .pipe(jshint())
    .pipe(jshint.reporter('jshint-stylish'));
});

gulp.task('browserify',['lint'], function() {
  var bundler = browserify();
  bundler.require('react');
  bundler.require('./src/ReactPIXI.js',{expose:'react-pixi'});

  // If we're running a gulp.watch and browserify finds an error, it will
  // throw an exception and terminate gulp unless we catch the error event.
  return bundler.bundle().on('error', errorHandler)
    .pipe(vsource('react-pixi-commonjs.js'))
    .pipe(gulp.dest('build'));
});

gulp.task('bundle', ['browserify'], function() {
  return gulp.src(['build/react-pixi-commonjs.js','src/react-pixi-exposeglobals.js'])
    .pipe(concat('react-pixi.js'))
    .pipe(vtransform(envify({NODE_ENV:"development"})))
    .pipe(gulp.dest('build'))
    .pipe(livereload());
});

gulp.task('bundle-min', ['browserify'], function() {
  return gulp.src(['build/react-pixi-commonjs.js','src/react-pixi-exposeglobals.js'])
    .pipe(concat('react-pixi.min.js'))
    .pipe(vtransform(envify({NODE_ENV:"production"})))
    .pipe(uglify({preserveComments:'some'}))
    .pipe(gulp.dest('build'))
    .pipe(livereload());
});

//
// the JSX example needs to be run through the jsx transform
//
gulp.task('jsxtransform', ['bundle'], function() {
  return gulp.src('examples/jsxtransform/jsxtransform.jsx', {base:'examples/jsxtransform'})
    .pipe(jsxtransform())
    .pipe(gulp.dest('examples/jsxtransform'))
    .pipe(livereload());
});

gulp.task('watch', ['bundle', 'bundle-min'], function() {
  gulp.watch(SOURCEGLOB, ['bundle','bundle-min']);
  gulp.watch(['examples/jsxtransform/*.jsx'], ['jsxtransform']);
});

gulp.task('livereload', ['lint','bundle','jsxtransform'], function() {
  var nodestatic = require('node-static');
  var fileserver = new nodestatic.Server('.');
  require('http').createServer(function(request, response) {
    request.addListener('end', function() {
      fileserver.serve(request,response);
    }).resume();
  }).listen(SERVERPORT);

  livereload.listen();

  gulp.watch([SOURCEGLOB], ['bundle','bundle-min']);
  gulp.watch(['examples/jsxtransform/*.jsx'], ['jsxtransform']);
});

gulp.task('test', ['bundle'], function(done) {
  karma.server.start(karmaconfiguration, function (exitCode) {
    gutil.log('Karma has exited with code ' + exitCode);
    done();
  });
});

gulp.task('dist-clean', function(done) {
  rimraf('dist', done);
});

// dist puts build results into dist/ for release via bower
gulp.task('dist', ['dist-clean','bundle', 'bundle-min', 'test'], function() {
  return gulp.src(['build/**'], {base:'build'})
    .pipe(gulp.dest('dist'));
});

//
// For easy use with ClojureScript (om-react-pixi) we need to
// arrange the files properly so that they may be properly
// packaged with leiningen. File are first arranged in dist-clojars
// and then packaged/deployed to clojars.org (currently by hand)
//

gulp.task('dist-clojars-clean', function(done) {
  rimraf('dist-clojars',done);
});

// Generate a leiningen project file for clojars. The source
// file itself is just a template so that we can fill in the
// version field. The version used is whatever is specified in package.json
gulp.task('dist-clojars-project', ['dist-clojars-clean'], function() {
  return gulp.src(['src/project_template.clj'], {base:'src'})
    .pipe(template({version:pkg.version}))
    .pipe(concat("project.clj"))
    .pipe(gulp.dest('dist-clojars/'));
});

// put the react-pixi javascript files into resources/react_pixi so that
// cljsbuild can refer to them via {:source-paths ["react_pixi"]}
gulp.task('dist-clojars-src', ['dist', 'dist-clojars-clean'], function() {
  return gulp.src(['dist/**'], {base:'dist'})
    .pipe(gulp.dest('dist-clojars/resources/react_pixi'));
});

// Dump other files (like pixi itself) into the resources/react_pixi dir
// so that ring or another web-server can serve it. Such a file can be accessed
// at a '/react_pixi/<xxx>' URL.
gulp.task('dist-clojars-resources', ['dist-clojars-clean'], function() {
  return gulp.src(['bower_components/pixi.js/bin/**'], {base:'bower_components/pixi.js/bin'})
    .pipe(gulp.dest('dist-clojars/resources/react_pixi'));
});

gulp.task('dist-clojars', ['dist-clojars-src','dist-clojars-project','dist-clojars-resources'], function() {
  // user must run lein deploy in the subdir
  gutil.log('ready to deploy');
  gutil.log('chdir into the "dist-clojars" directory and run "lein deploy clojars"');
});

//
// generate the bitmap references used in testing
//

gulp.task('pixelrefs', ['bundle'], function(done) {
  var command = path.normalize('./node_modules/.bin/phantomjs');
  var child = exec(command + ' test/pixels/generatetestrender.js',
                  function(error, stdout, stderr) {
                    gutil.log('result of reference image generation:\n' + stdout);
                    if (stderr.length > 0) {
                      gutil.log('stderr: ' + stderr);
                    }
                    if (error !== null) {
                      gutil.log('exec error: ' + error);
                    }
                    done();
                  });
});

gulp.task('default', ['bundle','bundle-min']);
<commit_msg>Make 'gulp watch' lint examples.<commit_after>//
// system-level requires
//

var exec = require('child_process').exec;
var path = require('path');
var envify = require('envify/custom');
var rimraf = require('rimraf');

//
// gulp-specific tools
//

var gulp = require('gulp');
var concat = require('gulp-concat');
var vsource = require('vinyl-source-stream');
var vtransform = require('vinyl-transform');
var jshint = require('gulp-jshint');
var template = require('gulp-template');
var livereload = require('gulp-livereload');
var gutil = require('gulp-util');
var uglify = require('gulp-uglify');
var jsxtransform = require('gulp-react');

//
// testing/packaging
//

var karma = require('karma');
var browserify = require('browserify');
var pkg = require('./package.json');

//
// config for the web server used to serve examples
//

var SERVERPORT = 8080;
var SOURCEGLOB = './src/**/*.js';
var EXAMPLESGLOB = './examples/**/*.js';

//
// final built output goes into build/<OUTPUTFILE>.js
//

var OUTPUTFILE = 'react-pixi';

var banner = ['/**',
              ' * <%= pkg.name %>',
              ' * @version <%= pkg.version %>',
              ' * @license <%= pkg.license %>',
              ' */',
              ''].join('\n');

var browserlist = ['Firefox'];
var karmaconfiguration = {
    browsers: browserlist,
    files: ['bower_components/lodash/dist/lodash.min.js',
            'bower_components/pixi.js/bin/pixi.dev.js',
            'build/react-pixi.js',
            'vendor/phantomjs-shims.js', // need a shim to work with the ancient version of Webkit used in PhantomJS
            'node_modules/resemblejs/resemble.js',
            'test/createTestFixtureMountPoint.js',
            'test/pixels/pixelTests.js',
            'test/basics/*.js',
            'test/components/*.js',
            {pattern:'test/pixels/*.png',included:false, served:true} // for render tests
           ],
    frameworks:['jasmine'],
    singleRun:true
};

function errorHandler(err) {
  gutil.log(err);
  this.emit('end'); // so that gulp knows the task is done
}

gulp.task('help', function() {
  console.log('Possible tasks:');
  console.log('"default" - compile react-pixi into build/react-pixi.js');
  console.log('"test" - run tests in test directory');
  console.log('"dist" - compile react-pixi into dist/ directory for distribution');
  console.log('"dist-clojars" - generate files in dist-clojars/ which can be deployed via leiningen');
  console.log('"watch" - watch react-pixi source files and rebuild');
  console.log('"livereload" - compile and launch web server/reload server');
  console.log('"pixelrefs" - generate reference images for render-specific tests');
});

gulp.task('lint', function() {
  return gulp.src([SOURCEGLOB,EXAMPLESGLOB])
    .pipe(jshint())
    .pipe(jshint.reporter('jshint-stylish'));
});

gulp.task('browserify',['lint'], function() {
  var bundler = browserify();
  bundler.require('react');
  bundler.require('./src/ReactPIXI.js',{expose:'react-pixi'});

  // If we're running a gulp.watch and browserify finds an error, it will
  // throw an exception and terminate gulp unless we catch the error event.
  return bundler.bundle().on('error', errorHandler)
    .pipe(vsource('react-pixi-commonjs.js'))
    .pipe(gulp.dest('build'));
});

gulp.task('bundle', ['browserify'], function() {
  return gulp.src(['build/react-pixi-commonjs.js','src/react-pixi-exposeglobals.js'])
    .pipe(concat('react-pixi.js'))
    .pipe(vtransform(envify({NODE_ENV:"development"})))
    .pipe(gulp.dest('build'))
    .pipe(livereload());
});

gulp.task('bundle-min', ['browserify'], function() {
  return gulp.src(['build/react-pixi-commonjs.js','src/react-pixi-exposeglobals.js'])
    .pipe(concat('react-pixi.min.js'))
    .pipe(vtransform(envify({NODE_ENV:"production"})))
    .pipe(uglify({preserveComments:'some'}))
    .pipe(gulp.dest('build'))
    .pipe(livereload());
});

//
// the JSX example needs to be run through the jsx transform
//
gulp.task('jsxtransform', ['bundle'], function() {
  return gulp.src('examples/jsxtransform/jsxtransform.jsx', {base:'examples/jsxtransform'})
    .pipe(jsxtransform())
    .pipe(gulp.dest('examples/jsxtransform'))
    .pipe(livereload());
});

gulp.task('watch', ['bundle', 'bundle-min'], function() {
  gulp.watch(SOURCEGLOB, ['bundle','bundle-min']);
  gulp.watch(['examples/jsxtransform/*.jsx'], ['jsxtransform']);
  gulp.watch(EXAMPLESGLOB,['lint'])
});

gulp.task('livereload', ['lint','bundle','jsxtransform'], function() {
  var nodestatic = require('node-static');
  var fileserver = new nodestatic.Server('.');
  require('http').createServer(function(request, response) {
    request.addListener('end', function() {
      fileserver.serve(request,response);
    }).resume();
  }).listen(SERVERPORT);

  livereload.listen();

  gulp.watch([SOURCEGLOB], ['bundle','bundle-min']);
  gulp.watch(['examples/jsxtransform/*.jsx'], ['jsxtransform']);
});

gulp.task('test', ['bundle'], function(done) {
  karma.server.start(karmaconfiguration, function (exitCode) {
    gutil.log('Karma has exited with code ' + exitCode);
    done();
  });
});

gulp.task('dist-clean', function(done) {
  rimraf('dist', done);
});

// dist puts build results into dist/ for release via bower
gulp.task('dist', ['dist-clean','bundle', 'bundle-min', 'test'], function() {
  return gulp.src(['build/**'], {base:'build'})
    .pipe(gulp.dest('dist'));
});

//
// For easy use with ClojureScript (om-react-pixi) we need to
// arrange the files properly so that they may be properly
// packaged with leiningen. File are first arranged in dist-clojars
// and then packaged/deployed to clojars.org (currently by hand)
//

gulp.task('dist-clojars-clean', function(done) {
  rimraf('dist-clojars',done);
});

// Generate a leiningen project file for clojars. The source
// file itself is just a template so that we can fill in the
// version field. The version used is whatever is specified in package.json
gulp.task('dist-clojars-project', ['dist-clojars-clean'], function() {
  return gulp.src(['src/project_template.clj'], {base:'src'})
    .pipe(template({version:pkg.version}))
    .pipe(concat("project.clj"))
    .pipe(gulp.dest('dist-clojars/'));
});

// put the react-pixi javascript files into resources/react_pixi so that
// cljsbuild can refer to them via {:source-paths ["react_pixi"]}
gulp.task('dist-clojars-src', ['dist', 'dist-clojars-clean'], function() {
  return gulp.src(['dist/**'], {base:'dist'})
    .pipe(gulp.dest('dist-clojars/resources/react_pixi'));
});

// Dump other files (like pixi itself) into the resources/react_pixi dir
// so that ring or another web-server can serve it. Such a file can be accessed
// at a '/react_pixi/<xxx>' URL.
gulp.task('dist-clojars-resources', ['dist-clojars-clean'], function() {
  return gulp.src(['bower_components/pixi.js/bin/**'], {base:'bower_components/pixi.js/bin'})
    .pipe(gulp.dest('dist-clojars/resources/react_pixi'));
});

gulp.task('dist-clojars', ['dist-clojars-src','dist-clojars-project','dist-clojars-resources'], function() {
  // user must run lein deploy in the subdir
  gutil.log('ready to deploy');
  gutil.log('chdir into the "dist-clojars" directory and run "lein deploy clojars"');
});

//
// generate the bitmap references used in testing
//

gulp.task('pixelrefs', ['bundle'], function(done) {
  var command = path.normalize('./node_modules/.bin/phantomjs');
  var child = exec(command + ' test/pixels/generatetestrender.js',
                  function(error, stdout, stderr) {
                    gutil.log('result of reference image generation:\n' + stdout);
                    if (stderr.length > 0) {
                      gutil.log('stderr: ' + stderr);
                    }
                    if (error !== null) {
                      gutil.log('exec error: ' + error);
                    }
                    done();
                  });
});

gulp.task('default', ['bundle','bundle-min']);
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { injectIntl, FormattedMessage } from 'react-intl';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import { push } from 'react-router-redux';
import AppButton from '../../common/AppButton';
import { topicSnapshotGenerate } from '../../../actions/topicActions';
import { updateFeedback } from '../../../actions/appActions';
import TopicVersionReadySummary from './TopicVersionReadySummary';
import GenerateVersionAdminDialog, { FIELD_NEW_VERSION, FIELD_SPIDER } from './GenerateVersionAdminDialog';
import Permissioned from '../../common/Permissioned';
import { PERMISSION_ADMIN } from '../../../lib/auth';

const localMessages = {
  title: { id: 'versions.comparison.title', defaultMessage: 'Version {versionNumber}: Summary' },
  createVersionAndStartSpider: { id: 'versions.comparison.generate', defaultMessage: 'Generate New Version' },
  versionDiffTitle: { id: 'versions.comparison.versionDiffTitle', defaultMessage: 'You\'ve Made Changes' },
  applyChanges: { id: 'versions.comparison.applyChanges', defaultMessage: 'apply your changes' },
  failed: { id: 'versions.comparison.failed', defaultMessage: 'Sorry, something went wrong!' },
  worked: { id: 'versions.comparison.worked', defaultMessage: 'We started generating the version' },
  currentVersionComparisonTitle: { id: 'versions.comparison.currentTitle', defaultMessage: 'Version {versionNumber}: Platform Summary' },
  nextVersionComparisonTitle: { id: 'versions.comparison.nextTitle', defaultMessage: 'Next Version: Platform Summary' },
  noPrevious: { id: 'versions.comparison.noneYet', defaultMessage: '(No current version to compare to, because you are setting up your first version)' },
};

/**
 * This wrapper shows the difference between the current topic version and the settings queued up for
 * the next one. This rolls up all the changes into one list so it is easier to think about.
 * Notes:
 * - `current` should be null if this is the first version
 * - Admins get some extra options for how to generate the version.
 */
class VersionComparisonContainer extends React.Component {
  state = {
    submittingVersion: false, // this isn't technically a form so we manually control the button disabled with this
  };

  render() {
    const { topic, topicId, current, next, focalSetDefinitions, handleGenerateVersion } = this.props;
    const submitting = this.state.submittingVersion;
    const currentSnapshotsId = current ? current.snapshots_id : null;
    return (

        <Grid>
          <Row>
            <Col lg={12}>

<FormattedMessage {...localMessages.versionDiffTitle} />

            </Col>
          </Row>
          <Row>
            <Col lg={5}>

                {current && (
                  <>

<FormattedMessage {...localMessages.title} values={{ versionNumber: current ? current.note : 1 }} />

                    <TopicVersionReadySummary topic={topic} snapshot={current} startWithDetailsShowing />
                  </>
                )}
                {(current === null) && (

<FormattedMessage {...localMessages.noPrevious} />

                )}

            </Col>
            <Col lg={2}>
              ➡
            </Col>
            <Col lg={5}>

<FormattedMessage {...localMessages.title} values={{ versionNumber: current ? current.note + 1 : 1 }} />

                <TopicVersionReadySummary
                  snapshot={next}
                  topic={topic}
                  focalSets={focalSetDefinitions}
                  startWithDetailsShowing
                />

              <AppButton
                label={localMessages.createVersionAndStartSpider}
                onClick={() => {
                  this.setState({ submittingVersion: true });
                  handleGenerateVersion(topicId, false, false, currentSnapshotsId);
                }}
                primary
                disabled={submitting}
              />
              <Permissioned onlyRole={PERMISSION_ADMIN}>
                <GenerateVersionAdminDialog
                  onGenerate={(values) => {
                    this.setState({ submittingVersion: true });
                    handleGenerateVersion(topicId, values[FIELD_NEW_VERSION] === 1, values[FIELD_SPIDER] === 1, currentSnapshotsId);
                  }}
                  initialValues={{
                    [FIELD_NEW_VERSION]: 1,
                    [FIELD_SPIDER]: 1,
                  }}
                />
              </Permissioned>
            </Col>
          </Row>
        </Grid>

    );
  }
}

VersionComparisonContainer.propTypes = {
  // from composition
  intl: PropTypes.object.isRequired,
  // from parent
  // from state
  current: PropTypes.object,
  next: PropTypes.object,
  topicId: PropTypes.number.isRequired,
  topic: PropTypes.object.isRequired,
  focalSetDefinitions: PropTypes.array,
  // from dispatch
  handleGenerateVersion: PropTypes.func.isRequired,
};

const mapStateToProps = (state) => ({
  topicId: state.topics.selected.id,
  topic: state.topics.selected,
  focalSetDefinitions: state.topics.selected.focalSets.definitions.list,
  current: state.topics.selected.snapshots.selected,
  next: state.topics.selected.info,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
  handleGenerateVersion: (topicId, newVersion, alsoSpider, currentSnapshotsId) => {
    const params = {
      snapshotId: newVersion ? null : currentSnapshotsId,
      spider: alsoSpider ? 1 : 0,
    };
    dispatch(topicSnapshotGenerate(topicId, params))
      .then((results) => {
        if (results && results.topics_id) {
          // let them know it worked
          dispatch(updateFeedback({ classes: 'info-notice', open: true, message: ownProps.intl.formatMessage(localMessages.worked) }));
          return dispatch(push(`/topics/${topicId}/versions`));
        }
        return dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(localMessages.failed) }));
      });
  },
});

export default
injectIntl(
  connect(mapStateToProps, mapDispatchToProps)(
    VersionComparisonContainer
  )
);
<commit_msg>#1815<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { injectIntl, FormattedMessage } from 'react-intl';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import { push } from 'react-router-redux';
import AppButton from '../../common/AppButton';
import { topicSnapshotGenerate } from '../../../actions/topicActions';
import { updateFeedback } from '../../../actions/appActions';
import TopicVersionReadySummary from './TopicVersionReadySummary';
import GenerateVersionAdminDialog, { FIELD_NEW_VERSION, FIELD_SPIDER } from './GenerateVersionAdminDialog';
import Permissioned from '../../common/Permissioned';
import { PERMISSION_ADMIN } from '../../../lib/auth';

const localMessages = {
  title: { id: 'versions.comparison.title', defaultMessage: 'Version {versionNumber}: Summary' },
  createVersionAndStartSpider: { id: 'versions.comparison.generate', defaultMessage: 'Generate New Version' },
  versionDiffTitle: { id: 'versions.comparison.versionDiffTitle', defaultMessage: 'You\'ve Made Changes' },
  applyChanges: { id: 'versions.comparison.applyChanges', defaultMessage: 'apply your changes' },
  failed: { id: 'versions.comparison.failed', defaultMessage: 'Sorry, something went wrong!' },
  worked: { id: 'versions.comparison.worked', defaultMessage: 'We started generating the version' },
  currentVersionComparisonTitle: { id: 'versions.comparison.currentTitle', defaultMessage: 'Version {versionNumber}: Platform Summary' },
  nextVersionComparisonTitle: { id: 'versions.comparison.nextTitle', defaultMessage: 'Next Version: Platform Summary' },
  noPrevious: { id: 'versions.comparison.noneYet', defaultMessage: '(No current version to compare to, because you are setting up your first version)' },
};

/**
 * This wrapper shows the difference between the current topic version and the settings queued up for
 * the next one. This rolls up all the changes into one list so it is easier to think about.
 * Notes:
 * - `current` should be null if this is the first version
 * - Admins get some extra options for how to generate the version.
 */
class VersionComparisonContainer extends React.Component {
  state = {
    submittingVersion: false, // this isn't technically a form so we manually control the button disabled with this
  };

  render() {
    const { topic, topicId, current, next, focalSetDefinitions, handleGenerateVersion } = this.props;
    const submitting = this.state.submittingVersion;
    const currentSnapshotsId = current ? current.snapshots_id : null;
    return (

        <Grid>
          <Row>
            <Col lg={12}>

<FormattedMessage {...localMessages.versionDiffTitle} />

            </Col>
          </Row>
          <Row>
            <Col lg={5}>

                {current && (
                  <>

<FormattedMessage {...localMessages.title} values={{ versionNumber: current ? current.note : 1 }} />

                    <TopicVersionReadySummary topic={topic} snapshot={current} startWithDetailsShowing />
                  </>
                )}
                {(current === null) && (

<FormattedMessage {...localMessages.noPrevious} />

                )}

            </Col>
            <Col lg={2}>
              ➡
            </Col>
            <Col lg={5}>

<FormattedMessage {...localMessages.title} values={{ versionNumber: current ? current.note + 1 : 1 }} />

                <TopicVersionReadySummary
                  snapshot={next}
                  topic={topic}
                  focalSets={focalSetDefinitions}
                  startWithDetailsShowing
                />

              <AppButton
                label={localMessages.createVersionAndStartSpider}
                onClick={() => {
                  this.setState({ submittingVersion: true });
                  handleGenerateVersion(topicId, true, false, currentSnapshotsId);
                }}
                primary
                disabled={submitting}
              />
              <Permissioned onlyRole={PERMISSION_ADMIN}>
                <GenerateVersionAdminDialog
                  onGenerate={(values) => {
                    this.setState({ submittingVersion: true });
                    handleGenerateVersion(topicId, values[FIELD_NEW_VERSION] === 1, values[FIELD_SPIDER] === 1, currentSnapshotsId);
                  }}
                  initialValues={{
                    [FIELD_NEW_VERSION]: 1,
                    [FIELD_SPIDER]: 1,
                  }}
                />
              </Permissioned>
            </Col>
          </Row>
        </Grid>

    );
  }
}

VersionComparisonContainer.propTypes = {
  // from composition
  intl: PropTypes.object.isRequired,
  // from parent
  // from state
  current: PropTypes.object,
  next: PropTypes.object,
  topicId: PropTypes.number.isRequired,
  topic: PropTypes.object.isRequired,
  focalSetDefinitions: PropTypes.array,
  // from dispatch
  handleGenerateVersion: PropTypes.func.isRequired,
};

const mapStateToProps = (state) => ({
  topicId: state.topics.selected.id,
  topic: state.topics.selected,
  focalSetDefinitions: state.topics.selected.focalSets.definitions.list,
  current: state.topics.selected.snapshots.selected,
  next: state.topics.selected.info,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
  handleGenerateVersion: (topicId, newVersion, alsoSpider, currentSnapshotsId) => {
    const params = {
      snapshotId: newVersion ? null : currentSnapshotsId,
      spider: alsoSpider ? 1 : 0,
    };
    dispatch(topicSnapshotGenerate(topicId, params))
      .then((results) => {
        if (results && results.topics_id) {
          // let them know it worked
          dispatch(updateFeedback({ classes: 'info-notice', open: true, message: ownProps.intl.formatMessage(localMessages.worked) }));
          return dispatch(push(`/topics/${topicId}/versions`));
        }
        return dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(localMessages.failed) }));
      });
  },
});

export default
injectIntl(
  connect(mapStateToProps, mapDispatchToProps)(
    VersionComparisonContainer
  )
);
<|endoftext|>


<commit_before>var config = require('config');
var mapModule = require('map');
var plugins = require('./leaflet_plugins');
var polyUtil = require('./polyline_encoded.js');
var routeboxer = require('./leaflet_routeboxer.js');
var leaflet_label = require('./leaflet_label/leaflet.label-src.js');
var session = require('session');

var center = config.geocode().center.split(',').map(parseFloat)
if (config.map_provider && config.map_provider() !== 'AmigoCloud') {
  L.mapbox.accessToken = config.mapbox_access_token();
}

module.exports = function(el) {
  var map, realtime, southWest, northEast, blurLayer;

  if (config.map_provider && config.map_provider() === 'AmigoCloud') {
    southWest = L.latLng(35.946877085397,-123.480610897013);
    northEast = L.latLng(40.763279543715,-118.789317362500);
    map = (new L.amigo.map(el, {
      amigoLogo: 'right',
      loadAmigoLayers: false,
      inertia: false,
      zoomAnimation: $('.hide-map').css('display') !== 'none',
      maxBounds: L.latLngBounds(southWest, northEast),
      minZoom: 8
    })).setView([center[1], center[0]], config.geocode().zoom);

    L.amigo.auth.setToken(config.support_data_token());

    blurLayer = L.tileLayer(
    'https://www.amigocloud.com/api/v1/users/'+
	'23/projects/3019/datasets/23835/tiles/{z}/{x}/{y}.png?' +
	'token=' + config.support_data_token(),
      {
        name: 'Uncovered Area'
      }
    );

    map.addAuthLayer({
      id: config.mapbox_map_id(),
      accessToken: config.mapbox_access_token(),
      name: 'Gray',
      provider: 'mapbox'
    });
    map.addBaseLayer(L.amigo.AmigoGray);
    map.layersControl.addBaseLayer(
      L.bingLayer(
        config.bing_key(),
	{
	  type: 'Road',
	  attribution: 'Bing Maps'
	}
      ),
      'Bing Road'
    );
    map.layersControl.addOverlay(blurLayer);
    blurLayer.addTo(map);

    module.exports.activeMap = map;

    map.realtimeControl = L.control.toggleRealTime().addTo(map);

    realtime = mapModule.realtime();

  } else {

    map = L.mapbox.map(el, config.mapbox_map_id(), {
      attributionControl: false,
      inertia: false,
      zoomAnimation: false
    }).setView([center[1], center[0]], config.geocode().zoom);
  }

  return map;
};

module.exports.getMap = function () {
  return this.activeMap;
};

module.exports.cleanRoute = function() {
    module.exports.activeRoute.removeLayer();
    module.exports.activeRoute = null;
};

module.exports.polyline_creadas = [];
module.exports.marker_creadas = [];
module.exports.makerpoint_creadas = [];

module.exports.getpolyline_creadas = function () {
  return this.polyline_creadas;
};

module.exports.getMarker_creadas = function () {
  return this.marker_creadas;
};

module.exports.cleanPolyline = function() {
    var polyline_creadas = this.getpolyline_creadas();
    var map = this.activeMap;
    for (i in polyline_creadas) {
        try {
                map.removeLayer(polyline_creadas[i]);

            } catch (e) {
                console.log("problema al eliminar " + e);
            }

  }
  this.polyline_creadas = [];

};

module.exports.cleanMarker = function() {
    var map = this.activeMap;
    for (i in this.marker_creadas) {
        try {
                map.removeLayer(this.marker_creadas[i]);

            } catch (e) {
                console.log("problema al eliminar " + e);
            }
    }

  this.marker_creadas = [];

};

module.exports.cleanMarkerpoint = function() {
    var map = this.activeMap;
    for (i in this.makerpoint_creadas) {
        try {
                map.removeLayer(this.makerpoint_creadas[i]);

            } catch (e) {
                console.log("problema al eliminar " + e);
            }
    }

  this.makerpoint_creadas = [];

};

module.exports.marker_map = function(from, to){
     var IconStart = L.icon({
        iconUrl: 'assets/images/graphics/start.svg',
        iconSize: [40, 55],
        iconAnchor: [20, 50],
        popupAnchor:  [0, -50]
    });
    var IconEnd = L.icon({
        iconUrl: 'assets/images/graphics/end.svg',
        iconSize: [40, 55],
        iconAnchor: [20, 50],
        popupAnchor:  [0, -50]
    });

    var markerform = new L.marker([from[0],from[1]], {icon: IconStart, draggable: true})
        .addTo(this.activeMap);
    var markerto = new L.marker([to[0],to[1]], {icon: IconEnd, draggable: true})
        .addTo(this.activeMap);
    var _this = this;

    markerform.on('dragend', function(e){
       var marker = e.target;
       var result = marker.getLatLng();
       _this.cleanPolyline();
       _this.cleanMarkerpoint();
       var plan = session.plan();

            plan.setAddress('from', result.lng + ',' + result.lat, function(err, rees) {
                plan.updateRoutes();
          });
    });

    markerto.on('dragend', function(e){
       var marker = e.target;
       var result = marker.getLatLng();
       _this.cleanPolyline();
       _this.cleanMarkerpoint();
       var plan = session.plan();
            plan.setAddress('to', result.lng + ',' + result.lat, function(err, rees) {
                plan.updateRoutes();
          });
    });

    this.marker_creadas.push(markerform);
    this.marker_creadas.push(markerto);
};

module.exports.marker_map_point = function(to, map){

    var name = to[2];

    var IconEnd = L.icon({
        iconUrl: 'assets/images/graphics/icono.png',
        iconSize: [20, 20],
        iconAnchor: [0, 0],
        popupAnchor:  [-3, -76]
    });
    var markers = [
      L.marker([to[0], to[1]], {icon: IconEnd}).bindLabel(name, { direction: 'auto' })
    ];

    var layer = L.layerGroup(markers).addTo(map);

    this.makerpoint_creadas.push(layer);
    //this.polyline_creadas.push(circle);
};

module.exports.drawRouteAmigo = function(legs,mode) {
    var route = legs.legGeometry.points;
    var circle_from = [legs.from.lat, legs.from.lon, legs.from.name];
    var circle_to = [legs.to.lat, legs.to.lon, legs.to.name];
    var color = '#000';
    var weight = 5;
    var dasharray= '';

        if (mode=="CAR") {
            color = '#9E9E9E';
            dasharray= '6';
            weight = 3;

        }else if(mode=="BICYCLE") {
            color = '#FF0000';
            dasharray= '6';
            weight = 3;

        }else if(mode=="SUBWAY" || mode=="RAIL") {
            color = '#FF0000';
             weight = 8;
             this.marker_map_point(circle_from, this.activeMap);
             this.marker_map_point(circle_to, this.activeMap);

        }
        else if(mode == "WALK") {
            color = '#0BC8F4';
            dasharray= '6';
             weight = 3;
        }
        else if(mode=="BUS") {
            color = '#FEF0B5';
             weight = 8;
             this.marker_map_point(circle_from, this.activeMap);
             this.marker_map_point(circle_to, this.activeMap);
        }

       var color_options = {
            color: color,
            opacity: 1,
            weight: weight,
            dashArray: dasharray
        };

      route = new L.Polyline(L.PolylineUtil.decode(route, 5), color_options);
      this.polyline_creadas.push(route);
      var boxes = L.RouteBoxer.box(route, 5);
      //var bounds = new L.LatLngBounds([]);
      //var bounds = [];
      var boxpolys = new Array(boxes.length);
      /*
      for (var i = 0; i < boxes.length; i++) {
        //bounds.extend(boxes[i]);
        bounds.push(boxes[i]);
      }*/

      route.addTo(this.activeMap);
      //this.activeMap.fitBounds(bounds);
};

<commit_msg>adding map zoom<commit_after>var config = require('config');
var mapModule = require('map');
var plugins = require('./leaflet_plugins');
var polyUtil = require('./polyline_encoded.js');
var routeboxer = require('./leaflet_routeboxer.js');
var leaflet_label = require('./leaflet_label/leaflet.label-src.js');
var session = require('session');

var center = config.geocode().center.split(',').map(parseFloat)
if (config.map_provider && config.map_provider() !== 'AmigoCloud') {
  L.mapbox.accessToken = config.mapbox_access_token();
}

module.exports = function(el) {
  var map, realtime, southWest, northEast, blurLayer;

  if (config.map_provider && config.map_provider() === 'AmigoCloud') {
    southWest = L.latLng(35.946877085397,-123.480610897013);
    northEast = L.latLng(40.763279543715,-118.789317362500);
    map = (new L.amigo.map(el, {
      amigoLogo: 'right',
      loadAmigoLayers: false,
      inertia: false,
      zoomAnimation: $('.hide-map').css('display') !== 'none',
      maxBounds: L.latLngBounds(southWest, northEast),
      minZoom: 8
    })).setView([center[1], center[0]], config.geocode().zoom);

    L.amigo.auth.setToken(config.support_data_token());

    blurLayer = L.tileLayer(
    'https://www.amigocloud.com/api/v1/users/'+
	'23/projects/3019/datasets/23835/tiles/{z}/{x}/{y}.png?' +
	'token=' + config.support_data_token(),
      {
        name: 'Uncovered Area'
      }
    );

    map.addAuthLayer({
      id: config.mapbox_map_id(),
      accessToken: config.mapbox_access_token(),
      name: 'Gray',
      provider: 'mapbox'
    });
    map.addBaseLayer(L.amigo.AmigoGray);
    map.layersControl.addBaseLayer(
      L.bingLayer(
        config.bing_key(),
	{
	  type: 'Road',
	  attribution: 'Bing Maps'
	}
      ),
      'Bing Road'
    );
    map.layersControl.addOverlay(blurLayer);
    blurLayer.addTo(map);

    module.exports.activeMap = map;

    map.realtimeControl = L.control.toggleRealTime().addTo(map);

    realtime = mapModule.realtime();

  } else {

    map = L.mapbox.map(el, config.mapbox_map_id(), {
      attributionControl: false,
      inertia: false,
      zoomAnimation: false
    }).setView([center[1], center[0]], config.geocode().zoom);
  }

  return map;
};

module.exports.getMap = function () {
  return this.activeMap;
};

module.exports.cleanRoute = function() {
    module.exports.activeRoute.removeLayer();
    module.exports.activeRoute = null;
};

module.exports.polyline_creadas = [];
module.exports.marker_creadas = [];
module.exports.makerpoint_creadas = [];

module.exports.getpolyline_creadas = function () {
  return this.polyline_creadas;
};

module.exports.getMarker_creadas = function () {
  return this.marker_creadas;
};

module.exports.cleanPolyline = function() {
    var polyline_creadas = this.getpolyline_creadas();
    var map = this.activeMap;
    for (i in polyline_creadas) {
        try {
                map.removeLayer(polyline_creadas[i]);

            } catch (e) {
                console.log("problema al eliminar " + e);
            }

  }
  this.polyline_creadas = [];

};

module.exports.cleanMarker = function() {
    var map = this.activeMap;
    for (i in this.marker_creadas) {
        try {
                map.removeLayer(this.marker_creadas[i]);

            } catch (e) {
                console.log("problema al eliminar " + e);
            }
    }

  this.marker_creadas = [];

};

module.exports.cleanMarkerpoint = function() {
    var map = this.activeMap;
    for (i in this.makerpoint_creadas) {
        try {
                map.removeLayer(this.makerpoint_creadas[i]);

            } catch (e) {
                console.log("problema al eliminar " + e);
            }
    }

  this.makerpoint_creadas = [];

};

module.exports.marker_map = function(from, to){
     var IconStart = L.icon({
        iconUrl: 'assets/images/graphics/start.svg',
        iconSize: [40, 55],
        iconAnchor: [20, 50],
        popupAnchor:  [0, -50]
    });
    var IconEnd = L.icon({
        iconUrl: 'assets/images/graphics/end.svg',
        iconSize: [40, 55],
        iconAnchor: [20, 50],
        popupAnchor:  [0, -50]
    });

    var markerform = new L.marker([from[0],from[1]], {icon: IconStart, draggable: true})
        .addTo(this.activeMap);
    var markerto = new L.marker([to[0],to[1]], {icon: IconEnd, draggable: true})
        .addTo(this.activeMap);
    var _this = this;

    markerform.on('dragend', function(e){
       var marker = e.target;
       var result = marker.getLatLng();
       _this.cleanPolyline();
       _this.cleanMarkerpoint();
       var plan = session.plan();

            plan.setAddress('from', result.lng + ',' + result.lat, function(err, rees) {
                plan.updateRoutes();
          });
    });

    markerto.on('dragend', function(e){
       var marker = e.target;
       var result = marker.getLatLng();
       _this.cleanPolyline();
       _this.cleanMarkerpoint();
       var plan = session.plan();
            plan.setAddress('to', result.lng + ',' + result.lat, function(err, rees) {
                plan.updateRoutes();
          });
    });

    this.marker_creadas.push(markerform);
    this.marker_creadas.push(markerto);
};

module.exports.marker_map_point = function(to, map){

    var name = to[2];

    var IconEnd = L.icon({
        iconUrl: 'assets/images/graphics/icono.png',
        iconSize: [20, 20],
        iconAnchor: [0, 0],
        popupAnchor:  [-3, -76]
    });
    var markers = [
      L.marker([to[0], to[1]], {icon: IconEnd}).bindLabel(name, { direction: 'auto' })
    ];

    var layer = L.layerGroup(markers).addTo(map);

     var myZoom = {
      start:  map.getZoom(),
      end: map.getZoom()
    };
    map.on('zoomstart', function(e) {
       myZoom.start = map.getZoom();
        console.log("start zoom 400 ", myZoom.start);
    });
    map.on('zoomend', function(e) {
        myZoom.end = map.getZoom();
         //console.log("End zoom ", myZoom.end);
        var diff = myZoom.start - myZoom.end;
        if (diff > 0) {
            circle.setRadius(circle.getRadius() * 2);
        } else if (diff < 0) {
            circle.setRadius(circle.getRadius() / 2);
        }
    });

    this.makerpoint_creadas.push(layer);
    //this.polyline_creadas.push(circle);
};

module.exports.drawRouteAmigo = function(legs,mode) {
    var route = legs.legGeometry.points;
    var circle_from = [legs.from.lat, legs.from.lon, legs.from.name];
    var circle_to = [legs.to.lat, legs.to.lon, legs.to.name];
    var color = '#000';
    var weight = 5;
    var dasharray= '';

        if (mode=="CAR") {
            color = '#9E9E9E';
            dasharray= '6';
            weight = 3;

        }else if(mode=="BICYCLE") {
            color = '#FF0000';
            dasharray= '6';
            weight = 3;

        }else if(mode=="SUBWAY" || mode=="RAIL") {
            color = '#FF0000';
             weight = 8;
             this.marker_map_point(circle_from, this.activeMap);
             this.marker_map_point(circle_to, this.activeMap);

        }
        else if(mode == "WALK") {
            color = '#0BC8F4';
            dasharray= '6';
             weight = 3;
        }
        else if(mode=="BUS") {
            color = '#FEF0B5';
             weight = 8;
             this.marker_map_point(circle_from, this.activeMap);
             this.marker_map_point(circle_to, this.activeMap);
        }

       var color_options = {
            color: color,
            opacity: 1,
            weight: weight,
            dashArray: dasharray
        };

      route = new L.Polyline(L.PolylineUtil.decode(route, 5), color_options);
      this.polyline_creadas.push(route);
      var boxes = L.RouteBoxer.box(route, 5);
      //var bounds = new L.LatLngBounds([]);
      //var bounds = [];
      var boxpolys = new Array(boxes.length);
      /*
      for (var i = 0; i < boxes.length; i++) {
        //bounds.extend(boxes[i]);
        bounds.push(boxes[i]);
      }*/

      route.addTo(this.activeMap);
      //this.activeMap.fitBounds(bounds);
};

<|endoftext|>


<commit_before>dojo.provide("dojox.charting.plot2d._PlotEvents");

dojo.declare("dojox.charting.plot2d._PlotEvents", null, {
	constructor: function(){
		this._shapeEvents = [];
		this._eventSeries = {};
	},
	destroy: function(){
		//	summary:
		//		Destroy any internal elements and event handlers.
		this.resetEvents();
		this.inherited(arguments);
	},
	plotEvent: function(o){
		//	summary:
		//		Stub function for use by specific plots.
		//	o: Object
		//		An object intended to represent event parameters.
	},
	raiseEvent: function(o){
		//	summary:
		//		Raises events in predefined order
		//	o: Object
		//		An object intended to represent event parameters.
		this.plotEvent(o);
		var t = dojo.delegate(o);
		t.originalEvent = o.type;
		t.originalPlot  = o.plot;
		t.type = "onindirect";
		dojo.forEach(this.chart.stack, function(plot){
			if(plot !== this && plot.plotEvent){
				t.plot = plot;
				plot.plotEvent(t);
			}
		}, this);
	},
	connect: function(object, method){
		//	summary:
		//		Helper function to connect any object's method to our plotEvent.
		//	object: Object
		//		The object to connect to.
		//	method: String|Function
		//		The method to fire when our plotEvent is fired.
		//	returns: Array
		//		The handle as returned from dojo.connect (see dojo.connect).
		this.dirty = true;
		return dojo.connect(this, "plotEvent", object, method);	//	Array
	},
	events: function(){
		//	summary:
		//		Find out if any event handlers have been connected to our plotEvent.
		//	returns: Boolean
		//		A flag indicating that there are handlers attached.
		var ls = this.plotEvent._listeners;
		if(!ls || !ls.length){ return false; }
		for(var i in ls){
			if(!(i in Array.prototype)){
				return true;
			}
		}
		return false;
	},
	resetEvents: function(){
		//	summary:
		//		Reset all events attached to our plotEvent (i.e. disconnect).
		if(this._shapeEvents.length){
			dojo.forEach(this._shapeEvents, function(item){
				item.shape.disconnect(item.handle);
			});
			this._shapeEvents = [];
		}
		this.raiseEvent({type: "onplotreset", plot: this});
	},
	_connectSingleEvent: function(o, eventName){
		this._shapeEvents.push({
			shape:  o.shape,
			handle: o.shape.connect(eventName, this, function(e){
				o.type  = eventName;
				o.event = e;
				this.raiseEvent(o);
			})
		});
	},
	_connectEvents: function(o){
		o.chart = this.chart;
		o.plot  = this;
		o.hAxis = this.hAxis || null;
		o.vAxis = this.vAxis || null;
		this._connectSingleEvent(o, "onmouseover");
		this._connectSingleEvent(o, "onmouseout");
		this._connectSingleEvent(o, "onclick");
	},
	_reconnectEvents: function(seriesName){
		var a = this._eventSeries[seriesName];
		if(a){
			dojo.forEach(a, this._connectEvents, this);
		}
	},
	fireEvent: function(seriesName, eventName, index){
		var s = this._eventSeries[seriesName];
		if(s && s.length && index < s.length){
			var o = s[index];
			o.type  = eventName;
			o.event = null;
			this.raiseEvent(o);
		}
	}
});
<commit_msg>charting: added an optional event object for programmatic events, thx vmware!, refs #11374.<commit_after>dojo.provide("dojox.charting.plot2d._PlotEvents");

dojo.declare("dojox.charting.plot2d._PlotEvents", null, {
	constructor: function(){
		this._shapeEvents = [];
		this._eventSeries = {};
	},
	destroy: function(){
		//	summary:
		//		Destroy any internal elements and event handlers.
		this.resetEvents();
		this.inherited(arguments);
	},
	plotEvent: function(o){
		//	summary:
		//		Stub function for use by specific plots.
		//	o: Object
		//		An object intended to represent event parameters.
	},
	raiseEvent: function(o){
		//	summary:
		//		Raises events in predefined order
		//	o: Object
		//		An object intended to represent event parameters.
		this.plotEvent(o);
		var t = dojo.delegate(o);
		t.originalEvent = o.type;
		t.originalPlot  = o.plot;
		t.type = "onindirect";
		dojo.forEach(this.chart.stack, function(plot){
			if(plot !== this && plot.plotEvent){
				t.plot = plot;
				plot.plotEvent(t);
			}
		}, this);
	},
	connect: function(object, method){
		//	summary:
		//		Helper function to connect any object's method to our plotEvent.
		//	object: Object
		//		The object to connect to.
		//	method: String|Function
		//		The method to fire when our plotEvent is fired.
		//	returns: Array
		//		The handle as returned from dojo.connect (see dojo.connect).
		this.dirty = true;
		return dojo.connect(this, "plotEvent", object, method);	//	Array
	},
	events: function(){
		//	summary:
		//		Find out if any event handlers have been connected to our plotEvent.
		//	returns: Boolean
		//		A flag indicating that there are handlers attached.
		var ls = this.plotEvent._listeners;
		if(!ls || !ls.length){ return false; }
		for(var i in ls){
			if(!(i in Array.prototype)){
				return true;
			}
		}
		return false;
	},
	resetEvents: function(){
		//	summary:
		//		Reset all events attached to our plotEvent (i.e. disconnect).
		if(this._shapeEvents.length){
			dojo.forEach(this._shapeEvents, function(item){
				item.shape.disconnect(item.handle);
			});
			this._shapeEvents = [];
		}
		this.raiseEvent({type: "onplotreset", plot: this});
	},
	_connectSingleEvent: function(o, eventName){
		this._shapeEvents.push({
			shape:  o.shape,
			handle: o.shape.connect(eventName, this, function(e){
				o.type  = eventName;
				o.event = e;
				this.raiseEvent(o);
				o.event = null;
			})
		});
	},
	_connectEvents: function(o){
		o.chart = this.chart;
		o.plot  = this;
		o.hAxis = this.hAxis || null;
		o.vAxis = this.vAxis || null;
		this._connectSingleEvent(o, "onmouseover");
		this._connectSingleEvent(o, "onmouseout");
		this._connectSingleEvent(o, "onclick");
	},
	_reconnectEvents: function(seriesName){
		var a = this._eventSeries[seriesName];
		if(a){
			dojo.forEach(a, this._connectEvents, this);
		}
	},
	fireEvent: function(seriesName, eventName, index, eventObject){
		//	summary:
		//		Emulates firing an event for a given data value (specified by
		//		an index) of a given series.
		//	seriesName: String:
		//		Series name.
		//	eventName: String:
		//		Event name to emulate.
		//	index:	Number:
		//		Valid data value index used to raise an event.
		//	eventObject: Object?:
		//		Optional event object. Especially useful for synthetic events.
		//		Default: null.
		var s = this._eventSeries[seriesName];
		if(s && s.length && index < s.length){
			var o = s[index];
			o.type  = eventName;
			o.event = eventObject || null;
			this.raiseEvent(o);
			o.event = null;
		}
	}
});
<|endoftext|>


<commit_before>/*jslint es6 */

let locationData = {
    latitude: 0,
    longitude: 0
};

let riseSet = {
    sunrise: "",
    sunset: ""
};

let babylonianDay = {
    lengthOfDayHour: "",
    numberOfSecondsinDay: "",
    dayHours: [],
    nightHours: []
};

let locationError = false;

function round(value, decimals) {
    "use strict";
    return Number(Math.round(value + "e" + decimals) + "e-" + decimals);
}

function getGeoIp() {
    "use strict";
    let url = "https://freegeoip.net/json/";
    fetch(url, {
            headers: {}
        })
        .then(
            function(response) {
                if (response.status !== 200) {
                    console.log("Looks like there was a problem: " + response.status);
                    return;
                }
                //console.log(response);
                response.json().then(function(data) {
                    console.log("Data for: " + data.city + ", " + data.country_name);
                    locationData.latitude = data.latitude;
                    locationData.longitude = data.longitude;

                    let d = new Date();
                    let date = d.getFullYear() + "-" + (d.getMonth()+1) + "-" + d.getDate();

                    getSunriseSunset(locationData.latitude, locationData.longitude, date);
                });
            }
        )
        .catch(function(err) {
            console.log("Location Fetch Error", err);
            locationError = true;
        });

}

function getSunriseSunset(lat, long, date) {
    let url = "https://api.sunrise-sunset.org/json";
    url = url + "?lat=" + lat; //locationData.latitude;
    url = url + "&lng=" + long; //locationData.longitude;
    url = url + "&date=" + date;
    url = url + "&formatted=0";
    fetch(url, {
            headers: {}
        })
        .then(
            function(response) {
                if (response.status !== 200) {
                    console.log("Looks like there was a problem: " + response.status);
                    return;
                }
                //console.log(response);
                response.json().then(function(data) {
                    //console.log(data);
                    riseSet.sunrise = new Date(data.results.sunrise);
                    riseSet.sunset = new Date(data.results.sunset);
                    console.log("Sunrise: " + riseSet.sunrise);
                    console.log("Sunset: " + riseSet.sunset);

                    computeBabylonianTime();
                });
            }
        )
        .catch(function(err) {
            console.log("Sunrise/Sunset Fetch Error", err);
        });
}

function computeBabylonianTime() {
    babylonianDay.lDay = (riseSet.sunset - riseSet.sunrise);
    // Length of night is just number of milliseconds in a day minus length of daylight
    babylonianDay.lNight = 1000 * 60 * 60 * 24 - babylonianDay.lDay;
    babylonianDay.lDayHour = babylonianDay.lDay / 12;
    babylonianDay.lNightHour = babylonianDay.lNight / 12;
    hour = riseSet.sunrise;
    for (i = 0; i < 12; i++) {
        if (i < 6) {
            part = "ASR";
        } else {
            part = "BST";
        }
        babylonianDay.dayHours[i] = {
            realtime: hour,
            hour: hour.getHours(),
            minutes: hour.getMinutes(),
            name: i.toString(),
            ush: Math.floor(babylonianDay.lDayHour / 240000),
            gar: Math.floor(babylonianDay.lDayHour / 240000) * 60,
            part: part
        };
        hour = new Date(hour.getTime() + babylonianDay.lDayHour);
    }
    hour = riseSet.sunset;
    for (i = 0; i < 12; i++) {
        if (i < 6) {
            part = "AST";
        } else {
            part = "BSR";
        }
        babylonianDay.nightHours[i] = {
            realtime: hour,
            hour: hour.getHours(),
            minutes: hour.getMinutes(),
            name: i.toString(),
            ush: Math.floor(babylonianDay.lNightHour / 240000),
            gar: Math.floor(babylonianDay.lNightHour / 240000) * 60,
            part: part
        };
        hour = new Date(hour.getTime() + babylonianDay.lNightHour);
    }

    document.getElementById("lDayHour").innerHTML = round(babylonianDay.lDayHour / 60000, 2);
    document.getElementById("lNightHour").innerHTML = round(babylonianDay.lNightHour / 60000, 2);
    document.getElementById("numberOfUsh").innerHTML = babylonianDay.dayHours[0].ush;

    console.log("Done setting up!");
}

function setup() {
    getGeoIp();
}

setup();

window.onload = function() {
    hour = "00";
    ush = "00";
    gar = "00";
    part = "...";

    window.setInterval(function() {

        if (locationError == true) {
            document.getElementById("error").innerHTML = "Location Error: Disable your Adblocker";
            return;
        }

        // Get "now"
        d = new Date();

        // We need to find out if it"s day or night
        if (riseSet.sunrise - d < 0 && riseSet.sunset - d > 0) {
            daytime = true;
            // Now we need to find the hour we"re in
            for (i = 0; i < babylonianDay.dayHours.length; i++) {
                if (d - babylonianDay.dayHours[i].realtime < babylonianDay.lDayHour) {
                    part = babylonianDay.dayHours[i].part;
                    ush = Math.floor((d - babylonianDay.dayHours[i].realtime) / (babylonianDay.lDayHour / babylonianDay.dayHours[i].ush));
                    gar = Math.floor((d - (babylonianDay.dayHours[i].realtime.getTime() + Math.floor(ush * babylonianDay.lDayHour / babylonianDay.dayHours[i].ush))) / (babylonianDay.lDayHour / babylonianDay.dayHours[i].gar))
                    hour = i;

                    if (hour < 10) {
                        hour = "0" + hour.toString();
                    }
                    if (ush < 10) {
                        ush = "0" + ush.toString();
                    }
                    if (gar < 10) {
                        gar = "0" + gar.toString();
                    }
                    break;
                }
            }

        } else {
            daytime = false;
            // Now we need to find the hour we"re in
            for (i = 0; i < babylonianDay.nightHours.length; i++) {
                if (d - babylonianDay.nightHours[i].realtime < babylonianDay.lNightHour) {
                    part = babylonianDay.nightHours[i].part;
                    ush = Math.floor((d - babylonianDay.nightHours[i].realtime) / (babylonianDay.lNightHour / babylonianDay.nightHours[i].ush));
                    // (new Date() - (babylonianDay.dayHours[3].realtime.getTime() + Math.floor(ush * (babylonianDay.lDayHour / babylonianDay.dayHours[3].ush)))) / 4271
                    gar = Math.floor((d - (babylonianDay.nightHours[i].realtime.getTime() + Math.floor(ush * babylonianDay.lNightHour / babylonianDay.nightHours[i].ush))) / (babylonianDay.lNightHour / babylonianDay.nightHours[i].gar))
                    hour = i;

                    // Check if we're past midnight and have a negative ush

                    if (ush < 0) {
                        getSunriseSunset(locationData.latitude, locationData.longitude, "yesterday")
                    }

                    if (hour < 10) {
                        hour = "0" + hour.toString();
                    }
                    if (ush < 10) {
                        ush = "0" + ush.toString();
                    }
                    if (gar < 10) {
                        gar = "0" + gar.toString();
                    }
                    break;
                }
            }

        }
        document.getElementById("hour").innerHTML = hour;
        document.getElementById("ush").innerHTML = ush;
        document.getElementById("gar").innerHTML = gar;
        document.getElementById("part").innerHTML = part;
    }, 500);

}<commit_msg>fixed transition to sunrise<commit_after>/*jslint es6 */

let locationData = {
    latitude: 0,
    longitude: 0
};

let riseSet = {
    sunrise: "",
    sunset: ""
};

let babylonianDay = {
    lengthOfDayHour: "",
    numberOfSecondsinDay: "",
    dayHours: [],
    nightHours: []
};

let locationError = false;

function round(value, decimals) {
    "use strict";
    return Number(Math.round(value + "e" + decimals) + "e-" + decimals);
}

function getGeoIp() {
    "use strict";
    let url = "https://freegeoip.net/json/";
    fetch(url, {
            headers: {}
        })
        .then(
            function(response) {
                if (response.status !== 200) {
                    console.log("Looks like there was a problem: " + response.status);
                    return;
                }
                //console.log(response);
                response.json().then(function(data) {
                    console.log("Data for: " + data.city + ", " + data.country_name);
                    locationData.latitude = data.latitude;
                    locationData.longitude = data.longitude;

                    let d = new Date();
                    let date = d.getFullYear() + "-" + (d.getMonth()+1) + "-" + d.getDate();

                    getSunriseSunset(locationData.latitude, locationData.longitude, date);
                });
            }
        )
        .catch(function(err) {
            console.log("Location Fetch Error", err);
            locationError = true;
        });

}

function getSunriseSunset(lat, long, date) {
    let url = "https://api.sunrise-sunset.org/json";
    url = url + "?lat=" + lat; //locationData.latitude;
    url = url + "&lng=" + long; //locationData.longitude;
    url = url + "&date=" + date;
    url = url + "&formatted=0";
    fetch(url, {
            headers: {}
        })
        .then(
            function(response) {
                if (response.status !== 200) {
                    console.log("Looks like there was a problem: " + response.status);
                    return;
                }
                //console.log(response);
                response.json().then(function(data) {
                    //console.log(data);
                    sunrise = data.results.sunrise;
                    sunset = data.results.sunset;

                    riseSet.sunrise = new Date(data.results.sunrise);
                    riseSet.sunset = new Date(data.results.sunset);
                    console.log("Sunrise: " + riseSet.sunrise);
                    console.log("Sunset: " + riseSet.sunset);

                    computeBabylonianTime();
                });
            }
        )
        .catch(function(err) {
            console.log("Sunrise/Sunset Fetch Error", err);
        });
}

function computeBabylonianTime() {
    babylonianDay.lDay = (riseSet.sunset - riseSet.sunrise);
    // Length of night is just number of milliseconds in a day minus length of daylight
    babylonianDay.lNight = 1000 * 60 * 60 * 24 - babylonianDay.lDay;
    babylonianDay.lDayHour = babylonianDay.lDay / 12;
    babylonianDay.lNightHour = babylonianDay.lNight / 12;
    hour = riseSet.sunrise;
    for (i = 0; i < 12; i++) {
        if (i < 6) {
            part = "ASR";
        } else {
            part = "BST";
        }
        babylonianDay.dayHours[i] = {
            realtime: hour,
            hour: hour.getHours(),
            minutes: hour.getMinutes(),
            name: i.toString(),
            ush: Math.floor(babylonianDay.lDayHour / 240000),
            gar: Math.floor(babylonianDay.lDayHour / 240000) * 60,
            part: part
        };
        hour = new Date(hour.getTime() + babylonianDay.lDayHour);
    }
    hour = riseSet.sunset;
    for (i = 0; i < 12; i++) {
        if (i < 6) {
            part = "AST";
        } else {
            part = "BSR";
        }
        babylonianDay.nightHours[i] = {
            realtime: hour,
            hour: hour.getHours(),
            minutes: hour.getMinutes(),
            name: i.toString(),
            ush: Math.floor(babylonianDay.lNightHour / 240000),
            gar: Math.floor(babylonianDay.lNightHour / 240000) * 60,
            part: part
        };
        hour = new Date(hour.getTime() + babylonianDay.lNightHour);
    }

    document.getElementById("lDayHour").innerHTML = round(babylonianDay.lDayHour / 60000, 2);
    document.getElementById("lNightHour").innerHTML = round(babylonianDay.lNightHour / 60000, 2);
    document.getElementById("numberOfUsh").innerHTML = babylonianDay.dayHours[0].ush;

    console.log("Done setting up!");
}

function setup() {
    getGeoIp();
}

setup();

window.onload = function() {
    hour = "00";
    ush = "00";
    gar = "00";
    part = "...";

    window.setInterval(function() {

        if (locationError == true) {
            document.getElementById("error").innerHTML = "Location Error: Disable your Adblocker";
            return;
        }

        // Get "now"
        d = new Date();

        // We need to find out if it"s day or night
        if (riseSet.sunrise - d < 0 && riseSet.sunset - d > 0) {
            daytime = true;
            // Now we need to find the hour we"re in
            for (i = 0; i < babylonianDay.dayHours.length; i++) {
                if (d - babylonianDay.dayHours[i].realtime < babylonianDay.lDayHour) {
                    part = babylonianDay.dayHours[i].part;
                    ush = Math.floor((d - babylonianDay.dayHours[i].realtime) / (babylonianDay.lDayHour / babylonianDay.dayHours[i].ush));
                    gar = Math.floor((d - (babylonianDay.dayHours[i].realtime.getTime() + Math.floor(ush * babylonianDay.lDayHour / babylonianDay.dayHours[i].ush))) / (babylonianDay.lDayHour / babylonianDay.dayHours[i].gar))
                    hour = i;

                    if (ush > babylonianDay.dayHours[0].ush) {
                        console.log("Need to grab today's sunrise information");
                        getSunriseSunset(locationData.latitude, locationData.longitude, "today");
                    }

                    if (hour < 10) {
                        hour = "0" + hour.toString();
                    }
                    if (ush < 10) {
                        ush = "0" + ush.toString();
                    }
                    if (gar < 10) {
                        gar = "0" + gar.toString();
                    }
                    break;
                }
            }

        } else {
            daytime = false;
            // Now we need to find the hour we"re in
            for (i = 0; i < babylonianDay.nightHours.length; i++) {
                if (d - babylonianDay.nightHours[i].realtime < babylonianDay.lNightHour) {
                    part = babylonianDay.nightHours[i].part;
                    ush = Math.floor((d - babylonianDay.nightHours[i].realtime) / (babylonianDay.lNightHour / babylonianDay.nightHours[i].ush));
                    // (new Date() - (babylonianDay.dayHours[3].realtime.getTime() + Math.floor(ush * (babylonianDay.lDayHour / babylonianDay.dayHours[3].ush)))) / 4271
                    gar = Math.floor((d - (babylonianDay.nightHours[i].realtime.getTime() + Math.floor(ush * babylonianDay.lNightHour / babylonianDay.nightHours[i].ush))) / (babylonianDay.lNightHour / babylonianDay.nightHours[i].gar))
                    hour = i;

                    // Check if we're past midnight and have a negative ush

                    if (ush < 0) {
                        console.log("Need to grab yesterday's sunset information.");
                        getSunriseSunset(locationData.latitude, locationData.longitude, "yesterday")
                    }

                    if (hour < 10) {
                        hour = "0" + hour.toString();
                    }
                    if (ush < 10) {
                        ush = "0" + ush.toString();
                    }
                    if (gar < 10) {
                        gar = "0" + gar.toString();
                    }
                    break;
                }
            }

        }
        document.getElementById("hour").innerHTML = hour;
        document.getElementById("ush").innerHTML = ush;
        document.getElementById("gar").innerHTML = gar;
        document.getElementById("part").innerHTML = part;
    }, 500);

}<|endoftext|>