<commit_before>const _ = require('lodash');
const numeral = require('numeral');
const getPackagesDownloadsComparisons = require('./getPackagesDownloadsComparisons');

const formatDownloadCount = x => numeral(x).format('0,0');
const formatPercentage = x => numeral(x).format('+0.0%');

const getPackagesDescriptions = async packages => {
  let description;

  const comparisons = await getPackagesDownloadsComparisons(packages);
  const trimmedComparisons = _.compact(comparisons);

  if (trimmedComparisons.length === 0) {
    description = `Compare npm package download counts over time to spot trends and see which to use and which to avoid.`;
  } else if (trimmedComparisons.length === 1) {
    const packageStats = trimmedComparisons[0];
    description = `${
      packageStats.package
    } downloads - weekly: ${formatDownloadCount(
      packageStats.weekly.downloads,
    )} (${formatPercentage(
      packageStats.weekly.change,
    )}), monthly: ${formatDownloadCount(
      packageStats.monthly.downloads,
    )} (${formatPercentage(packageStats.monthly.change)})`;
  } else {
    description =
      'Comparison of monthly downloads - ' +
      _.orderBy(trimmedComparisons, x => x.monthly.downloads, 'desc')
        .map(
          packageStats =>
            `${packageStats.package}: ${formatDownloadCount(
              packageStats.monthly.downloads,
            )} (${formatPercentage(packageStats.monthly.change)})`,
        )
        .join(', ') +
      '; weekly downloads - ' +
      _.orderBy(trimmedComparisons, x => x.weekly.downloads, 'desc')
        .map(
          packageStats =>
            `${packageStats.package}: ${formatDownloadCount(
              packageStats.weekly.downloads,
            )} (${formatPercentage(packageStats.weekly.change)})`,
        )
        .join(', ');
  }

  return description;
};

module.exports = getPackagesDescriptions;
<commit_msg>✏ don't show monthly downloads in description<commit_after>const _ = require('lodash');
const numeral = require('numeral');
const getPackagesDownloadsComparisons = require('./getPackagesDownloadsComparisons');

const formatDownloadCount = x => numeral(x).format('0,0');
const formatPercentage = x => numeral(x).format('+0.0%');

const getPackagesDescriptions = async packages => {
  let description;

  const comparisons = await getPackagesDownloadsComparisons(packages);
  const trimmedComparisons = _.compact(comparisons);

  if (trimmedComparisons.length === 0) {
    description = `Compare npm package download counts over time to spot trends and see which to use and which to avoid.`;
  } else if (trimmedComparisons.length === 1) {
    const packageStats = trimmedComparisons[0];
    description = `${
      packageStats.package
    } downloads - weekly: ${formatDownloadCount(
      packageStats.weekly.downloads,
    )} (${formatPercentage(
      packageStats.weekly.change,
    )}), monthly: ${formatDownloadCount(
      packageStats.monthly.downloads,
    )} (${formatPercentage(packageStats.monthly.change)})`;
  } else {
    description =
      'Weekly downloads - ' +
      _.orderBy(trimmedComparisons, x => x.weekly.downloads, 'desc')
        .map(
          packageStats =>
            `${packageStats.package}: ${formatDownloadCount(
              packageStats.weekly.downloads,
            )} (${formatPercentage(packageStats.weekly.change)})`,
        )
        .join(', ');
  }

  return description;
};

module.exports = getPackagesDescriptions;
<|endoftext|>


<commit_before>(function () {

    var Replay = function (context, targetgl) {
        this.context = context;
        this.gl = targetgl;

        this.currentFrame = null;
        this.callIndex = 0;

        this.mirrorResources = {};

        // TODO: better event system
        this.onStep = function () { };
    };

    Replay.prototype.reset = function () {
        for (var n in this.mirrorResources) {
            var resource = this.mirrorResources[n];
            //resource.dispose();
        }
        this.mirrorResources = {};

        this.currentFrame = null;
        this.callIndex = 0;
    };

    Replay.prototype.runFrame = function (frame) {
        this.beginFrame(frame);
        this.stepUntilEnd();
    };

    Replay.prototype.beginFrame = function (frame) {
        var gl = this.gl;

        this.reset();

        this.currentFrame = frame;

        // Upload all resources used in this frame
        for (var n = 0; n < frame.resourcesRead.length; n++) {
            var resource = frame.resourcesRead[n];
            var mirror = resource.createMirror(gl);

            mirror.source = resource;
            resource.mirror = mirror;
            this.mirrorResources[resource.id] = mirror;
        }

        // Apply state
        frame.initialState.apply(gl, true);

        this.callIndex = 0;
        if (this.onStep) {
            this.onStep(this, this.currentFrame, this.callIndex);
        }
    };

    function getTargetValue(value) {
        if (value) {
            if (value.trackedObject) {
                return value.trackedObject.mirror;
            } else {
                return value;
            }
        } else {
            return value;
        }
    };

    Replay.prototype.step = function (callIndex) {
        var gl = this.gl;

        if (this.currentFrame == null) {
            return false;
        }
        if (this.callIndex + 1 >= this.currentFrame.calls.length) {
            return false;
        }

        this.callIndex = callIndex ? callIndex : (++this.callIndex);

        var call = this.currentFrame.calls[this.callIndex];

        var args = [];
        for (var n = 0; n < call.args.length; n++) {
            args[n] = getTargetValue(call.args[n]);

            if (args[n] && args[n].uniformName) {
                // TODO: faster way?
                args[n] = gl.getUniformLocation(args[n].sourceProgram.trackedObject.mirror, args[n].uniformName);
            }
        }

        // TODO: handle result?
        call.fn.apply(gl, args);

        // TODO: handle error?

        if (this.onStep) {
            this.onStep(this, this.currentFrame, this.callIndex);
        }

        return true;
    };

    Replay.prototype.stepUntil = function (callIndex) {
        if (this.callIndex > callIndex) {
            var frame = this.currentFrame;
            this.reset();
            this.beginFrame(frame);
        }
        while (this.callIndex < callIndex) {
            if (this.step() == false) {
                return false;
            }
        }
        return true;
    };

    Replay.prototype.stepUntilError = function () {
    };

    Replay.prototype.stepUntilDraw = function () {
    };

    Replay.prototype.stepUntilEnd = function () {
        while (this.step());
    };

    Replay.prototype.stepBack = function () {
        if (this.callIndex == 0) {
            return false;
        }
        return this.stepUntil(this.callIndex - 1);
    };

    gli = gli || {};
    gli.Replay = Replay;

})();
<commit_msg>stop leaking resources on step<commit_after>(function () {

    var Replay = function (context, targetgl) {
        this.context = context;
        this.gl = targetgl;

        this.currentFrame = null;
        this.callIndex = 0;

        this.mirrorResources = {};

        // TODO: better event system
        this.onStep = function () { };
    };

    Replay.prototype.reset = function () {
        for (var n in this.mirrorResources) {
            var resource = this.mirrorResources[n];
            resource.dispose();
        }
        this.mirrorResources = {};

        this.currentFrame = null;
        this.callIndex = 0;
    };

    Replay.prototype.runFrame = function (frame) {
        this.beginFrame(frame);
        this.stepUntilEnd();
    };

    Replay.prototype.beginFrame = function (frame) {
        var gl = this.gl;

        this.reset();

        this.currentFrame = frame;

        // Upload all resources used in this frame
        for (var n = 0; n < frame.resourcesRead.length; n++) {
            var resource = frame.resourcesRead[n];
            var mirror = resource.createMirror(gl);

            mirror.source = resource;
            resource.mirror = mirror;
            this.mirrorResources[resource.id] = mirror;
        }

        // Apply state
        frame.initialState.apply(gl, true);

        this.callIndex = 0;
        if (this.onStep) {
            this.onStep(this, this.currentFrame, this.callIndex);
        }
    };

    function getTargetValue(value) {
        if (value) {
            if (value.trackedObject) {
                return value.trackedObject.mirror;
            } else {
                return value;
            }
        } else {
            return value;
        }
    };

    Replay.prototype.step = function (callIndex) {
        var gl = this.gl;

        if (this.currentFrame == null) {
            return false;
        }
        if (this.callIndex + 1 >= this.currentFrame.calls.length) {
            return false;
        }

        this.callIndex = callIndex ? callIndex : (++this.callIndex);

        var call = this.currentFrame.calls[this.callIndex];

        var args = [];
        for (var n = 0; n < call.args.length; n++) {
            args[n] = getTargetValue(call.args[n]);

            if (args[n] && args[n].uniformName) {
                // TODO: faster way?
                args[n] = gl.getUniformLocation(args[n].sourceProgram.trackedObject.mirror, args[n].uniformName);
            }
        }

        // TODO: handle result?
        call.fn.apply(gl, args);

        // TODO: handle error?

        if (this.onStep) {
            this.onStep(this, this.currentFrame, this.callIndex);
        }

        return true;
    };

    Replay.prototype.stepUntil = function (callIndex) {
        if (this.callIndex > callIndex) {
            var frame = this.currentFrame;
            this.reset();
            this.beginFrame(frame);
        }
        while (this.callIndex < callIndex) {
            if (this.step() == false) {
                return false;
            }
        }
        return true;
    };

    Replay.prototype.stepUntilError = function () {
    };

    Replay.prototype.stepUntilDraw = function () {
    };

    Replay.prototype.stepUntilEnd = function () {
        while (this.step());
    };

    Replay.prototype.stepBack = function () {
        if (this.callIndex == 0) {
            return false;
        }
        return this.stepUntil(this.callIndex - 1);
    };

    gli = gli || {};
    gli.Replay = Replay;

})();
<|endoftext|>


<commit_before>MapEntity.Context = new function() {
    var self = this;
    var last_sort = {};
    self.getFullContext = function(map, kwargs) {
        var context = {},
            filter = kwargs && kwargs.filter,
            datatable = kwargs && kwargs.datatable;

        // Map view
        context['mapview'] = {'lat': map.getCenter().lat, 'lng': map.getCenter().lng, 'zoom': map.getZoom()};

        // layers shown by name
        var layers = [];
        $('form.leaflet-control-layers-list input:checked').each(function () {
            layers.push($.trim($(this).parent().text()));
        });
        context['maplayers'] = layers;

        // Form filters
        if (filter) {
            // exclude bbox field, since it comes from the map view.
            var fields = $($(filter).serializeArray()).filter(function (){ return this.name != 'bbox';});
            context['filter'] = $.param(fields);
        }

        // Sort columns
        if (datatable) {
            context['sortcolumns'] = last_sort;
            context['sortcolumns'][$('body').attr('data-appname')] = datatable.fnSettings().aaSorting;
        }

        // Extra-info, not restored so far but can be useful for screenshoting
        context['fullurl'] = window.location.toString();
        context['url'] = window.location.pathname.toString();
        context['viewport'] = {'width': $(window).width(), 'height': $(window).height()};
        context['mapsize'] = {'width': $('.map-panel').width(), 'height': $('.map-panel').height()};

        // Mark timestamp
        context['timestamp'] = new Date().getTime();

        return context;
    };

    self.saveFullContext = function(map, kwargs) {
        var prefix = kwargs.prefix || '',
            serialized = JSON.stringify(self.getFullContext(map, kwargs));
        localStorage.setItem(prefix + 'map-context', serialized);
    };

    self.__loadFullContext = function(kwargs) {
        if (!kwargs) kwargs = {};
        var prefix = kwargs.prefix || '',
            context = localStorage.getItem(prefix + 'map-context');
        if (context)
            return JSON.parse(context);
        return null;
    };

    self.restoreLatestMapView = function (map, prefixes, kwargs) {
        var latest = null;
        for (var i=0; i<prefixes.length; i++) {
            var prefix = prefixes[i],
                context = self.__loadFullContext($.extend(kwargs, {prefix: prefix}));
            if (!latest || (context && context.timestamp && context.timestamp > latest.timestamp)) {
                latest = context;
                console.debug(JSON.stringify(context));
            }
        }
        return self.restoreMapView(map, latest, kwargs);
    };

    self.restoreMapView = function(map, context, kwargs) {
        if (!context) context = self.__loadFullContext(kwargs);
        if (context && context.mapview) {
            map.setView(L.latLng(context.mapview.lat, context.mapview.lng), context.mapview.zoom);
            return true;
        } else {
            map.fitBounds(map.resetviewControl.getBounds());
            var maxZoom = $(map._container).data('fitmaxzoom');
            if (map.getZoom() > maxZoom) {
                console.log('Limited zoom to ', maxZoom, '. Was ', map.getZoom());
                map.setZoom(maxZoom);
            }
        }
        return false;
    };

    self.restoreFullContext = function(map, context, kwargs) {
        if (!kwargs) kwargs = {};
        var filter = kwargs.filter,
            datatable = kwargs.datatable,
            objectsname = kwargs.objectsname;

        if (!context || typeof context != 'object') {
            // If not received from URL, load from LocalStorage
            context = self.__loadFullContext(kwargs);
        }
        if (!context) {
            console.warn("No context found.");
            map.fitBounds(map.options.maxBounds);
            return;  // No context, no restore.
        }

        if (context.mapsize) {
            // Override min-height style
            map._container.style.minHeight = '0';
            // Force map size
            if (context.mapsize.width && context.mapsize.width > 0)
                $('.map-panel').width(context.mapsize.width);
            if (context.mapsize.height && context.mapsize.height > 0)
                $('.map-panel').height(context.mapsize.height);
            map.invalidateSize();
        }

        if (filter && context.filter) {
            $(filter).deserialize(context.filter);
            $(filter).find('select').trigger("liszt:updated");
        }

        if (datatable && context.sortcolumns) {
            if ($('body').attr('data-appname') in context.sortcolumns) {
                datatable.fnSort(context.sortcolumns[$('body').attr('data-appname')]);
                }
            last_sort = context['sortcolumns'];
        }

        // This map view change will refresh the list
        self.restoreMapView(map, context, kwargs);

        // Show layers by their name
        if (context.maplayers) {
            var layers = context.maplayers;
            layers.push(objectsname);
            $('form.leaflet-control-layers-list input').each(function () {
                if ($.trim($(this).parent().text()) != objectsname) {
                    $(this).removeAttr('checked');
                }
            });
            for(var i=0; i<layers.length; i++) {
                var layer = layers[i];
                $('form.leaflet-control-layers-list input').each(function () {
                    if ($.trim($(this).parent().text()) == layer) {
                        $(this).attr('checked', 'checked');
                    }
                });
            }
            if ((map.layerscontrol !== undefined)  && !!map.layerscontrol._map) {
                map.layerscontrol._onInputClick();
            }
        }

        if (context.print) {
            // Disable tiles animations when screenshoting
            $(map._container).removeClass('leaflet-fade-anim');
        }
    };
};
<commit_msg>Replace appname by modelname<commit_after>MapEntity.Context = new function() {
    var self = this;
    var last_sort = {};
    self.getFullContext = function(map, kwargs) {
        var context = {},
            filter = kwargs && kwargs.filter,
            datatable = kwargs && kwargs.datatable;

        // Map view
        context['mapview'] = {'lat': map.getCenter().lat, 'lng': map.getCenter().lng, 'zoom': map.getZoom()};

        // layers shown by name
        var layers = [];
        $('form.leaflet-control-layers-list input:checked').each(function () {
            layers.push($.trim($(this).parent().text()));
        });
        context['maplayers'] = layers;

        // Form filters
        if (filter) {
            // exclude bbox field, since it comes from the map view.
            var fields = $($(filter).serializeArray()).filter(function (){ return this.name != 'bbox';});
            context['filter'] = $.param(fields);
        }

        // Sort columns
        if (datatable) {
            context['sortcolumns'] = last_sort;
            context['sortcolumns'][$('body').attr('data-modelname')] = datatable.fnSettings().aaSorting;
        }

        // Extra-info, not restored so far but can be useful for screenshoting
        context['fullurl'] = window.location.toString();
        context['url'] = window.location.pathname.toString();
        context['viewport'] = {'width': $(window).width(), 'height': $(window).height()};
        context['mapsize'] = {'width': $('.map-panel').width(), 'height': $('.map-panel').height()};

        // Mark timestamp
        context['timestamp'] = new Date().getTime();

        return context;
    };

    self.saveFullContext = function(map, kwargs) {
        var prefix = kwargs.prefix || '',
            serialized = JSON.stringify(self.getFullContext(map, kwargs));
        localStorage.setItem(prefix + 'map-context', serialized);
    };

    self.__loadFullContext = function(kwargs) {
        if (!kwargs) kwargs = {};
        var prefix = kwargs.prefix || '',
            context = localStorage.getItem(prefix + 'map-context');
        if (context)
            return JSON.parse(context);
        return null;
    };

    self.restoreLatestMapView = function (map, prefixes, kwargs) {
        var latest = null;
        for (var i=0; i<prefixes.length; i++) {
            var prefix = prefixes[i],
                context = self.__loadFullContext($.extend(kwargs, {prefix: prefix}));
            if (!latest || (context && context.timestamp && context.timestamp > latest.timestamp)) {
                latest = context;
                console.debug(JSON.stringify(context));
            }
        }
        return self.restoreMapView(map, latest, kwargs);
    };

    self.restoreMapView = function(map, context, kwargs) {
        if (!context) context = self.__loadFullContext(kwargs);
        if (context && context.mapview) {
            map.setView(L.latLng(context.mapview.lat, context.mapview.lng), context.mapview.zoom);
            return true;
        } else {
            map.fitBounds(map.resetviewControl.getBounds());
            var maxZoom = $(map._container).data('fitmaxzoom');
            if (map.getZoom() > maxZoom) {
                console.log('Limited zoom to ', maxZoom, '. Was ', map.getZoom());
                map.setZoom(maxZoom);
            }
        }
        return false;
    };

    self.restoreFullContext = function(map, context, kwargs) {
        if (!kwargs) kwargs = {};
        var filter = kwargs.filter,
            datatable = kwargs.datatable,
            objectsname = kwargs.objectsname;

        if (!context || typeof context != 'object') {
            // If not received from URL, load from LocalStorage
            context = self.__loadFullContext(kwargs);
        }
        if (!context) {
            console.warn("No context found.");
            map.fitBounds(map.options.maxBounds);
            return;  // No context, no restore.
        }

        if (context.mapsize) {
            // Override min-height style
            map._container.style.minHeight = '0';
            // Force map size
            if (context.mapsize.width && context.mapsize.width > 0)
                $('.map-panel').width(context.mapsize.width);
            if (context.mapsize.height && context.mapsize.height > 0)
                $('.map-panel').height(context.mapsize.height);
            map.invalidateSize();
        }

        if (filter && context.filter) {
            $(filter).deserialize(context.filter);
            $(filter).find('select').trigger("liszt:updated");
        }

        if (datatable && context.sortcolumns) {
            if ($('body').attr('data-modelname') in context.sortcolumns) {
                datatable.fnSort(context.sortcolumns[$('body').attr('data-modelname')]);
            }
            last_sort = context['sortcolumns'];
        }

        // This map view change will refresh the list
        self.restoreMapView(map, context, kwargs);

        // Show layers by their name
        if (context.maplayers) {
            var layers = context.maplayers;
            layers.push(objectsname);
            $('form.leaflet-control-layers-list input').each(function () {
                if ($.trim($(this).parent().text()) != objectsname) {
                    $(this).removeAttr('checked');
                }
            });
            for(var i=0; i<layers.length; i++) {
                var layer = layers[i];
                $('form.leaflet-control-layers-list input').each(function () {
                    if ($.trim($(this).parent().text()) == layer) {
                        $(this).attr('checked', 'checked');
                    }
                });
            }
            if ((map.layerscontrol !== undefined)  && !!map.layerscontrol._map) {
                map.layerscontrol._onInputClick();
            }
        }

        if (context.print) {
            // Disable tiles animations when screenshoting
            $(map._container).removeClass('leaflet-fade-anim');
        }
    };
};
<|endoftext|>


<commit_before>/*

### withList

Paginated items container

Options:

  - queryName: an arbitrary name for the query
  - collection: the collection to fetch the documents from
  - fragment: the fragment that defines which properties to fetch
  - fragmentName: the name of the fragment, passed to getFragment
  - limit: the number of documents to show initially
  - pollInterval: how often the data should be updated, in ms (set to 0 to disable polling)

Props Received:

  - terms: an object that defines which documents to fetch

Terms object can have the following properties:

  - view: String
  - userId: String
  - cat: String
  - date: String
  - after: String
  - before: String
  - enableCache: Boolean
  - listId: String
  - query: String # search query
  - postId: String
  - limit: String

*/

import React, { PropTypes, Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import update from 'immutability-helper';
import { getFragment, getFragmentName } from 'meteor/vulcan:core';
import Mingo from 'mingo';
import { compose, withState } from 'recompose';
import { withApollo } from 'react-apollo';

const withList = (options) => {

  const { collection, limit = 10, pollInterval = 20000 } = options,
        queryName = options.queryName || `${collection.options.collectionName}ListQuery`,
        fragment = options.fragment || getFragment(options.fragmentName),
        fragmentName = getFragmentName(fragment),
        listResolverName = collection.options.resolvers.list && collection.options.resolvers.list.name,
        totalResolverName = collection.options.resolvers.total && collection.options.resolvers.total.name;

  return compose(

    // wrap component with Apollo HoC to give it access to the store
    withApollo,

    // wrap component with HoC that manages the terms object via its state
    withState('paginationTerms', 'setPaginationTerms', props => {

      // either get initial limit from options, or default to settings
      const paginationTerms = {
        limit,
        itemsPerPage: limit,
      };

      return paginationTerms;
    }),

    // wrap component with graphql HoC
    graphql(

      // build graphql query from options
      gql`
        query ${queryName}($terms: JSON) {
          ${totalResolverName}(terms: $terms)
          ${listResolverName}(terms: $terms) {
            __typename
            ...${fragmentName}
          }
        }
        ${fragment}
      `,

      {
        alias: 'withList',

        // graphql query options
        options({terms, paginationTerms, client: apolloClient}) {
          const mergedTerms = {...terms, ...paginationTerms};
          return {
            variables: {
              terms: mergedTerms,
            },
            // note: pollInterval can be set to 0 to disable polling (20s by default)
            pollInterval,
            reducer: (previousResults, action) => {

              // see queryReducer function defined below
              return queryReducer(previousResults, action, collection, mergedTerms, listResolverName, totalResolverName, queryName, apolloClient);

            },
          };
        },

        // define props returned by graphql HoC
        props(props) {

          const refetch = props.data.refetch,
                // results = Utils.convertDates(collection, props.data[listResolverName]),
                results = props.data[listResolverName],
                totalCount = props.data[totalResolverName],
                networkStatus = props.data.networkStatus,
                error = props.data.error;

          if (error) {
            console.log(error);
          }

          return {
            // see https://github.com/apollostack/apollo-client/blob/master/src/queries/store.ts#L28-L36
            // note: loading will propably change soon https://github.com/apollostack/apollo-client/issues/831
            loading: networkStatus === 1, // networkStatus = 1 <=> the graphql container is loading
            results,
            totalCount,
            refetch,
            networkStatus,
            error,
            count: results && results.length,

            // regular load more (reload everything)
            loadMore(providedTerms) {
              // if new terms are provided by presentational component use them, else default to incrementing current limit once
              const newTerms = typeof providedTerms === 'undefined' ? { /*...props.ownProps.terms,*/ ...props.ownProps.paginationTerms, limit: results.length + props.ownProps.paginationTerms.itemsPerPage } : providedTerms;

              props.ownProps.setPaginationTerms(newTerms);
            },

            // incremental loading version (only load new content)
            // note: not compatible with polling
            loadMoreInc(providedTerms) {

              // get terms passed as argument or else just default to incrementing the offset
              const newTerms = typeof providedTerms === 'undefined' ? { ...props.ownProps.terms, ...props.ownProps.paginationTerms, offset: results.length } : providedTerms;

              return props.data.fetchMore({
                variables: { terms: newTerms }, // ??? not sure about 'terms: newTerms'
                updateQuery(previousResults, { fetchMoreResult }) {
                  // no more post to fetch
                  if (!fetchMoreResult.data) {
                    return previousResults;
                  }
                  const newResults = {};
                  newResults[listResolverName] = [...previousResults[listResolverName], ...fetchMoreResult.data[listResolverName]];
                  // return the previous results "augmented" with more
                  return {...previousResults, ...newResults};
                },
              });
            },

            fragmentName,
            fragment,
            ...props.ownProps // pass on the props down to the wrapped component
          };
        },
      }
    )
  );
}

// define query reducer separately
const queryReducer = (previousResults, action, collection, mergedTerms, listResolverName, totalResolverName, queryName, apolloClient) => {

  // if collection has no mutations defined, just return previous results
  if (!collection.options.mutations) {
    return previousResults;
  }

  const newMutationName = collection.options.mutations.new && collection.options.mutations.new.name;
  const editMutationName = collection.options.mutations.edit && collection.options.mutations.edit.name;
  const removeMutationName = collection.options.mutations.remove && collection.options.mutations.remove.name;

  let newResults = previousResults;

  // get mongo selector and options objects based on current terms
  const { selector, options } = collection.getParameters(mergedTerms, apolloClient);
  const mingoQuery = Mingo.Query(selector);

  // function to remove a document from a results object, used by edit and remove cases below
  const removeFromResults = (results, document) => {
    const listWithoutDocument = results[listResolverName].filter(doc => doc._id !== document._id);
    const newResults = update(results, {
      [listResolverName]: { $set: listWithoutDocument }, // ex: postsList
      [totalResolverName]: { $set: results[totalResolverName] - 1 } // ex: postsListTotal
    });
    return newResults;
  }

  // add document to a results object
  const addToResults = (results, document) => {

    return update(results, {
      [listResolverName]: { $unshift: [document] },
      [totalResolverName]: { $set: results[totalResolverName] + 1 }
    });
  }

  // reorder results according to a sort
  const reorderResults = (results, sort) => {
    const list = results[listResolverName];
    // const convertedList = Utils.convertDates(collection, list); // convert date strings to date objects
    const convertedList = list;
    const cursor = mingoQuery.find(convertedList);
    const sortedList = cursor.sort(sort).all();
    results[listResolverName] = sortedList;
    return results;
  }

  // console.log('// withList reducer');
  // console.log('queryName: ', queryName);
  // console.log('terms: ', mergedTerms);
  // console.log('selector: ', selector);
  // console.log('options: ', options);
  // console.log('previousResults: ', previousResults);
  // console.log('previous titles: ', _.pluck(previousResults[listResolverName], 'title'))
  // console.log('action: ', action);

  switch (action.operationName) {

    case newMutationName:
      // if new document belongs to current list (based on view selector), add it
      const newDocument = action.result.data[newMutationName];
      if (mingoQuery.test(newDocument)) {
        newResults = addToResults(previousResults, newDocument);
        newResults = reorderResults(newResults, options.sort);
      }
      // console.log('** new **')
      // console.log('newDocument: ', newDocument)
      // console.log('belongs to list: ', mingoQuery.test(newDocument))
      break;

    case editMutationName:
      const editedDocument = action.result.data[editMutationName];
      if (mingoQuery.test(editedDocument)) {
        // edited document belongs to the list
        if (!_.findWhere(previousResults[listResolverName], {_id: editedDocument._id})) {
          // if document wasn't already in list, add it
          newResults = addToResults(previousResults, editedDocument);
        }
        newResults = reorderResults(newResults, options.sort);
      } else {
        // if edited doesn't belong to current list anymore (based on view selector), remove it
        newResults = removeFromResults(previousResults, editedDocument);
      }
      // console.log('** edit **')
      // console.log('editedDocument: ', editedDocument)
      // console.log('belongs to list: ', mingoQuery.test(editedDocument))
      // console.log('exists in list: ', !!_.findWhere(previousResults[listResolverName], {_id: editedDocument._id}))
      break;

    case removeMutationName:
      const removedDocument = action.result.data[removeMutationName];
      newResults = removeFromResults(previousResults, removedDocument);
      // console.log('** remove **')
      // console.log('removedDocument: ', removedDocument)
      break;

    case 'vote':
      // console.log('** vote **')
      // reorder results in case vote changed the order
      newResults = reorderResults(newResults, options.sort);
      break;

    default:
      // console.log('** no action **')
      return previousResults;
  }

  // console.log('newResults: ', newResults)
  // console.log('new titles: ', _.pluck(newResults[listResolverName], 'title'))
  // console.log('\n\n')

  // copy over arrays explicitely to ensure new sort is taken into account
  return {
    [listResolverName]: [...newResults[listResolverName]],
    [totalResolverName]: newResults[totalResolverName],
  }

}

export default withList;
<commit_msg>get limit from terms<commit_after>/*

### withList

Paginated items container

Options:

  - queryName: an arbitrary name for the query
  - collection: the collection to fetch the documents from
  - fragment: the fragment that defines which properties to fetch
  - fragmentName: the name of the fragment, passed to getFragment
  - limit: the number of documents to show initially
  - pollInterval: how often the data should be updated, in ms (set to 0 to disable polling)

Props Received:

  - terms: an object that defines which documents to fetch

Terms object can have the following properties:

  - view: String
  - userId: String
  - cat: String
  - date: String
  - after: String
  - before: String
  - enableCache: Boolean
  - listId: String
  - query: String # search query
  - postId: String
  - limit: String

*/

import React, { PropTypes, Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import update from 'immutability-helper';
import { getFragment, getFragmentName } from 'meteor/vulcan:core';
import Mingo from 'mingo';
import { compose, withState } from 'recompose';
import { withApollo } from 'react-apollo';

const withList = (options) => {

  const { collection, limit = 10, pollInterval = 20000 } = options,
        queryName = options.queryName || `${collection.options.collectionName}ListQuery`,
        fragment = options.fragment || getFragment(options.fragmentName),
        fragmentName = getFragmentName(fragment),
        listResolverName = collection.options.resolvers.list && collection.options.resolvers.list.name,
        totalResolverName = collection.options.resolvers.total && collection.options.resolvers.total.name;

  return compose(

    // wrap component with Apollo HoC to give it access to the store
    withApollo,

    // wrap component with HoC that manages the terms object via its state
    withState('paginationTerms', 'setPaginationTerms', props => {

      // get initial limit from props, or else options
      const paginationLimit = props.terms && props.terms.limit || limit;
      const paginationTerms = {
        limit: paginationLimit,
        itemsPerPage: paginationLimit,
      };

      return paginationTerms;
    }),

    // wrap component with graphql HoC
    graphql(

      // build graphql query from options
      gql`
        query ${queryName}($terms: JSON) {
          ${totalResolverName}(terms: $terms)
          ${listResolverName}(terms: $terms) {
            __typename
            ...${fragmentName}
          }
        }
        ${fragment}
      `,

      {
        alias: 'withList',

        // graphql query options
        options({terms, paginationTerms, client: apolloClient}) {
          const mergedTerms = {...terms, ...paginationTerms};
          return {
            variables: {
              terms: mergedTerms,
            },
            // note: pollInterval can be set to 0 to disable polling (20s by default)
            pollInterval,
            reducer: (previousResults, action) => {

              // see queryReducer function defined below
              return queryReducer(previousResults, action, collection, mergedTerms, listResolverName, totalResolverName, queryName, apolloClient);

            },
          };
        },

        // define props returned by graphql HoC
        props(props) {

          const refetch = props.data.refetch,
                // results = Utils.convertDates(collection, props.data[listResolverName]),
                results = props.data[listResolverName],
                totalCount = props.data[totalResolverName],
                networkStatus = props.data.networkStatus,
                error = props.data.error;

          if (error) {
            console.log(error);
          }

          return {
            // see https://github.com/apollostack/apollo-client/blob/master/src/queries/store.ts#L28-L36
            // note: loading will propably change soon https://github.com/apollostack/apollo-client/issues/831
            loading: networkStatus === 1, // networkStatus = 1 <=> the graphql container is loading
            results,
            totalCount,
            refetch,
            networkStatus,
            error,
            count: results && results.length,

            // regular load more (reload everything)
            loadMore(providedTerms) {
              // if new terms are provided by presentational component use them, else default to incrementing current limit once
              const newTerms = typeof providedTerms === 'undefined' ? { /*...props.ownProps.terms,*/ ...props.ownProps.paginationTerms, limit: results.length + props.ownProps.paginationTerms.itemsPerPage } : providedTerms;

              props.ownProps.setPaginationTerms(newTerms);
            },

            // incremental loading version (only load new content)
            // note: not compatible with polling
            loadMoreInc(providedTerms) {

              // get terms passed as argument or else just default to incrementing the offset
              const newTerms = typeof providedTerms === 'undefined' ? { ...props.ownProps.terms, ...props.ownProps.paginationTerms, offset: results.length } : providedTerms;

              return props.data.fetchMore({
                variables: { terms: newTerms }, // ??? not sure about 'terms: newTerms'
                updateQuery(previousResults, { fetchMoreResult }) {
                  // no more post to fetch
                  if (!fetchMoreResult.data) {
                    return previousResults;
                  }
                  const newResults = {};
                  newResults[listResolverName] = [...previousResults[listResolverName], ...fetchMoreResult.data[listResolverName]];
                  // return the previous results "augmented" with more
                  return {...previousResults, ...newResults};
                },
              });
            },

            fragmentName,
            fragment,
            ...props.ownProps // pass on the props down to the wrapped component
          };
        },
      }
    )
  );
}

// define query reducer separately
const queryReducer = (previousResults, action, collection, mergedTerms, listResolverName, totalResolverName, queryName, apolloClient) => {

  // if collection has no mutations defined, just return previous results
  if (!collection.options.mutations) {
    return previousResults;
  }

  const newMutationName = collection.options.mutations.new && collection.options.mutations.new.name;
  const editMutationName = collection.options.mutations.edit && collection.options.mutations.edit.name;
  const removeMutationName = collection.options.mutations.remove && collection.options.mutations.remove.name;

  let newResults = previousResults;

  // get mongo selector and options objects based on current terms
  const { selector, options } = collection.getParameters(mergedTerms, apolloClient);
  const mingoQuery = Mingo.Query(selector);

  // function to remove a document from a results object, used by edit and remove cases below
  const removeFromResults = (results, document) => {
    const listWithoutDocument = results[listResolverName].filter(doc => doc._id !== document._id);
    const newResults = update(results, {
      [listResolverName]: { $set: listWithoutDocument }, // ex: postsList
      [totalResolverName]: { $set: results[totalResolverName] - 1 } // ex: postsListTotal
    });
    return newResults;
  }

  // add document to a results object
  const addToResults = (results, document) => {

    return update(results, {
      [listResolverName]: { $unshift: [document] },
      [totalResolverName]: { $set: results[totalResolverName] + 1 }
    });
  }

  // reorder results according to a sort
  const reorderResults = (results, sort) => {
    const list = results[listResolverName];
    // const convertedList = Utils.convertDates(collection, list); // convert date strings to date objects
    const convertedList = list;
    const cursor = mingoQuery.find(convertedList);
    const sortedList = cursor.sort(sort).all();
    results[listResolverName] = sortedList;
    return results;
  }

  // console.log('// withList reducer');
  // console.log('queryName: ', queryName);
  // console.log('terms: ', mergedTerms);
  // console.log('selector: ', selector);
  // console.log('options: ', options);
  // console.log('previousResults: ', previousResults);
  // console.log('previous titles: ', _.pluck(previousResults[listResolverName], 'title'))
  // console.log('action: ', action);

  switch (action.operationName) {

    case newMutationName:
      // if new document belongs to current list (based on view selector), add it
      const newDocument = action.result.data[newMutationName];
      if (mingoQuery.test(newDocument)) {
        newResults = addToResults(previousResults, newDocument);
        newResults = reorderResults(newResults, options.sort);
      }
      // console.log('** new **')
      // console.log('newDocument: ', newDocument)
      // console.log('belongs to list: ', mingoQuery.test(newDocument))
      break;

    case editMutationName:
      const editedDocument = action.result.data[editMutationName];
      if (mingoQuery.test(editedDocument)) {
        // edited document belongs to the list
        if (!_.findWhere(previousResults[listResolverName], {_id: editedDocument._id})) {
          // if document wasn't already in list, add it
          newResults = addToResults(previousResults, editedDocument);
        }
        newResults = reorderResults(newResults, options.sort);
      } else {
        // if edited doesn't belong to current list anymore (based on view selector), remove it
        newResults = removeFromResults(previousResults, editedDocument);
      }
      // console.log('** edit **')
      // console.log('editedDocument: ', editedDocument)
      // console.log('belongs to list: ', mingoQuery.test(editedDocument))
      // console.log('exists in list: ', !!_.findWhere(previousResults[listResolverName], {_id: editedDocument._id}))
      break;

    case removeMutationName:
      const removedDocument = action.result.data[removeMutationName];
      newResults = removeFromResults(previousResults, removedDocument);
      // console.log('** remove **')
      // console.log('removedDocument: ', removedDocument)
      break;

    case 'vote':
      // console.log('** vote **')
      // reorder results in case vote changed the order
      newResults = reorderResults(newResults, options.sort);
      break;

    default:
      // console.log('** no action **')
      return previousResults;
  }

  // console.log('newResults: ', newResults)
  // console.log('new titles: ', _.pluck(newResults[listResolverName], 'title'))
  // console.log('\n\n')

  // copy over arrays explicitely to ensure new sort is taken into account
  return {
    [listResolverName]: [...newResults[listResolverName]],
    [totalResolverName]: newResults[totalResolverName],
  }

}

export default withList;
<|endoftext|>


<commit_before>(function() {

  // List of ports and the devices they map to
  var portToDevice = undefined;
  var currentDevice = undefined;

  // called when data received
  var readListener = undefined;

  // are we sending binary data? If so, don't automatically insert breaks for stuff like Ctrl-C
  var sendingBinary = false;
  // For throttled write
  var slowWrite = true;
  var writeData = "";
  var writeTimeout = undefined;

  function init() {
    Espruino.Core.Config.add("BAUD_RATE", {
      section : "Communications",
      name : "Baud Rate",
      description : "When connecting over serial, this is the baud rate that is used. 9600 is the default for Espruino",
      type : {9600:9600,14400:14400,19200:19200,28800:28800,38400:38400,57600:57600,115200:115200},
      defaultValue : 9600,
    });

    var devices = Espruino.Core.Serial.devices;
    for (var i=0;i<devices.length;i++)
      if (devices[i].init)
        devices[i].init();
  }

  var startListening=function(callback) {
    var oldListener = readListener;
    readListener = callback;
    return oldListener;
  };

  var getPorts=function(callback) {
    var ports = [];
    var newPortToDevice = [];
    // get all devices
    var responses = 0;
    var devices = Espruino.Core.Serial.devices;
    if (!devices || devices.length==0) {
         portToDevice = newPortToDevice;
      return callback(ports);
    }
    devices.forEach(function (device) {
      device.getPorts(function(devicePorts) {
        if (devicePorts) {
          devicePorts.forEach(function(port) {
            if (port.usb && port.usb[0]==0x0483 && port.usb[1]==0x5740)
              port.description = "Espruino board";
            ports.push(port);
            newPortToDevice[port.path] = device;
          });
        }
        responses++;
        if (responses == devices.length) {
          portToDevice = newPortToDevice;
          ports.sort(function(a,b) {
            if (a.unimportant && !b.unimportant) return 1;
            if (b.unimportant && !a.unimportant) return -1;
            return 0;
          });
          callback(ports);
        }
      });
    });
  };

  var openSerial=function(serialPort, connectCallback, disconnectCallback) {
    /* If openSerial is called, we need to have called getPorts first
      in order to figure out which one of the serial_ implementations
      we must call into. */
    if (portToDevice === undefined) {
      portToDevice = []; // stop recursive calls if something errors
      return getPorts(function() {
        openSerial(serialPort, connectCallback, disconnectCallback);
      });
    }

    if (!(serialPort in portToDevice)) {
      console.error("Port "+JSON.stringify(serialPort)+" not found");
      return connectCallback(undefined);
    }
    connectionInfo = undefined;
    currentDevice = portToDevice[serialPort];
    currentDevice.open(serialPort, function(cInfo) {
      // CONNECT
      if (!cInfo) {
//        Espruino.Core.Notifications.error("Unable to connect");
        console.error("Unable to open device (connectionInfo="+cInfo+")");
        connectCallback(undefined);
      } else {
        connectionInfo = cInfo;
        connectedPort = serialPort;
        console.log("Connected", cInfo);
        Espruino.callProcessor("connected", undefined, function() {
          connectCallback(cInfo);
        });
      }
    }, function(data) {
      // RECEIEVE DATA
      if (!(data instanceof ArrayBuffer)) console.warn("Serial port implementation is not returning ArrayBuffers");
      if (readListener) readListener(data);
    }, function() {
      currentDevice = undefined;
      if (!connectionInfo) {
        // we got a disconnect when we hadn't connected...
        // Just call connectCallback(undefined), don't bother sending disconnect
        connectCallback(undefined);
        return;
      }
      // DISCONNECT
      connectionInfo = undefined;
      if (writeTimeout!==undefined)
        clearTimeout(writeTimeout);
      writeTimeout = undefined;
      writeData = "";
      sendingBinary = false;

      Espruino.callProcessor("disconnected", undefined, function() {
        disconnectCallback();
      });
    });
  };

  var str2ab=function(str) {
    var buf=new ArrayBuffer(str.length);
    var bufView=new Uint8Array(buf);
    for (var i=0; i<str.length; i++) {
      var ch = str.charCodeAt(i);
      if (ch>=256) {
        console.warn("Attempted to send non-8 bit character - code "+ch);
        ch = "?".charCodeAt(0);
      }
      bufView[i] = ch;
    }
    return buf;
  };

  var closeSerial=function() {
    if (currentDevice) {
      currentDevice.close();
      currentDevice = undefined;
    } else
      console.error("Close called, but serial port not open");
  };

  var isConnected = function() {
    return currentDevice!==undefined;
  };

   // Throttled serial write
  var writeSerial = function(data, showStatus, callback) {
    if (showStatus===undefined) showStatus=true;

    // Queue our data to write
    if (writeData === "") {
      writeData = data;
    } else {
      console.error("Already sending data - calling callback immediately!");
      writeData += data;
      return callback();
    }

    /* if we're throttling our writes we want to send small
     * blocks of data at once. We still limit the size of
     * sent blocks to 512 because on Mac we seem to lose
     * data otherwise (not on any other platforms!) */
    var blockSize = slowWrite ? 15 : 512;

    showStatus &= writeData.length>blockSize;
    if (showStatus) {
      Espruino.Core.Status.setStatus("Sending...", writeData.length);
      console.log("---> "+JSON.stringify(data));
    }

    function sender() {
      writeTimeout = undefined; // we've been called

      if (writeData === "") {
        if (showStatus)
          Espruino.Core.Status.setStatus("Sent");
        if (callback)
          callback();
        return;
      }

      // Initially, split based on block size
      var d = undefined;
      var split = { start:blockSize, end:blockSize, delay:50 };
      // if we get something like Ctrl-C or `reset`, wait a bit for it to complete
      if (!sendingBinary) {
        function findSplitIdx(prev, substr, delay) {
          var match = writeData.match(substr);
          // not found
          if (match===null) return prev;
          // or previous find was earlier in str
          var end = match.index + match[0].length;
          if (end > prev.end) return prev;
          // found, and earlier
          prev.start = match.index;
          prev.end = end;
          prev.delay = delay;
          prev.match = match[0];
          return prev;
        }
        split = findSplitIdx(split, /\x03/, 250); // Ctrl-C
        split = findSplitIdx(split, /reset\(\);\n/, 250); // Reset
        split = findSplitIdx(split, /load\(\);\n/, 250); // Load
        split = findSplitIdx(split, /Modules.addCached\("[^\n]*"\);\n/, 250); // Adding a module
        if (split.match) console.log("Splitting at "+JSON.stringify(split.match)+", delay "+split.delay);
      }
      // Only send some of the data
      if (writeData.length>split.end) {
        d = writeData.substr(0,split.end);
        writeData = writeData.substr(split.end);
      } else {
        d = writeData;
        writeData = "";
      }
      // update status
      if (showStatus)
        Espruino.Core.Status.incrementProgress(d.length);
      // actually write data
      console.log("Sending block "+JSON.stringify(d)+", wait "+split.delay+"ms");
      currentDevice.write(d, function() {
        // Once written, start timeout
        writeTimeout = setTimeout(function() {
          console.log("Sent");
          sender();
        }, split.delay);
      });
    }

    sender(); // start sending instantly
  };

  // ----------------------------------------------------------
  Espruino.Core.Serial = {
    "devices" : [], // List of devices that can provide a serial API
    "init" : init,
    "getPorts": getPorts,
    "open": openSerial,
    "isConnected": isConnected,
    "startListening": startListening,
    "write": writeSerial,
    "close": closeSerial,
    "isSlowWrite": function() { return slowWrite; },
    "setSlowWrite": function(isOn, force) {
      if ((!force) && Espruino.Config.SERIAL_THROTTLE_SEND) {
        console.log("ForceThrottle option is set - set Slow Write = true");
        isOn = true;
      } else
        console.log("Set Slow Write = "+isOn);
      slowWrite = isOn;
    },
    "setBinary": function(isOn) {
      sendingBinary = isOn;
    }
  };
})();
<commit_msg>Fix the whole 'writing while code already sending' thing. Everything is now queued up properly<commit_after>(function() {

  // List of ports and the devices they map to
  var portToDevice = undefined;
  var currentDevice = undefined;

  // called when data received
  var readListener = undefined;

  // are we sending binary data? If so, don't automatically insert breaks for stuff like Ctrl-C
  var sendingBinary = false;
  // For throttled write
  var slowWrite = true;
  var writeData = [];
  var writeTimeout = undefined;

  function init() {
    Espruino.Core.Config.add("BAUD_RATE", {
      section : "Communications",
      name : "Baud Rate",
      description : "When connecting over serial, this is the baud rate that is used. 9600 is the default for Espruino",
      type : {9600:9600,14400:14400,19200:19200,28800:28800,38400:38400,57600:57600,115200:115200},
      defaultValue : 9600,
    });

    var devices = Espruino.Core.Serial.devices;
    for (var i=0;i<devices.length;i++)
      if (devices[i].init)
        devices[i].init();
  }

  var startListening=function(callback) {
    var oldListener = readListener;
    readListener = callback;
    return oldListener;
  };

  var getPorts=function(callback) {
    var ports = [];
    var newPortToDevice = [];
    // get all devices
    var responses = 0;
    var devices = Espruino.Core.Serial.devices;
    if (!devices || devices.length==0) {
         portToDevice = newPortToDevice;
      return callback(ports);
    }
    devices.forEach(function (device) {
      device.getPorts(function(devicePorts) {
        if (devicePorts) {
          devicePorts.forEach(function(port) {
            if (port.usb && port.usb[0]==0x0483 && port.usb[1]==0x5740)
              port.description = "Espruino board";
            ports.push(port);
            newPortToDevice[port.path] = device;
          });
        }
        responses++;
        if (responses == devices.length) {
          portToDevice = newPortToDevice;
          ports.sort(function(a,b) {
            if (a.unimportant && !b.unimportant) return 1;
            if (b.unimportant && !a.unimportant) return -1;
            return 0;
          });
          callback(ports);
        }
      });
    });
  };

  var openSerial=function(serialPort, connectCallback, disconnectCallback) {
    /* If openSerial is called, we need to have called getPorts first
      in order to figure out which one of the serial_ implementations
      we must call into. */
    if (portToDevice === undefined) {
      portToDevice = []; // stop recursive calls if something errors
      return getPorts(function() {
        openSerial(serialPort, connectCallback, disconnectCallback);
      });
    }

    if (!(serialPort in portToDevice)) {
      console.error("Port "+JSON.stringify(serialPort)+" not found");
      return connectCallback(undefined);
    }
    connectionInfo = undefined;
    currentDevice = portToDevice[serialPort];
    currentDevice.open(serialPort, function(cInfo) {
      // CONNECT
      if (!cInfo) {
//        Espruino.Core.Notifications.error("Unable to connect");
        console.error("Unable to open device (connectionInfo="+cInfo+")");
        connectCallback(undefined);
      } else {
        connectionInfo = cInfo;
        connectedPort = serialPort;
        console.log("Connected", cInfo);
        Espruino.callProcessor("connected", undefined, function() {
          connectCallback(cInfo);
        });
      }
    }, function(data) {
      // RECEIEVE DATA
      if (!(data instanceof ArrayBuffer)) console.warn("Serial port implementation is not returning ArrayBuffers");
      if (readListener) readListener(data);
    }, function() {
      currentDevice = undefined;
      if (!connectionInfo) {
        // we got a disconnect when we hadn't connected...
        // Just call connectCallback(undefined), don't bother sending disconnect
        connectCallback(undefined);
        return;
      }
      // DISCONNECT
      connectionInfo = undefined;
      if (writeTimeout!==undefined)
        clearTimeout(writeTimeout);
      writeTimeout = undefined;
      writeData = [];
      sendingBinary = false;

      Espruino.callProcessor("disconnected", undefined, function() {
        disconnectCallback();
      });
    });
  };

  var str2ab=function(str) {
    var buf=new ArrayBuffer(str.length);
    var bufView=new Uint8Array(buf);
    for (var i=0; i<str.length; i++) {
      var ch = str.charCodeAt(i);
      if (ch>=256) {
        console.warn("Attempted to send non-8 bit character - code "+ch);
        ch = "?".charCodeAt(0);
      }
      bufView[i] = ch;
    }
    return buf;
  };

  var closeSerial=function() {
    if (currentDevice) {
      currentDevice.close();
      currentDevice = undefined;
    } else
      console.error("Close called, but serial port not open");
  };

  var isConnected = function() {
    return currentDevice!==undefined;
  };

  var writeSerialWorker = function(isStarting) {
    writeTimeout = undefined; // we've been called

    if (writeData[0].data === "") {
      if (writeData[0].showStatus)
        Espruino.Core.Status.setStatus("Sent");
      if (writeData[0].callback)
        writeData[0].callback();
      writeData.shift(); // remove this empty first element
      if (!writeData.length) return; // anything left to do?
      isStarting = true;
    }

    if (isStarting) {
      /* if we're throttling our writes we want to send small
       * blocks of data at once. We still limit the size of
       * sent blocks to 512 because on Mac we seem to lose
       * data otherwise (not on any other platforms!) */
      writeData[0].blockSize = slowWrite ? 15 : 512;

      writeData[0].showStatus &= writeData[0].data.length>writeData[0].blockSize;
      if (writeData[0].showStatus) {
        Espruino.Core.Status.setStatus("Sending...", writeData[0].data.length);
        console.log("---> "+JSON.stringify(writeData[0].data));
      }
    }

    // Initially, split based on block size
    var d = undefined;
    var split = { start:writeData[0].blockSize, end:writeData[0].blockSize, delay:50 };
    // if we get something like Ctrl-C or `reset`, wait a bit for it to complete
    if (!sendingBinary) {
      function findSplitIdx(prev, substr, delay) {
        var match = writeData[0].data.match(substr);
        // not found
        if (match===null) return prev;
        // or previous find was earlier in str
        var end = match.index + match[0].length;
        if (end > prev.end) return prev;
        // found, and earlier
        prev.start = match.index;
        prev.end = end;
        prev.delay = delay;
        prev.match = match[0];
        return prev;
      }
      split = findSplitIdx(split, /\x03/, 250); // Ctrl-C
      split = findSplitIdx(split, /reset\(\);\n/, 250); // Reset
      split = findSplitIdx(split, /load\(\);\n/, 250); // Load
      split = findSplitIdx(split, /Modules.addCached\("[^\n]*"\);\n/, 250); // Adding a module
      if (split.match) console.log("Splitting at "+JSON.stringify(split.match)+", delay "+split.delay);
    }
    // Only send some of the data
    if (writeData[0].data.length>split.end) {
      d = writeData[0].data.substr(0,split.end);
      writeData[0].data = writeData[0].data.substr(split.end);
    } else {
      d = writeData[0].data;
      writeData[0].data = "";
    }
    // update status
    if (writeData[0].showStatus)
      Espruino.Core.Status.incrementProgress(d.length);
    // actually write data
    console.log("Sending block "+JSON.stringify(d)+", wait "+split.delay+"ms");
    currentDevice.write(d, function() {
      // Once written, start timeout
      writeTimeout = setTimeout(function() {
        writeSerialWorker();
      }, split.delay);
    });
  }

   // Throttled serial write
  var writeSerial = function(data, showStatus, callback) {
    if (showStatus===undefined) showStatus=true;

    // Queue our data to write
    writeData.push({data:data,callback:callback,showStatus:showStatus});
    // if we have more data we're already running. so break out
    if (writeData.length>1) return;

    writeSerialWorker(true); // start sending instantly
  };

  // ----------------------------------------------------------
  Espruino.Core.Serial = {
    "devices" : [], // List of devices that can provide a serial API
    "init" : init,
    "getPorts": getPorts,
    "open": openSerial,
    "isConnected": isConnected,
    "startListening": startListening,
    "write": writeSerial,
    "close": closeSerial,
    "isSlowWrite": function() { return slowWrite; },
    "setSlowWrite": function(isOn, force) {
      if ((!force) && Espruino.Config.SERIAL_THROTTLE_SEND) {
        console.log("ForceThrottle option is set - set Slow Write = true");
        isOn = true;
      } else
        console.log("Set Slow Write = "+isOn);
      slowWrite = isOn;
    },
    "setBinary": function(isOn) {
      sendingBinary = isOn;
    }
  };
})();
<|endoftext|>


<commit_before>"use strict";

var Cylon = require('cylon')
var tilted = false;
var talking = false;
var leds = {red:2,blue:3,green:4};
var color = "";
var whiteArray = {red:0,blue:0,green:0};
var blackArray = {red:0,blue:0,green:0};
var colorArray = {red:0,blue:0,green:0};

require('shelljs/global');

Cylon.robot({
  connections: {
    edison: { adaptor: 'intel-iot' }
  },
  devices: {
    //outputs:
    laser: { driver: 'led', pin: 7 },
    redLed: { driver: 'led', pin: 4 },
    greenLed: { driver: 'led', pin: 5 },
    blueLed: { driver: 'led', pin: 6 },

    //inputs:
    tilt: { driver: 'button', pin: 3 },
    button: { driver: 'button', pin: 2 },
    ambient: { driver: 'analog-sensor', pin: 3, lowerLimit: 100, upperLimit: 900 },
    color_detector: { driver: 'analog-sensor', pin: 0, lowerLimit: 0, upperLimit: 1024 },

  },
  work: function(my) {
    my.button.on('push', function() {
      my.laser.toggle();
    console.log("laser toggled");

    });

    every((1).seconds(), function() {
      console.log("Hello, human!");
    });

    var output_sound= function(color) {
    console.log(color + " detected");
    if (!talking){
        if (color === "red"){
            exec('sh red.sh');
        }
        if (color === "green"){
            exec('sh green.sh');
        }
        if (color === "blue"){
            exec('sh blue.sh');
        }
    }
    };

      my.tilt.on('push', function() {
          console.log("Tilt Activated");
          tilted = true;
      });
      my.tilt.on('release', function() {
          console.log("Tilt Deactivated");
          tilted = false;
      });

      var setRed = function(){
          my.redLed.turnOn()
          my.greenLed.turnOff()
          my.blueLed.turnOff()
      };
      var setBlue = function(){
          my.redLed.turnOff()
          my.greenLed.turnOff()
          my.blueLed.turnOn()
      }
      var setGreen = function(){
          my.redLed.turnOff()
          my.greenLed.turnOn()
          my.blueLed.turnOff()
      }
      var setOff = function(){
          my.redLed.turnOff()
          my.greenLed.turnOff()
          my.blueLed.turnOff()
      }

    var calibrate = function(cb){
            setRed();
            after((0.01).seconds(), function() {
                getReading(5, function(avgRead){
                    whiteArray.red = avgRead;
                    console.log("Set white red to "+avgRead);
                    setBlue();
                    after((0.01).seconds(), function() {
                        getReading(5, function(avgRead){
                            whiteArray.blue = avgRead;
                            console.log("Set white blue to "+avgRead);
                            setGreen();
                            after((0.01).seconds(), function() {
                                getReading(5, function(avgRead){
                                    whiteArray.green = avgRead;
                                    console.log("Set white green to "+avgRead);
                                    setOff();
                                    after((5).seconds(), function() {
                                        setRed();
                                        after((0.01).seconds(), function() {
                                            getReading(5, function(avgRead){
                                                blackArray.red = avgRead;
                                                console.log("Set black red to "+avgRead);
                                                setBlue();
                                                after((0.01).seconds(), function() {
                                                    getReading(5, function(avgRead){
                                                        blackArray.blue = avgRead;
                                                        console.log("Set black blue to "+avgRead);
                                                        setGreen();
                                                        after((0.01).seconds(), function() {
                                                            getReading(5, function(avgRead){
                                                                blackArray.green = avgRead;
                                                                console.log("Set black green to "+avgRead);
                                                                after((0.01).seconds(), function() {
                                                                    cb();
                                                                });
                                                            });
                                                        });
                                                    });
                                                });
                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        };

    var getReading = function(times, cb, tally){
        if (times ===0){
            var avgRead = (tally) / times;
            cb(avgRead);
        }
        if (!tally){
            tally=0;
        }

        var reading = my.ambient.analogRead();
        tally = reading + tally;
        after((0.01).seconds(), function() {
            getReading(times-1, cb, tally);
        });
    };

    after((5).seconds(), function() {
        calibrate(function(){
            every((1).seconds(), function() {
              console.log("Check color");
                    setRed();
                    after((0.01).seconds(), function() {
                        getReading(5, function(avgRead){
                            colorArray.red = avgRead;
                            console.log("Set colorArray red to "+avgRead);
                            var greydiff = whiteArray.red - blackArray.red;
                            colorArray.red = (colorArray.red - blackArray.red) / (greyDiff) * 255;//the reading returned minus the lowest value divided by the possible range multiplied by 255 is a value roughly between 0-255 representing the value
                            //for the current reflectivity(for the color it is exposed to) of what is being scanned
                            avgArray.red = (avgArray.red + colorArray.red) / 2;

                            setBlue();
                            after((0.01).seconds(), function() {
                                getReading(5, function(avgRead){
                                    colorArray.blue = avgRead;
                                    console.log("Set colorArray blue to "+avgRead);
                                    var greydiff = whiteArray.blue - blackArray.blue;
                                    colorArray.blue = (colorArray.blue - blackArray.blue) / (greyDiff) * 255;//the reading returned minus the lowest value divided by the possible range multiplied by 255 is a value roughly between 0-255 representing the value
                                    //for the current reflectivity(for the color it is exposed to) of what is being scanned
                                    avgArray.blue = (avgArray.blue + colorArray.blue) / 2;
                                    setGreen();
                                    after((0.01).seconds(), function() {
                                        getReading(5, function(avgRead){
                                            colorArray.green = avgRead;
                                            console.log("Set colorArray green to "+avgRead);
                                            var greydiff = whiteArray.green - blackArray.green;
                                            colorArray.green = (colorArray.green - blackArray.green) / (greyDiff) * 255;//the reading returned minus the lowest value divided by the possible range multiplied by 255 is a value roughly between 0-255 representing the value
                                            //for the current reflectivity(for the color it is exposed to) of what is being scanned
                                            avgArray.green = (avgArray.green + colorArray.green) / 2;
                                            after((0.01).seconds(), function() {
                                                setOff();
                                                if(avgArray.red > avgArray.green && avgArray.red > avgArray.blue) {
                                                    console.log("Detected Red");
                                                    output_sound("red");
                                                }
                                                else if(avgArray.green > avgArray.red && avgArray.green > avgArray.blue) {
                                                    console.log("Detected Green");
                                                    output_sound("green");

                                                }
                                                else {
                                                    console.log("Detected Blue");
                                                    output_sound("blue");
                                                }

                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
              });
            });
        });

    } //end setup

}).start();
<commit_msg>Change math<commit_after>"use strict";

var Cylon = require('cylon')
var tilted = false;
var talking = false;
var leds = {red:2,blue:3,green:4};
var color = "";
var whiteArray = {red:0,blue:0,green:0};
var blackArray = {red:0,blue:0,green:0};
var colorArray = {red:0,blue:0,green:0};

require('shelljs/global');

Cylon.robot({
  connections: {
    edison: { adaptor: 'intel-iot' }
  },
  devices: {
    //outputs:
    laser: { driver: 'led', pin: 7 },
    redLed: { driver: 'led', pin: 4 },
    greenLed: { driver: 'led', pin: 5 },
    blueLed: { driver: 'led', pin: 6 },

    //inputs:
    tilt: { driver: 'button', pin: 3 },
    button: { driver: 'button', pin: 2 },
    ambient: { driver: 'analog-sensor', pin: 3, lowerLimit: 100, upperLimit: 900 },
    color_detector: { driver: 'analog-sensor', pin: 0, lowerLimit: 0, upperLimit: 1024 },

  },
  work: function(my) {
    my.button.on('push', function() {
      my.laser.toggle();
    console.log("laser toggled");

    });

    every((1).seconds(), function() {
      console.log("Hello, human!");
    });

    var output_sound= function(color) {
    console.log(color + " detected");
    if (!talking){
        if (color === "red"){
            exec('sh red.sh');
        }
        if (color === "green"){
            exec('sh green.sh');
        }
        if (color === "blue"){
            exec('sh blue.sh');
        }
    }
    };

      my.tilt.on('push', function() {
          console.log("Tilt Activated");
          tilted = true;
      });
      my.tilt.on('release', function() {
          console.log("Tilt Deactivated");
          tilted = false;
      });

      var setRed = function(){
          my.redLed.turnOn()
          my.greenLed.turnOff()
          my.blueLed.turnOff()
      };
      var setBlue = function(){
          my.redLed.turnOff()
          my.greenLed.turnOff()
          my.blueLed.turnOn()
      }
      var setGreen = function(){
          my.redLed.turnOff()
          my.greenLed.turnOn()
          my.blueLed.turnOff()
      }
      var setOff = function(){
          my.redLed.turnOff()
          my.greenLed.turnOff()
          my.blueLed.turnOff()
      }

    var calibrate = function(cb){
            setRed();
            after((0.01).seconds(), function() {
                getReading(5, function(avgRead){
                    whiteArray.red = avgRead;
                    console.log("Set white red to "+avgRead);
                    setBlue();
                    after((0.01).seconds(), function() {
                        getReading(5, function(avgRead){
                            whiteArray.blue = avgRead;
                            console.log("Set white blue to "+avgRead);
                            setGreen();
                            after((0.01).seconds(), function() {
                                getReading(5, function(avgRead){
                                    whiteArray.green = avgRead;
                                    console.log("Set white green to "+avgRead);
                                    setOff();
                                    after((5).seconds(), function() {
                                        setRed();
                                        after((0.01).seconds(), function() {
                                            getReading(5, function(avgRead){
                                                blackArray.red = avgRead;
                                                console.log("Set black red to "+avgRead);
                                                setBlue();
                                                after((0.01).seconds(), function() {
                                                    getReading(5, function(avgRead){
                                                        blackArray.blue = avgRead;
                                                        console.log("Set black blue to "+avgRead);
                                                        setGreen();
                                                        after((0.01).seconds(), function() {
                                                            getReading(5, function(avgRead){
                                                                blackArray.green = avgRead;
                                                                console.log("Set black green to "+avgRead);
                                                                after((0.01).seconds(), function() {
                                                                    cb();
                                                                });
                                                            });
                                                        });
                                                    });
                                                });
                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        };

    var getReading = function(times, cb, tally, count){
        if (count ===0){
            var avgRead = (tally) / times;
            return cb(avgRead);
        }
        if (!tally){
            tally=0;
        }
        if (!count){
            count=times;
        }

        var reading = my.ambient.analogRead();
        tally = reading + tally;
        after((0.01).seconds(), function() {
            getReading(times, cb, tally, times-1);
        });
    };

    after((5).seconds(), function() {
        calibrate(function(){
            every((1).seconds(), function() {
              console.log("Check color");
                    setRed();
                    after((0.01).seconds(), function() {
                        getReading(5, function(avgRead){
                            colorArray.red = avgRead;
                            console.log("Set colorArray red to "+avgRead);
                            var greydiff = whiteArray.red - blackArray.red;
                            colorArray.red = (colorArray.red - blackArray.red) / (greyDiff) * 255;//the reading returned minus the lowest value divided by the possible range multiplied by 255 is a value roughly between 0-255 representing the value
                            //for the current reflectivity(for the color it is exposed to) of what is being scanned
                            avgArray.red = (avgArray.red + colorArray.red) / 2;

                            setBlue();
                            after((0.01).seconds(), function() {
                                getReading(5, function(avgRead){
                                    colorArray.blue = avgRead;
                                    console.log("Set colorArray blue to "+avgRead);
                                    var greydiff = whiteArray.blue - blackArray.blue;
                                    colorArray.blue = (colorArray.blue - blackArray.blue) / (greyDiff) * 255;//the reading returned minus the lowest value divided by the possible range multiplied by 255 is a value roughly between 0-255 representing the value
                                    //for the current reflectivity(for the color it is exposed to) of what is being scanned
                                    avgArray.blue = (avgArray.blue + colorArray.blue) / 2;
                                    setGreen();
                                    after((0.01).seconds(), function() {
                                        getReading(5, function(avgRead){
                                            colorArray.green = avgRead;
                                            console.log("Set colorArray green to "+avgRead);
                                            var greydiff = whiteArray.green - blackArray.green;
                                            colorArray.green = (colorArray.green - blackArray.green) / (greyDiff) * 255;//the reading returned minus the lowest value divided by the possible range multiplied by 255 is a value roughly between 0-255 representing the value
                                            //for the current reflectivity(for the color it is exposed to) of what is being scanned
                                            avgArray.green = (avgArray.green + colorArray.green) / 2;
                                            after((0.01).seconds(), function() {
                                                setOff();
                                                if(avgArray.red > avgArray.green && avgArray.red > avgArray.blue) {
                                                    console.log("Detected Red");
                                                    output_sound("red");
                                                }
                                                else if(avgArray.green > avgArray.red && avgArray.green > avgArray.blue) {
                                                    console.log("Detected Green");
                                                    output_sound("green");

                                                }
                                                else {
                                                    console.log("Detected Blue");
                                                    output_sound("blue");
                                                }

                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
              });
            });
        });

    } //end setup

}).start();
<|endoftext|>


<commit_before>var tilelive = require('@mapbox/tilelive');
var crypto = require('crypto');
var mapnik = require('mapnik');
var util = require('util');
var sm = new (require('@mapbox/sphericalmercator'))();

module.exports = Backend;

function Backend(opts, callback) {
    this._layer = opts.layer || undefined;
    this._scale = opts.scale || 1;
    this._source = null;
    var backend = this;
    if (opts.source) {
        setsource(opts.source, opts);
    } else if (opts.uri) {
        tilelive.load(opts.uri, function(err, source) {
            if (err) return callback(err);
            source.getInfo(function(err, info) {
                if (err) return callback(err);
                setsource(source, info);
            });
        });
    } else {
        if (callback) callback(new Error('opts.uri or opts.source must be set'));
    }

    function setsource(source, info) {
        backend._minzoom = typeof info.minzoom === 'number' ? info.minzoom : 0;
        backend._maxzoom = typeof info.maxzoom === 'number' ? info.maxzoom : 22;
        backend._vector_layers = info.vector_layers || undefined;
        backend._layer = backend._layer ||
            (info.vector_layers && info.vector_layers.length && info.vector_layers[0].id) ||
            '_image';
        backend._fillzoom = 'fillzoom' in info && !isNaN(parseInt(info.fillzoom, 10)) ?
            parseInt(info.fillzoom, 10) :
            undefined;
        backend._source = source;
        if (callback) callback(null, backend);
    }
};

Backend.prototype.getInfo = function(callback) {
    if (!this._source) return callback(new Error('Tilesource not loaded'));
    this._source.getInfo(callback);
};

// Wrapper around backend.getTile that implements a "locking" cache.
Backend.prototype.getTile = function(z, x, y, callback) {
    if (!this._source) return callback(new Error('Tilesource not loaded'));
    if (z < 0 || x < 0 || y < 0 || x >= Math.pow(2,z) || y >= Math.pow(2,z)) {
        return callback(new Error('Tile does not exist'));
    }
    var backend = this;
    var source = backend._source;
    var now = +new Date;
    var legacy = callback.legacy || false;
    var scale = callback.scale || backend._scale;
    var upgrade = callback.upgrade || false;

    // If scale > 1 adjusts source data zoom level inversely.
    // scale 2x => z-1, scale 4x => z-2, scale 8x => z-3, etc.
    if (legacy && z >= backend._minzoom) {
        var d = Math.round(Math.log(scale)/Math.log(2));
        var bz = (z - d) > backend._minzoom ? z - d : backend._minzoom;
        var bx = Math.floor(x / Math.pow(2, z - bz));
        var by = Math.floor(y / Math.pow(2, z - bz));
    } else {
        var bz = z | 0;
        var bx = x | 0;
        var by = y | 0;
    }

    var size = 0;
    var headers = {};

    // Overzooming support.
    if (bz > backend._maxzoom) {
        bz = backend._maxzoom;
        bx = Math.floor(x / Math.pow(2, z - bz));
        by = Math.floor(y / Math.pow(2, z - bz));
        headers['x-vector-backend-object'] = 'overzoom';
    }

    source.getTile(bz, bx, by, function sourceGet(err, body, head) {
        if (typeof backend._fillzoom === 'number' &&
            err && err.message === 'Tile does not exist' &&
            bz > backend._fillzoom) {
            bz = backend._fillzoom;
            bx = Math.floor(x / Math.pow(2, z - bz));
            by = Math.floor(y / Math.pow(2, z - bz));
            headers['x-vector-backend-object'] = 'fillzoom';
            return source.getTile(bz, bx, by, sourceGet);
        }
        if (err && err.message !== 'Tile does not exist') return callback(err);

        if (body instanceof mapnik.VectorTile) {
            size = body._srcbytes;
            headers = head || {};
            return makevtile(body);
        }

        var compression = false;
        if (body && body[0] == 0x78 && body[1] == 0x9C) {
            compression = 'inflate';
        } else if (body && body[0] == 0x1F && body[1] == 0x8B) {
            compression = 'gunzip';
        }

        if (!body || !body.length) {
            headers['x-vector-backend-object'] = 'empty';
            return makevtile();
        } else if (compression) {
            size = body.length;
            headers = head || {};
            return makevtile(body, 'pbf');
        // Image sources do not allow overzooming (yet).
        } else if (bz < z && headers['x-vector-backend-object'] !== 'fillzoom') {
            headers['x-vector-backend-object'] = 'empty';
            return makevtile();
        } else {
            size = body.length;
            headers = head || {};
            return makevtile(body);
        }
    });

    function makevtile(data, type) {
        // If no last modified is provided, use epoch.
        headers['Last-Modified'] = new Date(headers['Last-Modified'] || 0).toUTCString();

        // Set an ETag if not present.
        headers['ETag'] = headers['ETag'] || JSON.stringify(crypto.createHash('md5')
            .update((z+','+x+','+y) + (data||''))
            .digest('hex'));

        // Set content type.
        headers['Content-Type'] = 'application/x-protobuf';

        // Set x-vector-backend-status header.
        headers['x-vector-backend-object'] = headers['x-vector-backend-object'] || 'default';

        // Pass-thru of an upstream mapnik vector tile (not pbf) source.
        if (data instanceof mapnik.VectorTile) return callback(null, data, headers);

        var vtile = new mapnik.VectorTile(bz, bx, by);
        vtile._srcbytes = size;
        if (callback.setSrcData) vtile._srcdata = data;

        // null/zero length data is a solid tile be painted.
        if (!data || !data.length) return callback(null, vtile, headers);

        try {
            if (type === 'pbf') {
                // We use addData here over setData because we know it was just created
                // and is empty so skips a clear call internally in mapnik.
                vtile.addData(data,{upgrade:upgrade},function(err) {
                    if (err) return callback(err);
                    return callback(null, vtile, headers);
                });
            } else {
                vtile.addImageBuffer(data, backend._layer, function(err) {
                    if (err) return callback(err);
                    return callback(null, vtile, headers);
                });
            }
        } catch (err) {
            return callback(err);
        }
    };
};

// Proxies mapnik vtile.query method with the added convienice of
// letting the tilelive-vector backend do the hard work of finding
// the right tile to use.
Backend.prototype.queryTile = function(z, lon, lat, options, callback) {
    var xyz = sm.xyz([lon, lat, lon, lat], z);
    this.getTile(z, xyz.minX, xyz.minY, function(err, vtile, head) {
        if (err) return callback(err);
        vtile.query(lon, lat, options, function(err, features) {
            if (err) return callback(err);
            var results = [];
            for (var i = 0; i < features.length; i++) {
                results.push({
                    id: features[i].id(),
                    distance: features[i].distance,
                    layer: features[i].layer,
                    attributes: features[i].attributes(),
                    geometry: {
                        type: 'Point',
                        coordinates: features[i].x_hit ?
                            [ features[i].x_hit, features[i].y_hit ] :
                            [ lon, lat ]
                    }
                });
            }
            var headers = {};
            headers['Content-Type'] = 'application/json';
            headers['ETag'] = JSON.stringify(crypto.createHash('md5')
                .update(head && head['ETag'] || (z+','+lon+','+lat))
                .digest('hex'));
            headers['Last-Modified'] = new Date(head && head['Last-Modified'] || 0).toUTCString();
            return callback(null, results, headers);
        });
    });
};

<commit_msg>attach params (scale, legacy, upgrade) to sourceGet to ensure they trickle along<commit_after>var tilelive = require('@mapbox/tilelive');
var crypto = require('crypto');
var mapnik = require('mapnik');
var util = require('util');
var sm = new (require('@mapbox/sphericalmercator'))();

module.exports = Backend;

function Backend(opts, callback) {
    this._layer = opts.layer || undefined;
    this._scale = opts.scale || 1;
    this._source = null;
    var backend = this;
    if (opts.source) {
        setsource(opts.source, opts);
    } else if (opts.uri) {
        tilelive.load(opts.uri, function(err, source) {
            if (err) return callback(err);
            source.getInfo(function(err, info) {
                if (err) return callback(err);
                setsource(source, info);
            });
        });
    } else {
        if (callback) callback(new Error('opts.uri or opts.source must be set'));
    }

    function setsource(source, info) {
        backend._minzoom = typeof info.minzoom === 'number' ? info.minzoom : 0;
        backend._maxzoom = typeof info.maxzoom === 'number' ? info.maxzoom : 22;
        backend._vector_layers = info.vector_layers || undefined;
        backend._layer = backend._layer ||
            (info.vector_layers && info.vector_layers.length && info.vector_layers[0].id) ||
            '_image';
        backend._fillzoom = 'fillzoom' in info && !isNaN(parseInt(info.fillzoom, 10)) ?
            parseInt(info.fillzoom, 10) :
            undefined;
        backend._source = source;
        if (callback) callback(null, backend);
    }
};

Backend.prototype.getInfo = function(callback) {
    if (!this._source) return callback(new Error('Tilesource not loaded'));
    this._source.getInfo(callback);
};

// Wrapper around backend.getTile that implements a "locking" cache.
Backend.prototype.getTile = function(z, x, y, callback) {
    if (!this._source) return callback(new Error('Tilesource not loaded'));
    if (z < 0 || x < 0 || y < 0 || x >= Math.pow(2,z) || y >= Math.pow(2,z)) {
        return callback(new Error('Tile does not exist'));
    }
    var backend = this;
    var source = backend._source;
    var now = +new Date;
    var legacy = callback.legacy || false;
    var scale = callback.scale || backend._scale;
    var upgrade = callback.upgrade || false;

    // If scale > 1 adjusts source data zoom level inversely.
    // scale 2x => z-1, scale 4x => z-2, scale 8x => z-3, etc.
    if (legacy && z >= backend._minzoom) {
        var d = Math.round(Math.log(scale)/Math.log(2));
        var bz = (z - d) > backend._minzoom ? z - d : backend._minzoom;
        var bx = Math.floor(x / Math.pow(2, z - bz));
        var by = Math.floor(y / Math.pow(2, z - bz));
    } else {
        var bz = z | 0;
        var bx = x | 0;
        var by = y | 0;
    }

    var size = 0;
    var headers = {};

    // Overzooming support.
    if (bz > backend._maxzoom) {
        bz = backend._maxzoom;
        bx = Math.floor(x / Math.pow(2, z - bz));
        by = Math.floor(y / Math.pow(2, z - bz));
        headers['x-vector-backend-object'] = 'overzoom';
    }

    function makevtile(data, type) {
        // If no last modified is provided, use epoch.
        headers['Last-Modified'] = new Date(headers['Last-Modified'] || 0).toUTCString();

        // Set an ETag if not present.
        headers['ETag'] = headers['ETag'] || JSON.stringify(crypto.createHash('md5')
            .update((z+','+x+','+y) + (data||''))
            .digest('hex'));

        // Set content type.
        headers['Content-Type'] = 'application/x-protobuf';

        // Set x-vector-backend-status header.
        headers['x-vector-backend-object'] = headers['x-vector-backend-object'] || 'default';

        // Pass-thru of an upstream mapnik vector tile (not pbf) source.
        if (data instanceof mapnik.VectorTile) return callback(null, data, headers);

        var vtile = new mapnik.VectorTile(bz, bx, by);
        vtile._srcbytes = size;
        if (callback.setSrcData) vtile._srcdata = data;

        // null/zero length data is a solid tile be painted.
        if (!data || !data.length) return callback(null, vtile, headers);

        try {
            if (type === 'pbf') {
                // We use addData here over setData because we know it was just created
                // and is empty so skips a clear call internally in mapnik.
                vtile.addData(data,{upgrade:upgrade},function(err) {
                    if (err) return callback(err);
                    return callback(null, vtile, headers);
                });
            } else {
                vtile.addImageBuffer(data, backend._layer, function(err) {
                    if (err) return callback(err);
                    return callback(null, vtile, headers);
                });
            }
        } catch (err) {
            return callback(err);
        }
    };

    function sourceGet(err, body, head) {
        if (typeof backend._fillzoom === 'number' &&
            err && err.message === 'Tile does not exist' &&
            bz > backend._fillzoom) {
            bz = backend._fillzoom;
            bx = Math.floor(x / Math.pow(2, z - bz));
            by = Math.floor(y / Math.pow(2, z - bz));
            headers['x-vector-backend-object'] = 'fillzoom';
            return source.getTile(bz, bx, by, sourceGet);
        }
        if (err && err.message !== 'Tile does not exist') return callback(err);

        if (body instanceof mapnik.VectorTile) {
            size = body._srcbytes;
            headers = head || {};
            return makevtile(body);
        }

        var compression = false;
        if (body && body[0] == 0x78 && body[1] == 0x9C) {
            compression = 'inflate';
        } else if (body && body[0] == 0x1F && body[1] == 0x8B) {
            compression = 'gunzip';
        }

        if (!body || !body.length) {
            headers['x-vector-backend-object'] = 'empty';
            return makevtile();
        } else if (compression) {
            size = body.length;
            headers = head || {};
            return makevtile(body, 'pbf');
        // Image sources do not allow overzooming (yet).
        } else if (bz < z && headers['x-vector-backend-object'] !== 'fillzoom') {
            headers['x-vector-backend-object'] = 'empty';
            return makevtile();
        } else {
            size = body.length;
            headers = head || {};
            return makevtile(body);
        }
    };

    sourceGet.scale = scale;
    sourceGet.legacy = legacy;
    sourceGet.upgrade = upgrade;
    source.getTile(bz, bx, by, sourceGet);

};

// Proxies mapnik vtile.query method with the added convienice of
// letting the tilelive-vector backend do the hard work of finding
// the right tile to use.
Backend.prototype.queryTile = function(z, lon, lat, options, callback) {
    var xyz = sm.xyz([lon, lat, lon, lat], z);
    this.getTile(z, xyz.minX, xyz.minY, function(err, vtile, head) {
        if (err) return callback(err);
        vtile.query(lon, lat, options, function(err, features) {
            if (err) return callback(err);
            var results = [];
            for (var i = 0; i < features.length; i++) {
                results.push({
                    id: features[i].id(),
                    distance: features[i].distance,
                    layer: features[i].layer,
                    attributes: features[i].attributes(),
                    geometry: {
                        type: 'Point',
                        coordinates: features[i].x_hit ?
                            [ features[i].x_hit, features[i].y_hit ] :
                            [ lon, lat ]
                    }
                });
            }
            var headers = {};
            headers['Content-Type'] = 'application/json';
            headers['ETag'] = JSON.stringify(crypto.createHash('md5')
                .update(head && head['ETag'] || (z+','+lon+','+lat))
                .digest('hex'));
            headers['Last-Modified'] = new Date(head && head['Last-Modified'] || 0).toUTCString();
            return callback(null, results, headers);
        });
    });
};

<|endoftext|>