<commit_before>// ==UserScript==
// @id             iitc-plugin-portal-level-numbers@rongou
// @name           IITC plugin: Portal Level Numbers
// @category       Layer
// @version        0.1.2.@@DATETIMEVERSION@@
// @namespace      https://github.com/jonatkins/ingress-intel-total-conversion
// @updateURL      @@UPDATEURL@@
// @downloadURL    @@DOWNLOADURL@@
// @description    [@@BUILDNAME@@-@@BUILDDATE@@] Show portal level numbers on map.
// @include        https://www.ingress.com/intel*
// @include        http://www.ingress.com/intel*
// @match          https://www.ingress.com/intel*
// @match          http://www.ingress.com/intel*
// @grant          none
// ==/UserScript==

@@PLUGINSTART@@

// PLUGIN START ////////////////////////////////////////////////////////

// use own namespace for plugin
window.plugin.portalLevelNumbers = function() {};

window.plugin.portalLevelNumbers.levelLayers = {};
window.plugin.portalLevelNumbers.levelLayerGroup = null;

// Use portal add and remove event to control render of portal level numbers
window.plugin.portalLevelNumbers.portalAdded = function(data) {
  data.portal.on('add', function() {
    plugin.portalLevelNumbers.renderLevel(this.options.guid, this.getLatLng());
  });

  data.portal.on('remove', function() {
    plugin.portalLevelNumbers.removeLevel(this.options.guid);
  });
}

window.plugin.portalLevelNumbers.renderLevel = function(guid,latLng) {
    plugin.portalLevelNumbers.removeLevel(guid);

    var p = window.portals[guid];
    var levelNumber = p.options.level;
    var level = L.marker(latLng, {
      icon: L.divIcon({
        className: 'plugin-portal-level-numbers',
        iconAnchor: [6,7],
        iconSize: [12,10],
        html: levelNumber
        }),
      guid: guid
      });

    plugin.portalLevelNumbers.levelLayers[guid] = level;
    level.addTo(plugin.portalLevelNumbers.levelLayerGroup);
}

window.plugin.portalLevelNumbers.removeLevel = function(guid) {
    var previousLayer = plugin.portalLevelNumbers.levelLayers[guid];
    if(previousLayer) {
      plugin.portalLevelNumbers.levelLayerGroup.removeLayer(previousLayer);
      delete plugin.portalLevelNumbers.levelLayers[guid];
    }
}

var setup =  function() {
  $("<style>")
    .prop("type", "text/css")
    .html(".plugin-portal-level-numbers {\
            font-size: 10px;\
            color: #FFFFBB;\
            font-family: monospace;\
            text-align: center;\
            text-shadow: 0 0 0.5em black, 0 0 0.5em black, 0 0 0.5em black;\
            pointer-events: none;\
            -webkit-text-size-adjust:none;\
          }")
  .appendTo("head");

  window.plugin.portalLevelNumbers.levelLayerGroup = new L.LayerGroup();

  window.addLayerGroup('Portal Levels', window.plugin.portalLevelNumbers.levelLayerGroup, true);

  window.addHook('portalAdded', window.plugin.portalLevelNumbers.portalAdded);
}

// PLUGIN END //////////////////////////////////////////////////////////

@@PLUGINEND@@
<commit_msg>tweaked portal levels plugin. reduced level markers and prefer high level portals.<commit_after>// ==UserScript==
// @id             iitc-plugin-portal-level-numbers@rongou
// @name           IITC plugin: Portal Level Numbers
// @category       Layer
// @version        0.1.3.@@DATETIMEVERSION@@
// @namespace      https://github.com/jonatkins/ingress-intel-total-conversion
// @updateURL      @@UPDATEURL@@
// @downloadURL    @@DOWNLOADURL@@
// @description    [@@BUILDNAME@@-@@BUILDDATE@@] Show portal level numbers on map.
// @include        https://www.ingress.com/intel*
// @include        http://www.ingress.com/intel*
// @match          https://www.ingress.com/intel*
// @match          http://www.ingress.com/intel*
// @grant          none
// ==/UserScript==

@@PLUGINSTART@@

// PLUGIN START ////////////////////////////////////////////////////////

// use own namespace for plugin
window.plugin.portalLevelNumbers = function() {
};

window.plugin.portalLevelNumbers.ICON_SIZE = 12;
window.plugin.portalLevelNumbers.MOBILE_SCALE = 1.5;

window.plugin.portalLevelNumbers.levelLayers = {};
window.plugin.portalLevelNumbers.levelLayerGroup = null;

window.plugin.portalLevelNumbers.setupCSS = function() {
  $("<style>")
    .prop("type", "text/css")
    .html(".plugin-portal-level-numbers {\
            font-size: 10px;\
            color: #FFFFBB;\
            font-family: monospace;\
            text-align: center;\
            text-shadow: 0 0 0.5em black, 0 0 0.5em black, 0 0 0.5em black;\
            pointer-events: none;\
            -webkit-text-size-adjust:none;\
          }")
  .appendTo("head");
}

window.plugin.portalLevelNumbers.removeLabel = function(guid) {
  var previousLayer = window.plugin.portalLevelNumbers.levelLayers[guid];
  if(previousLayer) {
    window.plugin.portalLevelNumbers.levelLayerGroup.removeLayer(previousLayer);
    delete plugin.portalLevelNumbers.levelLayers[guid];
  }
}

window.plugin.portalLevelNumbers.addLabel = function(guid,latLng) {
  var previousLayer = window.plugin.portalLevelNumbers.levelLayers[guid];
  if (!previousLayer) {

    var p = window.portals[guid];
    var levelNumber = p.options.level;
    var level = L.marker(latLng, {
      icon: L.divIcon({
        className: 'plugin-portal-level-numbers',
        iconSize: [window.plugin.portalLevelNumbers.ICON_SIZE, window.plugin.portalLevelNumbers.ICON_SIZE],
        html: levelNumber
        }),
      guid: guid
    });
    plugin.portalLevelNumbers.levelLayers[guid] = level;
    level.addTo(plugin.portalLevelNumbers.levelLayerGroup);
  }
}

window.plugin.portalLevelNumbers.updatePortalLabels = function() {

  var SQUARE_SIZE = L.Browser.mobile ? (window.plugin.portalLevelNumbers.ICON_SIZE + 3) * window.plugin.portalLevelNumbers.MOBILE_SCALE
                                     : (window.plugin.portalLevelNumbers.ICON_SIZE + 3);

  // as this is called every time layers are toggled, there's no point in doing it when the layer is off
  if (!map.hasLayer(window.plugin.portalLevelNumbers.levelLayerGroup)) {
    return;
  }

  var portalPoints = {};

  for (var guid in window.portals) {
    var p = window.portals[guid];
    if (p._map) {  // only consider portals added to the map
      var point = map.project(p.getLatLng());
      portalPoints[guid] = point;
    }
  }

  // for efficient testing of intersection, group portals into buckets based on the defined rectangle size
  var buckets = {};
  for (var guid in portalPoints) {
    var point = portalPoints[guid];

    var bucketId = L.point([Math.floor(point.x/(SQUARE_SIZE*2)),Math.floor(point.y/SQUARE_SIZE*2)]);
    // the guid is added to four buckets. this way, when testing for overlap we don't need to test
    // all 8 buckets surrounding the one around the particular portal, only the bucket it is in itself
    var bucketIds = [bucketId, bucketId.add([1,0]), bucketId.add([0,1]), bucketId.add([1,1])];
    for (var i in bucketIds) {
      var b = bucketIds[i].toString();
      if (!buckets[b]) buckets[b] = {};
      buckets[b][guid] = true;
    }
  }

  var coveredPortals = {};

  for (var bucket in buckets) {
    var bucketGuids = buckets[bucket];
    for (var guid in bucketGuids) {
      var point = portalPoints[guid];
      // the bounds used for testing are twice as wide as the rectangle. this is so that there's no left/right
      // overlap between two different portals text
      var southWest = point.subtract([SQUARE_SIZE, SQUARE_SIZE]);
      var northEast = point.add([SQUARE_SIZE, SQUARE_SIZE]);
      var largeBounds = L.bounds(southWest, northEast);

      for (var otherGuid in bucketGuids) {
        // do not check portals already marked as covered
        if (guid != otherGuid && !coveredPortals[otherGuid]) {
          var otherPoint = portalPoints[otherGuid];

          if (largeBounds.contains(otherPoint)) {
            // another portal is within the rectangle - remove if it has not a higher level
            if (portals[guid].options.level > portals[otherGuid].options.level) continue;
            else coveredPortals[guid] = true;
            break;
          }
        }
      }
    }
  }

  for (var guid in coveredPortals) {
    delete portalPoints[guid];
  }

  // remove any not wanted
  for (var guid in window.plugin.portalLevelNumbers.levelLayers) {
    if (!(guid in portalPoints)) {
      window.plugin.portalLevelNumbers.removeLabel(guid);
    }
  }

  // and add those we do
  for (var guid in portalPoints) {
    window.plugin.portalLevelNumbers.addLabel(guid, portals[guid].getLatLng());
  }
}

// as calculating portal marker visibility can take some time when there's lots of portals shown, we'll do it on
// a short timer. this way it doesn't get repeated so much
window.plugin.portalLevelNumbers.delayedUpdatePortalLabels = function(wait) {

  if (window.plugin.portalLevelNumbers.timer === undefined) {
    window.plugin.portalLevelNumbers.timer = setTimeout ( function() {
      window.plugin.portalLevelNumbers.timer = undefined;
      window.plugin.portalLevelNumbers.updatePortalLabels();
    }, wait*1000);

  }
}

var setup = function() {

  window.plugin.portalLevelNumbers.setupCSS();

  window.plugin.portalLevelNumbers.levelLayerGroup = new L.LayerGroup();
  window.addLayerGroup('Portal Levels', window.plugin.portalLevelNumbers.levelLayerGroup, true);

  window.addHook('requestFinished', function() { setTimeout(function(){window.plugin.portalLevelNumbers.delayedUpdatePortalLabels(3.0);},1); });
  window.addHook('mapDataRefreshEnd', function() { window.plugin.portalLevelNumbers.delayedUpdatePortalLabels(0.5); });
  window.map.on('overlayadd overlayremove', function() { setTimeout(function(){window.plugin.portalLevelNumbers.delayedUpdatePortalLabels(1.0);},1); });

}

// PLUGIN END //////////////////////////////////////////////////////////

@@PLUGINEND@@
<|endoftext|>


<commit_before>/**
 * @module ol/reproj
 */
import {createCanvasContext2D} from './dom.js';
import {containsCoordinate, createEmpty, extend, forEachCorner, getCenter, getHeight, getTopLeft, getWidth} from './extent.js';
import {solveLinearSystem} from './math.js';
import {getPointResolution, transform} from './proj.js';

let brokenDiagonalRendering_;

/**
 * This draws a small triangle into a canvas by setting the triangle as the clip region
 * and then drawing a (too large) rectangle
 *
 * @param {CanvasRenderingContext2D} ctx The context in which to draw the triangle
 * @param {number} u1 The x-coordinate of the second point. The first point is 0,0.
 * @param {number} v1 The y-coordinate of the second point.
 * @param {number} u2 The x-coordinate of the third point.
 * @param {number} v2 The y-coordinate of the third point.
 */
function drawTestTriangle(ctx, u1, v1, u2, v2) {
  ctx.beginPath();
  ctx.moveTo(0, 0);
  ctx.lineTo(u1, v1);
  ctx.lineTo(u2, v2);
  ctx.closePath();
  ctx.save();
  ctx.clip();
  ctx.fillRect(0, 0, Math.max(u1, u2) + 1, Math.max(v1, v2));
  ctx.restore();
}

/**
 * Given the data from getImageData, see if the right values appear at the provided offset.
 * Returns true if either the color or transparency is off
 *
 * @param {Uint8ClampedArray} data The data returned from getImageData
 * @param {number} offset The pixel offset from the start of data.
 * @return {boolean} true if the diagonal rendering is broken
 */
function verifyBrokenDiagonalRendering(data, offset) {
  // the values ought to be close to the rgba(210, 0, 0, 0.75)
  return Math.abs(data[offset * 4] - 210) > 2 || Math.abs(data[offset * 4 + 3] - 0.75 * 255) > 2;
}

/**
 * Determines if the current browser configuration can render triangular clip regions correctly.
 * This value is cached so the function is only expensive the first time called.
 * Firefox on Windows (as of now) does not if HWA is enabled. See https://bugzilla.mozilla.org/show_bug.cgi?id=1606976
 * IE also doesn't. Chrome works, and everything seems to work on OSX and Android. This function caches the
 * result. I suppose that it is conceivably possible that a browser might flip modes while the app is
 * running, but lets hope not.
 *
 * @return {boolean} true if the Diagonal Rendering is broken.
 */
function isBrokenDiagonalRendering() {
  if (brokenDiagonalRendering_ === undefined) {
    const ctx = document.createElement('canvas').getContext('2d');
    ctx.globalCompositeOperation = 'lighter';
    ctx.fillStyle = 'rgba(210, 0, 0, 0.75)';
    drawTestTriangle(ctx, 4, 5, 4, 0);
    drawTestTriangle(ctx, 4, 5, 0, 5);
    const data = ctx.getImageData(0, 0, 3, 3).data;
    brokenDiagonalRendering_ = verifyBrokenDiagonalRendering(data, 0) ||
        verifyBrokenDiagonalRendering(data, 4) ||
        verifyBrokenDiagonalRendering(data, 8);
  }

  return brokenDiagonalRendering_;
}

/**
 * Calculates ideal resolution to use from the source in order to achieve
 * pixel mapping as close as possible to 1:1 during reprojection.
 * The resolution is calculated regardless of what resolutions
 * are actually available in the dataset (TileGrid, Image, ...).
 *
 * @param {import("./proj/Projection.js").default} sourceProj Source projection.
 * @param {import("./proj/Projection.js").default} targetProj Target projection.
 * @param {import("./coordinate.js").Coordinate} targetCenter Target center.
 * @param {number} targetResolution Target resolution.
 * @return {number} The best resolution to use. Can be +-Infinity, NaN or 0.
 */
export function calculateSourceResolution(sourceProj, targetProj,
  targetCenter, targetResolution) {

  const sourceCenter = transform(targetCenter, targetProj, sourceProj);

  // calculate the ideal resolution of the source data
  let sourceResolution = getPointResolution(targetProj, targetResolution, targetCenter);

  const targetMetersPerUnit = targetProj.getMetersPerUnit();
  if (targetMetersPerUnit !== undefined) {
    sourceResolution *= targetMetersPerUnit;
  }
  const sourceMetersPerUnit = sourceProj.getMetersPerUnit();
  if (sourceMetersPerUnit !== undefined) {
    sourceResolution /= sourceMetersPerUnit;
  }

  // Based on the projection properties, the point resolution at the specified
  // coordinates may be slightly different. We need to reverse-compensate this
  // in order to achieve optimal results.

  const sourceExtent = sourceProj.getExtent();
  if (!sourceExtent || containsCoordinate(sourceExtent, sourceCenter)) {
    const compensationFactor = getPointResolution(sourceProj, sourceResolution, sourceCenter) /
        sourceResolution;
    if (isFinite(compensationFactor) && compensationFactor > 0) {
      sourceResolution /= compensationFactor;
    }
  }

  return sourceResolution;
}

/**
 * Calculates ideal resolution to use from the source in order to achieve
 * pixel mapping as close as possible to 1:1 during reprojection.
 * The resolution is calculated regardless of what resolutions
 * are actually available in the dataset (TileGrid, Image, ...).
 *
 * @param {import("./proj/Projection.js").default} sourceProj Source projection.
 * @param {import("./proj/Projection.js").default} targetProj Target projection.
 * @param {import("./extent.js").Extent} targetExtent Target extent
 * @param {number} targetResolution Target resolution.
 * @return {number} The best resolution to use. Can be +-Infinity, NaN or 0.
 */
export function calculateSourceExtentResolution(sourceProj, targetProj,
  targetExtent, targetResolution) {

  const targetCenter = getCenter(targetExtent);
  let sourceResolution = calculateSourceResolution(sourceProj, targetProj, targetCenter, targetResolution);

  if (!isFinite(sourceResolution) || sourceResolution <= 0) {
    forEachCorner(targetExtent, function(corner) {
      sourceResolution = calculateSourceResolution(sourceProj, targetProj, corner, targetResolution);
      return isFinite(sourceResolution) && sourceResolution > 0;
    });
  }

  return sourceResolution;
}

/**
 * Renders the source data into new canvas based on the triangulation.
 *
 * @param {number} width Width of the canvas.
 * @param {number} height Height of the canvas.
 * @param {number} pixelRatio Pixel ratio.
 * @param {number} sourceResolution Source resolution.
 * @param {import("./extent.js").Extent} sourceExtent Extent of the data source.
 * @param {number} targetResolution Target resolution.
 * @param {import("./extent.js").Extent} targetExtent Target extent.
 * @param {import("./reproj/Triangulation.js").default} triangulation
 * Calculated triangulation.
 * @param {Array<{extent: import("./extent.js").Extent,
 *                 image: (HTMLCanvasElement|HTMLImageElement|HTMLVideoElement)}>} sources
 * Array of sources.
 * @param {number} gutter Gutter of the sources.
 * @param {boolean=} opt_renderEdges Render reprojection edges.
 * @return {HTMLCanvasElement} Canvas with reprojected data.
 */
export function render(width, height, pixelRatio,
  sourceResolution, sourceExtent, targetResolution, targetExtent,
  triangulation, sources, gutter, opt_renderEdges) {

  const context = createCanvasContext2D(Math.round(pixelRatio * width),
    Math.round(pixelRatio * height));

  if (sources.length === 0) {
    return context.canvas;
  }

  context.scale(pixelRatio, pixelRatio);

  context.globalCompositeOperation = 'lighter';

  const sourceDataExtent = createEmpty();
  sources.forEach(function(src, i, arr) {
    extend(sourceDataExtent, src.extent);
  });

  const canvasWidthInUnits = getWidth(sourceDataExtent);
  const canvasHeightInUnits = getHeight(sourceDataExtent);
  const stitchContext = createCanvasContext2D(
    Math.round(pixelRatio * canvasWidthInUnits / sourceResolution),
    Math.round(pixelRatio * canvasHeightInUnits / sourceResolution));

  const stitchScale = pixelRatio / sourceResolution;

  sources.forEach(function(src, i, arr) {
    const xPos = src.extent[0] - sourceDataExtent[0];
    const yPos = -(src.extent[3] - sourceDataExtent[3]);
    const srcWidth = getWidth(src.extent);
    const srcHeight = getHeight(src.extent);

    // This test should never fail -- but it does. Need to find a fix the upstream condition
    if (src.image.width > 0 && src.image.height > 0) {
      stitchContext.drawImage(
        src.image,
        gutter, gutter,
        src.image.width - 2 * gutter, src.image.height - 2 * gutter,
        xPos * stitchScale, yPos * stitchScale,
        srcWidth * stitchScale, srcHeight * stitchScale);
    }
  });

  const targetTopLeft = getTopLeft(targetExtent);

  triangulation.getTriangles().forEach(function(triangle, i, arr) {
    /* Calculate affine transform (src -> dst)
     * Resulting matrix can be used to transform coordinate
     * from `sourceProjection` to destination pixels.
     *
     * To optimize number of context calls and increase numerical stability,
     * we also do the following operations:
     * trans(-topLeftExtentCorner), scale(1 / targetResolution), scale(1, -1)
     * here before solving the linear system so [ui, vi] are pixel coordinates.
     *
     * Src points: xi, yi
     * Dst points: ui, vi
     * Affine coefficients: aij
     *
     * | x0 y0 1  0  0 0 |   |a00|   |u0|
     * | x1 y1 1  0  0 0 |   |a01|   |u1|
     * | x2 y2 1  0  0 0 | x |a02| = |u2|
     * |  0  0 0 x0 y0 1 |   |a10|   |v0|
     * |  0  0 0 x1 y1 1 |   |a11|   |v1|
     * |  0  0 0 x2 y2 1 |   |a12|   |v2|
     */
    const source = triangle.source;
    const target = triangle.target;
    let x0 = source[0][0], y0 = source[0][1];
    let x1 = source[1][0], y1 = source[1][1];
    let x2 = source[2][0], y2 = source[2][1];
    const u0 = (target[0][0] - targetTopLeft[0]) / targetResolution;
    const v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
    const u1 = (target[1][0] - targetTopLeft[0]) / targetResolution;
    const v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
    const u2 = (target[2][0] - targetTopLeft[0]) / targetResolution;
    const v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;

    // Shift all the source points to improve numerical stability
    // of all the subsequent calculations. The [x0, y0] is used here.
    // This is also used to simplify the linear system.
    const sourceNumericalShiftX = x0;
    const sourceNumericalShiftY = y0;
    x0 = 0;
    y0 = 0;
    x1 -= sourceNumericalShiftX;
    y1 -= sourceNumericalShiftY;
    x2 -= sourceNumericalShiftX;
    y2 -= sourceNumericalShiftY;

    const augmentedMatrix = [
      [x1, y1, 0, 0, u1 - u0],
      [x2, y2, 0, 0, u2 - u0],
      [0, 0, x1, y1, v1 - v0],
      [0, 0, x2, y2, v2 - v0]
    ];
    const affineCoefs = solveLinearSystem(augmentedMatrix);
    if (!affineCoefs) {
      return;
    }

    context.save();
    context.beginPath();

    if (isBrokenDiagonalRendering()) {
      // Make sure that everything is on pixel boundaries
      const u0r = Math.round(u0);
      const v0r = Math.round(v0);
      const u1r = Math.round(u1);
      const v1r = Math.round(v1);
      const u2r = Math.round(u2);
      const v2r = Math.round(v2);
      // Make sure that all lines are horizontal or vertical
      context.moveTo(u1r, v1r);
      // This is the diagonal line. Do it in 4 steps
      const steps = 4;
      const ud = u0r - u1r;
      const vd = v0r - v1r;
      for (let step = 0; step < steps; step++) {
        // Go horizontally
        context.lineTo(u1r + Math.round((step + 1) * ud / steps), v1r + Math.round(step * vd / (steps - 1)));
        // Go vertically
        if (step != (steps - 1)) {
          context.lineTo(u1r + Math.round((step + 1) * ud / steps), v1r + Math.round((step + 1) * vd / (steps - 1)));
        }
      }
      // We are almost at u0r, v0r
      context.lineTo(u2r, v2r);
    } else {
      context.moveTo(u1, v1);
      context.lineTo(u0, v0);
      context.lineTo(u2, v2);
    }

    context.clip();

    context.transform(
      affineCoefs[0], affineCoefs[2], affineCoefs[1], affineCoefs[3], u0, v0);

    context.translate(sourceDataExtent[0] - sourceNumericalShiftX,
      sourceDataExtent[3] - sourceNumericalShiftY);

    context.scale(sourceResolution / pixelRatio,
      -sourceResolution / pixelRatio);

    context.drawImage(stitchContext.canvas, 0, 0);
    context.restore();
  });

  if (opt_renderEdges) {
    context.save();

    context.strokeStyle = 'black';
    context.lineWidth = 1;

    triangulation.getTriangles().forEach(function(triangle, i, arr) {
      const target = triangle.target;
      const u0 = (target[0][0] - targetTopLeft[0]) / targetResolution;
      const v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
      const u1 = (target[1][0] - targetTopLeft[0]) / targetResolution;
      const v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
      const u2 = (target[2][0] - targetTopLeft[0]) / targetResolution;
      const v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;

      context.beginPath();
      context.moveTo(u1, v1);
      context.lineTo(u0, v0);
      context.lineTo(u2, v2);
      context.closePath();
      context.stroke();
    });

    context.restore();
  }
  return context.canvas;
}
<commit_msg>Fix pixel rounding<commit_after>/**
 * @module ol/reproj
 */
import {createCanvasContext2D} from './dom.js';
import {containsCoordinate, createEmpty, extend, forEachCorner, getCenter, getHeight, getTopLeft, getWidth} from './extent.js';
import {solveLinearSystem} from './math.js';
import {getPointResolution, transform} from './proj.js';

let brokenDiagonalRendering_;

/**
 * This draws a small triangle into a canvas by setting the triangle as the clip region
 * and then drawing a (too large) rectangle
 *
 * @param {CanvasRenderingContext2D} ctx The context in which to draw the triangle
 * @param {number} u1 The x-coordinate of the second point. The first point is 0,0.
 * @param {number} v1 The y-coordinate of the second point.
 * @param {number} u2 The x-coordinate of the third point.
 * @param {number} v2 The y-coordinate of the third point.
 */
function drawTestTriangle(ctx, u1, v1, u2, v2) {
  ctx.beginPath();
  ctx.moveTo(0, 0);
  ctx.lineTo(u1, v1);
  ctx.lineTo(u2, v2);
  ctx.closePath();
  ctx.save();
  ctx.clip();
  ctx.fillRect(0, 0, Math.max(u1, u2) + 1, Math.max(v1, v2));
  ctx.restore();
}

/**
 * Given the data from getImageData, see if the right values appear at the provided offset.
 * Returns true if either the color or transparency is off
 *
 * @param {Uint8ClampedArray} data The data returned from getImageData
 * @param {number} offset The pixel offset from the start of data.
 * @return {boolean} true if the diagonal rendering is broken
 */
function verifyBrokenDiagonalRendering(data, offset) {
  // the values ought to be close to the rgba(210, 0, 0, 0.75)
  return Math.abs(data[offset * 4] - 210) > 2 || Math.abs(data[offset * 4 + 3] - 0.75 * 255) > 2;
}

/**
 * Determines if the current browser configuration can render triangular clip regions correctly.
 * This value is cached so the function is only expensive the first time called.
 * Firefox on Windows (as of now) does not if HWA is enabled. See https://bugzilla.mozilla.org/show_bug.cgi?id=1606976
 * IE also doesn't. Chrome works, and everything seems to work on OSX and Android. This function caches the
 * result. I suppose that it is conceivably possible that a browser might flip modes while the app is
 * running, but lets hope not.
 *
 * @return {boolean} true if the Diagonal Rendering is broken.
 */
function isBrokenDiagonalRendering() {
  if (brokenDiagonalRendering_ === undefined) {
    const ctx = document.createElement('canvas').getContext('2d');
    ctx.globalCompositeOperation = 'lighter';
    ctx.fillStyle = 'rgba(210, 0, 0, 0.75)';
    drawTestTriangle(ctx, 4, 5, 4, 0);
    drawTestTriangle(ctx, 4, 5, 0, 5);
    const data = ctx.getImageData(0, 0, 3, 3).data;
    brokenDiagonalRendering_ = verifyBrokenDiagonalRendering(data, 0) ||
        verifyBrokenDiagonalRendering(data, 4) ||
        verifyBrokenDiagonalRendering(data, 8);
  }

  return brokenDiagonalRendering_;
}

/**
 * Calculates ideal resolution to use from the source in order to achieve
 * pixel mapping as close as possible to 1:1 during reprojection.
 * The resolution is calculated regardless of what resolutions
 * are actually available in the dataset (TileGrid, Image, ...).
 *
 * @param {import("./proj/Projection.js").default} sourceProj Source projection.
 * @param {import("./proj/Projection.js").default} targetProj Target projection.
 * @param {import("./coordinate.js").Coordinate} targetCenter Target center.
 * @param {number} targetResolution Target resolution.
 * @return {number} The best resolution to use. Can be +-Infinity, NaN or 0.
 */
export function calculateSourceResolution(sourceProj, targetProj,
  targetCenter, targetResolution) {

  const sourceCenter = transform(targetCenter, targetProj, sourceProj);

  // calculate the ideal resolution of the source data
  let sourceResolution = getPointResolution(targetProj, targetResolution, targetCenter);

  const targetMetersPerUnit = targetProj.getMetersPerUnit();
  if (targetMetersPerUnit !== undefined) {
    sourceResolution *= targetMetersPerUnit;
  }
  const sourceMetersPerUnit = sourceProj.getMetersPerUnit();
  if (sourceMetersPerUnit !== undefined) {
    sourceResolution /= sourceMetersPerUnit;
  }

  // Based on the projection properties, the point resolution at the specified
  // coordinates may be slightly different. We need to reverse-compensate this
  // in order to achieve optimal results.

  const sourceExtent = sourceProj.getExtent();
  if (!sourceExtent || containsCoordinate(sourceExtent, sourceCenter)) {
    const compensationFactor = getPointResolution(sourceProj, sourceResolution, sourceCenter) /
        sourceResolution;
    if (isFinite(compensationFactor) && compensationFactor > 0) {
      sourceResolution /= compensationFactor;
    }
  }

  return sourceResolution;
}

/**
 * Calculates ideal resolution to use from the source in order to achieve
 * pixel mapping as close as possible to 1:1 during reprojection.
 * The resolution is calculated regardless of what resolutions
 * are actually available in the dataset (TileGrid, Image, ...).
 *
 * @param {import("./proj/Projection.js").default} sourceProj Source projection.
 * @param {import("./proj/Projection.js").default} targetProj Target projection.
 * @param {import("./extent.js").Extent} targetExtent Target extent
 * @param {number} targetResolution Target resolution.
 * @return {number} The best resolution to use. Can be +-Infinity, NaN or 0.
 */
export function calculateSourceExtentResolution(sourceProj, targetProj,
  targetExtent, targetResolution) {

  const targetCenter = getCenter(targetExtent);
  let sourceResolution = calculateSourceResolution(sourceProj, targetProj, targetCenter, targetResolution);

  if (!isFinite(sourceResolution) || sourceResolution <= 0) {
    forEachCorner(targetExtent, function(corner) {
      sourceResolution = calculateSourceResolution(sourceProj, targetProj, corner, targetResolution);
      return isFinite(sourceResolution) && sourceResolution > 0;
    });
  }

  return sourceResolution;
}

/**
 * Renders the source data into new canvas based on the triangulation.
 *
 * @param {number} width Width of the canvas.
 * @param {number} height Height of the canvas.
 * @param {number} pixelRatio Pixel ratio.
 * @param {number} sourceResolution Source resolution.
 * @param {import("./extent.js").Extent} sourceExtent Extent of the data source.
 * @param {number} targetResolution Target resolution.
 * @param {import("./extent.js").Extent} targetExtent Target extent.
 * @param {import("./reproj/Triangulation.js").default} triangulation
 * Calculated triangulation.
 * @param {Array<{extent: import("./extent.js").Extent,
 *                 image: (HTMLCanvasElement|HTMLImageElement|HTMLVideoElement)}>} sources
 * Array of sources.
 * @param {number} gutter Gutter of the sources.
 * @param {boolean=} opt_renderEdges Render reprojection edges.
 * @return {HTMLCanvasElement} Canvas with reprojected data.
 */
export function render(width, height, pixelRatio,
  sourceResolution, sourceExtent, targetResolution, targetExtent,
  triangulation, sources, gutter, opt_renderEdges) {

  const context = createCanvasContext2D(Math.round(pixelRatio * width),
    Math.round(pixelRatio * height));

  if (sources.length === 0) {
    return context.canvas;
  }

  context.scale(pixelRatio, pixelRatio);

  function pixelRound(value) {
    return Math.round(value * pixelRatio) / pixelRatio;
  }

  context.globalCompositeOperation = 'lighter';

  const sourceDataExtent = createEmpty();
  sources.forEach(function(src, i, arr) {
    extend(sourceDataExtent, src.extent);
  });

  const canvasWidthInUnits = getWidth(sourceDataExtent);
  const canvasHeightInUnits = getHeight(sourceDataExtent);
  const stitchContext = createCanvasContext2D(
    Math.round(pixelRatio * canvasWidthInUnits / sourceResolution),
    Math.round(pixelRatio * canvasHeightInUnits / sourceResolution));

  const stitchScale = pixelRatio / sourceResolution;

  sources.forEach(function(src, i, arr) {
    const xPos = src.extent[0] - sourceDataExtent[0];
    const yPos = -(src.extent[3] - sourceDataExtent[3]);
    const srcWidth = getWidth(src.extent);
    const srcHeight = getHeight(src.extent);

    // This test should never fail -- but it does. Need to find a fix the upstream condition
    if (src.image.width > 0 && src.image.height > 0) {
      stitchContext.drawImage(
        src.image,
        gutter, gutter,
        src.image.width - 2 * gutter, src.image.height - 2 * gutter,
        xPos * stitchScale, yPos * stitchScale,
        srcWidth * stitchScale, srcHeight * stitchScale);
    }
  });

  const targetTopLeft = getTopLeft(targetExtent);

  triangulation.getTriangles().forEach(function(triangle, i, arr) {
    /* Calculate affine transform (src -> dst)
     * Resulting matrix can be used to transform coordinate
     * from `sourceProjection` to destination pixels.
     *
     * To optimize number of context calls and increase numerical stability,
     * we also do the following operations:
     * trans(-topLeftExtentCorner), scale(1 / targetResolution), scale(1, -1)
     * here before solving the linear system so [ui, vi] are pixel coordinates.
     *
     * Src points: xi, yi
     * Dst points: ui, vi
     * Affine coefficients: aij
     *
     * | x0 y0 1  0  0 0 |   |a00|   |u0|
     * | x1 y1 1  0  0 0 |   |a01|   |u1|
     * | x2 y2 1  0  0 0 | x |a02| = |u2|
     * |  0  0 0 x0 y0 1 |   |a10|   |v0|
     * |  0  0 0 x1 y1 1 |   |a11|   |v1|
     * |  0  0 0 x2 y2 1 |   |a12|   |v2|
     */
    const source = triangle.source;
    const target = triangle.target;
    let x0 = source[0][0], y0 = source[0][1];
    let x1 = source[1][0], y1 = source[1][1];
    let x2 = source[2][0], y2 = source[2][1];
    const u0 = (target[0][0] - targetTopLeft[0]) / targetResolution;
    const v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
    const u1 = (target[1][0] - targetTopLeft[0]) / targetResolution;
    const v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
    const u2 = (target[2][0] - targetTopLeft[0]) / targetResolution;
    const v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;

    // Shift all the source points to improve numerical stability
    // of all the subsequent calculations. The [x0, y0] is used here.
    // This is also used to simplify the linear system.
    const sourceNumericalShiftX = x0;
    const sourceNumericalShiftY = y0;
    x0 = 0;
    y0 = 0;
    x1 -= sourceNumericalShiftX;
    y1 -= sourceNumericalShiftY;
    x2 -= sourceNumericalShiftX;
    y2 -= sourceNumericalShiftY;

    const augmentedMatrix = [
      [x1, y1, 0, 0, u1 - u0],
      [x2, y2, 0, 0, u2 - u0],
      [0, 0, x1, y1, v1 - v0],
      [0, 0, x2, y2, v2 - v0]
    ];
    const affineCoefs = solveLinearSystem(augmentedMatrix);
    if (!affineCoefs) {
      return;
    }

    context.save();
    context.beginPath();

    if (isBrokenDiagonalRendering()) {
      // Make sure that everything is on pixel boundaries
      const u0r = pixelRound(u0);
      const v0r = pixelRound(v0);
      const u1r = pixelRound(u1);
      const v1r = pixelRound(v1);
      const u2r = pixelRound(u2);
      const v2r = pixelRound(v2);
      // Make sure that all lines are horizontal or vertical
      context.moveTo(u1r, v1r);
      // This is the diagonal line. Do it in 4 steps
      const steps = 4;
      const ud = u0r - u1r;
      const vd = v0r - v1r;
      for (let step = 0; step < steps; step++) {
        // Go horizontally
        context.lineTo(u1r + pixelRound((step + 1) * ud / steps), v1r + pixelRound(step * vd / (steps - 1)));
        // Go vertically
        if (step != (steps - 1)) {
          context.lineTo(u1r + pixelRound((step + 1) * ud / steps), v1r + pixelRound((step + 1) * vd / (steps - 1)));
        }
      }
      // We are almost at u0r, v0r
      context.lineTo(u2r, v2r);
    } else {
      context.moveTo(u1, v1);
      context.lineTo(u0, v0);
      context.lineTo(u2, v2);
    }

    context.clip();

    context.transform(
      affineCoefs[0], affineCoefs[2], affineCoefs[1], affineCoefs[3], u0, v0);

    context.translate(sourceDataExtent[0] - sourceNumericalShiftX,
      sourceDataExtent[3] - sourceNumericalShiftY);

    context.scale(sourceResolution / pixelRatio,
      -sourceResolution / pixelRatio);

    context.drawImage(stitchContext.canvas, 0, 0);
    context.restore();
  });

  if (opt_renderEdges) {
    context.save();

    context.strokeStyle = 'black';
    context.lineWidth = 1;

    triangulation.getTriangles().forEach(function(triangle, i, arr) {
      const target = triangle.target;
      const u0 = (target[0][0] - targetTopLeft[0]) / targetResolution;
      const v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
      const u1 = (target[1][0] - targetTopLeft[0]) / targetResolution;
      const v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
      const u2 = (target[2][0] - targetTopLeft[0]) / targetResolution;
      const v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;

      context.beginPath();
      context.moveTo(u1, v1);
      context.lineTo(u0, v0);
      context.lineTo(u2, v2);
      context.closePath();
      context.stroke();
    });

    context.restore();
  }
  return context.canvas;
}
<|endoftext|>


<commit_before>/**
 * paginator.js
 * Description: A lightweight and dependency-free pagination library
 * Version: 0.0.1
 * Author: Ossama Edbali
 */

var paginator = (function () {

    var _ = {},
        $ = {};

    var defaults = {
        'appendTo': null,
        'itemsPerPage': 10,
        'displayedPageNumbers': 5,
        'currentPage': 1,
        'margins': 2,
        'prevText': 'Prev',
        'nextText': 'Next',
        'theme': 'default' // or 'no-theme' (so it can be customised)

    };

    // BEGIN: Utilities
    $.extend = function (defaults, options) {
        var extended = {};
        var prop;
        for (prop in defaults) {
            if (Object.prototype.hasOwnProperty.call(defaults, prop)) {
                extended[prop] = defaults[prop];
            }
        }
        for (prop in options) {
            if (Object.prototype.hasOwnProperty.call(options, prop)) {
                extended[prop] = options[prop];
            }
        }
        return extended;
    };

    $.isElement = function (o) {
        return o.nodeType && o.nodeType === 1;
    };

    $.getElements = function (e) {
        if (typeof e === 'string') {
            return document.querySelectorAll(e); // NodeList
        }

        if ($.isElement(e)) {
            return e.children; // HTMLCollection
        }
    };

    $.getParent = function () {
        var o = this._opts;

        // [squeeze]: ternary op here
        if (o.appendTo === null) {
            // Append to elements parent
            var testElement = this._elements[0]; // [fix]: empty array
            return testElement.parentNode;
        }
        else {
            return document.querySelector(o.appendTo);
        }
    };

    $.isOutOfRange = function (i, arr) {
        return i < 0 || i >= arr.length;
    };

    $.toArray = function (collection) {
        var arr = [];
        for (var i = collection.length; i--; arr.unshift(collection[i]));
        return arr;
    };

    $.wrap = function (fn) {
        var args = Array.prototype.slice.call(arguments, 1),
            self = this;

        return function () {
            fn.apply(self, args);
        };
    };
    // END: Utilities

    // BEGIN: Private member
    $.createLinks = function (parent) {
        var self = this,
            o = self._opts,
            elements = self._elements,
            numPages = Math.ceil(elements.length / o.itemsPerPage);

        // Create wrapper around the links
        var wrapper = document.createElement('div'),
            fragment = document.createDocumentFragment();
        wrapper.className = 'paginator-wrapper ' + (o.theme === 'default' ? 'default-theme' : 'no-theme');

        // Create previous
        var prev = document.createElement('a');
        prev.className = 'paginator-link';
        prev.setAttribute('href', '#page-prev');
        prev.innerHTML = o.prevText;
        prev.addEventListener('click', $.wrap.call(self, self.previous));
        fragment.appendChild(prev);

        // Create page links
        for (var i = 1; i <= numPages; i++) {
            var plink = document.createElement('a');
            plink.className = 'paginator-link';
            if (o.currentPage === i) {
                plink.className += ' paginator-active';
            }
            plink.setAttribute('href', '#page-' + i);
            plink.innerHTML = i + '';
            plink.addEventListener('click', $.wrap.call(self, self.select, i - 1), false);

            self._links.push(plink);
            fragment.appendChild(plink);

            // Add dots
            if (i === o.margins || numPages - i === o.margins) {
                var dots = document.createElement('span');
                dots.className = 'paginator-dots';
                dots.innerHTML = '...';
                fragment.appendChild(dots);
            }
        }

        // Create next
        var next = document.createElement('a');
        next.className = 'paginator-link';
        next.setAttribute('href', '#page-next');
        next.innerHTML = o.nextText;
        next.addEventListener('click', $.wrap.call(self, self.next));
        fragment.appendChild(next);
        //wrapper.appendChild(next);
        wrapper.appendChild(fragment);

        if (parent) {
            parent.appendChild(wrapper);
        }
    };

    $.createPages = function (parent) {
        var o = this._opts,
            elements = $.toArray(this._elements),
            numItems = elements.length,
            itemsPerPage = o.itemsPerPage,
            numPages = Math.ceil(numItems / itemsPerPage);

        for (var i = 1; i <= numPages; i++) {
            var page = document.createElement('div'),
                toAdd = elements.slice(0, itemsPerPage);

            page.className = 'paginator-page';
            if (o.currentPage === i) {
                page.className += ' paginator-active';
            }

            // Append to page
            for (var j = 0, l = toAdd.length; j < l; j++) {
                page.appendChild(toAdd[j]);
            }

            // Append page and update elements
            parent.appendChild(page);
            this._pages.push(page);
            elements = elements.slice(itemsPerPage);
        }
    };

    $.showInterval = function () {
        var o = this._opts;
        var activePageNum = this._activePageNum;
        var numLinks = this._links.length;

        // Reset all links
        for (var i = 0, l = this._links.length; i < l; i++) {
            this._links[i].style.display = 'none';
        }

        // Show margins
        for (i = 0; i < o.margins; i++) {
            this._links[i].style.display = 'inline';
        }

        for (i = numLinks - 1; i >= numLinks - o.margins; i--) {
            this._links[i].style.display = 'inline';
        }

        /*if (activePageNum > o.displayedPageNumbers + o.margins) {
            // Add dots
            var dots = document.createElement('span');

        }*/

        // Show interval
        var start = (activePageNum + o.displayedPageNumbers >= numLinks) ? numLinks - o.displayedPageNumbers : activePageNum,
            end = (activePageNum + o.displayedPageNumbers >= numLinks) ? numLinks : activePageNum + o.displayedPageNumbers;

        for (i = start; i < end; i++) {
            if (this._links[i].style.display === 'none') {
                this._links[i].style.display = 'inline';
            }
        }

        var marginLink = this._links[o.margins - 1],
            marginLinkNeighbour = this._links[o.margins];

        if (marginLinkNeighbour.style.display === 'none') {

        }
    };

    function insertAfter(newNode, referenceNode) {
        referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
    }

    // END: Private member

    _.Paginator = function (elements, opts) {
        var self = this;

        // Unpack options
        opts = opts || {};
        self._opts = $.extend(defaults, opts);
        self._elements = $.getElements(elements);
        self._pages = [];
        self._links = [];
        self._activePageNum = self._opts.currentPage - 1;

        // Initialise links and pages
        var parent = $.getParent.call(self);
        $.createPages.call(self, parent);
        $.createLinks.call(self, parent);
        $.showInterval.call(self);
    };

    _.create = function (elements, opts) {
        return new _.Paginator(elements, opts);
    };

    // BEGIN: Public methods
    _.Paginator.prototype.select = function (pageNum) {
        var currentPage = this.getCurrentPage();

        // Remove
        currentPage.classList.remove('paginator-active');
        this._links[this._activePageNum].classList.remove('paginator-active');

        // Add
        this._pages[pageNum].classList.add('paginator-active');
        this._links[pageNum].classList.add('paginator-active');

        // Update active page number
        this._activePageNum = pageNum;
        $.showInterval.call(this);
    };

    _.Paginator.prototype.getCurrentPage = function () { // Getter and setter
        return this._pages[this._activePageNum];
    };

    _.Paginator.prototype.previous = function () {
        var pageNum = this._activePageNum - 1;
        if (!$.isOutOfRange(pageNum, this._pages)) {
            this.select(pageNum);
        }
    };

    _.Paginator.prototype.next = function () {
        var pageNum = this._activePageNum + 1;
        if (!$.isOutOfRange(pageNum, this._pages)) {
            this.select(pageNum);
        }
    };

    _.Paginator.prototype.pagesCount = function () {
        return this._pages.length;
    };

    _.Paginator.prototype.disable = function () {

    };

    _.Paginator.prototype.enable = function () {

    };

    // END: Public methods

    return _;

})();<commit_msg>Added margin support<commit_after>/**
 * paginator.js
 * Description: A lightweight and dependency-free pagination library
 * Version: 0.0.1
 * Author: Ossama Edbali
 */

var paginator = (function () {

    var _ = {},
        $ = {};

    var defaults = {
        'appendTo': null,
        'itemsPerPage': 10,
        'displayedPageNumbers': 5,
        'currentPage': 1,
        'margins': 2,
        'prevText': 'Prev',
        'nextText': 'Next',
        'theme': 'default' // or 'no-theme' (so it can be customised)

    };

    // BEGIN: Utilities
    $.extend = function (defaults, options) {
        var extended = {};
        var prop;
        for (prop in defaults) {
            if (Object.prototype.hasOwnProperty.call(defaults, prop)) {
                extended[prop] = defaults[prop];
            }
        }
        for (prop in options) {
            if (Object.prototype.hasOwnProperty.call(options, prop)) {
                extended[prop] = options[prop];
            }
        }
        return extended;
    };

    $.isElement = function (o) {
        return o.nodeType && o.nodeType === 1;
    };

    $.getElements = function (e) {
        if (typeof e === 'string') {
            return document.querySelectorAll(e); // NodeList
        }

        if ($.isElement(e)) {
            return e.children; // HTMLCollection
        }
    };

    $.getParent = function () {
        var o = this._opts;

        // [squeeze]: ternary op here
        if (o.appendTo === null) {
            // Append to elements parent
            var testElement = this._elements[0]; // [fix]: empty array
            return testElement.parentNode;
        }
        else {
            return document.querySelector(o.appendTo);
        }
    };

    $.isOutOfRange = function (i, arr) {
        return i < 0 || i >= arr.length;
    };

    $.toArray = function (collection) {
        var arr = [];
        for (var i = collection.length; i--; arr.unshift(collection[i]));
        return arr;
    };

    $.wrap = function (fn) {
        var args = Array.prototype.slice.call(arguments, 1),
            self = this;

        return function () {
            fn.apply(self, args);
        };
    };
    // END: Utilities

    // BEGIN: Private member
    $.createLinks = function (parent) {
        var self = this,
            o = self._opts,
            elements = self._elements,
            numPages = Math.ceil(elements.length / o.itemsPerPage);

        // Create wrapper around the links
        var wrapper = document.createElement('div'),
            fragment = document.createDocumentFragment();
        wrapper.className = 'paginator-wrapper ' + (o.theme === 'default' ? 'default-theme' : 'no-theme');

        // Create previous
        var prev = document.createElement('a');
        prev.className = 'paginator-link';
        prev.setAttribute('href', '#page-prev');
        prev.innerHTML = o.prevText;
        prev.addEventListener('click', $.wrap.call(self, self.previous));
        fragment.appendChild(prev);

        // Create page links
        for (var i = 1; i <= numPages; i++) {
            var plink = document.createElement('a');
            plink.className = 'paginator-link';
            if (o.currentPage === i) {
                plink.className += ' paginator-active';
            }
            plink.setAttribute('href', '#page-' + i);
            plink.innerHTML = i + '';
            plink.addEventListener('click', $.wrap.call(self, self.select, i - 1), false);

            self._links.push(plink);
            fragment.appendChild(plink);

            // Add dots
            if (i === o.margins || numPages - i === o.margins) {
                var dots = document.createElement('span');
                dots.className = 'paginator-dots';
                dots.innerHTML = '...';
                fragment.appendChild(dots);
            }
        }

        // Create next
        var next = document.createElement('a');
        next.className = 'paginator-link';
        next.setAttribute('href', '#page-next');
        next.innerHTML = o.nextText;
        next.addEventListener('click', $.wrap.call(self, self.next));
        fragment.appendChild(next);

        // Append to wrapper and save
        wrapper.appendChild(fragment);
        self._linksWrapper = wrapper;

        if (parent) {
            parent.appendChild(wrapper);
        }
    };

    $.createPages = function (parent) {
        var o = this._opts,
            elements = $.toArray(this._elements),
            numItems = elements.length,
            itemsPerPage = o.itemsPerPage,
            numPages = Math.ceil(numItems / itemsPerPage);

        for (var i = 1; i <= numPages; i++) {
            var page = document.createElement('div'),
                toAdd = elements.slice(0, itemsPerPage);

            page.className = 'paginator-page';
            if (o.currentPage === i) {
                page.className += ' paginator-active';
            }

            // Append to page
            for (var j = 0, l = toAdd.length; j < l; j++) {
                page.appendChild(toAdd[j]);
            }

            // Append page and update elements
            parent.appendChild(page);
            this._pages.push(page);
            elements = elements.slice(itemsPerPage);
        }
    };

    $.showInterval = function () {
        var o = this._opts;
        var activePageNum = this._activePageNum;
        var numLinks = this._links.length;

        // Reset all links
        for (var i = 0, l = this._links.length; i < l; i++) {
            this._links[i].style.display = 'none';
        }

        // Show margins
        for (i = 0; i < o.margins; i++) {
            this._links[i].style.display = 'inline';
        }

        for (i = numLinks - 1; i >= numLinks - o.margins; i--) {
            this._links[i].style.display = 'inline';
        }

        // Show interval
        var start = (activePageNum + o.displayedPageNumbers >= numLinks) ? numLinks - o.displayedPageNumbers : activePageNum,
            end = (activePageNum + o.displayedPageNumbers >= numLinks) ? numLinks : activePageNum + o.displayedPageNumbers;

        for (i = start; i < end; i++) {
            if (this._links[i].style.display === 'none') {
                this._links[i].style.display = 'inline';
            }
        }

        // Show/hide dots
        var dots = this._linksWrapper.getElementsByClassName('paginator-dots');
        if (activePageNum > o.margins) {
            dots[0].style.display = 'inline';
        }
        else {
            dots[0].style.display = 'none';
        }

        /*if (activePageNum < this._links.length - o.margins) {
            dots[1].style.display = 'inline';
        }
        else {
            dots[1].style.display = 'none';
        }*/
    };

    function insertAfter(newNode, referenceNode) {
        referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
    }

    // END: Private member

    _.Paginator = function (elements, opts) {
        var self = this;

        // Unpack options
        opts = opts || {};
        self._opts = $.extend(defaults, opts);
        self._elements = $.getElements(elements);
        self._pages = [];
        self._links = [];
        self._linksWrapper = null;
        self._activePageNum = self._opts.currentPage - 1;

        // Initialise links and pages
        var parent = $.getParent.call(self);
        $.createPages.call(self, parent);
        $.createLinks.call(self, parent);
        $.showInterval.call(self);
    };

    _.create = function (elements, opts) {
        return new _.Paginator(elements, opts);
    };

    // BEGIN: Public methods
    _.Paginator.prototype.select = function (pageNum) {
        var currentPage = this.getCurrentPage();

        // Remove
        currentPage.classList.remove('paginator-active');
        this._links[this._activePageNum].classList.remove('paginator-active');

        // Add
        this._pages[pageNum].classList.add('paginator-active');
        this._links[pageNum].classList.add('paginator-active');

        // Update active page number
        this._activePageNum = pageNum;
        $.showInterval.call(this);
    };

    _.Paginator.prototype.getCurrentPage = function () { // Getter and setter
        return this._pages[this._activePageNum];
    };

    _.Paginator.prototype.previous = function () {
        var pageNum = this._activePageNum - 1;
        if (!$.isOutOfRange(pageNum, this._pages)) {
            this.select(pageNum);
        }
    };

    _.Paginator.prototype.next = function () {
        var pageNum = this._activePageNum + 1;
        if (!$.isOutOfRange(pageNum, this._pages)) {
            this.select(pageNum);
        }
    };

    _.Paginator.prototype.pagesCount = function () {
        return this._pages.length;
    };

    _.Paginator.prototype.disable = function () {

    };

    _.Paginator.prototype.enable = function () {

    };

    // END: Public methods

    return _;

})();<|endoftext|>


<commit_before>#!/usr/bin/env node

/**
 * Module dependencies.
 */

var program = require('commander');

program
  .version('0.0.2')
  .option('-v, --verbose', 'Display additional log output')

program
  .command('install [component] [plugin]', 'install one or more components with their default settings or a specific plugin')
//  .command('init [template] [directory]', 'initialise the current folder or the directory with the default or specified template')
  .command('status', 'displays current configuration')
  .parse(process.argv);<commit_msg>Fetch package.json version number dynamically for CLI.<commit_after>#!/usr/bin/env node

/**
 * Module dependencies.
 */

var program = require('commander');
var pjson = require('./package.json');

program
  .version(pjson.version)
  .option('-v, --verbose', 'Display additional log output')

program
  .command('install [component] [plugin]', 'install one or more components with their default settings or a specific plugin')
//  .command('init [template] [directory]', 'initialise the current folder or the directory with the default or specified template')
  .command('status', 'displays current configuration')
  .parse(process.argv);<|endoftext|>


<commit_before>/**
 * Manage downloads
 */

(function() {

var cache = {};
var form = $('form');
var minified = true;

forId(function (category) {
	this.section = $u.element.create('section', {
		className: 'options',
		contents: {
			tag: 'h1',
			contents: category.charAt(0).toUpperCase() + category.slice(1)
		},
		inside: '#components'
	});
}, function (id, category, info) {
	var checked = false, disabled = false;

	switch (this.option) {
		case 'mandatory': disabled = true; // fallthrough
		case 'default': checked = true;
	}

	var all = this;

	var filepath = this.path.replace(/\{id}/g, id);

	var info = this[id] = {
		title: this[id].title || this[id],
		hasCSS: this[id].hasCSS !== undefined? this[id].hasCSS : this.hasCSS,
		enabled: checked,
		files: {
			minified: {
				paths: [],
				size: 0
			},
			dev: {
				paths: [],
				size: 0
			}
		}
	};

	if (!/\.css$/.test(filepath)) {
		info.files.minified.paths.push(filepath.replace(/(\.js)?$/, '.min.js'));
		info.files.dev.paths.push(filepath.replace(/(\.js)?$/, '.js'));
	}

	if ((this[id].hasCSS && !/\.js$/.test(filepath)) || /\.css$/.test(filepath)) {
		var cssFile = filepath.replace(/(\.css)?$/, '.css');

		info.files.minified.paths.push(cssFile);
		info.files.dev.paths.push(cssFile);
	}
	console.log(this.link);
	$u.element.create('label', {
		attributes: {
			'data-id': id
		},
		contents: [
			{
				tag: 'input',
				attributes: {
					'data-id': id
				},
				properties: {
					type: 'checkbox',
					checked: checked,
					disabled: disabled,
					onclick: function () {
						all[id].enabled = this.checked;

						update(category, id);
					}
				}
			},
			this.link? {
				tag: 'a',
				properties: {
					href: this.link.replace(/\{id}/g, id)
				},
				contents: info.title
			} : info.title,
			' ',
			{
				tag: 'strong',
				className: 'filesize'
			}
		],
		inside: this.section
	});
});

form.elements.compression[0].onclick =
form.elements.compression[1].onclick = function() {
	minified = !!+this.value;

	fetchFiles();
}

function forId(categoryCallback, callback) {
	if (arguments.length == 1) {
		callback = categoryCallback;
		categoryCallback = undefined;
	}

	for (var category in components) {
		var all = components[category];

		categoryCallback && categoryCallback.call(all, category);

		for (var id in all) {
			if(['path', 'hasCSS', 'option', 'section', 'link'].indexOf(id) > -1) {
				continue;
			}

			callback.call(all, id, category, all[id]);
		}
	}
}

function fetchFiles() {
	forId(function(id, category) {
		var distro = this[id].files[minified? 'minified' : 'dev'],
						files = distro.paths;

		files.forEach(function (filepath) {
			var file = cache[filepath] = cache[filepath] || {};

			if (!file.contents) {

				(function(category, id, file, filepath, distro){

				$u.xhr({
					url: filepath,
					callback: function(xhr) {
						if (xhr.status < 400) {

							file.contents = xhr.responseText;

							file.size = +xhr.getResponseHeader('Content-Length') || file.contents.length;

							distro.size += file.size;

							update(category, id);
						}
					}
				});
				})(category, id, file, filepath, distro);
			}
			else {
				update(category, id);
			}
		});
	});
}

fetchFiles();

function prettySize(size) {
	return Math.round(100 * size / 1024)/100 + 'KB';
}

function update(category, id){
	var distro = components[category][id].files[minified? 'minified' : 'dev'];

	$('label[data-id="' + id + '"] .filesize').textContent = prettySize(distro.size);

	// Update total size
	var totalSize = 0;

	forId(function (id, category, info) {
		if(info.enabled) {
			totalSize += info.files[minified? 'minified' : 'dev'].size;
		}
	});

	$('#filesize').textContent = prettySize(totalSize);

	generateCode();
}

function generateCode(){
	var css = '', js = '';

	forId(function (id, category, info) {
		if(info.enabled) {
			info.files[minified? 'minified' : 'dev'].paths.forEach(function (path) {
				if (cache[path]) {
					if (/.css$/.test(path)) {
						css += cache[path].contents + '\n';
					}
					else {
						js += cache[path].contents + '\n';
					}
				}
			});
		}
	});

	var jsCode = $('#download-js code'),
	    cssCode = $('#download-css code');

	jsCode.textContent = js;
	Prism.highlightElement(jsCode, true);

	cssCode.textContent = css;
	Prism.highlightElement(cssCode, true);

	$('#download-js .download-button').href = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(js);
	$('#download-css .download-button').href = 'data:text/css;charset=utf-8,' + encodeURIComponent(css);
}

})();<commit_msg>fixed bugs in download.js<commit_after>/**
 * Manage downloads
 */

(function() {

var cache = {};
var form = $('form');
var minified = true;

forId(function (category) {
	this.meta.section = $u.element.create('section', {
		className: 'options',
		contents: {
			tag: 'h1',
			contents: category.charAt(0).toUpperCase() + category.slice(1)
		},
		inside: '#components'
	});
}, function (id, category, info) {
	var checked = false, disabled = false;
	var option = this[id].option || this.meta.option;

	switch (option) {
		case 'mandatory': disabled = true; // fallthrough
		case 'default': checked = true;
	}

	var all = this;

	var filepath = this.meta.path.replace(/\{id}/g, id);

	var info = this[id] = {
		title: this[id].title || this[id],
		hasCSS: this[id].hasCSS !== undefined? this[id].hasCSS : this.meta.hasCSS,
		enabled: checked,
		files: {
			minified: {
				paths: [],
				size: 0
			},
			dev: {
				paths: [],
				size: 0
			}
		}
	};

	if (!/\.css$/.test(filepath)) {
		info.files.minified.paths.push(filepath.replace(/(\.js)?$/, '.min.js'));
		info.files.dev.paths.push(filepath.replace(/(\.js)?$/, '.js'));
	}

	if ((this[id].hasCSS && !/\.js$/.test(filepath)) || /\.css$/.test(filepath)) {
		var cssFile = filepath.replace(/(\.css)?$/, '.css');

		info.files.minified.paths.push(cssFile);
		info.files.dev.paths.push(cssFile);
	}

	$u.element.create('label', {
		attributes: {
			'data-id': id
		},
		contents: [
			{
				tag: 'input',
				attributes: {
					'data-id': id
				},
				properties: {
					type: 'checkbox',
					checked: checked,
					disabled: disabled,
					onclick: function () {
						all[id].enabled = this.checked;

						update(category, id);
					}
				}
			},
			this.meta.link? {
				tag: 'a',
				properties: {
					href: this.meta.link.replace(/\{id}/g, id)
				},
				contents: info.title
			} : info.title,
			' ',
			{
				tag: 'strong',
				className: 'filesize'
			}
		],
		inside: this.meta.section
	});
});

form.elements.compression[0].onclick =
form.elements.compression[1].onclick = function() {
	minified = !!+this.value;

	fetchFiles();
}

function forId(categoryCallback, callback) {
	if (arguments.length == 1) {
		callback = categoryCallback;
		categoryCallback = undefined;
	}

	for (var category in components) {
		var all = components[category];

		categoryCallback && categoryCallback.call(all, category);

		for (var id in all) {
			if(id === 'meta') {
				continue;
			}

			callback.call(all, id, category, all[id]);
		}
	}
}

function fetchFiles() {
	forId(function(id, category) {
		var distro = this[id].files[minified? 'minified' : 'dev'],
						files = distro.paths;

		files.forEach(function (filepath) {
			var file = cache[filepath] = cache[filepath] || {};

			if (!file.contents) {

				(function(category, id, file, filepath, distro){

				$u.xhr({
					url: filepath,
					callback: function(xhr) {
						if (xhr.status < 400) {

							file.contents = xhr.responseText;

							file.size = +xhr.getResponseHeader('Content-Length') || file.contents.length;

							distro.size += file.size;

							update(category, id);
						}
					}
				});
				})(category, id, file, filepath, distro);
			}
			else {
				update(category, id);
			}
		});
	});
}

fetchFiles();

function prettySize(size) {
	return Math.round(100 * size / 1024)/100 + 'KB';
}

function update(category, id){
	var distro = components[category][id].files[minified? 'minified' : 'dev'];

	$('label[data-id="' + id + '"] .filesize').textContent = prettySize(distro.size);

	// Update total size
	var totalSize = 0;

	forId(function (id, category, info) {
		if(info.enabled) {
			totalSize += info.files[minified? 'minified' : 'dev'].size;
		}
	});

	$('#filesize').textContent = prettySize(totalSize);

	generateCode();
}

function generateCode(){
	var css = '', js = '';

	forId(function (id, category, info) {
		if(info.enabled) {
			info.files[minified? 'minified' : 'dev'].paths.forEach(function (path) {
				if (cache[path]) {
					if (/.css$/.test(path)) {
						css += cache[path].contents + '\n';
					}
					else {
						js += cache[path].contents + '\n';
					}
				}
			});
		}
	});

	var jsCode = $('#download-js code'),
	    cssCode = $('#download-css code');

	jsCode.textContent = js;
	Prism.highlightElement(jsCode, true);

	cssCode.textContent = css;
	Prism.highlightElement(cssCode, true);

	$('#download-js .download-button').href = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(js);
	$('#download-css .download-button').href = 'data:text/css;charset=utf-8,' + encodeURIComponent(css);
}

})();<|endoftext|>


<commit_before>var path = require('path')
var fs = require('fs')
var get = require('simple-get')
var pump = require('pump')
var tfs = require('tar-fs')
var noop = require('noop-logger')
var zlib = require('zlib')
var util = require('./util')
var error = require('./error')
var url = require('url')
var tunnel = require('tunnel-agent')

function downloadPrebuild (opts, cb) {
  var downloadUrl = util.getDownloadUrl(opts)
  var cachedPrebuild = util.cachedPrebuild(downloadUrl)
  var localPrebuild = util.localPrebuild(downloadUrl)
  var tempFile = util.tempFile(cachedPrebuild)

  var log = opts.log || noop

  if (opts.nolocal) return download()

  log.info('looking for local prebuild @', localPrebuild)
  fs.exists(localPrebuild, function (exists) {
    if (exists) {
      log.info('found local prebuild')
      cachedPrebuild = localPrebuild
      return unpack()
    }

    download()
  })

  function download () {
    ensureNpmCacheDir(function (err) {
      if (err) return onerror(err)

      log.info('looking for cached prebuild @', cachedPrebuild)
      fs.exists(cachedPrebuild, function (exists) {
        if (exists) {
          log.info('found cached prebuild')
          return unpack()
        }

        log.http('request', 'GET ' + downloadUrl)
        var reqOpts = { url: downloadUrl }
        var proxy = opts['https-proxy'] || opts.proxy

        if (proxy) {
          var parsedDownloadUrl = url.parse(downloadUrl)
          var parsedProxy = url.parse(proxy)
          var uriProtocol = (parsedDownloadUrl.protocol === 'https:' ? 'https' : 'http')
          var proxyProtocol = (parsedProxy.protocol === 'https:' ? 'Https' : 'Http')
          var tunnelFnName = [uriProtocol, proxyProtocol].join('Over')
          reqOpts.agent = tunnel[tunnelFnName]({
            proxy: {
              host: parsedProxy.hostname,
              port: +parsedProxy.port,
              proxyAuth: parsedProxy.auth
            }
          })
        }

        var req = get(reqOpts, function (err, res) {
          if (err) return onerror(err)
          log.http(res.statusCode, downloadUrl)
          if (res.statusCode !== 200) return onerror()
          fs.mkdir(util.prebuildCache(), function () {
            log.info('downloading to @', tempFile)
            pump(res, fs.createWriteStream(tempFile), function (err) {
              if (err) return onerror(err)
              fs.rename(tempFile, cachedPrebuild, function (err) {
                if (err) return cb(err)
                log.info('renaming to @', cachedPrebuild)
                unpack()
              })
            })
          })
        })

        req.setTimeout(30 * 1000, function () {
          req.abort()
        })
      })

      function onerror (err) {
        fs.unlink(tempFile, function () {
          cb(err || error.noPrebuilts(opts))
        })
      }
    })
  }

  function unpack () {
    var binaryName

    var updateName = opts.updateName || function (entry) {
      if (/\.node$/i.test(entry.name)) binaryName = entry.name
    }

    log.info('unpacking @', cachedPrebuild)
    pump(fs.createReadStream(cachedPrebuild), zlib.createGunzip(), tfs.extract(opts.path, {readable: true, writable: true}).on('entry', updateName), function (err) {
      if (err) return cb(err)
      if (!binaryName) return cb(error.invalidArchive())

      var resolved
      try {
        resolved = path.resolve(opts.path || '.', binaryName)
      } catch (err) {
        return cb(err)
      }
      log.info('unpack', 'resolved to ' + resolved)

      if (opts.abi === process.versions.modules) {
        try {
          require(resolved)
        } catch (err) {
          return cb(err)
        }
        log.info('unpack', 'required ' + resolved + ' successfully')
      }
      cb(null, resolved)
    })
  }

  function ensureNpmCacheDir (cb) {
    var cacheFolder = util.npmCache()
    if (fs.access) {
      fs.access(cacheFolder, fs.R_OK | fs.W_OK, function (err) {
        if (err && err.code === 'ENOENT') {
          return makeNpmCacheDir()
        }
        cb(err)
      })
    } else {
      fs.exists(cacheFolder, function (exists) {
        if (!exists) return makeNpmCacheDir()
        cb()
      })
    }

    function makeNpmCacheDir () {
      log.info('npm cache directory missing, creating it...')
      fs.mkdir(cacheFolder, cb)
    }
  }
}

module.exports = downloadPrebuild
<commit_msg>Allow create files for hard links<commit_after>var path = require('path')
var fs = require('fs')
var get = require('simple-get')
var pump = require('pump')
var tfs = require('tar-fs')
var noop = require('noop-logger')
var zlib = require('zlib')
var util = require('./util')
var error = require('./error')
var url = require('url')
var tunnel = require('tunnel-agent')

function downloadPrebuild (opts, cb) {
  var downloadUrl = util.getDownloadUrl(opts)
  var cachedPrebuild = util.cachedPrebuild(downloadUrl)
  var localPrebuild = util.localPrebuild(downloadUrl)
  var tempFile = util.tempFile(cachedPrebuild)

  var log = opts.log || noop

  if (opts.nolocal) return download()

  log.info('looking for local prebuild @', localPrebuild)
  fs.exists(localPrebuild, function (exists) {
    if (exists) {
      log.info('found local prebuild')
      cachedPrebuild = localPrebuild
      return unpack()
    }

    download()
  })

  function download () {
    ensureNpmCacheDir(function (err) {
      if (err) return onerror(err)

      log.info('looking for cached prebuild @', cachedPrebuild)
      fs.exists(cachedPrebuild, function (exists) {
        if (exists) {
          log.info('found cached prebuild')
          return unpack()
        }

        log.http('request', 'GET ' + downloadUrl)
        var reqOpts = { url: downloadUrl }
        var proxy = opts['https-proxy'] || opts.proxy

        if (proxy) {
          var parsedDownloadUrl = url.parse(downloadUrl)
          var parsedProxy = url.parse(proxy)
          var uriProtocol = (parsedDownloadUrl.protocol === 'https:' ? 'https' : 'http')
          var proxyProtocol = (parsedProxy.protocol === 'https:' ? 'Https' : 'Http')
          var tunnelFnName = [uriProtocol, proxyProtocol].join('Over')
          reqOpts.agent = tunnel[tunnelFnName]({
            proxy: {
              host: parsedProxy.hostname,
              port: +parsedProxy.port,
              proxyAuth: parsedProxy.auth
            }
          })
        }

        var req = get(reqOpts, function (err, res) {
          if (err) return onerror(err)
          log.http(res.statusCode, downloadUrl)
          if (res.statusCode !== 200) return onerror()
          fs.mkdir(util.prebuildCache(), function () {
            log.info('downloading to @', tempFile)
            pump(res, fs.createWriteStream(tempFile), function (err) {
              if (err) return onerror(err)
              fs.rename(tempFile, cachedPrebuild, function (err) {
                if (err) return cb(err)
                log.info('renaming to @', cachedPrebuild)
                unpack()
              })
            })
          })
        })

        req.setTimeout(30 * 1000, function () {
          req.abort()
        })
      })

      function onerror (err) {
        fs.unlink(tempFile, function () {
          cb(err || error.noPrebuilts(opts))
        })
      }
    })
  }

  function unpack () {
    var binaryName

    var updateName = opts.updateName || function (entry) {
      if (/\.node$/i.test(entry.name)) binaryName = entry.name
    }

    log.info('unpacking @', cachedPrebuild)

    var options = {
      readable: true,
      writable: true,
      hardlinkAsFilesFallback: true
    }
    var extract = tfs.extract(opts.path, options).on('entry', updateName)

    pump(fs.createReadStream(cachedPrebuild), zlib.createGunzip(), extract,
    function (err) {
      if (err) return cb(err)
      if (!binaryName) return cb(error.invalidArchive())

      var resolved
      try {
        resolved = path.resolve(opts.path || '.', binaryName)
      } catch (err) {
        return cb(err)
      }
      log.info('unpack', 'resolved to ' + resolved)

      if (opts.abi === process.versions.modules) {
        try {
          require(resolved)
        } catch (err) {
          return cb(err)
        }
        log.info('unpack', 'required ' + resolved + ' successfully')
      }
      cb(null, resolved)
    })
  }

  function ensureNpmCacheDir (cb) {
    var cacheFolder = util.npmCache()
    if (fs.access) {
      fs.access(cacheFolder, fs.R_OK | fs.W_OK, function (err) {
        if (err && err.code === 'ENOENT') {
          return makeNpmCacheDir()
        }
        cb(err)
      })
    } else {
      fs.exists(cacheFolder, function (exists) {
        if (!exists) return makeNpmCacheDir()
        cb()
      })
    }

    function makeNpmCacheDir () {
      log.info('npm cache directory missing, creating it...')
      fs.mkdir(cacheFolder, cb)
    }
  }
}

module.exports = downloadPrebuild
<|endoftext|>


<commit_before>define(['require'], function(require) {
  'use strict';

  /** bs stands for BufferSource */

  /** A utility function for concatenating ArrayBuffers or views thereof. */
  function bsConcat(arrays) {
    var size = arrays.reduce((total, a) => total + a.byteLength, 0);
    var index = 0;
    return arrays.reduce((result, a) => {
      result.set(new Uint8Array(a), index);
      index += a.byteLength;
      return result;
    }, new Uint8Array(size));
  }

  /** Constant time for equal length buffers. */
  function bsEqual(a, b) {
    a = new Uint8Array(a);
    b = new Uint8Array(b);
    if (a.length !== b.length) {
      return false;
    }
    return !a.reduce((eq, v, i) => eq + (v ^ b[i]), 0);
  }

  /** Produce a hex string with the given separator. */
  function bsHex(a, sep) {
    return new Uint8Array(a)
      .join(' ').split(' ')
      .map(x => parseInt(x, 10))
      .map(x => ((x < 16) ? '0' : '') + x.toString(16))
      .join(sep || '');
  }

  /** A simple helper for splitting a BufferSource into pieces. */
  function Parser(buf) {
    this.position = buf.byteOffset || 0;
    this._buf = ArrayBuffer.isView(buf) ? buf.buffer : buf;
  }
  Parser.prototype = {
    next: function(len) {
      var chunk = new Uint8Array(this._buf, this.position, len);
      this.position += len;
      return chunk;
    },
    range: function(start, end) {
      return new Uint8Array(this._buf, start, end - start);
    },
    get remaining() {
      return this._buf.byteLength - this.position;
    }
  };

  /** Divides a BufferSource into even chunks. */
  function bsDivide(a, size) {
    var parser = new Parser(a);
    var result = [];
    while (parser.remaining >= size) {
      result.push(parser.next(size));
    }
    if (parser.remaining) {
      result.push(parser.next(parser.remaining));
    }
    return result;
  }

  /* I can't believe that this is needed here, in this day and age ...
   * Note: these are not efficient, merely expedient.
   */
  var base64url = {
    _strmap: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
    encode: function(data) {
      data = new Uint8Array(data);
      var len = Math.ceil(data.length * 4 / 3);
      return bsDivide(data, 3).map(chunk => [
        chunk[0] >>> 2,
        ((chunk[0] & 0x3) << 4) | (chunk[1] >>> 4),
        ((chunk[1] & 0xf) << 2) | (chunk[2] >>> 6),
        chunk[2] & 0x3f
      ].map(v => base64url._strmap[v]).join('')).join('').slice(0, len);
    },
    _lookup: function(s, i) {
      return base64url._strmap.indexOf(s.charAt(i));
    },
    decode: function(str) {
      var v = new Uint8Array(Math.floor(str.length * 3 / 4));
      var vi = 0;
      for (var si = 0; si < str.length;) {
        var w = base64url._lookup(str, si++);
        var x = base64url._lookup(str, si++);
        var y = base64url._lookup(str, si++);
        var z = base64url._lookup(str, si++);
        v[vi++] = w << 2 | x >>> 4;
        v[vi++] = x << 4 | y >>> 2;
        v[vi++] = y << 6 | z;
      }
      return v;
    }
  };

  /** Like Promise.all(), except that it takes and returns an object. */
  function promiseDict(o) {
    var result = {};
    return Promise.all(
      Object.keys(o).map(
        k => Promise.resolve(o[k]).then(r => result[k] = r)
      )
    ).then(_ => result);
  }

  /** Merges the list of dictionaries.  With `host`, it merges into the provided
   * object; without host, it creates a new one. */
  function mergeDict(objs, host) {
    return objs.reduce((c, o) => {
      Object.keys(o).forEach(k => c[k] = o[k]);
      return c;
    }, host || {});
  }

  return {
    base64url: base64url,
    bsConcat: bsConcat,
    bsDivide: bsDivide,
    bsEqual: bsEqual,
    bsHex: bsHex,
    mergeDict: mergeDict,
    promiseDict: promiseDict,
    Parser: Parser
  };
});
<commit_msg>Commentary<commit_after>define(['require'], function(require) {
  'use strict';

  /** bs stands for BufferSource */

  /** A utility function for concatenating ArrayBuffers or views thereof. */
  function bsConcat(arrays) {
    var size = arrays.reduce((total, a) => total + a.byteLength, 0);
    var index = 0;
    return arrays.reduce((result, a) => {
      result.set(new Uint8Array(a), index);
      index += a.byteLength;
      return result;
    }, new Uint8Array(size));
  }

  /** Constant time for equal length buffers. */
  function bsEqual(a, b) {
    a = new Uint8Array(a);
    b = new Uint8Array(b);
    if (a.length !== b.length) {
      return false;
    }
    return !a.reduce((eq, v, i) => eq + (v ^ b[i]), 0);
  }

  /** Produce a hex string with the given separator. */
  function bsHex(a, sep) {
    return new Uint8Array(a)
      .join(' ').split(' ')
      .map(x => parseInt(x, 10))
      .map(x => ((x < 16) ? '0' : '') + x.toString(16))
      .join(sep || '');
  }

  /** A simple helper for splitting a BufferSource into pieces. */
  function Parser(buf) {
    this.position = buf.byteOffset || 0;
    this._buf = ArrayBuffer.isView(buf) ? buf.buffer : buf;
  }
  Parser.prototype = {
    next: function(len) {
      var chunk = new Uint8Array(this._buf, this.position, len);
      this.position += len;
      return chunk;
    },
    range: function(start, end) {
      return new Uint8Array(this._buf, start, end - start);
    },
    get remaining() {
      return this._buf.byteLength - this.position;
    }
  };

  /** Divides a BufferSource into even chunks. */
  function bsDivide(a, size) {
    var parser = new Parser(a);
    var result = [];
    while (parser.remaining >= size) {
      result.push(parser.next(size));
    }
    if (parser.remaining) {
      result.push(parser.next(parser.remaining));
    }
    return result;
  }

  /* I can't believe that this is needed here, in this day and age ...
   * Note: these are not efficient, merely expedient.
   */
  var base64url = {
    _strmap: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
    encode: function(data) {
      data = new Uint8Array(data);
      var len = Math.ceil(data.length * 4 / 3);
      return bsDivide(data, 3).map(chunk => [
        chunk[0] >>> 2,
        ((chunk[0] & 0x3) << 4) | (chunk[1] >>> 4),
        ((chunk[1] & 0xf) << 2) | (chunk[2] >>> 6),
        chunk[2] & 0x3f
      ].map(v => base64url._strmap[v]).join('')).join('').slice(0, len);
    },
    _lookup: function(s, i) {
      return base64url._strmap.indexOf(s.charAt(i));
    },
    decode: function(str) {
      var v = new Uint8Array(Math.floor(str.length * 3 / 4));
      var vi = 0;
      for (var si = 0; si < str.length;) {
        var w = base64url._lookup(str, si++);
        var x = base64url._lookup(str, si++);
        var y = base64url._lookup(str, si++);
        var z = base64url._lookup(str, si++);
        v[vi++] = w << 2 | x >>> 4;
        v[vi++] = x << 4 | y >>> 2;
        v[vi++] = y << 6 | z;
      }
      return v;
    }
  };

  /** Like Promise.all(), except that it takes and returns a dictionary. */
  function promiseDict(o) {
    var result = {};
    return Promise.all(
      Object.keys(o).map(
        k => Promise.resolve(o[k]).then(r => result[k] = r)
      )
    ).then(_ => result);
  }

  /** Merges the list of dictionaries.  With `host`, it merges into the provided
   * object; without host, it creates a new one. */
  function mergeDict(objs, host) {
    return objs.reduce((c, o) => {
      Object.keys(o).forEach(k => c[k] = o[k]);
      return c;
    }, host || {});
  }

  return {
    base64url: base64url,
    bsConcat: bsConcat,
    bsDivide: bsDivide,
    bsEqual: bsEqual,
    bsHex: bsHex,
    mergeDict: mergeDict,
    promiseDict: promiseDict,
    Parser: Parser
  };
});
<|endoftext|>


<commit_before>var request    = require('request');
var cheerio    = require('cheerio');
var fs         = require('fs');
var mkdirp     = require('mkdirp');
var getDirName = require('path').dirname;
var Sensor     = require('./sensor.js');

module.exports = {

  getData: function(url, fireSensorName, meatSensorName, callback) {
    request(url, (err, status, html) => {
      var json = {};
      if (!err) {
        var $ = cheerio.load(html);
        json.fireSensor = parse($, fireSensorName);
        json.meatSensor = parse($, meatSensorName);
      }
      callback(err, json);
    });
  },

  updateLog: function(path, time, fireTemp, meatTemp) {
    var line = time + ',' + fireTemp + ',' + meatTemp + '\n';

    fs.access(path, fs.R_OK | fs.W_OK, (err) => {
      if (err) {
        console.log('File not found. Creating new file: ' + path);
        line = 'time,fireTemp,meatTemp\n' + line;
      }

      mkdirp(getDirName(path), (err) => {
        if (err) return console.log(err);
        console.log(line);
        fs.appendFile(path, line);
      });
    });
  },

  // returns "month/date/year hours:minutes:seconds"
  // ex: "06/07/2014 06:30:00"
  getFormattedTime: function(d) {
    var month = d.getMonth() + 1;
    var date  = d.getDate();
    var year  = d.getFullYear();
    var hours = d.getHours();
    var mins  = d.getMinutes();
    var secs  = d.getSeconds();

    if (month < 10) month = '0' + month;
    if (date  < 10) date  = '0' + date;
    if (hours < 10) hours = '0' + hours;
    if (mins  < 10) mins  = '0' + mins;
    if (secs  < 10) secs  = '0' + secs;

    return month + '/' + date + '/' + year + ' ' + hours + ':' + mins + ':' + secs;
  }

}

function parse($, sensorName) {
  var sensor = {};
  $('input[value=' + sensorName + ']').filter(function() {
    var serialNumber = $(this).attr("name").substring(Sensor.prefixSerial.length);
    sensor = new Sensor({
      name: sensorName,
      serial: serialNumber,
      temps: {
        current : $(this).parent().next('td').text(),
        target  : $('input[name=' + Sensor.prefixTarget + serialNumber + ']').val(),
        low     : $('input[name=' + Sensor.prefixLow    + serialNumber + ']').val(),
        high    : $('input[name=' + Sensor.prefixHigh   + serialNumber + ']').val()
      }
    });
  });
  return sensor;
}<commit_msg>fix missing callback<commit_after>var request    = require('request');
var cheerio    = require('cheerio');
var fs         = require('fs');
var mkdirp     = require('mkdirp');
var getDirName = require('path').dirname;
var Sensor     = require('./sensor.js');

module.exports = {

  getData: function(url, fireSensorName, meatSensorName, callback) {
    request(url, (err, status, html) => {
      var json = {};
      if (!err) {
        var $ = cheerio.load(html);
        json.fireSensor = parse($, fireSensorName);
        json.meatSensor = parse($, meatSensorName);
      }
      callback(err, json);
    });
  },

  updateLog: function(path, time, fireTemp, meatTemp) {
    var line = time + ',' + fireTemp + ',' + meatTemp + '\n';

    fs.access(path, fs.R_OK | fs.W_OK, (err) => {
      if (err) {
        console.log('File not found. Creating new file: ' + path);
        line = 'time,fireTemp,meatTemp\n' + line;
      }

      mkdirp(getDirName(path), (err) => {
        if (err) return console.log(err);
        console.log(line);
        fs.appendFile(path, line, (err) => {
          if (err) return console.log(err);
        });
      });
    });
  },

  // returns "month/date/year hours:minutes:seconds"
  // ex: "06/07/2014 06:30:00"
  getFormattedTime: function(d) {
    var month = d.getMonth() + 1;
    var date  = d.getDate();
    var year  = d.getFullYear();
    var hours = d.getHours();
    var mins  = d.getMinutes();
    var secs  = d.getSeconds();

    if (month < 10) month = '0' + month;
    if (date  < 10) date  = '0' + date;
    if (hours < 10) hours = '0' + hours;
    if (mins  < 10) mins  = '0' + mins;
    if (secs  < 10) secs  = '0' + secs;

    return month + '/' + date + '/' + year + ' ' + hours + ':' + mins + ':' + secs;
  }

}

function parse($, sensorName) {
  var sensor = {};
  $('input[value=' + sensorName + ']').filter(function() {
    var serialNumber = $(this).attr("name").substring(Sensor.prefixSerial.length);
    sensor = new Sensor({
      name: sensorName,
      serial: serialNumber,
      temps: {
        current : $(this).parent().next('td').text(),
        target  : $('input[name=' + Sensor.prefixTarget + serialNumber + ']').val(),
        low     : $('input[name=' + Sensor.prefixLow    + serialNumber + ']').val(),
        high    : $('input[name=' + Sensor.prefixHigh   + serialNumber + ']').val()
      }
    });
  });
  return sensor;
}
<|endoftext|>


<commit_before>// console emulation

var msgArea = document.getElementById("msgArea");

function print() {
    var i;
    var msgArea = document.getElementById("msgArea");
    var elem = el("div");

    for (i = 0; i < arguments.length; i++) {
	elem.appendChild(text(arguments[i]));
    }
    msgArea.insertBefore(elem, msgArea.firstChild);
}

// css helpers

function url(base) {
    var i;
    var ret = "url(" + base + ")";
    for (i = 1; i < arguments.length; i++) {
	ret = ret + " " + arguments[i];
    }
    return ret;
}

function color (r, g, b, a) {
    return "rgba(" + (r + ", " +
		     g + ", " +
		     b + ", " +
		     a + ")");
}

function restyle() {
    document.body.className = document.body.className;
}

// closure library

function nullfunc() {};

function compose() {
    var funcs = arguments;
    return function () {
	var i;
	var ret;
	for (i = 0; i < funcs.length; i++) {
	    ret = funcs[i].apply(this, arguments);
	}
	return ret;
    };
}

// dom manipulation helpers

function text(t) {
    return document.createTextNode(t);
}

function el () {
    return document.createElement.apply(document, arguments);
}

function get () {
    return document.getElementById.apply(document, arguments);
}

function div (name) {
    ret = el("div");
    ret.appendChild(document.createTextNode(name));
    return ret;
}

function unparent(node) {
    node && node.parentNode && node.parentNode.removeChild(node);
}

// element position helpers

function distToElem(elt, x, y) {
    var dx = (elt.offsetLeft + 0.5 * elt.offsetWidth) - x;
    var dy = (elt.offsetTop  + 0.5 * elt.offsetHeight) - y;
    return Math.sqrt(dx * dx + dy * dy);
}

function move(elt, x, y) {
    if (!elt.wrapper) {
	elt.wrapper = el("span");
	elt.wrapper.style.position = "absolute";
	document.body.appendChild(elt.wrapper);
	elt.wrapper.appendChild(elt);
    }
    elt.wrapper.style.top = y;
    elt.wrapper.style.left = x;
}

function unmove(elt) {
    if (elt.wrapper) {
	unparent(elt.wrapper);
	elt.wrapper = null;
    }
}

// mathy stuff

function clamp (value, min, max) {
    return Math.max(min, Math.min(max, value));
}

function randomChoice(array) {
    return array[Math.floor(Math.abs(Math.random() * array.length))];
}

// XHR Request

function request(method, uri, closure) {
    var req = new XMLHttpRequest();

    req.onreadystatechange = function () {
	if (req.readyState == 4) {
	    closure(req.responseText);
	}
    };

    req.open(method, uri, true,
	     login.value,
	     password.value);

    req.send();
}

// Utilites for Mobile Browsers

function isTouchDevice(){
    try {
	document.createEvent("TouchEvent");
	return true;
    } catch(e) {
	return false;
    }
}

function mobileScrollFix(element) {
    var scrollStartPos = 0;

    function touchstart(event) {
	scrollStartPos = this.scrollTop + event.touches[0].pageY;
    }

    function touchmove(event) {
	this.scrollTop = scrollStartPos - event.touches[0].pageY;
	event.preventDefault();
    }

    if (isTouchDevice()) {
	element.ontouchstart = touchstart;
	element.ontouchmove = touchmove;
    }
}

// Page load handling -- don't overwrite document.onreadystatechange
var whenReady = (function () {
   "use strict";

   var handlers = [];
   var i;

   document.onreadystatechange = function () {
       if (document.readyState === "complete") {
	   for (i = 0; i < handlers.length; i++) {
	       handlers[i]();
	   }
       }
   };

   function ready(handler) {
       handlers.push(handler);
   }

   return ready;
})();<commit_msg>Check in signal object<commit_after>// console emulation

var msgArea = document.getElementById("msgArea");

function print() {
    var i;
    var msgArea = document.getElementById("msgArea");
    var elem = el("div");

    for (i = 0; i < arguments.length; i++) {
	elem.appendChild(text(arguments[i]));
    }
    msgArea.insertBefore(elem, msgArea.firstChild);
}

// css helpers

function url(base) {
    var i;
    var ret = "url(" + base + ")";
    for (i = 1; i < arguments.length; i++) {
	ret = ret + " " + arguments[i];
    }
    return ret;
}

function color (r, g, b, a) {
    return "rgba(" + (r + ", " +
		     g + ", " +
		     b + ", " +
		     a + ")");
}

function restyle() {
    document.body.className = document.body.className;
}

// closure library

function nullfunc() {};

function compose() {
    var funcs = arguments;
    return function () {
	var i;
	var ret;
	for (i = 0; i < funcs.length; i++) {
	    ret = funcs[i].apply(this, arguments);
	}
	return ret;
    };
}

// dom manipulation helpers

function text(t) {
    return document.createTextNode(t);
}

function el () {
    return document.createElement.apply(document, arguments);
}

function get () {
    return document.getElementById.apply(document, arguments);
}

function div (name) {
    ret = el("div");
    ret.appendChild(document.createTextNode(name));
    return ret;
}

function unparent(node) {
    node && node.parentNode && node.parentNode.removeChild(node);
}

// element position helpers

function distToElem(elt, x, y) {
    var dx = (elt.offsetLeft + 0.5 * elt.offsetWidth) - x;
    var dy = (elt.offsetTop  + 0.5 * elt.offsetHeight) - y;
    return Math.sqrt(dx * dx + dy * dy);
}

function move(elt, x, y) {
    if (!elt.wrapper) {
	elt.wrapper = el("span");
	elt.wrapper.style.position = "absolute";
	document.body.appendChild(elt.wrapper);
	elt.wrapper.appendChild(elt);
    }
    elt.wrapper.style.top = y;
    elt.wrapper.style.left = x;
}

function unmove(elt) {
    if (elt.wrapper) {
	unparent(elt.wrapper);
	elt.wrapper = null;
    }
}

// mathy stuff

function clamp (value, min, max) {
    return Math.max(min, Math.min(max, value));
}

function randomChoice(array) {
    return array[Math.floor(Math.abs(Math.random() * array.length))];
}

// XHR Request

function request(method, uri, closure) {
    var req = new XMLHttpRequest();

    req.onreadystatechange = function () {
	if (req.readyState == 4) {
	    closure(req.responseText);
	}
    };

    req.open(method, uri, true,
	     login.value,
	     password.value);

    req.send();
}

// Utilites for Mobile Browsers

function isTouchDevice(){
    try {
	document.createEvent("TouchEvent");
	return true;
    } catch(e) {
	return false;
    }
}

function mobileScrollFix(element) {
    var scrollStartPos = 0;

    function touchstart(event) {
	scrollStartPos = this.scrollTop + event.touches[0].pageY;
    }

    function touchmove(event) {
	this.scrollTop = scrollStartPos - event.touches[0].pageY;
	event.preventDefault();
    }

    if (isTouchDevice()) {
	element.ontouchstart = touchstart;
	element.ontouchmove = touchmove;
    }
}

// Page load handling -- don't overwrite document.onreadystatechange
var whenReady = (function () {
   "use strict";

   var handlers = [];
   var i;

   document.onreadystatechange = function () {
       if (document.readyState === "complete") {
	   for (i = 0; i < handlers.length; i++) {
	       handlers[i]();
	   }
       }
   };

   function ready(handler) {
       handlers.push(handler);
   }

   return ready;
})();

// Event-driven programming support

function signal () {
    var handlers = {};
    var ret = {};
    var id = 0;

    ret.connect = function (handler) {
	handlers[id] = handler;
	return id++;
    };

    ret.disconnect = function (id) {
	console.log(handlers, id);
	delete handlers[id];
    };

    ret.emit = function () {
	var args = arguments;
	var that = this;

	for (handler in handlers) {
	   handlers[handler].apply(that, args);
	};
    };

    return ret;
};

<|endoftext|>