<commit_before>// enemies.js
//
// All the things that want to kill the player.
//
// Depend: mob.js, tile.js

(function(global, $) {

  var zombie = function(args) {
    // set the zombie defaults
    args.size = {
      x: 16,
      y: 20
    };
    var that = mob(args);

    // zombies are not solid
    that.solid = false;

    // zombies are damageable
    utils.damageable(that, {
      health: 20,
      whendamaged: function() {
        that.movestate.standing = false;
      }
    });

    // custom movestates
    that.movestate.wantstodig = false;

    that.draw = function(drawthing) {
      drawthing.sprite1.push(function(ctx) {
        ctx.fillStyle('hsl(95, 35%, 33%)');
        ctx.fillRect(that.x, that.y, that.size.x + 1, that.size.y + 1);
      });
    };

    var canpassthrough = function(x, y) {
      var tile = args.game.tilemap.get(x, y);
      if (tile) {
        return !tile.solid;
      };
      return true;
    }

    that.tick = function() {
  		// gravity has to have some effect here...
			mob.gravitytick.call(this);
			// track player along the x axis...
			if (args.game.player.x < that.x) {
			  that.vel.x = -1;
			}
			else if (args.game.player.x > that.x) {
			  that.vel.x = 1;
			};
			// have I been killed?
			if (that.health <= 0) {
			  that.killed = true;
			}
			// if no forward progress was made, jump or dig
	    that.movestate.wantstodigx = false;
			var tilepos = tile.totilepos(that.x, that.y);
			var tiledeltax = that.vel.x < 1 ? -1 : 1;
			tilepos.x += tiledeltax;
			var forwardprogress = typeof that.lastx === 'undefined' || that.x !== that.lastx;
			if (!forwardprogress) {
			  // can we jump?
			  var passthrough1 = canpassthrough(tilepos.x, tilepos.y - 1);
			  var passthrough2 = canpassthrough(tilepos.x, tilepos.y - 2);
			  if (!passthrough1 && !passthrough2) {
			    that.movestate.wantstodigx = true;
			  }
			  else {
			    that.jump();
			  }
			}
			that.lastx = that.x;
    };

    that.collide = function(collider) {
      if (typeof collider.damage !== 'function') {
        return;
      }
      if (that.movestate.wantstodigx) {
        collider.damage(1);
      }
      if (collider.delicious) {
        collider.damage(1);
      }
    }

    return that;
  }

  global.zombie = zombie;

})(this, jQuery);<commit_msg>zombies throw in the right direction when damaging<commit_after>// enemies.js
//
// All the things that want to kill the player.
//
// Depend: mob.js, tile.js

(function(global, $) {

  var zombie = function(args) {
    // set the zombie defaults
    args.size = {
      x: 16,
      y: 20
    };
    var that = mob(args);

    // zombies are not solid
    that.solid = false;

    // zombies are damageable
    utils.damageable(that, {
      health: 20,
      whendamaged: function() {
        that.movestate.standing = false;
      }
    });

    // custom movestates
    that.movestate.wantstodig = false;

    that.draw = function(drawthing) {
      drawthing.sprite1.push(function(ctx) {
        ctx.fillStyle('hsl(95, 35%, 33%)');
        ctx.fillRect(that.x, that.y, that.size.x + 1, that.size.y + 1);
      });
    };

    var canpassthrough = function(x, y) {
      var tile = args.game.tilemap.get(x, y);
      if (tile) {
        return !tile.solid;
      };
      return true;
    }

    that.tick = function() {
  		// gravity has to have some effect here...
			mob.gravitytick.call(this);
			// track player along the x axis...
			if (args.game.player.x < that.x) {
			  that.vel.x = -1;
			}
			else if (args.game.player.x > that.x) {
			  that.vel.x = 1;
			};
			// have I been killed?
			if (that.health <= 0) {
			  that.killed = true;
			}
			// if no forward progress was made, jump or dig
	    that.movestate.wantstodigx = false;
			var tilepos = tile.totilepos(that.x, that.y);
			var tiledeltax = that.vel.x < 1 ? -1 : 1;
			tilepos.x += tiledeltax;
			var forwardprogress = typeof that.lastx === 'undefined' || that.x !== that.lastx;
			if (!forwardprogress) {
			  // can we jump?
			  var passthrough1 = canpassthrough(tilepos.x, tilepos.y - 1);
			  var passthrough2 = canpassthrough(tilepos.x, tilepos.y - 2);
			  if (!passthrough1 && !passthrough2) {
			    that.movestate.wantstodigx = true;
			  }
			  else {
			    that.jump();
			  }
			}
			that.lastx = that.x;
    };

    that.collide = function(collider) {
      if (typeof collider.damage !== 'function') {
        return;
      }
      if (that.movestate.wantstodigx) {
        collider.damage(1);
      }
      if (collider.delicious) {
        collider.damage(1, that.vel.x > 0);
      }
    }

    return that;
  }

  global.zombie = zombie;

})(this, jQuery);<|endoftext|>


<commit_before>const Image = require('@11ty/eleventy-img');

module.exports = async (src, alt, className, outputFormat = 'jpeg') => {
  if (alt === undefined) {
    throw new Error(`Missing ALT attribute from: ${src}`);
  }

  const classAttr = className ? `class="${className}"` : '';

  try {
    const stats = await Image(src, {
      cacheDuration: '12w',
      formats: [outputFormat],
      outputDir: '_site/assets/avatar/',
      widths: [96],
    });

    const props = stats[outputFormat].pop();

    return `<img ${classAttr} src="${props.outputPath.replace('_site', '')}" alt="${alt}" loading="lazy">`;
  } catch (err) {
    // eslint-disable-next-line no-console
    console.error(err);

    return '';
  }
};
<commit_msg>Add urlPath and correct src source<commit_after>const Image = require('@11ty/eleventy-img');

module.exports = async (src, alt, className, outputFormat = 'jpeg') => {
  if (alt === undefined) {
    throw new Error(`Missing ALT attribute from: ${src}`);
  }

  const classAttr = className ? `class="${className}"` : '';

  try {
    const stats = await Image(src, {
      cacheDuration: '12w',
      formats: [outputFormat],
      urlPath: '/assets/avatar/',
      outputDir: '_site/assets/avatar/',
      widths: [96],
    });

    const props = stats[outputFormat].pop();

    return `<img ${classAttr} src="${props.url}" alt="${alt}" loading="lazy">`;
  } catch (err) {
    // eslint-disable-next-line no-console
    console.error(err);

    return '';
  }
};
<|endoftext|>


<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2017, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

/* the logic here (and the tests) are a port of tideline's
   js/plot/util/commonbolus.js */

import _ from 'lodash';

import { formatDecimalNumber } from './format';

/**
 * fixFloatingPoint
 * @param {Number} numeric value
 *
 * @return {Number} numeric value rounded to 3 decimal places
 */
function fixFloatingPoint(n) {
  return parseFloat(formatDecimalNumber(n, 3));
}

/**
 * getBolusFromInsulinEvent
 * @param {Object} insulinEvent - a Tidepool wizard or bolus object
 *
 * @return {Object} a Tidepool bolus object
 */
export function getBolusFromInsulinEvent(insulinEvent) {
  let bolus = insulinEvent;
  if (insulinEvent.bolus) {
    bolus = insulinEvent.bolus;
  }
  return bolus;
}

/**
 * getProgrammed
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} value of insulin programmed for delivery in the given insulinEvent
 */
export function getProgrammed(insulinEvent) {
  let bolus = insulinEvent;
  if (_.get(insulinEvent, 'type') === 'wizard') {
    bolus = getBolusFromInsulinEvent(insulinEvent);
    if (!bolus.normal && !bolus.extended) {
      return NaN;
    }
  }
  if (bolus.extended != null && bolus.expectedExtended != null) {
    if (bolus.normal != null) {
      if (bolus.expectedNormal != null) {
        return fixFloatingPoint(bolus.expectedNormal + bolus.expectedExtended);
      }
      return fixFloatingPoint(bolus.normal + bolus.expectedExtended);
    }
    return bolus.expectedExtended;
  } else if (bolus.extended != null) {
    if (bolus.normal != null) {
      if (bolus.expectedNormal != null) {
        // this situation should not exist!
        throw new Error(
          'Combo bolus found with a cancelled `normal` portion and non-cancelled `extended`!'
        );
      }
      return fixFloatingPoint(bolus.normal + bolus.extended);
    }
    return bolus.extended;
  }
  return bolus.expectedNormal || bolus.normal;
}

/**
 * getRecommended
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} total recommended insulin dose
 */
export function getRecommended(insulinEvent) {
  // a simple manual/"quick" bolus won't have a `recommended` field
  if (!insulinEvent.recommended) {
    return NaN;
  }
  const netRecommendation = _.get(insulinEvent, ['recommended', 'net'], null);
  if (netRecommendation !== null) {
    return netRecommendation;
  }
  let rec = 0;
  rec += _.get(insulinEvent, ['recommended', 'carb'], 0);
  rec += _.get(insulinEvent, ['recommended', 'correction'], 0);

  return fixFloatingPoint(rec);
}

/**
 * getDelivered
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} units of insulin delivered in this insulinEvent
 */
export function getDelivered(insulinEvent) {
  let bolus = insulinEvent;
  if (_.get(insulinEvent, 'type') === 'wizard') {
    bolus = getBolusFromInsulinEvent(insulinEvent);
    if (!bolus.normal && !bolus.extended) {
      return NaN;
    }
  }
  if (bolus.extended != null) {
    if (bolus.normal != null) {
      return fixFloatingPoint(bolus.extended + bolus.normal);
    }
    return bolus.extended;
  }
  return bolus.normal;
}

/**
 * getDuration
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} duration value in milliseconds
 */
export function getDuration(insulinEvent) {
  let bolus = insulinEvent;
  if (_.get(insulinEvent, 'type') === 'wizard') {
    bolus = getBolusFromInsulinEvent(insulinEvent);
  }
  // don't want truthiness here because want to return duration
  // from a bolus interrupted immediately (duration = 0)
  if (bolus.duration == null) {
    return NaN;
  }
  return bolus.duration;
}

/**
 * getExtended
 * @param {Object} insulinEvent - a Tidepool wizard or bolus object
 *
 * @return {Number} units of insulin delivered over an extended duration
 */
export function getExtended(insulinEvent) {
  const bolus = getBolusFromInsulinEvent(insulinEvent);

  // don't want truthiness here because want to return expectedExtended
  // from a bolus interrupted immediately (extended = 0)
  if (bolus.extended == null) {
    return NaN;
  }

  return bolus.extended;
}

/**
 * getMaxDuration
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} duration value in milliseconds
 */
export function getMaxDuration(insulinEvent) {
  let bolus = insulinEvent;
  if (_.get(insulinEvent, 'type') === 'wizard') {
    bolus = getBolusFromInsulinEvent(insulinEvent);
  }
  // don't want truthiness here because want to return expectedDuration
  // from a bolus interrupted immediately (duration = 0)
  if (bolus.duration == null) {
    return NaN;
  }
  return bolus.expectedDuration || bolus.duration;
}

/**
 * getMaxValue
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} max programmed or recommended value wrt the insulinEvent
 */
export function getMaxValue(insulinEvent) {
  let bolus = insulinEvent;
  if (_.get(insulinEvent, 'type') === 'wizard') {
    bolus = getBolusFromInsulinEvent(insulinEvent);
    if (!bolus.normal && !bolus.extended) {
      return NaN;
    }
  }
  const programmed = getProgrammed(bolus);
  const recommended = getRecommended(insulinEvent) || 0;
  return (recommended > programmed) ? recommended : programmed;
}

/**
 * hasExtended
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Boolean} whether the bolus has an extended delivery portion
 */
export function hasExtended(insulinEvent) {
  const bolus = getBolusFromInsulinEvent(insulinEvent);

  // NB: intentionally invoking truthiness here
  // a bolus with `extended` value 0 and `expectedExtended` value 0 is pointless to render
  return Boolean(bolus.extended || bolus.expectedExtended) || false;
}

/**
 * isInterruptedBolus
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Boolean} whether the bolus was interrupted or not
 */
export function isInterruptedBolus(insulinEvent) {
  const bolus = getBolusFromInsulinEvent(insulinEvent);

  const cancelledDuringNormal = Boolean(
    bolus.normal != null &&
    bolus.expectedNormal &&
    bolus.normal !== bolus.expectedNormal
  );

  const cancelledDuringExtended = Boolean(
    bolus.extended != null &&
    bolus.expectedExtended &&
    bolus.extended !== bolus.expectedExtended
  );

  if (bolus.normal) {
    if (!bolus.extended) {
      return cancelledDuringNormal;
    }
    return cancelledDuringNormal || cancelledDuringExtended;
  }
  return cancelledDuringExtended;
}

/**
 * isOverride
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Boolean} whether the bolus programmed was larger than the calculated recommendation
 */
export function isOverride(insulinEvent) {
  return getRecommended(insulinEvent) < getProgrammed(insulinEvent);
}

/**
 * isUnderride
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Boolean} whether the bolus programmed was smaller than the calculated recommendation
 */
export function isUnderride(insulinEvent) {
  return getRecommended(insulinEvent) > getProgrammed(insulinEvent);
}
<commit_msg>replace '== null' checks with check for falseness of _.inRange(x, Infinity)<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2017, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

/* the logic here (and the tests) are a port of tideline's
   js/plot/util/commonbolus.js */

import _ from 'lodash';

import { formatDecimalNumber } from './format';

/**
 * fixFloatingPoint
 * @param {Number} numeric value
 *
 * @return {Number} numeric value rounded to 3 decimal places
 */
function fixFloatingPoint(n) {
  return parseFloat(formatDecimalNumber(n, 3));
}

/**
 * getBolusFromInsulinEvent
 * @param {Object} insulinEvent - a Tidepool wizard or bolus object
 *
 * @return {Object} a Tidepool bolus object
 */
export function getBolusFromInsulinEvent(insulinEvent) {
  let bolus = insulinEvent;
  if (insulinEvent.bolus) {
    bolus = insulinEvent.bolus;
  }
  return bolus;
}

/**
 * getProgrammed
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} value of insulin programmed for delivery in the given insulinEvent
 */
export function getProgrammed(insulinEvent) {
  let bolus = insulinEvent;
  if (_.get(insulinEvent, 'type') === 'wizard') {
    bolus = getBolusFromInsulinEvent(insulinEvent);
    if (!bolus.normal && !bolus.extended) {
      return NaN;
    }
  }
  if (bolus.extended != null && bolus.expectedExtended != null) {
    if (bolus.normal != null) {
      if (bolus.expectedNormal != null) {
        return fixFloatingPoint(bolus.expectedNormal + bolus.expectedExtended);
      }
      return fixFloatingPoint(bolus.normal + bolus.expectedExtended);
    }
    return bolus.expectedExtended;
  } else if (bolus.extended != null) {
    if (bolus.normal != null) {
      if (bolus.expectedNormal != null) {
        // this situation should not exist!
        throw new Error(
          'Combo bolus found with a cancelled `normal` portion and non-cancelled `extended`!'
        );
      }
      return fixFloatingPoint(bolus.normal + bolus.extended);
    }
    return bolus.extended;
  }
  return bolus.expectedNormal || bolus.normal;
}

/**
 * getRecommended
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} total recommended insulin dose
 */
export function getRecommended(insulinEvent) {
  // a simple manual/"quick" bolus won't have a `recommended` field
  if (!insulinEvent.recommended) {
    return NaN;
  }
  const netRecommendation = _.get(insulinEvent, ['recommended', 'net'], null);
  if (netRecommendation !== null) {
    return netRecommendation;
  }
  let rec = 0;
  rec += _.get(insulinEvent, ['recommended', 'carb'], 0);
  rec += _.get(insulinEvent, ['recommended', 'correction'], 0);

  return fixFloatingPoint(rec);
}

/**
 * getDelivered
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} units of insulin delivered in this insulinEvent
 */
export function getDelivered(insulinEvent) {
  let bolus = insulinEvent;
  if (_.get(insulinEvent, 'type') === 'wizard') {
    bolus = getBolusFromInsulinEvent(insulinEvent);
    if (!bolus.normal && !bolus.extended) {
      return NaN;
    }
  }
  if (bolus.extended != null) {
    if (bolus.normal != null) {
      return fixFloatingPoint(bolus.extended + bolus.normal);
    }
    return bolus.extended;
  }
  return bolus.normal;
}

/**
 * getDuration
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} duration value in milliseconds
 */
export function getDuration(insulinEvent) {
  let bolus = insulinEvent;
  if (_.get(insulinEvent, 'type') === 'wizard') {
    bolus = getBolusFromInsulinEvent(insulinEvent);
  }
  // don't want truthiness here because want to return duration
  // from a bolus interrupted immediately (duration = 0)
  if (!_.inRange(bolus.duration, Infinity)) {
    return NaN;
  }
  return bolus.duration;
}

/**
 * getExtended
 * @param {Object} insulinEvent - a Tidepool wizard or bolus object
 *
 * @return {Number} units of insulin delivered over an extended duration
 */
export function getExtended(insulinEvent) {
  const bolus = getBolusFromInsulinEvent(insulinEvent);

  // don't want truthiness here because want to return expectedExtended
  // from a bolus interrupted immediately (extended = 0)
  if (!_.inRange(bolus.extended, Infinity)) {
    return NaN;
  }

  return bolus.extended;
}

/**
 * getMaxDuration
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} duration value in milliseconds
 */
export function getMaxDuration(insulinEvent) {
  let bolus = insulinEvent;
  if (_.get(insulinEvent, 'type') === 'wizard') {
    bolus = getBolusFromInsulinEvent(insulinEvent);
  }
  // don't want truthiness here because want to return expectedDuration
  // from a bolus interrupted immediately (duration = 0)
  if (!_.inRange(bolus.duration, Infinity)) {
    return NaN;
  }
  return bolus.expectedDuration || bolus.duration;
}

/**
 * getMaxValue
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Number} max programmed or recommended value wrt the insulinEvent
 */
export function getMaxValue(insulinEvent) {
  let bolus = insulinEvent;
  if (_.get(insulinEvent, 'type') === 'wizard') {
    bolus = getBolusFromInsulinEvent(insulinEvent);
    if (!bolus.normal && !bolus.extended) {
      return NaN;
    }
  }
  const programmed = getProgrammed(bolus);
  const recommended = getRecommended(insulinEvent) || 0;
  return (recommended > programmed) ? recommended : programmed;
}

/**
 * hasExtended
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Boolean} whether the bolus has an extended delivery portion
 */
export function hasExtended(insulinEvent) {
  const bolus = getBolusFromInsulinEvent(insulinEvent);

  // NB: intentionally invoking truthiness here
  // a bolus with `extended` value 0 and `expectedExtended` value 0 is pointless to render
  return Boolean(bolus.extended || bolus.expectedExtended) || false;
}

/**
 * isInterruptedBolus
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Boolean} whether the bolus was interrupted or not
 */
export function isInterruptedBolus(insulinEvent) {
  const bolus = getBolusFromInsulinEvent(insulinEvent);

  const cancelledDuringNormal = Boolean(
    bolus.normal != null &&
    bolus.expectedNormal &&
    bolus.normal !== bolus.expectedNormal
  );

  const cancelledDuringExtended = Boolean(
    bolus.extended != null &&
    bolus.expectedExtended &&
    bolus.extended !== bolus.expectedExtended
  );

  if (bolus.normal) {
    if (!bolus.extended) {
      return cancelledDuringNormal;
    }
    return cancelledDuringNormal || cancelledDuringExtended;
  }
  return cancelledDuringExtended;
}

/**
 * isOverride
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Boolean} whether the bolus programmed was larger than the calculated recommendation
 */
export function isOverride(insulinEvent) {
  return getRecommended(insulinEvent) < getProgrammed(insulinEvent);
}

/**
 * isUnderride
 * @param {Object} insulinEvent - a Tidepool bolus or wizard object
 *
 * @return {Boolean} whether the bolus programmed was smaller than the calculated recommendation
 */
export function isUnderride(insulinEvent) {
  return getRecommended(insulinEvent) > getProgrammed(insulinEvent);
}
<|endoftext|>


<commit_before>'use strict';
import _clone from 'lodash/clone';
import _get from 'lodash/get';
import compile from 'lodash/template';
import jsonLogic from 'json-logic-js';

const FormioUtils = {
  jsonLogic, // Share

  /**
   * Determines the boolean value of a setting.
   *
   * @param value
   * @return {boolean}
   */
  boolValue: function(value) {
    if (typeof value === 'boolean') {
      return value;
    }
    else if (typeof value === 'string') {
      return (value.toLowerCase() === 'true');
    }
    else {
      return !!value;
    }
  },

  /**
   * Determine if a component is a layout component or not.
   *
   * @param {Object} component
   *   The component to check.
   *
   * @returns {Boolean}
   *   Whether or not the component is a layout component.
   */
  isLayoutComponent: function isLayoutComponent(component) {
    return (
      (component.columns && Array.isArray(component.columns)) ||
      (component.rows && Array.isArray(component.rows)) ||
      (component.components && Array.isArray(component.components))
    ) ? true : false;
  },

  /**
   * Iterate through each component within a form.
   *
   * @param {Object} components
   *   The components to iterate.
   * @param {Function} fn
   *   The iteration function to invoke for each component.
   * @param {Boolean} includeAll
   *   Whether or not to include layout components.
   * @param {String} path
   *   The current data path of the element. Example: data.user.firstName
   * @param {Object} parent
   *   The parent object.
   */
  eachComponent: function eachComponent(components, fn, includeAll, path, parent) {
    if (!components) return;
    path = path || '';
    components.forEach(function(component) {
      var hasColumns = component.columns && Array.isArray(component.columns);
      var hasRows = component.rows && Array.isArray(component.rows);
      var hasComps = component.components && Array.isArray(component.components);
      var noRecurse = false;
      var newPath = component.key ? (path ? (path + '.' + component.key) : component.key) : '';

      // Keep track of parent references.
      if (parent) {
        // Ensure we don't create infinite JSON structures.
        component.parent = _clone(parent);
        delete component.parent.components;
        delete component.parent.componentMap;
        delete component.parent.columns;
        delete component.parent.rows;
      }

      if (includeAll || component.tree || (!hasColumns && !hasRows && !hasComps)) {
        noRecurse = fn(component, newPath);
      }

      var subPath = function() {
        if (component.key && ((component.type === 'datagrid') || (component.type === 'container'))) {
          return newPath;
        }
        return path;
      };

      if (!noRecurse) {
        if (hasColumns) {
          component.columns.forEach(function(column) {
            eachComponent(column.components, fn, includeAll, subPath(), parent ? component : null);
          });
        }

        else if (hasRows) {
          component.rows.forEach(function(row) {
            row.forEach((column) => {
              eachComponent(column.components, fn, includeAll, subPath(), parent ? component : null);
            });
          });
        }

        else if (hasComps) {
          eachComponent(component.components, fn, includeAll, subPath(), parent ? component : null);
        }
      }
    });
  },

  /**
   * Matches if a component matches the query.
   *
   * @param component
   * @param query
   * @return {boolean}
   */
  matchComponent: function(component, query) {
    if (typeof query === 'string') {
      return component.key === query;
    }
    else {
      var matches = false;
      for (var search in query) {
        matches = (_get(component, search) === query[search]);
        if (!matches) {
          break;
        }
      }
      return matches;
    }
  },

  /**
   * Get a component by its key
   *
   * @param {Object} components
   *   The components to iterate.
   * @param {String|Object} key
   *   The key of the component to get, or a query of the component to search.
   *
   * @returns {Object}
   *   The component that matches the given key, or undefined if not found.
   */
  getComponent: function getComponent(components, key) {
    var result;
    FormioUtils.eachComponent(components, function(component, path) {
      if (FormioUtils.matchComponent(component, key)) {
        component.path = path;
        result = component;
        return true;
      }
    });
    return result;
  },

  /**
   * Finds a component provided a query of properties of that component.
   *
   * @param components
   * @param query
   * @return {*}
   */
  findComponents: function findComponents(components, query) {
    var results = [];
    FormioUtils.eachComponent(components, function(component, path) {
      if (FormioUtils.matchComponent(component, query)) {
        component.path = path;
        results.push(component);
      }
    }, true);
    return results;
  },

  /**
   * Flatten the form components for data manipulation.
   *
   * @param {Object} components
   *   The components to iterate.
   * @param {Boolean} includeAll
   *   Whether or not to include layout components.
   *
   * @returns {Object}
   *   The flattened components map.
   */
  flattenComponents: function flattenComponents(components, includeAll) {
    var flattened = {};
    FormioUtils.eachComponent(components, function(component, path) {
      flattened[path] = component;
    }, includeAll);
    return flattened;
  },

  /**
   * Returns if this component has a conditional statement.
   *
   * @param component - The component JSON schema.
   *
   * @returns {boolean} - TRUE - This component has a conditional, FALSE - No conditional provided.
   */
  hasCondition: function(component) {
    return (component.hasOwnProperty('customConditional') && component.customConditional) ||
      (component.hasOwnProperty('conditional') && component.conditional && component.conditional.when) ||
      (component.hasOwnProperty('conditional') && component.conditional && component.conditional.json);
  },

  /**
   * Checks the conditions for a provided component and data.
   *
   * @param component
   *   The component to check for the condition.
   * @param row
   *   The data within a row
   * @param data
   *   The full submission data.
   *
   * @returns {boolean}
   */
  checkCondition: function(component, row, data) {
    if (component.hasOwnProperty('customConditional') && component.customConditional) {
      try {
        var script = '(function() { var show = true;';
        script += component.customConditional.toString();
        script += '; return show; })()';
        var result = eval(script);
        return result.toString() === 'true';
      }
      catch (e) {
        console.warn('An error occurred in a custom conditional statement for component ' + component.key, e);
        return true;
      }
    }
    else if (component.hasOwnProperty('conditional') && component.conditional && component.conditional.when) {
      var cond = component.conditional;
      var value = null;
      if (row) {
        value = this.getValue({data: row}, cond.when);
      }
      if (data && (value === null || typeof value === 'undefined')) {
        value = this.getValue({data: data}, cond.when);
      }
      // FOR-400 - Fix issue where falsey values were being evaluated as show=true
      if (value === null || typeof value === 'undefined') {
        return false;
      }
      // Special check for selectboxes component.
      if (typeof value === 'object' && value.hasOwnProperty(cond.eq)) {
        return value[cond.eq].toString() === cond.show.toString();
      }
      // FOR-179 - Check for multiple values.
      if (value instanceof Array && value.indexOf(cond.eq) !== -1) {
        return (cond.show.toString() === 'true');
      }

      return (value.toString() === cond.eq.toString()) === (cond.show.toString() === 'true');
    }
    else if (component.hasOwnProperty('conditional') && component.conditional && component.conditional.json) {
      return jsonLogic.apply(component.conditional.json, {
        data: data,
        row: row
      });
    }

    // Default to show.
    return true;
  },

  /**
   * Get the value for a component key, in the given submission.
   *
   * @param {Object} submission
   *   A submission object to search.
   * @param {String} key
   *   A for components API key to search for.
   */
  getValue: function getValue(submission, key) {
    var data = submission.data || {};

    var search = function search(data) {
      var i;
      var value;

      if (!data) {
        return null;
      }

      if (typeof data === 'object' && !(data instanceof Array)) {
        if (data.hasOwnProperty(key)) {
          return data[key];
        }

        var keys = Object.keys(data);
        for (i = 0; i < keys.length; i++) {
          if (typeof data[keys[i]] === 'object') {
            value = search(data[keys[i]]);
          }

          if (value) {
            return value;
          }
        }
      }
    };

    return search(data);
  },

  /**
   * Interpolate a string and add data replacements.
   *
   * @param string
   * @param data
   * @returns {XML|string|*|void}
   */
  interpolate: function(string, data) {
    const templateSettings = {
      evaluate: /\{\%(.+?)\%\}/g,
      interpolate: /\{\{(.+?)\}\}/g,
      escape: /\{\{\{(.+?)\}\}\}/g
    };
    try {
      return compile(string, templateSettings)(data);
    }
    catch (err) {
      console.warn('Error interpolating template', err, string, data);
    }
  },

  /**
   * Make a filename guaranteed to be unique.
   * @param name
   * @returns {string}
   */
  uniqueName: function(name) {
    var parts = name.toLowerCase().replace(/[^0-9a-z\.]/g, '').split('.');
    var fileName = parts[0];
    var ext = '';
    if (parts.length > 1) {
      ext = '.' + parts[(parts.length - 1)];
    }
    return fileName.substr(0, 10) + '-' + this.guid() + ext;
  },

  guid: function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
      return v.toString(16);
    });
  }
};

module.exports = global.FormioUtils = FormioUtils;
<commit_msg>FOR-611 - Done<commit_after>'use strict';
import _clone from 'lodash/clone';
import _get from 'lodash/get';
import _isString from 'lodash/isString';
import _round from 'lodash/round';
import _pad from 'lodash/pad';
import _chunk from 'lodash/chunk';
import _isNaN from 'lodash/isNaN';
import compile from 'lodash/template';
import jsonLogic from 'json-logic-js';

const FormioUtils = {
  jsonLogic, // Share

  /**
   * Determines the boolean value of a setting.
   *
   * @param value
   * @return {boolean}
   */
  boolValue: function(value) {
    if (typeof value === 'boolean') {
      return value;
    }
    else if (typeof value === 'string') {
      return (value.toLowerCase() === 'true');
    }
    else {
      return !!value;
    }
  },

  /**
   * Determine if a component is a layout component or not.
   *
   * @param {Object} component
   *   The component to check.
   *
   * @returns {Boolean}
   *   Whether or not the component is a layout component.
   */
  isLayoutComponent: function isLayoutComponent(component) {
    return (
      (component.columns && Array.isArray(component.columns)) ||
      (component.rows && Array.isArray(component.rows)) ||
      (component.components && Array.isArray(component.components))
    ) ? true : false;
  },

  /**
   * Iterate through each component within a form.
   *
   * @param {Object} components
   *   The components to iterate.
   * @param {Function} fn
   *   The iteration function to invoke for each component.
   * @param {Boolean} includeAll
   *   Whether or not to include layout components.
   * @param {String} path
   *   The current data path of the element. Example: data.user.firstName
   * @param {Object} parent
   *   The parent object.
   */
  eachComponent: function eachComponent(components, fn, includeAll, path, parent) {
    if (!components) return;
    path = path || '';
    components.forEach(function(component) {
      var hasColumns = component.columns && Array.isArray(component.columns);
      var hasRows = component.rows && Array.isArray(component.rows);
      var hasComps = component.components && Array.isArray(component.components);
      var noRecurse = false;
      var newPath = component.key ? (path ? (path + '.' + component.key) : component.key) : '';

      // Keep track of parent references.
      if (parent) {
        // Ensure we don't create infinite JSON structures.
        component.parent = _clone(parent);
        delete component.parent.components;
        delete component.parent.componentMap;
        delete component.parent.columns;
        delete component.parent.rows;
      }

      if (includeAll || component.tree || (!hasColumns && !hasRows && !hasComps)) {
        noRecurse = fn(component, newPath);
      }

      var subPath = function() {
        if (component.key && ((component.type === 'datagrid') || (component.type === 'container'))) {
          return newPath;
        }
        return path;
      };

      if (!noRecurse) {
        if (hasColumns) {
          component.columns.forEach(function(column) {
            eachComponent(column.components, fn, includeAll, subPath(), parent ? component : null);
          });
        }

        else if (hasRows) {
          component.rows.forEach(function(row) {
            row.forEach((column) => {
              eachComponent(column.components, fn, includeAll, subPath(), parent ? component : null);
            });
          });
        }

        else if (hasComps) {
          eachComponent(component.components, fn, includeAll, subPath(), parent ? component : null);
        }
      }
    });
  },

  /**
   * Matches if a component matches the query.
   *
   * @param component
   * @param query
   * @return {boolean}
   */
  matchComponent: function(component, query) {
    if (typeof query === 'string') {
      return component.key === query;
    }
    else {
      var matches = false;
      for (var search in query) {
        matches = (_get(component, search) === query[search]);
        if (!matches) {
          break;
        }
      }
      return matches;
    }
  },

  /**
   * Get a component by its key
   *
   * @param {Object} components
   *   The components to iterate.
   * @param {String|Object} key
   *   The key of the component to get, or a query of the component to search.
   *
   * @returns {Object}
   *   The component that matches the given key, or undefined if not found.
   */
  getComponent: function getComponent(components, key) {
    var result;
    FormioUtils.eachComponent(components, function(component, path) {
      if (FormioUtils.matchComponent(component, key)) {
        component.path = path;
        result = component;
        return true;
      }
    });
    return result;
  },

  /**
   * Finds a component provided a query of properties of that component.
   *
   * @param components
   * @param query
   * @return {*}
   */
  findComponents: function findComponents(components, query) {
    var results = [];
    FormioUtils.eachComponent(components, function(component, path) {
      if (FormioUtils.matchComponent(component, query)) {
        component.path = path;
        results.push(component);
      }
    }, true);
    return results;
  },

  /**
   * Flatten the form components for data manipulation.
   *
   * @param {Object} components
   *   The components to iterate.
   * @param {Boolean} includeAll
   *   Whether or not to include layout components.
   *
   * @returns {Object}
   *   The flattened components map.
   */
  flattenComponents: function flattenComponents(components, includeAll) {
    var flattened = {};
    FormioUtils.eachComponent(components, function(component, path) {
      flattened[path] = component;
    }, includeAll);
    return flattened;
  },

  /**
   * Returns if this component has a conditional statement.
   *
   * @param component - The component JSON schema.
   *
   * @returns {boolean} - TRUE - This component has a conditional, FALSE - No conditional provided.
   */
  hasCondition: function(component) {
    return (component.hasOwnProperty('customConditional') && component.customConditional) ||
      (component.hasOwnProperty('conditional') && component.conditional && component.conditional.when) ||
      (component.hasOwnProperty('conditional') && component.conditional && component.conditional.json);
  },

  /**
   * Extension of standard #parseFloat(value) function, that also clears input string.
   *
   * @param {any} value
   *   The value to parse.
   *
   * @returns {Number}
   *   Parsed value.
   */
  parseFloat: function(value) {
    return parseFloat(_isString(value)
      ? value.replace(/[^\de.+-]/gi, '')
      : value);
  },

  /**
   * Formats provided value in way how Currency component uses it.
   *
   * @param {any} value
   *   The value to format.
   *
   * @returns {String}
   *   Value formatted for Currency component.
   */
  formatAsCurrency: function(value) {
    var parsedValue = this.parseFloat(value);

    if (_isNaN(parsedValue)) {
      return '';
    }

    var parts = _round(parsedValue, 2)
      .toString()
      .split('.');
    parts[0] = _chunk(Array.from(parts[0]).reverse(), 3)
      .reverse()
      .map(function(part) {
        return part
          .reverse()
          .join('');
      })
      .join(',');
    parts[1] = _pad(parts[1], 2, '0');
    return parts.join('.');
  },

  /**
   * Checks the calculated value for a provided component and data.
   *
   * @param {Object} component
   *   The component to check for the calculated value.
   * @param {Object} submission
   *   A submission object.
   * @param data
   *   The full submission data.
   */
  checkCalculated: function(component, submission, data) {
    // Process calculated value stuff if present.
    if (component.calculateValue) {
      if (typeof component.calculateValue === 'string') {
        try {
          data[component.key] = eval('(function(data, util) { var value = [];' + component.calculateValue.toString() + '; return value; })(data, this)');
        }
        catch (e) {
          console.warn('An error occurred calculating a value for ' + component.key, e);
        }
      }
      else {
        try {
          data[component.key] = formioUtils.jsonLogic.apply(component.calculateValue, {
            data: submission ? submission.data : data,
            row: data
          });
        }
        catch (e) {
          console.warn('An error occurred calculating a value for ' + component.key, e);
        }
      }
    }
  },

  /**
   * Checks the conditions for a provided component and data.
   *
   * @param component
   *   The component to check for the condition.
   * @param row
   *   The data within a row
   * @param data
   *   The full submission data.
   *
   * @returns {boolean}
   */
  checkCondition: function(component, row, data) {
    if (component.hasOwnProperty('customConditional') && component.customConditional) {
      try {
        var script = '(function() { var show = true;';
        script += component.customConditional.toString();
        script += '; return show; })()';
        var result = eval(script);
        return result.toString() === 'true';
      }
      catch (e) {
        console.warn('An error occurred in a custom conditional statement for component ' + component.key, e);
        return true;
      }
    }
    else if (component.hasOwnProperty('conditional') && component.conditional && component.conditional.when) {
      var cond = component.conditional;
      var value = null;
      if (row) {
        value = this.getValue({data: row}, cond.when);
      }
      if (data && (value === null || typeof value === 'undefined')) {
        value = this.getValue({data: data}, cond.when);
      }
      // FOR-400 - Fix issue where falsey values were being evaluated as show=true
      if (value === null || typeof value === 'undefined') {
        return false;
      }
      // Special check for selectboxes component.
      if (typeof value === 'object' && value.hasOwnProperty(cond.eq)) {
        return value[cond.eq].toString() === cond.show.toString();
      }
      // FOR-179 - Check for multiple values.
      if (value instanceof Array && value.indexOf(cond.eq) !== -1) {
        return (cond.show.toString() === 'true');
      }

      return (value.toString() === cond.eq.toString()) === (cond.show.toString() === 'true');
    }
    else if (component.hasOwnProperty('conditional') && component.conditional && component.conditional.json) {
      return jsonLogic.apply(component.conditional.json, {
        data: data,
        row: row
      });
    }

    // Default to show.
    return true;
  },

  /**
   * Get the value for a component key, in the given submission.
   *
   * @param {Object} submission
   *   A submission object to search.
   * @param {String} key
   *   A for components API key to search for.
   */
  getValue: function getValue(submission, key) {
    var data = submission.data || {};

    var search = function search(data) {
      var i;
      var value;

      if (!data) {
        return null;
      }

      if (typeof data === 'object' && !(data instanceof Array)) {
        if (data.hasOwnProperty(key)) {
          return data[key];
        }

        var keys = Object.keys(data);
        for (i = 0; i < keys.length; i++) {
          if (typeof data[keys[i]] === 'object') {
            value = search(data[keys[i]]);
          }

          if (value) {
            return value;
          }
        }
      }
    };

    return search(data);
  },

  /**
   * Interpolate a string and add data replacements.
   *
   * @param string
   * @param data
   * @returns {XML|string|*|void}
   */
  interpolate: function(string, data) {
    const templateSettings = {
      evaluate: /\{\%(.+?)\%\}/g,
      interpolate: /\{\{(.+?)\}\}/g,
      escape: /\{\{\{(.+?)\}\}\}/g
    };
    try {
      return compile(string, templateSettings)(data);
    }
    catch (err) {
      console.warn('Error interpolating template', err, string, data);
    }
  },

  /**
   * Make a filename guaranteed to be unique.
   * @param name
   * @returns {string}
   */
  uniqueName: function(name) {
    var parts = name.toLowerCase().replace(/[^0-9a-z\.]/g, '').split('.');
    var fileName = parts[0];
    var ext = '';
    if (parts.length > 1) {
      ext = '.' + parts[(parts.length - 1)];
    }
    return fileName.substr(0, 10) + '-' + this.guid() + ext;
  },

  guid: function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
      return v.toString(16);
    });
  }
};

module.exports = global.FormioUtils = FormioUtils;
<|endoftext|>


<commit_before>const CommunicationProtocol = require('../network/communication/abstract/communication-protocol')
const Communication = require('../network/communication/communication')
const debug = (require('debug'))('foglet-core:media')
const uuid = require('uuid/v4')
const lmerge = require('lodash.merge')
const MediaRecorderStream = require('media-recorder-stream')
const MediaSource = require('mediasource')
const Stream = require('stream')

class ReadableFromStream extends Stream.Readable {
  constructor (source, parent, options) {
    super(options)
    this.source = source
    this.parent = parent
    this.objectMode = true
    this.count = 0
    let stack = ''
    this.source.on('data', (data) => {
      if (this.count === 0) {
        if (!this.parent._activeStream.has(data.id)) {
          this.parent._activeStream.set(data.id, {source: this, options: data})
        }
        this.parent.emit('receive', data.id)
        this.count++
      } else {
        if (data.type === 'full') {
          this.push(data.payload)
          this.count++
        } else if (data.type === 'end') {
          stack += data.payload
          this.push(new Uint8Array(JSON.parse(stack).data))
          this.count++
          stack = ''
        } else {
          stack += data.payload
        }
      }
    })
    this.source.on('end', () => {
      this.end()
    })
  }

  _read (size) {}
}

/**
 * Media Stream Manager
 * If using Video/audio stream for all users: use the broadcast primitive (Data Channel)
 * If using Video/audio stream for only one user, use the unicast primitive (Streaming)
 * But pay attention that using unicast method, when a shuffling occur the connection might diseapear.
 * For this usage, create an overlay network with only this peer connected to you.
 * Or shut down the shuffle mechanism but this is not recommended.
 * @extends CommunicationProtocol
 */
class Media extends CommunicationProtocol {
  constructor (source, protocol, options) {
    super(source, `foglet-media-internal-${protocol}`)
    this.options = {
      retry: 1,
      chunkSize: 56 * 1024 // pay attention to the maximum, or it will not work.
    }
    this._activeMedia = new Map()
    this._activeStream = new Map()
    this._communication = new Communication(source, `foglet-media-internal-${protocol}`)

    this.NI = this._source.rps.NI
    this.NO = this._source.rps.NO

    this.i = this._source.rps.i
    this.o = this._source.rps.o
    this._source.rps.on('stream', (id, stream) => {
      this._receive(id, stream)
    })

    this._communication.onStreamBroadcast((id, stream) => {
      debug('Receive a media stream: ', id, stream)
      this._reconstruct(stream)
    })
  }

  get pid () {
    return this._source.rps._pid()
  }

  /**
   * Send a message to only one neighbor...
   * @param {Object} id - The id to send the stream (media) to
   * @param  {Object}  media  - The stream to send
   * @return {boolean}
   */
  sendUnicast (id, media) {
    if (!media.id) media.id = uuid()
    if (!this._activeMedia.has(media.id)) {
      this._activeMedia.set(media.id, media)
      this._setListeners(media)
    }
    return this._send(id, media, this.options.retry)
  }

  /**
   * Send a MediaStream using our broadcast primitives using Data Channel.
   * @param {Object} id - The id to send the stream (media) to
   * @param  {Object}  media  - The stream to send
   * @return {boolean}
   */
  sendBroadcast (media, options = {}) {
    // https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder
    options = lmerge({
      mimeType: 'video/webm; codecs="vp8"', // You MUST set the MIME type
      interval: 100, // A short interval is recommended to keep buffer sizes low
      bitsPerSecond: 128 * 1024
    }, options)

    if (!media.id) media.id = uuid()
    if (!this._activeMedia.has(media.id)) {
      this._activeMedia.set(media.id, media)
      this._setListeners(media)
    }
    const ms = new MediaRecorderStream(media, options)
    const stream = this._communication.streamBroadcast()
    options.id = media.id
    stream.write(options)
    ms.on('data', (data) => {
      const chunkified = this.chunkify(JSON.stringify(data))
      if (chunkified.length === 0) {
        stream.write({
          type: 'full',
          id: 0,
          payload: chunkified[0]
        })
      } else {
        for (let i = 0; i < chunkified.length; i++) {
          if (i === chunkified.length - 1) {
            stream.write({
              type: 'end',
              id: i,
              payload: chunkified[i]
            })
          } else {
            stream.write({
              type: 'chunk',
              id: i,
              payload: chunkified[i]
            })
          }
        }
      }
    })
    ms.on('end', () => {
      stream.end()
    })
  }

  _reconstruct (stream, options = null) {
    const readable = new ReadableFromStream(stream, this)
    readable.on('error', (err) => {
      console.error(err)
    })
  }

  getStreamMedia (id, elem) {
    if (!this._activeStream.has(id)) return undefined
    const wrapper = new MediaSource(elem)
    const writable = wrapper.createWriteStream(this._activeStream.get(id).options.mimeType)
    elem.addEventListener('error', function () {
      // listen for errors on the video/audio element directly
      var errorCode = elem.error
      var detailedError = wrapper.detailedError
      console.error(errorCode, detailedError)
      // wrapper.detailedError will often have a more detailed error message
    })

    writable.on('error', function (err) {
      // listening to the stream 'error' event is optional
      console.error(err)
    })
    this._activeStream.get(id).source.pipe(writable)
  }

  _send (peerId, media, retry = this.options.retry) {
    let promise
    // #1 normal behavior
    if (this.i.has(peerId)) {
      promise = this._sendBis(this.pid, peerId, media, retry, this.NI)
    } else if (this.o.has(peerId)) {
      promise = this._sendBis(this.pid, peerId, media, retry, this.NO)
    } else {
      // #2 last chance behavior
      promise = new Promise((resolve, reject) => {
        const _send = (r) => {
          this._sendBis(this.pid, peerId, media, retry, this.NO).then(() => {
            resolve()
          }).catch((e) => {
            this._sendBis(this.pid, peerId, media, retry, this.NI).then(() => {
              resolve()
            }).catch((e) => {
              if (r < retry) {
                setTimeout(() => {
                  _send(r + 1)
                }, 1000)
              } else {
                reject(e)
              }
            })
          })
        }
        _send(0)
      })
    };
    return promise
  }

  _sendBis (protocolId, peerId, media, retry = 0, Neighbor) {
    return new Promise((resolve, reject) => {
      // #1 get the proper entry in the tables
      let entry = null
      if (Neighbor.living.contains(peerId)) {
        entry = Neighbor.living.get(peerId)
      } else if (Neighbor.dying.has(peerId)) {
        entry = Neighbor.dying.get(peerId) // (TODO) warn: not safe
      };

      // #2 define the recursive sending function
      let __send = (r) => {
        try {
          entry.socket.addStream(media)
          debug('[%s][%s] --- MEDIA msg --> %s:%s', protocolId, Neighbor.PEER, peerId, protocolId)
          resolve()
        } catch (e) {
          debug('[%s][%s] -X- MEDIA msg -X> %s:%s', protocolId, Neighbor.PEER, peerId, protocolId)
          if (r < retry) {
            setTimeout(() => { __send(r + 1) }, 1000)
          } else {
            reject(e)
          };
        };
      }
      // #3 start to send
      __send(0)
    })
  };

  /**
   * Handler executed when a message is recevied
   * @param  {string} id  - Message issuer's ID
   * @param  {Object} stream - The stream received
   * @return {void}
   */
  _receive (id, stream) {
    debug('Receive a media stream: ', id, stream)
    if (!stream.id) stream.id = uuid()
    if (!this._activeMedia.has(stream.id)) {
      this._activeMedia.set(stream.id, {peer: id, stream})
      this._setListeners(stream)
    }
    this.emit('receive', id, stream)
  }

  _setListeners (media) {
    media.onactive = () => {
      console.log('Media %s is active...', media.id)
    }
    media.oninactive = () => {
      console.log('Media %s is inactive... (Disconnection or a Shuffling occured.)', media.id)
      // this._sendRequest(media.id)
    }
    media.onended = () => {
      console.log('Media %s is finished...', media.id)
    }
  }

  /**
   * Chunk a string into n message of size 'chunkSize'
   * @param {string} string
   * @param {Number=this.options.chunkSize} chunkSize
   */
  chunkify (string, chunkSize = this.options.chunkSize) {
    // https://stackoverflow.com/questions/7033639/split-large-string-in-n-size-chunks-in-javascript
    return string.match(new RegExp('.{1,' + chunkSize + '}', 'g'))
  }
}

module.exports = Media
<commit_msg>media doc fixed<commit_after>const CommunicationProtocol = require('../network/communication/abstract/communication-protocol')
const Communication = require('../network/communication/communication')
const debug = (require('debug'))('foglet-core:media')
const uuid = require('uuid/v4')
const lmerge = require('lodash.merge')
const MediaRecorderStream = require('media-recorder-stream')
const MediaSource = require('mediasource')
const Stream = require('stream')

class ReadableFromStream extends Stream.Readable {
  constructor (source, parent, options) {
    super(options)
    this.source = source
    this.parent = parent
    this.objectMode = true
    this.count = 0
    let stack = ''
    this.source.on('data', (data) => {
      if (this.count === 0) {
        if (!this.parent._activeStream.has(data.id)) {
          this.parent._activeStream.set(data.id, {source: this, options: data})
        }
        this.parent.emit('receive', data.id)
        this.count++
      } else {
        if (data.type === 'full') {
          this.push(data.payload)
          this.count++
        } else if (data.type === 'end') {
          stack += data.payload
          this.push(new Uint8Array(JSON.parse(stack).data))
          this.count++
          stack = ''
        } else {
          stack += data.payload
        }
      }
    })
    this.source.on('end', () => {
      this.end()
    })
  }

  _read (size) {}
}

/**
 * Media Stream Manager
 * If using Video/audio stream for all users: use the broadcast primitive (Data Channel)
 * If using Video/audio stream for only one user, use the unicast primitive (Streaming)
 * But pay attention that using unicast method, when a shuffling occur the connection might diseapear.
 * For this usage, create an overlay network with only this peer connected to you.
 * Or shut down the shuffle mechanism but this is not recommended.
 * @extends CommunicationProtocol
 */
class Media extends CommunicationProtocol {
  constructor (source, protocol, options) {
    super(source, `foglet-media-internal-${protocol}`)
    this.options = {
      retry: 1,
      chunkSize: 56 * 1024 // pay attention to the maximum, or it will not work.
    }
    this._activeMedia = new Map()
    this._activeStream = new Map()
    this._communication = new Communication(source, `foglet-media-internal-${protocol}`)

    this.NI = this._source.rps.NI
    this.NO = this._source.rps.NO

    this.i = this._source.rps.i
    this.o = this._source.rps.o
    this._source.rps.on('stream', (id, stream) => {
      this._receive(id, stream)
    })

    this._communication.onStreamBroadcast((id, stream) => {
      debug('Receive a media stream: ', id, stream)
      this._reconstruct(stream)
    })
  }

  get pid () {
    return this._source.rps._pid()
  }

  /**
   * Send a message to only one neighbor...
   * @param {Object} id - The id to send the stream (media) to
   * @param  {Object}  media  - The stream to send
   * @return {boolean}
   */
  sendUnicast (id, media) {
    if (!media.id) media.id = uuid()
    if (!this._activeMedia.has(media.id)) {
      this._activeMedia.set(media.id, media)
      this._setListeners(media)
    }
    return this._send(id, media, this.options.retry)
  }

  /**
   * Send a MediaStream using our broadcast primitives using Data Channel.
   * @param {Object}  media  - The stream to send
   * @param {Object} options - MediaRecorder options (see MediaRecorder API)
   * @return {boolean}
   */
  sendBroadcast (media, options = {}) {
    // https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder
    options = lmerge({
      mimeType: 'video/webm; codecs="vp8"', // You MUST set the MIME type
      interval: 100, // A short interval is recommended to keep buffer sizes low
      bitsPerSecond: 128 * 1024
    }, options)

    if (!media.id) media.id = uuid()
    if (!this._activeMedia.has(media.id)) {
      this._activeMedia.set(media.id, media)
      this._setListeners(media)
    }
    const ms = new MediaRecorderStream(media, options)
    const stream = this._communication.streamBroadcast()
    options.id = media.id
    stream.write(options)
    ms.on('data', (data) => {
      const chunkified = this.chunkify(JSON.stringify(data))
      if (chunkified.length === 0) {
        stream.write({
          type: 'full',
          id: 0,
          payload: chunkified[0]
        })
      } else {
        for (let i = 0; i < chunkified.length; i++) {
          if (i === chunkified.length - 1) {
            stream.write({
              type: 'end',
              id: i,
              payload: chunkified[i]
            })
          } else {
            stream.write({
              type: 'chunk',
              id: i,
              payload: chunkified[i]
            })
          }
        }
      }
    })
    ms.on('end', () => {
      stream.end()
    })
  }

  _reconstruct (stream, options = null) {
    const readable = new ReadableFromStream(stream, this)
    readable.on('error', (err) => {
      console.error(err)
    })
  }

  getStreamMedia (id, elem) {
    if (!this._activeStream.has(id)) return undefined
    const wrapper = new MediaSource(elem)
    const writable = wrapper.createWriteStream(this._activeStream.get(id).options.mimeType)
    elem.addEventListener('error', function () {
      // listen for errors on the video/audio element directly
      var errorCode = elem.error
      var detailedError = wrapper.detailedError
      console.error(errorCode, detailedError)
      // wrapper.detailedError will often have a more detailed error message
    })

    writable.on('error', function (err) {
      // listening to the stream 'error' event is optional
      console.error(err)
    })
    this._activeStream.get(id).source.pipe(writable)
  }

  _send (peerId, media, retry = this.options.retry) {
    let promise
    // #1 normal behavior
    if (this.i.has(peerId)) {
      promise = this._sendBis(this.pid, peerId, media, retry, this.NI)
    } else if (this.o.has(peerId)) {
      promise = this._sendBis(this.pid, peerId, media, retry, this.NO)
    } else {
      // #2 last chance behavior
      promise = new Promise((resolve, reject) => {
        const _send = (r) => {
          this._sendBis(this.pid, peerId, media, retry, this.NO).then(() => {
            resolve()
          }).catch((e) => {
            this._sendBis(this.pid, peerId, media, retry, this.NI).then(() => {
              resolve()
            }).catch((e) => {
              if (r < retry) {
                setTimeout(() => {
                  _send(r + 1)
                }, 1000)
              } else {
                reject(e)
              }
            })
          })
        }
        _send(0)
      })
    };
    return promise
  }

  _sendBis (protocolId, peerId, media, retry = 0, Neighbor) {
    return new Promise((resolve, reject) => {
      // #1 get the proper entry in the tables
      let entry = null
      if (Neighbor.living.contains(peerId)) {
        entry = Neighbor.living.get(peerId)
      } else if (Neighbor.dying.has(peerId)) {
        entry = Neighbor.dying.get(peerId) // (TODO) warn: not safe
      };

      // #2 define the recursive sending function
      let __send = (r) => {
        try {
          entry.socket.addStream(media)
          debug('[%s][%s] --- MEDIA msg --> %s:%s', protocolId, Neighbor.PEER, peerId, protocolId)
          resolve()
        } catch (e) {
          debug('[%s][%s] -X- MEDIA msg -X> %s:%s', protocolId, Neighbor.PEER, peerId, protocolId)
          if (r < retry) {
            setTimeout(() => { __send(r + 1) }, 1000)
          } else {
            reject(e)
          };
        };
      }
      // #3 start to send
      __send(0)
    })
  };

  /**
   * Handler executed when a message is recevied
   * @param  {string} id  - Message issuer's ID
   * @param  {Object} stream - The stream received
   * @return {void}
   */
  _receive (id, stream) {
    debug('Receive a media stream: ', id, stream)
    if (!stream.id) stream.id = uuid()
    if (!this._activeMedia.has(stream.id)) {
      this._activeMedia.set(stream.id, {peer: id, stream})
      this._setListeners(stream)
    }
    this.emit('receive', id, stream)
  }

  _setListeners (media) {
    media.onactive = () => {
      console.log('Media %s is active...', media.id)
    }
    media.oninactive = () => {
      console.log('Media %s is inactive... (Disconnection or a Shuffling occured.)', media.id)
      // this._sendRequest(media.id)
    }
    media.onended = () => {
      console.log('Media %s is finished...', media.id)
    }
  }

  /**
   * Chunk a string into n message of size 'chunkSize'
   * @param {string} string
   * @param {Number=this.options.chunkSize} chunkSize
   */
  chunkify (string, chunkSize = this.options.chunkSize) {
    // https://stackoverflow.com/questions/7033639/split-large-string-in-n-size-chunks-in-javascript
    return string.match(new RegExp('.{1,' + chunkSize + '}', 'g'))
  }
}

module.exports = Media
<|endoftext|>


<commit_before>export const consolePrefix = 'SweetAlert2:'

/**
 * Filter the unique values into a new array
 * @param arr
 */
export const uniqueArray = (arr) => {
  const result = []
  for (var i in arr) {
    if (result.indexOf(arr[i]) === -1) {
      result.push(arr[i])
    }
  }
  return result
}

/**
 * Convert object into iterable Map
 * https://stackoverflow.com/a/36644532/1331425
 * @param obj
 */
export const objectToMap = (obj) => {
  if (obj instanceof Map) {
    return obj
  }
  const map = new Map()
  Object.keys(obj).forEach(key => {
    map.set(key, obj[key])
  })
  return map
}

/**
 * Standardise console warnings
 * @param message
 */
export const warn = (message) => {
  console.warn(`${consolePrefix} ${message}`)
}

/**
 * Standardise console errors
 * @param message
 */
export const error = (message) => {
  console.error(`${consolePrefix} ${message}`)
}

/**
 * Private global state for `warnOnce`
 * @type {Array}
 * @private
 */
const previousWarnOnceMessages = []

/**
 * Show a console warning, but only if it hasn't already been shown
 * @param message
 */
export const warnOnce = (message) => {
  if (!previousWarnOnceMessages.includes(message)) {
    previousWarnOnceMessages.push(message)
    warn(message)
  }
}

/**
 * If `arg` is a function, call it (with no arguments or context) and return the result.
 * Otherwise, just pass the value through
 * @param arg
 */
export const callIfFunction = (arg) => typeof arg === 'function' ? arg() : arg
<commit_msg>In certain cases, using a for in loop will loop over dom `NodeList` prototype, yielding unexpected behaviour.<commit_after>export const consolePrefix = 'SweetAlert2:'

/**
 * Filter the unique values into a new array
 * @param arr
 */
export const uniqueArray = (arr) => {
  var result = []
  arr.map(i => {
    if (result.indexOf(i) === -1) {
      result.push(i)
    }
  })
  return result
}

/**
 * Convert object into iterable Map
 * https://stackoverflow.com/a/36644532/1331425
 * @param obj
 */
export const objectToMap = (obj) => {
  if (obj instanceof Map) {
    return obj
  }
  const map = new Map()
  Object.keys(obj).forEach(key => {
    map.set(key, obj[key])
  })
  return map
}

/**
 * Standardise console warnings
 * @param message
 */
export const warn = (message) => {
  console.warn(`${consolePrefix} ${message}`)
}

/**
 * Standardise console errors
 * @param message
 */
export const error = (message) => {
  console.error(`${consolePrefix} ${message}`)
}

/**
 * Private global state for `warnOnce`
 * @type {Array}
 * @private
 */
const previousWarnOnceMessages = []

/**
 * Show a console warning, but only if it hasn't already been shown
 * @param message
 */
export const warnOnce = (message) => {
  if (!previousWarnOnceMessages.includes(message)) {
    previousWarnOnceMessages.push(message)
    warn(message)
  }
}

/**
 * If `arg` is a function, call it (with no arguments or context) and return the result.
 * Otherwise, just pass the value through
 * @param arg
 */
export const callIfFunction = (arg) => typeof arg === 'function' ? arg() : arg
<|endoftext|>


<commit_before>// Miscellaneous utilities
/*jshint worker: true*/

import log from './log';
import Thread from './thread';
import WorkerBroker from './worker_broker';
import Geo from '../geo';

import yaml from 'js-yaml';

var Utils;
export default Utils = {};

WorkerBroker.addTarget('Utils', Utils);

// Basic Safari detection
// http://stackoverflow.com/questions/7944460/detect-safari-browser
Utils.isSafari = function () {
    return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
};

// Basic IE11 or Edge detection
Utils.isMicrosoft = function () {
    return /(Trident\/7.0|Edge[ /](\d+[\.\d]+))/i.test(navigator.userAgent);
};

Utils.io = function (url, timeout = 60000, responseType = 'text', method = 'GET', headers = {}, proxy = false) {
    if (Thread.is_worker && Utils.isMicrosoft()) {
        // Some versions of IE11 and Edge will hang web workers when performing XHR requests
        // These requests can be proxied through the main thread
        // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/9545866/
        log('debug', 'Proxying request for URL to worker', url);
        return WorkerBroker.postMessage('Utils.io', url, timeout, responseType, method, headers, true);
    }
    else {
        var request = new XMLHttpRequest();
        var promise = new Promise((resolve, reject) => {
            request.open(method, url, true);
            request.timeout = timeout;
            request.responseType = responseType;
            request.onload = () => {
                if (request.status === 200) {
                    if (['text', 'json'].indexOf(request.responseType) > -1) {
                        resolve(request.responseText);
                    }
                    else {
                        resolve(request.response);
                    }
                } else {
                    reject(Error('Request error with a status of ' + request.statusText));
                }
            };
            request.onerror = (evt) => {
                reject(Error('There was a network error' + evt.toString()));
            };
            request.ontimeout = (evt) => {
                reject(Error('timeout '+ evt.toString()));
            };
            request.send();
        });

        Object.defineProperty(promise, 'request', {
            value: request
        });

        return promise.then(response => {
            return (proxy && WorkerBroker.withTransferables(response)) || response;
        });
    }
};

Utils.parseResource = function (body) {
    var data;
    try {
        // jsyaml 'json' option allows duplicate keys
        // Keeping this for backwards compatibility, but should consider migrating to requiring
        // unique keys, as this is YAML spec. But Tangram ES currently accepts dupe keys as well,
        // so should consider how best to unify.
        data = yaml.safeLoad(body, { json: true });
    } catch (e) {
        throw e;
    }
    return data;
};

Utils.loadResource = function (source) {
    return new Promise((resolve, reject) => {
        if (typeof source === 'string') {
            Utils.io(source).then((body) => {
                try {
                    let data = Utils.parseResource(body);
                    resolve(data);
                }
                catch(e) {
                    reject(e);
                }
            }, reject);
        } else {
            resolve(source);
        }
    });
};

// Needed for older browsers that still support WebGL (Safari 6 etc.)
Utils.requestAnimationFramePolyfill = function () {
    if (typeof window.requestAnimationFrame !== 'function') {
        window.requestAnimationFrame =
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame    ||
            window.oRequestAnimationFrame      ||
            window.msRequestAnimationFrame     ||
            function (cb) {
                setTimeout(cb, 1000 /60);
            };
    }
};

// Stringify an object into JSON, but convert functions to strings
Utils.serializeWithFunctions = function (obj) {
    var serialized = JSON.stringify(obj, function(k, v) {
        // Convert functions to strings
        if (typeof v === 'function') {
            return v.toString();
        }
        return v;
    });

    return serialized;
};

// Recursively parse an object, attempting to convert string properties that look like functions back into functions
Utils.stringsToFunctions = function(obj, wrap) {
    // Convert string
    if (typeof obj === 'string') {
        obj = Utils.stringToFunction(obj, wrap);
    }
    // Loop through object properties
    else if (obj != null && typeof obj === 'object') {
        for (let p in obj) {
            obj[p] = Utils.stringsToFunctions(obj[p], wrap);
        }
    }
    return obj;
};

// Convert string back into a function
Utils.stringToFunction = function(val, wrap) {
    // Parse function signature and body
    let fmatch =
        (typeof val === 'string') &&
        val.match(/^\s*function[^(]*\(([^)]*)\)\s*?\{([\s\S]*)\}$/m);

    if (fmatch && fmatch.length > 2) {
        try {
            let src = fmatch[2];
            let args = fmatch[1].length > 0 && fmatch[1].split(',').map(x => x.trim()).filter(x => x);
            args = args.length > 0 ? args : ['context']; // default to single 'context' argument

            if (typeof wrap === 'function') {
                return new Function(...args, wrap(src)); // jshint ignore:line
            }
            else {
                return new Function(...args, src); // jshint ignore:line
            }
        }
        catch (e) {
            // fall-back to original value if parsing failed
            return val;
        }
    }
    return val;
};

// Default to allowing high pixel density
// Returns true if display density changed
Utils.use_high_density_display = true;
Utils.updateDevicePixelRatio = function () {
    let prev = Utils.device_pixel_ratio;
    Utils.device_pixel_ratio = (Utils.use_high_density_display && window.devicePixelRatio) || 1;
    return Utils.device_pixel_ratio !== prev;
};

if (Thread.is_main) {
    Utils.updateDevicePixelRatio();
}

// Used for differentiating between power-of-2 and non-power-of-2 textures
// Via: http://stackoverflow.com/questions/19722247/webgl-wait-for-texture-to-load
Utils.isPowerOf2 = function(value) {
    return (value & (value - 1)) === 0;
};

// Interpolate 'x' along a series of control points
// 'points' is an array of control points in the form [x, y]
//
// Example:
//     Control points:
//         [0, 5]:  when x=0, y=5
//         [4, 10]: when x=4, y=10
//
//     Utils.interpolate(2, [[0, 5], [4, 10]]);
//     -> computes x=2, halfway between x=0 and x=4: (10 - 5) / 2 +5
//     -> returns 7.5
//
// TODO: add other interpolation methods besides linear
//
Utils.interpolate = function(x, points, transform) {
    // If this doesn't resemble a list of control points, just return the original value
    if (!Array.isArray(points) || !Array.isArray(points[0])) {
        return points;
    }
    else if (points.length < 1) {
        return points;
    }

    var x1, x2, d, y, y1, y2;

    // Min bounds
    if (x <= points[0][0]) {
        y = points[0][1];
        if (typeof transform === 'function') {
            y = transform(y);
        }
    }
    // Max bounds
    else if (x >= points[points.length-1][0]) {
        y = points[points.length-1][1];
        if (typeof transform === 'function') {
            y = transform(y);
        }
    }
    // Find which control points x is between
    else {
        for (var i=0; i < points.length - 1; i++) {
            if (x >= points[i][0] && x < points[i+1][0]) {
                // Linear interpolation
                x1 = points[i][0];
                x2 = points[i+1][0];

                // Multiple values
                if (Array.isArray(points[i][1])) {
                    y = [];
                    for (var c=0; c < points[i][1].length; c++) {
                        if (typeof transform === 'function') {
                            y1 = transform(points[i][1][c]);
                            y2 = transform(points[i+1][1][c]);
                            d = y2 - y1;
                            y[c] = d * (x - x1) / (x2 - x1) + y1;
                        }
                        else {
                            d = points[i+1][1][c] - points[i][1][c];
                            y[c] = d * (x - x1) / (x2 - x1) + points[i][1][c];
                        }
                    }
                }
                // Single value
                else {
                    if (typeof transform === 'function') {
                        y1 = transform(points[i][1]);
                        y2 = transform(points[i+1][1]);
                        d = y2 - y1;
                        y = d * (x - x1) / (x2 - x1) + y1;
                    }
                    else {
                        d = points[i+1][1] - points[i][1];
                        y = d * (x - x1) / (x2 - x1) + points[i][1];
                    }
                }
                break;
            }
        }
    }
    return y;
};

Utils.toCSSColor = function (color) {
    if (color[3] === 1) { // full opacity
        return `rgb(${color.slice(0, 3).map(c => Math.round(c * 255)).join(', ')})`;
    }
    // RGB is between [0, 255] opacity is between [0, 1]
    return `rgba(${color.map((c, i) => (i < 3 && Math.round(c * 255)) || c).join(', ')})`;
};

Utils.pointInTile = function (point) {
    return point[0] >= 0 && point[1] > -Geo.tile_scale && point[0] < Geo.tile_scale && point[1] <= 0;
};
<commit_msg>fix passthrough of XHR request object (used to cancel tile requests)<commit_after>// Miscellaneous utilities
/*jshint worker: true*/

import log from './log';
import Thread from './thread';
import WorkerBroker from './worker_broker';
import Geo from '../geo';

import yaml from 'js-yaml';

var Utils;
export default Utils = {};

WorkerBroker.addTarget('Utils', Utils);

// Basic Safari detection
// http://stackoverflow.com/questions/7944460/detect-safari-browser
Utils.isSafari = function () {
    return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
};

// Basic IE11 or Edge detection
Utils.isMicrosoft = function () {
    return /(Trident\/7.0|Edge[ /](\d+[\.\d]+))/i.test(navigator.userAgent);
};

Utils.io = function (url, timeout = 60000, responseType = 'text', method = 'GET', headers = {}, proxy = false) {
    if (Thread.is_worker && Utils.isMicrosoft()) {
        // Some versions of IE11 and Edge will hang web workers when performing XHR requests
        // These requests can be proxied through the main thread
        // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/9545866/
        log('debug', 'Proxying request for URL to worker', url);
        return WorkerBroker.postMessage('Utils.io', url, timeout, responseType, method, headers, true);
    }
    else {
        var request = new XMLHttpRequest();
        var promise = new Promise((resolve, reject) => {
            request.open(method, url, true);
            request.timeout = timeout;
            request.responseType = responseType;
            request.onload = () => {
                if (request.status === 200) {
                    if (['text', 'json'].indexOf(request.responseType) > -1) {
                        resolve(request.responseText);
                    }
                    else {
                        resolve(request.response);
                    }
                } else {
                    reject(Error('Request error with a status of ' + request.statusText));
                }
            };
            request.onerror = (evt) => {
                reject(Error('There was a network error' + evt.toString()));
            };
            request.ontimeout = (evt) => {
                reject(Error('timeout '+ evt.toString()));
            };
            request.send();
        });

        promise = promise.then(response => {
            return (proxy && WorkerBroker.withTransferables(response)) || response;
        });

        // Attach request to promise to allow for XHR cancellation
        Object.defineProperty(promise, 'request', {
            value: request
        });

        return promise;
    }
};

Utils.parseResource = function (body) {
    var data;
    try {
        // jsyaml 'json' option allows duplicate keys
        // Keeping this for backwards compatibility, but should consider migrating to requiring
        // unique keys, as this is YAML spec. But Tangram ES currently accepts dupe keys as well,
        // so should consider how best to unify.
        data = yaml.safeLoad(body, { json: true });
    } catch (e) {
        throw e;
    }
    return data;
};

Utils.loadResource = function (source) {
    return new Promise((resolve, reject) => {
        if (typeof source === 'string') {
            Utils.io(source).then((body) => {
                try {
                    let data = Utils.parseResource(body);
                    resolve(data);
                }
                catch(e) {
                    reject(e);
                }
            }, reject);
        } else {
            resolve(source);
        }
    });
};

// Needed for older browsers that still support WebGL (Safari 6 etc.)
Utils.requestAnimationFramePolyfill = function () {
    if (typeof window.requestAnimationFrame !== 'function') {
        window.requestAnimationFrame =
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame    ||
            window.oRequestAnimationFrame      ||
            window.msRequestAnimationFrame     ||
            function (cb) {
                setTimeout(cb, 1000 /60);
            };
    }
};

// Stringify an object into JSON, but convert functions to strings
Utils.serializeWithFunctions = function (obj) {
    var serialized = JSON.stringify(obj, function(k, v) {
        // Convert functions to strings
        if (typeof v === 'function') {
            return v.toString();
        }
        return v;
    });

    return serialized;
};

// Recursively parse an object, attempting to convert string properties that look like functions back into functions
Utils.stringsToFunctions = function(obj, wrap) {
    // Convert string
    if (typeof obj === 'string') {
        obj = Utils.stringToFunction(obj, wrap);
    }
    // Loop through object properties
    else if (obj != null && typeof obj === 'object') {
        for (let p in obj) {
            obj[p] = Utils.stringsToFunctions(obj[p], wrap);
        }
    }
    return obj;
};

// Convert string back into a function
Utils.stringToFunction = function(val, wrap) {
    // Parse function signature and body
    let fmatch =
        (typeof val === 'string') &&
        val.match(/^\s*function[^(]*\(([^)]*)\)\s*?\{([\s\S]*)\}$/m);

    if (fmatch && fmatch.length > 2) {
        try {
            let src = fmatch[2];
            let args = fmatch[1].length > 0 && fmatch[1].split(',').map(x => x.trim()).filter(x => x);
            args = args.length > 0 ? args : ['context']; // default to single 'context' argument

            if (typeof wrap === 'function') {
                return new Function(...args, wrap(src)); // jshint ignore:line
            }
            else {
                return new Function(...args, src); // jshint ignore:line
            }
        }
        catch (e) {
            // fall-back to original value if parsing failed
            return val;
        }
    }
    return val;
};

// Default to allowing high pixel density
// Returns true if display density changed
Utils.use_high_density_display = true;
Utils.updateDevicePixelRatio = function () {
    let prev = Utils.device_pixel_ratio;
    Utils.device_pixel_ratio = (Utils.use_high_density_display && window.devicePixelRatio) || 1;
    return Utils.device_pixel_ratio !== prev;
};

if (Thread.is_main) {
    Utils.updateDevicePixelRatio();
}

// Used for differentiating between power-of-2 and non-power-of-2 textures
// Via: http://stackoverflow.com/questions/19722247/webgl-wait-for-texture-to-load
Utils.isPowerOf2 = function(value) {
    return (value & (value - 1)) === 0;
};

// Interpolate 'x' along a series of control points
// 'points' is an array of control points in the form [x, y]
//
// Example:
//     Control points:
//         [0, 5]:  when x=0, y=5
//         [4, 10]: when x=4, y=10
//
//     Utils.interpolate(2, [[0, 5], [4, 10]]);
//     -> computes x=2, halfway between x=0 and x=4: (10 - 5) / 2 +5
//     -> returns 7.5
//
// TODO: add other interpolation methods besides linear
//
Utils.interpolate = function(x, points, transform) {
    // If this doesn't resemble a list of control points, just return the original value
    if (!Array.isArray(points) || !Array.isArray(points[0])) {
        return points;
    }
    else if (points.length < 1) {
        return points;
    }

    var x1, x2, d, y, y1, y2;

    // Min bounds
    if (x <= points[0][0]) {
        y = points[0][1];
        if (typeof transform === 'function') {
            y = transform(y);
        }
    }
    // Max bounds
    else if (x >= points[points.length-1][0]) {
        y = points[points.length-1][1];
        if (typeof transform === 'function') {
            y = transform(y);
        }
    }
    // Find which control points x is between
    else {
        for (var i=0; i < points.length - 1; i++) {
            if (x >= points[i][0] && x < points[i+1][0]) {
                // Linear interpolation
                x1 = points[i][0];
                x2 = points[i+1][0];

                // Multiple values
                if (Array.isArray(points[i][1])) {
                    y = [];
                    for (var c=0; c < points[i][1].length; c++) {
                        if (typeof transform === 'function') {
                            y1 = transform(points[i][1][c]);
                            y2 = transform(points[i+1][1][c]);
                            d = y2 - y1;
                            y[c] = d * (x - x1) / (x2 - x1) + y1;
                        }
                        else {
                            d = points[i+1][1][c] - points[i][1][c];
                            y[c] = d * (x - x1) / (x2 - x1) + points[i][1][c];
                        }
                    }
                }
                // Single value
                else {
                    if (typeof transform === 'function') {
                        y1 = transform(points[i][1]);
                        y2 = transform(points[i+1][1]);
                        d = y2 - y1;
                        y = d * (x - x1) / (x2 - x1) + y1;
                    }
                    else {
                        d = points[i+1][1] - points[i][1];
                        y = d * (x - x1) / (x2 - x1) + points[i][1];
                    }
                }
                break;
            }
        }
    }
    return y;
};

Utils.toCSSColor = function (color) {
    if (color[3] === 1) { // full opacity
        return `rgb(${color.slice(0, 3).map(c => Math.round(c * 255)).join(', ')})`;
    }
    // RGB is between [0, 255] opacity is between [0, 1]
    return `rgba(${color.map((c, i) => (i < 3 && Math.round(c * 255)) || c).join(', ')})`;
};

Utils.pointInTile = function (point) {
    return point[0] >= 0 && point[1] > -Geo.tile_scale && point[0] < Geo.tile_scale && point[1] <= 0;
};
<|endoftext|>


<commit_before>import React, { Component, Children } from 'react';
import PropTypes from 'prop-types';

const problemMarker = {}; // marker used for invalid or unknown value, likely due to error

class Federal extends Component {
  constructor(props) {
    super(props);

    const { store, actions } = Object.assign({
      store: {},
      actions: {}
    }, props);

    const dispatch = Object.keys(actions).reduce((mapping, actionName) => {
      mapping[actionName] = (data, callback) => {
        const state = this.state;
        const oldStore = state.store;
        const newStore = actions[actionName](oldStore, data);

        if (newStore === oldStore) {
          return;
        }

        state.store = newStore;
        this.setState(state, () => {
          prettyLog('Prev Store', oldStore);
          prettyLog('Federal Action', actionName);
          prettyLog('Next Store', newStore);

          if (typeof callback === 'function') {
            callback(oldStore, newStore);
          }
        });
      };
      return mapping;
    }, {});

    this.state = {
      store,
      dispatch
    };
  }

  getChildContext() {
    return {
      store: this.state.store,
      dispatch: this.state.dispatch
    };
  }

  render() {
    const { children } = this.props;

    return !children ? null :
      React.isValidElement(children) ? Children.only(children) :
      Array.isArray(children) ? (
        {children}
      ) :
      null;
  }

  static childContextTypes = {
    store: PropTypes.object.isRequired,
    dispatch: PropTypes.object.isRequired
  }
}

function connect(selector = store => store) {
  return function wrapper(InboundComponent) {
    class FederalWrap extends Component {
      render() {
        const { store, dispatch } = this.context;

        const storeSelected = typeof selector === 'function' ? selector(store) :
          Array.isArray(selector) ? selector.reduce((selection, currentSelector) => {
            return currentSelector(selection);
          }, store) :
          problemMarker;

        if (storeSelected === problemMarker) {
          throw new Error(`An invalid selector was passed to connect() for ${InboundComponent.displayName}`);
        }

        // props passed manually will override those in the store
        const usedProps = Object.assign({
          dispatch
        }, storeSelected, this.props);

        return (
          <InboundComponent {...usedProps} />
        );
      }

      static contextTypes = {
        store: PropTypes.object.isRequired,
        dispatch: PropTypes.object.isRequired
      }
    }

    return FederalWrap;
  };
}

function prettyLog(label, data) {
  // todo: make this compatible with non-chrome browsers
  console.log(`%c ${padRight(label, 16)}`, [
    'color: #e4421e',
    'font-style: italic',
    'font-weight: bold'
  ].join(';'), data);
}

function padRight(label, len) {
  if (label.length >= len) {
    return label;
  }
  return padRight(label + ' ', len);
}

export default Federal;
export { connect };
<commit_msg>only log if <> production<commit_after>import React, { Component, Children } from 'react';
import PropTypes from 'prop-types';

const problemMarker = {}; // marker used for invalid or unknown value, likely due to error

class Federal extends Component {
  constructor(props) {
    super(props);

    const { store, actions } = Object.assign({
      store: {},
      actions: {}
    }, props);

    const dispatch = Object.keys(actions).reduce((mapping, actionName) => {
      mapping[actionName] = (data, callback) => {
        const state = this.state;
        const oldStore = state.store;
        const newStore = actions[actionName](oldStore, data);

        if (newStore === oldStore) {
          return;
        }

        state.store = newStore;
        this.setState(state, () => {
          if (process.env.NODE_ENV !== 'production') {
            prettyLog('Prev Store', oldStore);
            prettyLog('Federal Action', actionName);
            prettyLog('Next Store', newStore);
          }

          if (typeof callback === 'function') {
            callback(oldStore, newStore);
          }
        });
      };
      return mapping;
    }, {});

    this.state = {
      store,
      dispatch
    };
  }

  getChildContext() {
    return {
      store: this.state.store,
      dispatch: this.state.dispatch
    };
  }

  render() {
    const { children } = this.props;

    return !children ? null :
      React.isValidElement(children) ? Children.only(children) :
      Array.isArray(children) ? (
        {children}
      ) :
      null;
  }

  static childContextTypes = {
    store: PropTypes.object.isRequired,
    dispatch: PropTypes.object.isRequired
  }
}

function connect(selector = store => store) {
  return function wrapper(InboundComponent) {
    class FederalWrap extends Component {
      render() {
        const { store, dispatch } = this.context;

        const storeSelected = typeof selector === 'function' ? selector(store) :
          Array.isArray(selector) ? selector.reduce((selection, currentSelector) => {
            return currentSelector(selection);
          }, store) :
          problemMarker;

        if (storeSelected === problemMarker) {
          throw new Error(`An invalid selector was passed to connect() for ${InboundComponent.displayName}`);
        }

        // props passed manually will override those in the store
        const usedProps = Object.assign({
          dispatch
        }, storeSelected, this.props);

        return (
          <InboundComponent {...usedProps} />
        );
      }

      static contextTypes = {
        store: PropTypes.object.isRequired,
        dispatch: PropTypes.object.isRequired
      }
    }

    return FederalWrap;
  };
}

function prettyLog(label, data) {
  // todo: make this compatible with non-chrome browsers
  console.log(`%c ${padRight(label, 16)}`, [
    'color: #e4421e',
    'font-style: italic',
    'font-weight: bold'
  ].join(';'), data);
}

function padRight(label, len) {
  if (label.length >= len) {
    return label;
  }
  return padRight(label + ' ', len);
}

export default Federal;
export { connect };
<|endoftext|>