<commit_before>'use strict';
/**
 * Implementation of model queries for mongo
 */

const _ = require('lodash');
const {
  convertRestQueryParams,
  buildQuery,
  models: modelUtils,
} = require('strapi-utils');

module.exports = ({ model, modelKey, strapi }) => {
  const hasPK = obj => _.has(obj, model.primaryKey) || _.has(obj, 'id');
  const getPK = obj =>
    _.has(obj, model.primaryKey) ? obj[model.primaryKey] : obj.id;

  const assocKeys = model.associations.map(ast => ast.alias);
  const groupKeys = Object.keys(model.attributes).filter(key => {
    return model.attributes[key].type === 'group';
  });
  const excludedKeys = assocKeys.concat(groupKeys);

  const defaultPopulate = model.associations
    .filter(ast => ast.autoPopulate !== false)
    .map(ast => ast.alias);

  const pickRelations = values => {
    return _.pick(values, assocKeys);
  };

  const omitExernalValues = values => {
    return _.omit(values, excludedKeys);
  };

  async function createGroups(entry, values) {
    if (groupKeys.length === 0) return;

    for (let key of groupKeys) {
      const attr = model.attributes[key];
      const { group, required = false, repeatable = false } = attr;

      const groupModel = strapi.groups[group];

      if (required === true && !_.has(values, key)) {
        const err = new Error(`Group ${key} is required`);
        err.status = 400;
        throw err;
      }

      if (!_.has(values, key)) continue;

      const groupValue = values[key];

      if (repeatable === true) {
        validateRepeatableInput(groupValue, { key, ...attr });
        const groups = await Promise.all(
          groupValue.map(value => {
            return strapi.query(group).create(value);
          })
        );

        const groupsArr = groups.map(groupEntry => ({
          kind: groupModel.globalId,
          ref: groupEntry,
        }));

        entry[key] = groupsArr;
        await entry.save();
      } else {
        validateNonRepeatableInput(groupValue, { key, ...attr });
        if (groupValue === null) return;

        const groupEntry = await strapi.query(group).create(groupValue);
        entry[key] = [
          {
            kind: groupModel.globalId,
            ref: groupEntry,
          },
        ];
        await entry.save();
      }
    }
  }

  async function updateGroups(entry, values) {
    if (groupKeys.length === 0) return;

    for (let key of groupKeys) {
      // if key isn't present then don't change the current group data
      if (!_.has(values, key)) continue;

      const attr = model.attributes[key];
      const { group, repeatable = false } = attr;

      const groupModel = strapi.groups[group];
      const groupValue = values[key];

      const updateOrCreateGroup = async value => {
        // check if value has an id then update else create
        if (hasPK(value)) {
          return strapi.query(group).update(
            {
              [model.primaryKey]: getPK(value),
            },
            value
          );
        }
        return strapi.query(group).create(value);
      };

      if (repeatable === true) {
        validateRepeatableInput(groupValue, { key, ...attr });

        await deleteOldGroups(entry, groupValue, { key, groupModel });

        const groups = await Promise.all(groupValue.map(updateOrCreateGroup));
        const groupsArr = groups.map(group => ({
          kind: groupModel.globalId,
          ref: group,
        }));

        entry[key] = groupsArr;
        await entry.save();
      } else {
        validateNonRepeatableInput(groupValue, { key, ...attr });

        await deleteOldGroups(entry, groupValue, { key, groupModel });

        if (groupValue === null) return;

        const group = await updateOrCreateGroup(groupValue);
        entry[key] = [
          {
            kind: groupModel.globalId,
            ref: group,
          },
        ];
        await entry.save();
      }
    }
    return;
  }

  async function deleteOldGroups(entry, groupValue, { key, groupModel }) {
    const groupArr = Array.isArray(groupValue) ? groupValue : [groupValue];

    const idsToKeep = groupArr.filter(hasPK).map(getPK);
    const allIds = await (entry[key] || [])
      .filter(el => el.ref)
      .map(el => el.ref._id);

    // verify the provided ids are realted to this entity.
    idsToKeep.forEach(id => {
      if (allIds.findIndex(currentId => currentId.toString() === id) === -1) {
        const err = new Error(
          `Some of the provided groups in ${key} are not related to the entity`
        );
        err.status = 400;
        throw err;
      }
    });

    const idsToDelete = allIds.reduce((acc, id) => {
      if (idsToKeep.includes(id.toString())) return acc;
      return acc.concat(id);
    }, []);

    if (idsToDelete.length > 0) {
      await strapi
        .query(groupModel.uid)
        .delete({ [`${model.primaryKey}_in`]: idsToDelete });
    }
  }

  async function deleteGroups(entry) {
    if (groupKeys.length === 0) return;

    for (let key of groupKeys) {
      const attr = model.attributes[key];
      const { group } = attr;
      const groupModel = strapi.groups[group];

      if (Array.isArray(entry[key]) && entry[key].length > 0) {
        const idsToDelete = entry[key].map(el => el.ref);
        await strapi
          .query(groupModel.uid)
          .delete({ [`${model.primaryKey}_in`]: idsToDelete });
      }
    }
  }

  function find(params, populate) {
    const populateOpt = populate || defaultPopulate;

    const filters = convertRestQueryParams(params);

    return buildQuery({
      model,
      filters,
      populate: populateOpt,
    }).then(results => {
      return results.map(result => (result ? result.toObject() : null));
    });
  }

  async function findOne(params, populate) {
    const primaryKey = getPK(params);

    if (primaryKey) {
      params = {
        [model.primaryKey]: primaryKey,
      };
    }

    const entry = await model
      .findOne(params)
      .populate(populate || defaultPopulate);

    return entry ? entry.toObject() : null;
  }

  function count(params) {
    const filters = convertRestQueryParams(params);

    return buildQuery({
      model,
      filters: { where: filters.where },
    }).count();
  }

  async function create(values) {
    // Extract values related to relational data.
    const relations = pickRelations(values);
    const data = omitExernalValues(values);

    // Create entry with no-relational data.
    const entry = await model.create(data);

    await createGroups(entry, values);

    // Create relational data and return the entry.
    return model.updateRelations({
      [model.primaryKey]: getPK(entry),
      values: relations,
    });
  }

  async function update(params, values) {
    const primaryKey = getPK(params);

    if (primaryKey) {
      params = {
        [model.primaryKey]: primaryKey,
      };
    }

    const entry = await model.findOne(params);

    if (!entry) {
      const err = new Error('entry.notFound');
      err.status = 404;
      throw err;
    }

    // Extract values related to relational data.
    const relations = pickRelations(values);
    const data = omitExernalValues(values);

    // update groups first in case it fails don't update the entity
    await updateGroups(entry, values);
    // Update entry with no-relational data.
    await entry.updateOne(data);

    // Update relational data and return the entry.
    return model.updateRelations(Object.assign(params, { values: relations }));
  }

  async function deleteMany(params) {
    const primaryKey = getPK(params);

    if (primaryKey) return deleteOne(params);

    const entries = await find(params);
    return await Promise.all(entries.map(entry => deleteOne({ id: entry.id })));
  }

  async function deleteOne(params) {
    const entry = await model
      .findOneAndRemove({ [model.primaryKey]: getPK(params) })
      .populate(defaultPopulate);

    if (!entry) {
      const err = new Error('entry.notFound');
      err.status = 404;
      throw err;
    }

    await deleteGroups(entry);

    await Promise.all(
      model.associations.map(async association => {
        if (!association.via || !entry._id || association.dominant) {
          return true;
        }

        const search =
          _.endsWith(association.nature, 'One') ||
          association.nature === 'oneToMany'
            ? { [association.via]: entry._id }
            : { [association.via]: { $in: [entry._id] } };
        const update =
          _.endsWith(association.nature, 'One') ||
          association.nature === 'oneToMany'
            ? { [association.via]: null }
            : { $pull: { [association.via]: entry._id } };

        // Retrieve model.
        const model = association.plugin
          ? strapi.plugins[association.plugin].models[
              association.model || association.collection
            ]
          : strapi.models[association.model || association.collection];

        return model.updateMany(search, update);
      })
    );

    return entry.toObject ? entry.toObject() : null;
  }

  function search(params, populate) {
    // Convert `params` object to filters compatible with Mongo.
    const filters = modelUtils.convertParams(modelKey, params);

    const $or = buildSearchOr(model, params._q);

    return model
      .find({ $or })
      .sort(filters.sort)
      .skip(filters.start)
      .limit(filters.limit)
      .populate(populate || defaultPopulate);
  }

  function countSearch(params) {
    const $or = buildSearchOr(model, params._q);
    return model.find({ $or }).countDocuments();
  }

  return {
    findOne,
    find,
    create,
    update,
    delete: deleteMany,
    count,
    search,
    countSearch,
  };
};

const buildSearchOr = (model, query) => {
  return Object.keys(model.attributes).reduce((acc, curr) => {
    switch (model.attributes[curr].type) {
      case 'integer':
      case 'float':
      case 'decimal':
        if (!_.isNaN(_.toNumber(query))) {
          return acc.concat({ [curr]: query });
        }

        return acc;
      case 'string':
      case 'text':
      case 'password':
        return acc.concat({ [curr]: { $regex: query, $options: 'i' } });
      case 'boolean':
        if (query === 'true' || query === 'false') {
          return acc.concat({ [curr]: query === 'true' });
        }

        return acc;
      default:
        return acc;
    }
  }, []);
};

function validateRepeatableInput(value, { key, min, max }) {
  if (!Array.isArray(value)) {
    const err = new Error(`Group ${key} is repetable. Expected an array`);
    err.status = 400;
    throw err;
  }

  value.forEach(val => {
    if (typeof val !== 'object' || Array.isArray(val) || val === null) {
      const err = new Error(
        `Group ${key} as invalid items. Expected each items to be objects`
      );
      err.status = 400;
      throw err;
    }
  });

  if (min && value.length < min) {
    const err = new Error(`Group ${key} must contain at least ${min} items`);
    err.status = 400;
    throw err;
  }
  if (max && value.length > max) {
    const err = new Error(`Group ${key} must contain at most ${max} items`);
    err.status = 400;
    throw err;
  }
}

function validateNonRepeatableInput(value, { key, required }) {
  if (typeof value !== 'object' || Array.isArray(value)) {
    const err = new Error(`Group ${key} should be an object`);
    err.status = 400;
    throw err;
  }

  if (required === true && value === null) {
    const err = new Error(`Group ${key} is required`);
    err.status = 400;
    throw err;
  }
}
<commit_msg>Convert mongo search results to plain js objects<commit_after>'use strict';
/**
 * Implementation of model queries for mongo
 */

const _ = require('lodash');
const {
  convertRestQueryParams,
  buildQuery,
  models: modelUtils,
} = require('strapi-utils');

module.exports = ({ model, modelKey, strapi }) => {
  const hasPK = obj => _.has(obj, model.primaryKey) || _.has(obj, 'id');
  const getPK = obj =>
    _.has(obj, model.primaryKey) ? obj[model.primaryKey] : obj.id;

  const assocKeys = model.associations.map(ast => ast.alias);
  const groupKeys = Object.keys(model.attributes).filter(key => {
    return model.attributes[key].type === 'group';
  });
  const excludedKeys = assocKeys.concat(groupKeys);

  const defaultPopulate = model.associations
    .filter(ast => ast.autoPopulate !== false)
    .map(ast => ast.alias);

  const pickRelations = values => {
    return _.pick(values, assocKeys);
  };

  const omitExernalValues = values => {
    return _.omit(values, excludedKeys);
  };

  async function createGroups(entry, values) {
    if (groupKeys.length === 0) return;

    for (let key of groupKeys) {
      const attr = model.attributes[key];
      const { group, required = false, repeatable = false } = attr;

      const groupModel = strapi.groups[group];

      if (required === true && !_.has(values, key)) {
        const err = new Error(`Group ${key} is required`);
        err.status = 400;
        throw err;
      }

      if (!_.has(values, key)) continue;

      const groupValue = values[key];

      if (repeatable === true) {
        validateRepeatableInput(groupValue, { key, ...attr });
        const groups = await Promise.all(
          groupValue.map(value => {
            return strapi.query(group).create(value);
          })
        );

        const groupsArr = groups.map(groupEntry => ({
          kind: groupModel.globalId,
          ref: groupEntry,
        }));

        entry[key] = groupsArr;
        await entry.save();
      } else {
        validateNonRepeatableInput(groupValue, { key, ...attr });
        if (groupValue === null) return;

        const groupEntry = await strapi.query(group).create(groupValue);
        entry[key] = [
          {
            kind: groupModel.globalId,
            ref: groupEntry,
          },
        ];
        await entry.save();
      }
    }
  }

  async function updateGroups(entry, values) {
    if (groupKeys.length === 0) return;

    for (let key of groupKeys) {
      // if key isn't present then don't change the current group data
      if (!_.has(values, key)) continue;

      const attr = model.attributes[key];
      const { group, repeatable = false } = attr;

      const groupModel = strapi.groups[group];
      const groupValue = values[key];

      const updateOrCreateGroup = async value => {
        // check if value has an id then update else create
        if (hasPK(value)) {
          return strapi.query(group).update(
            {
              [model.primaryKey]: getPK(value),
            },
            value
          );
        }
        return strapi.query(group).create(value);
      };

      if (repeatable === true) {
        validateRepeatableInput(groupValue, { key, ...attr });

        await deleteOldGroups(entry, groupValue, { key, groupModel });

        const groups = await Promise.all(groupValue.map(updateOrCreateGroup));
        const groupsArr = groups.map(group => ({
          kind: groupModel.globalId,
          ref: group,
        }));

        entry[key] = groupsArr;
        await entry.save();
      } else {
        validateNonRepeatableInput(groupValue, { key, ...attr });

        await deleteOldGroups(entry, groupValue, { key, groupModel });

        if (groupValue === null) return;

        const group = await updateOrCreateGroup(groupValue);
        entry[key] = [
          {
            kind: groupModel.globalId,
            ref: group,
          },
        ];
        await entry.save();
      }
    }
    return;
  }

  async function deleteOldGroups(entry, groupValue, { key, groupModel }) {
    const groupArr = Array.isArray(groupValue) ? groupValue : [groupValue];

    const idsToKeep = groupArr.filter(hasPK).map(getPK);
    const allIds = await (entry[key] || [])
      .filter(el => el.ref)
      .map(el => el.ref._id);

    // verify the provided ids are realted to this entity.
    idsToKeep.forEach(id => {
      if (allIds.findIndex(currentId => currentId.toString() === id) === -1) {
        const err = new Error(
          `Some of the provided groups in ${key} are not related to the entity`
        );
        err.status = 400;
        throw err;
      }
    });

    const idsToDelete = allIds.reduce((acc, id) => {
      if (idsToKeep.includes(id.toString())) return acc;
      return acc.concat(id);
    }, []);

    if (idsToDelete.length > 0) {
      await strapi
        .query(groupModel.uid)
        .delete({ [`${model.primaryKey}_in`]: idsToDelete });
    }
  }

  async function deleteGroups(entry) {
    if (groupKeys.length === 0) return;

    for (let key of groupKeys) {
      const attr = model.attributes[key];
      const { group } = attr;
      const groupModel = strapi.groups[group];

      if (Array.isArray(entry[key]) && entry[key].length > 0) {
        const idsToDelete = entry[key].map(el => el.ref);
        await strapi
          .query(groupModel.uid)
          .delete({ [`${model.primaryKey}_in`]: idsToDelete });
      }
    }
  }

  function find(params, populate) {
    const populateOpt = populate || defaultPopulate;

    const filters = convertRestQueryParams(params);

    return buildQuery({
      model,
      filters,
      populate: populateOpt,
    }).then(results =>
      results.map(result => (result ? result.toObject() : null))
    );
  }

  async function findOne(params, populate) {
    const primaryKey = getPK(params);

    if (primaryKey) {
      params = {
        [model.primaryKey]: primaryKey,
      };
    }

    const entry = await model
      .findOne(params)
      .populate(populate || defaultPopulate);

    return entry ? entry.toObject() : null;
  }

  function count(params) {
    const filters = convertRestQueryParams(params);

    return buildQuery({
      model,
      filters: { where: filters.where },
    }).count();
  }

  async function create(values) {
    // Extract values related to relational data.
    const relations = pickRelations(values);
    const data = omitExernalValues(values);

    // Create entry with no-relational data.
    const entry = await model.create(data);

    await createGroups(entry, values);

    // Create relational data and return the entry.
    return model.updateRelations({
      [model.primaryKey]: getPK(entry),
      values: relations,
    });
  }

  async function update(params, values) {
    const primaryKey = getPK(params);

    if (primaryKey) {
      params = {
        [model.primaryKey]: primaryKey,
      };
    }

    const entry = await model.findOne(params);

    if (!entry) {
      const err = new Error('entry.notFound');
      err.status = 404;
      throw err;
    }

    // Extract values related to relational data.
    const relations = pickRelations(values);
    const data = omitExernalValues(values);

    // update groups first in case it fails don't update the entity
    await updateGroups(entry, values);
    // Update entry with no-relational data.
    await entry.updateOne(data);

    // Update relational data and return the entry.
    return model.updateRelations(Object.assign(params, { values: relations }));
  }

  async function deleteMany(params) {
    const primaryKey = getPK(params);

    if (primaryKey) return deleteOne(params);

    const entries = await find(params);
    return await Promise.all(entries.map(entry => deleteOne({ id: entry.id })));
  }

  async function deleteOne(params) {
    const entry = await model
      .findOneAndRemove({ [model.primaryKey]: getPK(params) })
      .populate(defaultPopulate);

    if (!entry) {
      const err = new Error('entry.notFound');
      err.status = 404;
      throw err;
    }

    await deleteGroups(entry);

    await Promise.all(
      model.associations.map(async association => {
        if (!association.via || !entry._id || association.dominant) {
          return true;
        }

        const search =
          _.endsWith(association.nature, 'One') ||
          association.nature === 'oneToMany'
            ? { [association.via]: entry._id }
            : { [association.via]: { $in: [entry._id] } };
        const update =
          _.endsWith(association.nature, 'One') ||
          association.nature === 'oneToMany'
            ? { [association.via]: null }
            : { $pull: { [association.via]: entry._id } };

        // Retrieve model.
        const model = association.plugin
          ? strapi.plugins[association.plugin].models[
              association.model || association.collection
            ]
          : strapi.models[association.model || association.collection];

        return model.updateMany(search, update);
      })
    );

    return entry.toObject ? entry.toObject() : null;
  }

  function search(params, populate) {
    // Convert `params` object to filters compatible with Mongo.
    const filters = modelUtils.convertParams(modelKey, params);

    const $or = buildSearchOr(model, params._q);

    return model
      .find({ $or })
      .sort(filters.sort)
      .skip(filters.start)
      .limit(filters.limit)
      .populate(populate || defaultPopulate)
      .then(results =>
        results.map(result => (result ? result.toObject() : null))
      );
  }

  function countSearch(params) {
    const $or = buildSearchOr(model, params._q);
    return model.find({ $or }).countDocuments();
  }

  return {
    findOne,
    find,
    create,
    update,
    delete: deleteMany,
    count,
    search,
    countSearch,
  };
};

const buildSearchOr = (model, query) => {
  return Object.keys(model.attributes).reduce((acc, curr) => {
    switch (model.attributes[curr].type) {
      case 'integer':
      case 'float':
      case 'decimal':
        if (!_.isNaN(_.toNumber(query))) {
          return acc.concat({ [curr]: query });
        }

        return acc;
      case 'string':
      case 'text':
      case 'password':
        return acc.concat({ [curr]: { $regex: query, $options: 'i' } });
      case 'boolean':
        if (query === 'true' || query === 'false') {
          return acc.concat({ [curr]: query === 'true' });
        }

        return acc;
      default:
        return acc;
    }
  }, []);
};

function validateRepeatableInput(value, { key, min, max }) {
  if (!Array.isArray(value)) {
    const err = new Error(`Group ${key} is repetable. Expected an array`);
    err.status = 400;
    throw err;
  }

  value.forEach(val => {
    if (typeof val !== 'object' || Array.isArray(val) || val === null) {
      const err = new Error(
        `Group ${key} as invalid items. Expected each items to be objects`
      );
      err.status = 400;
      throw err;
    }
  });

  if (min && value.length < min) {
    const err = new Error(`Group ${key} must contain at least ${min} items`);
    err.status = 400;
    throw err;
  }
  if (max && value.length > max) {
    const err = new Error(`Group ${key} must contain at most ${max} items`);
    err.status = 400;
    throw err;
  }
}

function validateNonRepeatableInput(value, { key, required }) {
  if (typeof value !== 'object' || Array.isArray(value)) {
    const err = new Error(`Group ${key} should be an object`);
    err.status = 400;
    throw err;
  }

  if (required === true && value === null) {
    const err = new Error(`Group ${key} is required`);
    err.status = 400;
    throw err;
  }
}
<|endoftext|>


<commit_before>/**
 * Created with IntelliJ IDEA.
 * User: sebastianbromberg
 * Date: 12/8/13
 * Time: 15:58
 * To change this template use File | Settings | File Templates.
 *
 *
 * Modified by Vinicius Andrei Cerbaro
 * User: cerbaro
 * Date 10/3/15
 *
 */

var util = require('util');

exports.errorHelper = function errorHelper(err, cb) {

  //If it isn't a mongoose-validation error, just throw it.
  if (err.name !== 'ValidationError') if(cb) return cb(err); else return err;
  var messages = {
      'required': "%s is required.",
      'min': "%s below minimum.",
      'max': "%s above maximum.",
      'enum': "%s not an allowed value."
  };

  //A validationerror can contain more than one error.
  var errors = [];

  //Loop over the errors object of the Validation Error
  Object.keys(err.errors).forEach(function (field) {
      // Getting from .proprerties now.
      var eObj = err.errors[field].properties;

      // If we have a message on the schema.
      if(eObj.hasOwnProperty("message")) errors.push(eObj.message);

      //If we don't have a message for `type`, just push the error through
      else if (!messages.hasOwnProperty(eObj.type)) errors.push(eObj.type);

      //Otherwise, use util.format to format the message, and passing the path
      else errors.push(require('util').format(messages[eObj.type], eObj.path));
  });

  if(cb) return cb(errors); else return errors;
}
<commit_msg>Fixed callback<commit_after>/**
 * Created with IntelliJ IDEA.
 * User: sebastianbromberg
 * Date: 12/8/13
 * Time: 15:58
 * To change this template use File | Settings | File Templates.
 *
 *
 * Modified by Vinicius Andrei Cerbaro
 * User: cerbaro
 * Date 10/4/15
 *
 */

var util = require('util');

exports.errorHelper = function errorHelper(err, callback) {

  //If it isn't a mongoose-validation error, just throw it.
  if (err.name !== 'ValidationError')
    return callback(err);

  var messages = {
      'required': "%s is required.",
      'min': "%s below minimum.",
      'max': "%s above maximum.",
      'enum': "%s not an allowed value."
  };

  //A validationerror can contain more than one error.
  var errors = [];

  //Loop over the errors object of the Validation Error
  Object.keys(err.errors).forEach(function (field) {

      // Getting from .proprerties now.
      var eObj = err.errors[field].properties;

      // If we have a message on the schema.
      if(eObj.hasOwnProperty("message")) errors.push(eObj.message);

      //If we don't have a message for `type`, just push the error through
      else if (!messages.hasOwnProperty(eObj.type)) errors.push(eObj.type);

      //Otherwise, use util.format to format the message, and passing the path
      else errors.push(require('util').format(messages[eObj.type], eObj.path));
  });

  return callback(errors);
}
<|endoftext|>


<commit_before>// FIXME large resolutions lead to too large framebuffers :-(
// FIXME animated shaders! check in redraw

goog.provide('ol.renderer.webgl.TileLayer');
goog.provide('ol.renderer.webgl.tilelayerrenderer');
goog.provide('ol.renderer.webgl.tilelayerrenderer.shader.Fragment');
goog.provide('ol.renderer.webgl.tilelayerrenderer.shader.Vertex');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.debug.Logger');
goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('goog.vec.Mat4');
goog.require('goog.vec.Vec4');
goog.require('goog.webgl');
goog.require('ol.Coordinate');
goog.require('ol.MapEventType');
goog.require('ol.Size');
goog.require('ol.TileLayer');
goog.require('ol.TileState');
goog.require('ol.renderer.webgl.FragmentShader');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.renderer.webgl.VertexShader');

/**
 * @constructor
 * @extends {ol.renderer.webgl.FragmentShader}
 */
ol.renderer.webgl.tilelayerrenderer.shader.Fragment = function() {
  goog.base(this, [
    'precision mediump float;',
    '',
    'uniform sampler2D uTexture;',
    '',
    'varying vec2 vTexCoord;',
    '',
    'void main(void) {',
    ' gl_FragColor = texture2D(uTexture, vTexCoord);',
    '}'
  ].join('\n'));
};
goog.inherits(
    ol.renderer.webgl.tilelayerrenderer.shader.Fragment,
    ol.renderer.webgl.FragmentShader);
goog.addSingletonGetter(ol.renderer.webgl.tilelayerrenderer.shader.Fragment);

/**
 * @constructor
 * @extends {ol.renderer.webgl.VertexShader}
 */
ol.renderer.webgl.tilelayerrenderer.shader.Vertex = function() {
  goog.base(this, [
    'attribute vec2 aPosition;',
    'attribute vec2 aTexCoord;',
    '',
    'varying vec2 vTexCoord;',
    '',
    'uniform vec4 uTileOffset;',
    '',
    'void main(void) {',
    '  gl_Position.xy = aPosition * uTileOffset.xy + uTileOffset.zw;',
    '  gl_Position.z = 0.;',
    '  gl_Position.w = 1.;',
    '  vTexCoord = aTexCoord;',
    '}'
  ].join('\n'));
};
goog.inherits(
    ol.renderer.webgl.tilelayerrenderer.shader.Vertex,
    ol.renderer.webgl.VertexShader);
goog.addSingletonGetter(ol.renderer.webgl.tilelayerrenderer.shader.Vertex);

/**
 * @constructor
 * @extends {ol.renderer.webgl.Layer}
 * @param {ol.renderer.Map} mapRenderer Map renderer.
 * @param {ol.TileLayer} tileLayer Tile layer.
 */
ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {

  goog.base(this, mapRenderer, tileLayer);

  if (goog.DEBUG) {
    /**
     * @inheritDoc
     */
    this.logger = goog.debug.Logger.getLogger(
        'ol.renderer.webgl.tilelayerrenderer.' + goog.getUid(this));
  }

  /**
   * @private
   * @type {ol.renderer.webgl.FragmentShader}
   */
  this.fragmentShader_ =
      ol.renderer.webgl.tilelayerrenderer.shader.Fragment.getInstance();

  /**
   * @private
   * @type {ol.renderer.webgl.VertexShader}
   */
  this.vertexShader_ =
      ol.renderer.webgl.tilelayerrenderer.shader.Vertex.getInstance();

  /**
   * @private
   * @type {{aPosition: number,
   *         aTexCoord: number,
   *         uTileOffset: WebGLUniformLocation,
   *         uTexture: WebGLUniformLocation}|null}
   */
  this.locations_ = null;

  /**
   * @private
   * @type {WebGLBuffer}
   */
  this.arrayBuffer_ = null;

  /**
   * @private
   * @type {WebGLTexture}
   */
  this.texture_ = null;

  /**
   * @private
   * @type {WebGLFramebuffer}
   */
  this.framebuffer_ = null;

  /**
   * @private
   * @type {number|undefined}
   */
  this.framebufferDimension_ = undefined;

  /**
   * @private
   * @type {Object.<number, (number|null)>}
   */
  this.tileChangeListenerKeys_ = {};

  /**
   * @private
   * @type {goog.vec.Mat4.AnyType}
   */
  this.matrix_ = goog.vec.Mat4.createNumber();

};
goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);

/**
 * @param {number} framebufferDimension Framebuffer dimension.
 * @private
 */
ol.renderer.webgl.TileLayer.prototype.bindFramebuffer_ =
    function(framebufferDimension) {

  var mapRenderer = this.getMapRenderer();
  var gl = mapRenderer.getGL();

  if (!goog.isDef(this.framebufferDimension_) ||
      this.framebufferDimension_ != framebufferDimension) {

    if (goog.DEBUG) {
      this.logger.info('re-sizing framebuffer');
    }

    if (ol.renderer.webgl.FREE_RESOURCES_IMMEDIATELY) {
      if (goog.DEBUG) {
        this.logger.info('freeing WebGL resources');
      }
      if (!gl.isContextLost()) {
        gl.deleteFramebuffer(this.framebuffer_);
        gl.deleteTexture(this.texture_);
      }
    } else {
      var map = this.getMap();
      goog.events.listenOnce(
          map,
          ol.MapEventType.POST_RENDER,
          goog.partial(function(gl, framebuffer, texture) {
            if (goog.DEBUG) {
              this.logger.info('freeing WebGL resources on postrender');
            }
            if (!gl.isContextLost()) {
              gl.deleteFramebuffer(framebuffer);
              gl.deleteTexture(texture);
            }
          }, gl, this.framebuffer_, this.texture_));
    }

    var texture = gl.createTexture();
    gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
    gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA,
        framebufferDimension, framebufferDimension, 0, goog.webgl.RGBA,
        goog.webgl.UNSIGNED_BYTE, null);
    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER,
        goog.webgl.LINEAR);
    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER,
        goog.webgl.LINEAR);

    var framebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer);
    gl.framebufferTexture2D(goog.webgl.FRAMEBUFFER,
        goog.webgl.COLOR_ATTACHMENT0, goog.webgl.TEXTURE_2D, texture, 0);

    this.texture_ = texture;
    this.framebuffer_ = framebuffer;
    this.framebufferDimension_ = framebufferDimension;

  } else {
    gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, this.framebuffer_);
  }

};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
  var mapRenderer = this.getMapRenderer();
  var gl = mapRenderer.getGL();
  if (!gl.isContextLost()) {
    gl.deleteBuffer(this.arrayBuffer_);
    gl.deleteFramebuffer(this.framebuffer_);
    gl.deleteTexture(this.texture_);
  }
  goog.base(this, 'disposeInternal');
};

/**
 * @return {ol.TileLayer} Layer.
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.getLayer = function() {
  return /** @type {ol.TileLayer} */ goog.base(this, 'getLayer');
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.getMatrix = function() {
  return this.matrix_;
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.getTexture = function() {
  return this.texture_;
};

/**
 * @protected
 */
ol.renderer.webgl.TileLayer.prototype.handleTileChange = function() {
  this.dispatchChangeEvent();
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
  this.locations_ = null;
  this.arrayBuffer_ = null;
  this.texture_ = null;
  this.framebuffer_ = null;
  this.framebufferDimension_ = undefined;
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.render = function() {

  var animate = false;

  var mapRenderer = this.getMapRenderer();
  var map = this.getMap();
  var gl = mapRenderer.getGL();

  goog.asserts.assert(map.isDef());
  var mapCenter = map.getCenter();
  var mapExtent = map.getExtent();
  var mapResolution = /** @type {number} */ map.getResolution();
  var mapRotatedExtent = map.getRotatedExtent();
  var mapRotation = map.getRotation();

  var tileLayer = this.getLayer();
  var tileStore = tileLayer.getStore();
  var tileGrid = tileStore.getTileGrid();
  var z = tileGrid.getZForResolution(mapResolution);
  var tileResolution = tileGrid.getResolution(z);
  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
      mapRotatedExtent, tileResolution);
  var tileRangeSize = tileRange.getSize();
  var tileSize = tileGrid.getTileSize();

  var maxDimension = Math.max(
      tileRangeSize.width * tileSize.width,
      tileRangeSize.height * tileSize.height);
  var framebufferDimension =
      Math.pow(2, Math.ceil(Math.log(maxDimension) / Math.log(2)));
  var framebufferExtentSize = new ol.Size(
      tileResolution * framebufferDimension,
      tileResolution * framebufferDimension);
  var origin = tileGrid.getOrigin(z);
  var minX = origin.x + tileRange.minX * tileSize.width * tileResolution;
  var minY = origin.y + tileRange.minY * tileSize.height * tileResolution;
  var framebufferExtent = new ol.Extent(
      minX,
      minY,
      minX + framebufferExtentSize.width,
      minY + framebufferExtentSize.height);

  this.bindFramebuffer_(framebufferDimension);
  gl.viewport(0, 0, framebufferDimension, framebufferDimension);

  gl.clearColor(0, 0, 0, 0);
  gl.clear(goog.webgl.COLOR_BUFFER_BIT);
  gl.disable(goog.webgl.BLEND);

  var program = mapRenderer.getProgram(
      this.fragmentShader_, this.vertexShader_);
  gl.useProgram(program);
  if (goog.isNull(this.locations_)) {
    this.locations_ = {
      aPosition: gl.getAttribLocation(program, 'aPosition'),
      aTexCoord: gl.getAttribLocation(program, 'aTexCoord'),
      uTileOffset: gl.getUniformLocation(program, 'uTileOffset'),
      uTexture: gl.getUniformLocation(program, 'uTexture')
    };
  }

  if (goog.isNull(this.arrayBuffer_)) {
    var arrayBuffer = gl.createBuffer();
    gl.bindBuffer(goog.webgl.ARRAY_BUFFER, arrayBuffer);
    gl.bufferData(goog.webgl.ARRAY_BUFFER, new Float32Array([
      0, 0, 0, 1,
      1, 0, 1, 1,
      0, 1, 0, 0,
      1, 1, 1, 0
    ]), goog.webgl.STATIC_DRAW);
    this.arrayBuffer_ = arrayBuffer;
  } else {
    gl.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_);
  }

  gl.enableVertexAttribArray(this.locations_.aPosition);
  gl.vertexAttribPointer(
      this.locations_.aPosition, 2, goog.webgl.FLOAT, false, 16, 0);
  gl.enableVertexAttribArray(this.locations_.aTexCoord);
  gl.vertexAttribPointer(
      this.locations_.aTexCoord, 2, goog.webgl.FLOAT, false, 16, 8);
  gl.uniform1i(this.locations_.uTexture, 0);

  /**
   * @type {Object.<number, Object.<string, ol.Tile>>}
   */
  var tilesToDrawByZ = {};

  /**
   * @type {Array.<ol.Tile>}
   */
  var imagesToLoad = [];

  tilesToDrawByZ[z] = {};
  tileRange.forEachTileCoord(z, function(tileCoord) {

    var tile = tileStore.getTile(tileCoord);

    if (goog.isNull(tile)) {
    } else if (tile.getState() == ol.TileState.LOADED) {
      if (mapRenderer.isImageTextureLoaded(tile.getImage())) {
        tilesToDrawByZ[z][tileCoord.toString()] = tile;
        return;
      } else {
        imagesToLoad.push(tile.getImage());
      }
    } else {
      var tileKey = goog.getUid(tile);
      if (!(tileKey in this.tileChangeListenerKeys_)) {
        tile.load();
        // FIXME will need to handle aborts as well
        this.tileChangeListenerKeys_[tileKey] = goog.events.listen(tile,
            goog.events.EventType.CHANGE, this.handleTileChange, false, this);
      }
    }

    // FIXME this could be more efficient about filling partial holes
    tileGrid.forEachTileCoordParentTileRange(
        tileCoord,
        function(z, tileRange) {
          var fullyCovered = true;
          tileRange.forEachTileCoord(z, function(tileCoord) {
            var tileCoordKey = tileCoord.toString();
            if (tilesToDrawByZ[z] && tilesToDrawByZ[z][tileCoordKey]) {
              return;
            }
            var tile = tileStore.getTile(tileCoord);
            if (!goog.isNull(tile) &&
                tile.getState() == ol.TileState.LOADED) {
              if (!tilesToDrawByZ[z]) {
                tilesToDrawByZ[z] = {};
              }
              tilesToDrawByZ[z][tileCoordKey] = tile;
            } else {
              fullyCovered = false;
            }
          });
          return fullyCovered;
        });

  }, this);

  /** @type {Array.<number>} */
  var zs = goog.object.getKeys(tilesToDrawByZ);
  goog.array.sort(zs);
  var uTileOffset = goog.vec.Vec4.createFloat32();
  goog.array.forEach(zs, function(z) {
    goog.object.forEach(tilesToDrawByZ[z], function(tile) {
      var tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord);
      var sx = 2 * tileExtent.getWidth() / framebufferExtentSize.width;
      var sy = 2 * tileExtent.getHeight() / framebufferExtentSize.height;
      var tx = 2 * (tileExtent.minX - framebufferExtent.minX) /
          framebufferExtentSize.width - 1;
      var ty = 2 * (tileExtent.minY - framebufferExtent.minY) /
          framebufferExtentSize.height - 1;
      goog.vec.Vec4.setFromValues(uTileOffset, sx, sy, tx, ty);
      gl.uniform4fv(this.locations_.uTileOffset, uTileOffset);
      mapRenderer.bindImageTexture(
          tile.getImage(), goog.webgl.LINEAR, goog.webgl.LINEAR);
      gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4);
    }, this);
  }, this);

  goog.vec.Mat4.makeIdentity(this.matrix_);
  goog.vec.Mat4.translate(this.matrix_,
      (mapCenter.x - framebufferExtent.minX) /
          (framebufferExtent.maxX - framebufferExtent.minX),
      (mapCenter.y - framebufferExtent.minY) /
          (framebufferExtent.maxY - framebufferExtent.minY),
      0);
  goog.vec.Mat4.scale(this.matrix_,
      (mapExtent.maxX - mapExtent.minX) /
          (framebufferExtent.maxX - framebufferExtent.minX),
      (mapExtent.maxY - mapExtent.minY) /
          (framebufferExtent.maxY - framebufferExtent.minY),
      1);
  if (goog.isDef(mapRotation)) {
    goog.vec.Mat4.rotate(this.matrix_,
        mapRotation,
        0,
        0,
        1);
  }
  goog.vec.Mat4.translate(this.matrix_,
      -0.5,
      -0.5,
      0);

  if (!goog.array.isEmpty(imagesToLoad)) {
    goog.events.listenOnce(
        map,
        ol.MapEventType.POST_RENDER,
        goog.partial(function(mapRenderer, imagesToLoad) {
          if (goog.DEBUG) {
            this.logger.info('uploading textures');
          }
          goog.array.forEach(imagesToLoad, function(image) {
            mapRenderer.bindImageTexture(
                image, goog.webgl.LINEAR, goog.webgl.LINEAR);
          });
        }, mapRenderer, imagesToLoad));
    animate = true;
  }

  return animate;

};
<commit_msg>Correct type for imagesToLoad.  Add note about shorter path.<commit_after>// FIXME large resolutions lead to too large framebuffers :-(
// FIXME animated shaders! check in redraw

goog.provide('ol.renderer.webgl.TileLayer');
goog.provide('ol.renderer.webgl.tilelayerrenderer');
goog.provide('ol.renderer.webgl.tilelayerrenderer.shader.Fragment');
goog.provide('ol.renderer.webgl.tilelayerrenderer.shader.Vertex');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.debug.Logger');
goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('goog.vec.Mat4');
goog.require('goog.vec.Vec4');
goog.require('goog.webgl');
goog.require('ol.Coordinate');
goog.require('ol.MapEventType');
goog.require('ol.Size');
goog.require('ol.TileLayer');
goog.require('ol.TileState');
goog.require('ol.renderer.webgl.FragmentShader');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.renderer.webgl.VertexShader');

/**
 * @constructor
 * @extends {ol.renderer.webgl.FragmentShader}
 */
ol.renderer.webgl.tilelayerrenderer.shader.Fragment = function() {
  goog.base(this, [
    'precision mediump float;',
    '',
    'uniform sampler2D uTexture;',
    '',
    'varying vec2 vTexCoord;',
    '',
    'void main(void) {',
    ' gl_FragColor = texture2D(uTexture, vTexCoord);',
    '}'
  ].join('\n'));
};
goog.inherits(
    ol.renderer.webgl.tilelayerrenderer.shader.Fragment,
    ol.renderer.webgl.FragmentShader);
goog.addSingletonGetter(ol.renderer.webgl.tilelayerrenderer.shader.Fragment);

/**
 * @constructor
 * @extends {ol.renderer.webgl.VertexShader}
 */
ol.renderer.webgl.tilelayerrenderer.shader.Vertex = function() {
  goog.base(this, [
    'attribute vec2 aPosition;',
    'attribute vec2 aTexCoord;',
    '',
    'varying vec2 vTexCoord;',
    '',
    'uniform vec4 uTileOffset;',
    '',
    'void main(void) {',
    '  gl_Position.xy = aPosition * uTileOffset.xy + uTileOffset.zw;',
    '  gl_Position.z = 0.;',
    '  gl_Position.w = 1.;',
    '  vTexCoord = aTexCoord;',
    '}'
  ].join('\n'));
};
goog.inherits(
    ol.renderer.webgl.tilelayerrenderer.shader.Vertex,
    ol.renderer.webgl.VertexShader);
goog.addSingletonGetter(ol.renderer.webgl.tilelayerrenderer.shader.Vertex);

/**
 * @constructor
 * @extends {ol.renderer.webgl.Layer}
 * @param {ol.renderer.Map} mapRenderer Map renderer.
 * @param {ol.TileLayer} tileLayer Tile layer.
 */
ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {

  goog.base(this, mapRenderer, tileLayer);

  if (goog.DEBUG) {
    /**
     * @inheritDoc
     */
    this.logger = goog.debug.Logger.getLogger(
        'ol.renderer.webgl.tilelayerrenderer.' + goog.getUid(this));
  }

  /**
   * @private
   * @type {ol.renderer.webgl.FragmentShader}
   */
  this.fragmentShader_ =
      ol.renderer.webgl.tilelayerrenderer.shader.Fragment.getInstance();

  /**
   * @private
   * @type {ol.renderer.webgl.VertexShader}
   */
  this.vertexShader_ =
      ol.renderer.webgl.tilelayerrenderer.shader.Vertex.getInstance();

  /**
   * @private
   * @type {{aPosition: number,
   *         aTexCoord: number,
   *         uTileOffset: WebGLUniformLocation,
   *         uTexture: WebGLUniformLocation}|null}
   */
  this.locations_ = null;

  /**
   * @private
   * @type {WebGLBuffer}
   */
  this.arrayBuffer_ = null;

  /**
   * @private
   * @type {WebGLTexture}
   */
  this.texture_ = null;

  /**
   * @private
   * @type {WebGLFramebuffer}
   */
  this.framebuffer_ = null;

  /**
   * @private
   * @type {number|undefined}
   */
  this.framebufferDimension_ = undefined;

  /**
   * @private
   * @type {Object.<number, (number|null)>}
   */
  this.tileChangeListenerKeys_ = {};

  /**
   * @private
   * @type {goog.vec.Mat4.AnyType}
   */
  this.matrix_ = goog.vec.Mat4.createNumber();

};
goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);

/**
 * @param {number} framebufferDimension Framebuffer dimension.
 * @private
 */
ol.renderer.webgl.TileLayer.prototype.bindFramebuffer_ =
    function(framebufferDimension) {

  var mapRenderer = this.getMapRenderer();
  var gl = mapRenderer.getGL();

  if (!goog.isDef(this.framebufferDimension_) ||
      this.framebufferDimension_ != framebufferDimension) {

    if (goog.DEBUG) {
      this.logger.info('re-sizing framebuffer');
    }

    if (ol.renderer.webgl.FREE_RESOURCES_IMMEDIATELY) {
      if (goog.DEBUG) {
        this.logger.info('freeing WebGL resources');
      }
      if (!gl.isContextLost()) {
        gl.deleteFramebuffer(this.framebuffer_);
        gl.deleteTexture(this.texture_);
      }
    } else {
      var map = this.getMap();
      goog.events.listenOnce(
          map,
          ol.MapEventType.POST_RENDER,
          goog.partial(function(gl, framebuffer, texture) {
            if (goog.DEBUG) {
              this.logger.info('freeing WebGL resources on postrender');
            }
            if (!gl.isContextLost()) {
              gl.deleteFramebuffer(framebuffer);
              gl.deleteTexture(texture);
            }
          }, gl, this.framebuffer_, this.texture_));
    }

    var texture = gl.createTexture();
    gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
    gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA,
        framebufferDimension, framebufferDimension, 0, goog.webgl.RGBA,
        goog.webgl.UNSIGNED_BYTE, null);
    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER,
        goog.webgl.LINEAR);
    gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER,
        goog.webgl.LINEAR);

    var framebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer);
    gl.framebufferTexture2D(goog.webgl.FRAMEBUFFER,
        goog.webgl.COLOR_ATTACHMENT0, goog.webgl.TEXTURE_2D, texture, 0);

    this.texture_ = texture;
    this.framebuffer_ = framebuffer;
    this.framebufferDimension_ = framebufferDimension;

  } else {
    gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, this.framebuffer_);
  }

};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
  var mapRenderer = this.getMapRenderer();
  var gl = mapRenderer.getGL();
  if (!gl.isContextLost()) {
    gl.deleteBuffer(this.arrayBuffer_);
    gl.deleteFramebuffer(this.framebuffer_);
    gl.deleteTexture(this.texture_);
  }
  goog.base(this, 'disposeInternal');
};

/**
 * @return {ol.TileLayer} Layer.
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.getLayer = function() {
  return /** @type {ol.TileLayer} */ goog.base(this, 'getLayer');
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.getMatrix = function() {
  return this.matrix_;
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.getTexture = function() {
  return this.texture_;
};

/**
 * @protected
 */
ol.renderer.webgl.TileLayer.prototype.handleTileChange = function() {
  this.dispatchChangeEvent();
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
  this.locations_ = null;
  this.arrayBuffer_ = null;
  this.texture_ = null;
  this.framebuffer_ = null;
  this.framebufferDimension_ = undefined;
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.render = function() {

  var animate = false;

  var mapRenderer = this.getMapRenderer();
  var map = this.getMap();
  var gl = mapRenderer.getGL();

  goog.asserts.assert(map.isDef());
  var mapCenter = map.getCenter();
  var mapExtent = map.getExtent();
  var mapResolution = /** @type {number} */ map.getResolution();
  var mapRotatedExtent = map.getRotatedExtent();
  var mapRotation = map.getRotation();

  var tileLayer = this.getLayer();
  var tileStore = tileLayer.getStore();
  var tileGrid = tileStore.getTileGrid();
  var z = tileGrid.getZForResolution(mapResolution);
  var tileResolution = tileGrid.getResolution(z);
  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
      mapRotatedExtent, tileResolution);
  var tileRangeSize = tileRange.getSize();
  var tileSize = tileGrid.getTileSize();

  var maxDimension = Math.max(
      tileRangeSize.width * tileSize.width,
      tileRangeSize.height * tileSize.height);
  var framebufferDimension =
      Math.pow(2, Math.ceil(Math.log(maxDimension) / Math.log(2)));
  var framebufferExtentSize = new ol.Size(
      tileResolution * framebufferDimension,
      tileResolution * framebufferDimension);
  var origin = tileGrid.getOrigin(z);
  var minX = origin.x + tileRange.minX * tileSize.width * tileResolution;
  var minY = origin.y + tileRange.minY * tileSize.height * tileResolution;
  var framebufferExtent = new ol.Extent(
      minX,
      minY,
      minX + framebufferExtentSize.width,
      minY + framebufferExtentSize.height);

  this.bindFramebuffer_(framebufferDimension);
  gl.viewport(0, 0, framebufferDimension, framebufferDimension);

  gl.clearColor(0, 0, 0, 0);
  gl.clear(goog.webgl.COLOR_BUFFER_BIT);
  gl.disable(goog.webgl.BLEND);

  var program = mapRenderer.getProgram(
      this.fragmentShader_, this.vertexShader_);
  gl.useProgram(program);
  if (goog.isNull(this.locations_)) {
    this.locations_ = {
      aPosition: gl.getAttribLocation(program, 'aPosition'),
      aTexCoord: gl.getAttribLocation(program, 'aTexCoord'),
      uTileOffset: gl.getUniformLocation(program, 'uTileOffset'),
      uTexture: gl.getUniformLocation(program, 'uTexture')
    };
  }

  if (goog.isNull(this.arrayBuffer_)) {
    var arrayBuffer = gl.createBuffer();
    gl.bindBuffer(goog.webgl.ARRAY_BUFFER, arrayBuffer);
    gl.bufferData(goog.webgl.ARRAY_BUFFER, new Float32Array([
      0, 0, 0, 1,
      1, 0, 1, 1,
      0, 1, 0, 0,
      1, 1, 1, 0
    ]), goog.webgl.STATIC_DRAW);
    this.arrayBuffer_ = arrayBuffer;
  } else {
    gl.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_);
  }

  gl.enableVertexAttribArray(this.locations_.aPosition);
  gl.vertexAttribPointer(
      this.locations_.aPosition, 2, goog.webgl.FLOAT, false, 16, 0);
  gl.enableVertexAttribArray(this.locations_.aTexCoord);
  gl.vertexAttribPointer(
      this.locations_.aTexCoord, 2, goog.webgl.FLOAT, false, 16, 8);
  gl.uniform1i(this.locations_.uTexture, 0);

  /**
   * @type {Object.<number, Object.<string, ol.Tile>>}
   */
  var tilesToDrawByZ = {};

  /**
   * @type {Array.<Image>}
   */
  var imagesToLoad = [];

  tilesToDrawByZ[z] = {};
  tileRange.forEachTileCoord(z, function(tileCoord) {

    var tile = tileStore.getTile(tileCoord);

    if (goog.isNull(tile)) {
      // FIXME - consider returning here as this is outside the store's extent
    } else if (tile.getState() == ol.TileState.LOADED) {
      if (mapRenderer.isImageTextureLoaded(tile.getImage())) {
        tilesToDrawByZ[z][tileCoord.toString()] = tile;
        return;
      } else {
        imagesToLoad.push(tile.getImage());
      }
    } else {
      var tileKey = goog.getUid(tile);
      if (!(tileKey in this.tileChangeListenerKeys_)) {
        tile.load();
        // FIXME will need to handle aborts as well
        this.tileChangeListenerKeys_[tileKey] = goog.events.listen(tile,
            goog.events.EventType.CHANGE, this.handleTileChange, false, this);
      }
    }

    // FIXME this could be more efficient about filling partial holes
    tileGrid.forEachTileCoordParentTileRange(
        tileCoord,
        function(z, tileRange) {
          var fullyCovered = true;
          tileRange.forEachTileCoord(z, function(tileCoord) {
            var tileCoordKey = tileCoord.toString();
            if (tilesToDrawByZ[z] && tilesToDrawByZ[z][tileCoordKey]) {
              return;
            }
            var tile = tileStore.getTile(tileCoord);
            if (!goog.isNull(tile) &&
                tile.getState() == ol.TileState.LOADED) {
              if (!tilesToDrawByZ[z]) {
                tilesToDrawByZ[z] = {};
              }
              tilesToDrawByZ[z][tileCoordKey] = tile;
            } else {
              fullyCovered = false;
            }
          });
          return fullyCovered;
        });

  }, this);

  /** @type {Array.<number>} */
  var zs = goog.object.getKeys(tilesToDrawByZ);
  goog.array.sort(zs);
  var uTileOffset = goog.vec.Vec4.createFloat32();
  goog.array.forEach(zs, function(z) {
    goog.object.forEach(tilesToDrawByZ[z], function(tile) {
      var tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord);
      var sx = 2 * tileExtent.getWidth() / framebufferExtentSize.width;
      var sy = 2 * tileExtent.getHeight() / framebufferExtentSize.height;
      var tx = 2 * (tileExtent.minX - framebufferExtent.minX) /
          framebufferExtentSize.width - 1;
      var ty = 2 * (tileExtent.minY - framebufferExtent.minY) /
          framebufferExtentSize.height - 1;
      goog.vec.Vec4.setFromValues(uTileOffset, sx, sy, tx, ty);
      gl.uniform4fv(this.locations_.uTileOffset, uTileOffset);
      mapRenderer.bindImageTexture(
          tile.getImage(), goog.webgl.LINEAR, goog.webgl.LINEAR);
      gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4);
    }, this);
  }, this);

  goog.vec.Mat4.makeIdentity(this.matrix_);
  goog.vec.Mat4.translate(this.matrix_,
      (mapCenter.x - framebufferExtent.minX) /
          (framebufferExtent.maxX - framebufferExtent.minX),
      (mapCenter.y - framebufferExtent.minY) /
          (framebufferExtent.maxY - framebufferExtent.minY),
      0);
  goog.vec.Mat4.scale(this.matrix_,
      (mapExtent.maxX - mapExtent.minX) /
          (framebufferExtent.maxX - framebufferExtent.minX),
      (mapExtent.maxY - mapExtent.minY) /
          (framebufferExtent.maxY - framebufferExtent.minY),
      1);
  if (goog.isDef(mapRotation)) {
    goog.vec.Mat4.rotate(this.matrix_,
        mapRotation,
        0,
        0,
        1);
  }
  goog.vec.Mat4.translate(this.matrix_,
      -0.5,
      -0.5,
      0);

  if (!goog.array.isEmpty(imagesToLoad)) {
    goog.events.listenOnce(
        map,
        ol.MapEventType.POST_RENDER,
        goog.partial(function(mapRenderer, imagesToLoad) {
          if (goog.DEBUG) {
            this.logger.info('uploading textures');
          }
          goog.array.forEach(imagesToLoad, function(image) {
            mapRenderer.bindImageTexture(
                image, goog.webgl.LINEAR, goog.webgl.LINEAR);
          });
        }, mapRenderer, imagesToLoad));
    animate = true;
  }

  return animate;

};
<|endoftext|>


<commit_before>var restify = require('restify');
var bunyan = require('bunyan');
var util = require("util");

var config = require('./param.json');

var logger = bunyan.createLogger({
    name: "mantisbt-sync-jira"
});

var server = restify.createServer({
    name: "mantisbt-sync-jira",
    log: logger
});

var mantisClient = restify.createJsonClient({
    url: config.source.url,
    version: '*',
    log: logger
});

if (config.source.username) {
    mantisClient.basicAuth(config.source.username, config.source.password);
}

var jiraClient = restify.createJsonClient({
    url: config.target.url,
    version: '*',
    log: logger
});

if (config.target.username) {
    jiraClient.basicAuth(config.target.username, config.target.password);
}

function createSubTasks(parentKey) {

    if (config.subTasks && parentKey) {
		logger.info("Creating subtask for Jira issue %s", parentKey);

		for (var i = 0; i < config.subTasks.length; i++) {
			var subtask = config.subTasks[i];
            var jiraSubtask = {
                "fields": {
                    "project": {
                        "key": config.target.project.key
                    },
                    "issuetype": {
                        "id": subtask.issueType.id
                    },
                    parent: {
                        "key": parentKey
                    },
                    "summary": subtask.summary,
                    "description": subtask.description,
                    "reporter": config.source.username
                }
            };

			logger.info("Creating subtask for Jira issue %s - %s", parentKey, subtask.summary);

            jiraClient.post('/jira2/rest/api/2/issue', jiraSubtask, function (err, req, res, obj) {
                if (err) {
                    logger.error(err);
                }
            });
        }
    }

	logger.info("End creating subtask for Jira issue %s", parentKey);
}

function pushToJira(issue) {

    var jql = util.format('"cf[10017]" ~ "%s"', issue.id);
    var query = {
        "jql": jql,
        "startAt": 0,
        "maxResults": 1,
        "fields": [
            "key"
        ]
    };

    jiraClient.post('/jira2/rest/api/2/search', query, function (err, req, res, obj) {
        if (err) {
            logger.error(err);
        } else {
            if (!obj.issues || !obj.issues.length) {

                var jiraIssue = {
                    "fields": {
                        "project": {
                            "key": config.target.project.key
                        },
                        "issuetype": {
                            "id": config.convert.issueType.id
                        },
                        "summary": issue.summary,
                        "description": issue.description,
                        "reporter": config.source.username,
						"customfield_10017": JSON.stringify(issue.id),
						"customfield_10013": "A renseigner"
                    }
                };

				logger.info("Creating Jira issue for Mantis %d", issue.id);

                jiraClient.post('/jira2/rest/api/2/issue', jiraIssue, function (err, req, res, obj) {
                    if (err) {
                        logger.error(err);
                    } else if (obj.key) {
						logger.info("Jira issue created : %s", obj.key);
                        createSubTasks(obj.key);
                    }
                });
            }
        }
    });
}

// Function to fetch all active issues in Mantis
function getSourceIssues() {

    var uri = util.format('/bugs/search/findByProjectIdAndStatusIdNotIn?project=%s&status=%d&projection=%s',
        config.source.project.id, 90, "bugDetails");

    mantisClient.get(uri, function (err, req, res, obj) {
        if (err) {
            logger.error(err);
        } else {
            var issues = obj._embedded.bugs;
            for (var issue in issues) {
                pushToJira(issue);
            }
        }
    });
}

// Function to fetch an issue in Mantis
function getSourceIssue(issueId) {

    var uri = util.format('/bugs/%d?projection=%s', issueId, "bugDetails");

    mantisClient.get(uri, function (err, req, res, obj) {
        if (err) {
            logger.error(err);
        } else if (obj) {
			   logger.info("Pushing issue %d to Jira", issueId);
               pushToJira(obj);
        }
    });
}

server.get('/launch/:projectId', function create(req, res, next) {
    logger.info("Staring sync for project %s", req.params.projectId);
    getSourceIssues();
    res.send(200);
    return next();
});

server.get('/launch/issue/:issueId', function create(req, res, next) {
    logger.info("Staring sync for issue %s", req.params.issueId);
    getSourceIssue(req.params.issueId);
    res.send(200);
    return next();
});

server.listen(8080, function () {
    console.log('%s listening at %s', server.name, server.url);
});<commit_msg>Fixes wrong array parsing when creating issues in batch mode<commit_after>var restify = require('restify');
var bunyan = require('bunyan');
var util = require("util");

var config = require('./param.json');

var logger = bunyan.createLogger({
    name: "mantisbt-sync-jira"
});

var server = restify.createServer({
    name: "mantisbt-sync-jira",
    log: logger
});

var mantisClient = restify.createJsonClient({
    url: config.source.url,
    version: '*',
    log: logger
});

if (config.source.username) {
    mantisClient.basicAuth(config.source.username, config.source.password);
}

var jiraClient = restify.createJsonClient({
    url: config.target.url,
    version: '*',
    log: logger
});

if (config.target.username) {
    jiraClient.basicAuth(config.target.username, config.target.password);
}

function createSubTasks(parentKey) {

    if (config.subTasks && parentKey) {
		logger.info("Creating subtask for Jira issue %s", parentKey);

		for (var i = 0; i < config.subTasks.length; i++) {
			var subtask = config.subTasks[i];
            var jiraSubtask = {
                "fields": {
                    "project": {
                        "key": config.target.project.key
                    },
                    "issuetype": {
                        "id": subtask.issueType.id
                    },
                    parent: {
                        "key": parentKey
                    },
                    "summary": subtask.summary,
                    "description": subtask.description,
                    "reporter": config.source.username
                }
            };

			logger.info("Creating subtask for Jira issue %s - %s", parentKey, subtask.summary);

            jiraClient.post('/jira2/rest/api/2/issue', jiraSubtask, function (err, req, res, obj) {
                if (err) {
                    logger.error(err);
                }
            });
        }
    }

	logger.info("End creating subtask for Jira issue %s", parentKey);
}

function pushToJira(issue) {

    var jql = util.format('"cf[10017]" ~ "%s"', issue.id);
    var query = {
        "jql": jql,
        "startAt": 0,
        "maxResults": 1,
        "fields": [
            "key"
        ]
    };

    jiraClient.post('/jira2/rest/api/2/search', query, function (err, req, res, obj) {
        if (err) {
            logger.error(err);
        } else {
            if (!obj.issues || !obj.issues.length) {

                var jiraIssue = {
                    "fields": {
                        "project": {
                            "key": config.target.project.key
                        },
                        "issuetype": {
                            "id": config.convert.issueType.id
                        },
                        "summary": issue.summary,
                        "description": issue.description,
                        "reporter": config.source.username,
						"customfield_10017": JSON.stringify(issue.id),
						"customfield_10013": "A renseigner"
                    }
                };

				logger.info("Creating Jira issue for Mantis %d", issue.id);

                jiraClient.post('/jira2/rest/api/2/issue', jiraIssue, function (err, req, res, obj) {
                    if (err) {
                        logger.error(err);
                    } else if (obj.key) {
						logger.info("Jira issue created : %s", obj.key);
                        createSubTasks(obj.key);
                    }
                });
            }
        }
    });
}

// Function to fetch all active issues in Mantis
function getSourceIssues() {

    var uri = util.format('/bugs/search/findByProjectIdAndStatusIdNotIn?project=%s&status=%d&projection=%s',
        config.source.project.id, 90, "bugDetails");

    mantisClient.get(uri, function (err, req, res, obj) {
        if (err) {
            logger.error(err);
        } else {
            var issues = obj._embedded.bugs;
			for (var i = 0; i < issues.length; i++) {
				var task = issues[i];
				//console.log("Create issue : " + JSON.stringify(task));
                pushToJira(task);
            }
        }
    });
}

// Function to fetch an issue in Mantis
function getSourceIssue(issueId) {

    var uri = util.format('/bugs/%d?projection=%s', issueId, "bugDetails");

    mantisClient.get(uri, function (err, req, res, obj) {
        if (err) {
            logger.error(err);
        } else if (obj) {
			   logger.info("Pushing issue %d to Jira", issueId);
			   //console.log("Create issue : " + JSON.stringify(obj));
               pushToJira(obj);
        }
    });
}

server.get('/launch/:projectId', function create(req, res, next) {
    logger.info("Staring sync for project %s", req.params.projectId);
    getSourceIssues();
    res.send(200);
    return next();
});

server.get('/launch/issue/:issueId', function create(req, res, next) {
    logger.info("Staring sync for issue %s", req.params.issueId);
    getSourceIssue(req.params.issueId);
    res.send(200);
    return next();
});

server.listen(8080, function () {
    console.log('%s listening at %s', server.name, server.url);
});<|endoftext|>


<commit_before>var h = 360,
		s = 0,
		b = 100,
		a = 100;

var h2 = 12,
		s2 = 40,
		b2 = 23,
		a2 = 100;

var h3 = 0,
		s3 = 0,
		b3 = 15,
		a3 = 100,
		a3Wipe = 40;

// vars for line
var eX, eY, // position
		eX2, eY2,
		eW = 2; // width

// for bounces on wall
var bounces = 0,
		bouncesMax,
		bouncesTotal = 0; // current total amount of bounces

// for conversion from polar to cartesian coordinates.
// basically getting x and y from an angle and a radius.
// angle: 0 to 360 ... duh
var angle,
		radius ; // sets the speed

var quadrant;

var wall;	// which wall? t,r,b,l?

// for Dot
var dot = [];

// for saving position of corner of triangle
var tWallPosX = [],
		tWallPosY = [],
		triangle = [];

// for filling whole document
var w = window,
		wX = w.innerWidth,
		wY = w.innerHeight;

/*
* only executed once
*/
function setup(){
	createCanvas(wX, wY);
	// createCanvas(1600/2, 900/2);

	// change color mode to hsl
	colorMode(HSB, 360, 100, 100, 100);

	// start line at...
	eX = random(0,width);
	eY = 0;
	angle = radians(random(1,179));
	radius = 10; //speed of line drawing
	bouncesMax = 4;

	// rectangle/background
	background(h3, s3, b3);
}

/*
* this will be executed all the time
*/
function draw(){
	// translate from angle: polar to cartesian coordianates
	eX += cartesianX(radius,angle);
	eY += cartesianY(radius,angle);

	// DRAW LINE
	stroke(h,s,b,a);
	strokeWeight(eW);
	line(eX,eY, eX2,eY2);

	// quadrants:
	// 1: 0° - 90°
	// 2: 90° - 180°
	// 3: 180° - 270°
	// 4: 270° - 360°
	if (angle >= radians(0) && angle < radians(90)) {
		quadrant = 1;
	}
	else if (angle >= radians(90) && angle < radians(180)) {
		quadrant = 2;
	}
	else if (angle >= radians(180) && angle < radians(270)) {
		quadrant = 3;
	}
	else if (angle >= radians(270) && angle < radians(360)) {
		quadrant = 4;
	}

	// wall sides
	if (eY < 0) {
		wall = 1;
	}
	else if (eX > width) {
		wall = 2;
	}
	else if (eY > height) {
		wall = 3;
	}
	else if (eX < 0) {
		wall = 4;
	}

	// if bounce from specific wall from specific angle
	// 0° - 90°
	if (quadrant === 1 && wall === 2) { // right
		angle += radians(90);
		bounces++;
		eX = width;
	}
	else if (quadrant === 1 && wall === 3) { // bottom
		angle -= radians(90);
		bounces++;
		eY = height;
	}
	// 90° - 180°
	else if (quadrant === 2 && wall === 3) { // bottom
		angle += radians(90);
		bounces++;
		eY = height;
	}
	else if (quadrant === 2 && wall === 4) { // left
		angle -= radians(90);
		bounces++;
		eX = 0;
	}
	// 180° - 270°
	else if (quadrant === 3 && wall === 4) { // left
		angle += radians(90);
		bounces++;
		eX = 0;
	}
	else if (quadrant === 3 && wall === 1) { // top
		angle -= radians(90);
		bounces++;
		eY = 0;
	}
	// 270° - 360°
	else if (quadrant === 4 && wall === 1) { // top
		angle += radians(90);
		bounces++;
		eY = 0;
	}
	else if (quadrant === 4 && wall === 2) { // right
		angle -= radians(90);
		bounces++;
		eX = width;
	}

	// stay in 360°
	if (angle >= radians(360)) {
		angle -= radians(360);
	}
	else if (angle < radians(0)) {
		angle += radians(360);
	}

	if (frameCount === 1) {
		dot[bouncesTotal] = new Dot();
		dot[bouncesTotal].show();
	}
	// if specific amount of bouncing, reset position
	if (bounces > bouncesMax) {
		noStroke();
		// eX = random(0,width);
		// eY = 0;
		angle = radians(random(0,180));
		bounces = 0; // reset bounces counter
		frameCount = 1;
	}

	// if at wall-bounce, add thing
	if (eX === width || eY === height || eX === 0 || eY === 0) {

		// save position at wall
		tWallPosX[bouncesTotal] = eX;
		tWallPosY[bouncesTotal] = eY;

		// create new Dot at wall
		dot[bouncesTotal] = new Dot();
		dot[bouncesTotal].show();
		triangle[bouncesTotal] = new Triangle();

		// draw triangle, start at third bounce
		if (bouncesTotal >= 2) {
			triangle[bouncesTotal].show();
		}

		// add count
		bouncesTotal++;
	}

	// fadeout effect
	background(h3, s3, b3, 1);

	// get last coordinate for drawing a line
	eX2 = eX;
	eY2 = eY;

	console.log(bouncesTotal);
}

/*
* custom functions
*/
// create cartesian coordinates
function cartesianX(radius,angle) {
	var x = radius*cos(angle);

	return x;
}

function cartesianY(radius,angle) {
	var y = radius*sin(angle);

	return y;
}

/*
* Classes
*/
// Dot constructor
var Dot = function() {
	this.x = eX;
	this.y = eY;
	// this.width = eW;
	// this.color = color(h,s,b,a);
};

// show method
Dot.prototype.show = function() {
	noStroke();
	// fill(this.color);
	// ellipse(this.x,this.y, this.width*5,this.width*5);
};

// Triangle constructor
var Triangle = function() {
	this.x1 = tWallPosX[bouncesTotal];
	this.y1 = tWallPosY[bouncesTotal];
	this.x2 = tWallPosX[bouncesTotal-2];
	this.y2 = tWallPosY[bouncesTotal-2];

	// this.colorFill = color(h2,s2,b2,10);
	this.colorStroke = color(h,s,b,a);
};

Triangle.prototype.show = function() {
	stroke(this.colorStroke);
	// noFill();
	// noStroke();
	// fill(this.colorFill);

	// triangle(
	// 	this.x1,this.y1,
	// 	this.x2,this.y2,
	// 	this.x3,this.y3
	// );
line(
		this.x1,this.y1,
		this.x2,this.y2
	);
};<commit_msg>quicker drawing<commit_after>var h = 360,
		s = 0,
		b = 100,
		a = 100;

var h2 = 12,
		s2 = 40,
		b2 = 23,
		a2 = 100;

var h3 = 0,
		s3 = 0,
		b3 = 15,
		a3 = 100,
		a3Wipe = 40;

// vars for line
var eX, eY, // position
		eX2, eY2,
		eW = 2; // width

// for bounces on wall
var bounces = 0,
		bouncesMax,
		bouncesTotal = 0; // current total amount of bounces

// for conversion from polar to cartesian coordinates.
// basically getting x and y from an angle and a radius.
// angle: 0 to 360 ... duh
var angle,
		radius ; // sets the speed

var quadrant;

var wall;	// which wall? t,r,b,l?

// for Dot
var dot = [];

// for saving position of corner of triangle
var tWallPosX = [],
		tWallPosY = [],
		triangle = [];

// for filling whole document
var w = window,
		wX = w.innerWidth,
		wY = w.innerHeight;

/*
* only executed once
*/
function setup(){
	createCanvas(wX, wY);
	// createCanvas(1600/2, 900/2);

	// change color mode to hsl
	colorMode(HSB, 360, 100, 100, 100);

	// start line at...
	eX = random(0,width);
	eY = 0;
	angle = radians(random(1,179));
	radius = 20; //speed of line drawing
	bouncesMax = 4;

	// rectangle/background
	background(h3, s3, b3);
}

/*
* this will be executed all the time
*/
function draw(){
	// translate from angle: polar to cartesian coordianates
	eX += cartesianX(radius,angle);
	eY += cartesianY(radius,angle);

	// DRAW LINE
	stroke(h,s,b,a);
	strokeWeight(eW);
	line(eX,eY, eX2,eY2);

	// quadrants:
	// 1: 0° - 90°
	// 2: 90° - 180°
	// 3: 180° - 270°
	// 4: 270° - 360°
	if (angle >= radians(0) && angle < radians(90)) {
		quadrant = 1;
	}
	else if (angle >= radians(90) && angle < radians(180)) {
		quadrant = 2;
	}
	else if (angle >= radians(180) && angle < radians(270)) {
		quadrant = 3;
	}
	else if (angle >= radians(270) && angle < radians(360)) {
		quadrant = 4;
	}

	// wall sides
	if (eY < 0) {
		wall = 1;
	}
	else if (eX > width) {
		wall = 2;
	}
	else if (eY > height) {
		wall = 3;
	}
	else if (eX < 0) {
		wall = 4;
	}

	// if bounce from specific wall from specific angle
	// 0° - 90°
	if (quadrant === 1 && wall === 2) { // right
		angle += radians(90);
		bounces++;
		eX = width;
	}
	else if (quadrant === 1 && wall === 3) { // bottom
		angle -= radians(90);
		bounces++;
		eY = height;
	}
	// 90° - 180°
	else if (quadrant === 2 && wall === 3) { // bottom
		angle += radians(90);
		bounces++;
		eY = height;
	}
	else if (quadrant === 2 && wall === 4) { // left
		angle -= radians(90);
		bounces++;
		eX = 0;
	}
	// 180° - 270°
	else if (quadrant === 3 && wall === 4) { // left
		angle += radians(90);
		bounces++;
		eX = 0;
	}
	else if (quadrant === 3 && wall === 1) { // top
		angle -= radians(90);
		bounces++;
		eY = 0;
	}
	// 270° - 360°
	else if (quadrant === 4 && wall === 1) { // top
		angle += radians(90);
		bounces++;
		eY = 0;
	}
	else if (quadrant === 4 && wall === 2) { // right
		angle -= radians(90);
		bounces++;
		eX = width;
	}

	// stay in 360°
	if (angle >= radians(360)) {
		angle -= radians(360);
	}
	else if (angle < radians(0)) {
		angle += radians(360);
	}

	if (frameCount === 1) {
		dot[bouncesTotal] = new Dot();
		dot[bouncesTotal].show();
	}
	// if specific amount of bouncing, reset position
	if (bounces > bouncesMax) {
		noStroke();
		// eX = random(0,width);
		// eY = 0;
		angle = radians(random(0,180));
		bounces = 0; // reset bounces counter
		frameCount = 1;
	}

	// if at wall-bounce, add thing
	if (eX === width || eY === height || eX === 0 || eY === 0) {

		// save position at wall
		tWallPosX[bouncesTotal] = eX;
		tWallPosY[bouncesTotal] = eY;

		// create new Dot at wall
		dot[bouncesTotal] = new Dot();
		dot[bouncesTotal].show();
		triangle[bouncesTotal] = new Triangle();

		// draw triangle, start at third bounce
		if (bouncesTotal >= 2) {
			triangle[bouncesTotal].show();
		}

		// add count
		bouncesTotal++;
	}

	// fadeout effect
	background(h3, s3, b3, 1);

	// get last coordinate for drawing a line
	eX2 = eX;
	eY2 = eY;

	console.log(bouncesTotal);
}

/*
* custom functions
*/
// create cartesian coordinates
function cartesianX(radius,angle) {
	var x = radius*cos(angle);

	return x;
}

function cartesianY(radius,angle) {
	var y = radius*sin(angle);

	return y;
}

/*
* Classes
*/
// Dot constructor
var Dot = function() {
	this.x = eX;
	this.y = eY;
	// this.width = eW;
	// this.color = color(h,s,b,a);
};

// show method
Dot.prototype.show = function() {
	noStroke();
	// fill(this.color);
	// ellipse(this.x,this.y, this.width*5,this.width*5);
};

// Triangle constructor
var Triangle = function() {
	this.x1 = tWallPosX[bouncesTotal];
	this.y1 = tWallPosY[bouncesTotal];
	this.x2 = tWallPosX[bouncesTotal-2];
	this.y2 = tWallPosY[bouncesTotal-2];

	// this.colorFill = color(h2,s2,b2,10);
	this.colorStroke = color(h,s,b,a);
};

Triangle.prototype.show = function() {
	stroke(this.colorStroke);
	// noFill();
	// noStroke();
	// fill(this.colorFill);

	// triangle(
	// 	this.x1,this.y1,
	// 	this.x2,this.y2,
	// 	this.x3,this.y3
	// );
line(
		this.x1,this.y1,
		this.x2,this.y2
	);
};<|endoftext|>


<commit_before>//
// Define new statistics that Monitor can track by adding to this file. Each class should have:
//
// - stats, a member which implements the standard interface found in stats.js
// - start(context, args), optional, called when execution of the instrumented code is about to start
// - end(context, result), optional, called when the instrumented code finishes executing
//
// Defining .disableIntervalCollection and .disableCumulativeCollection to the collection of per-interval
// and overall statistics respectively.
//

/*jslint sub:true */
var BUILD_AS_SINGLE_FILE;
if (!BUILD_AS_SINGLE_FILE) {
var stats = require('../stats');
var Histogram = stats.Histogram;
var Peak = stats.Peak;
var ResultsCounter = stats.ResultsCounter;
var Uniques = stats.Uniques;
var Accumulator = stats.Accumulator;
var LogFile = stats.LogFile;
var StatsCollectors = exports;
} else {
var StatsCollectors = {};
}

/** Track the runtime of an operation, storing stats in a stats.js#Histogram  */
StatsCollectors['runtime'] = StatsCollectors['latency'] = function RuntimeCollector(params) {
    var self = this;
    self.stats = new Histogram(params);
    self.start = function(context) { context.start = new Date(); };
    self.end = function(context) { self.stats.put(new Date() - context.start); };
};

/** Track HTTP response codes, storing stats in a stats.js#ResultsCounter object. The client must call
.end({res: http.ClientResponse}). */
StatsCollectors['result-codes'] = function ResultCodesCollector() {
    var self = this;
    self.stats = new ResultsCounter();
    self.end = function(context, http) { self.stats.put(http.res.statusCode); };
};

/** Track the concurrent executions (ie. stuff between calls to .start() and .end()), storing in a
stats.js#Peak. */
StatsCollectors['concurrency'] = function ConcurrencyCollector() {
    var self = this, c = 0;
    self.stats = new Peak();
    self.start = function() { c++; };
    self.end = function() { self.stats.put(c--); };
};

/** Track the size of HTTP request bodies sent by adding up the content-length headers. This function
doesn't really work as you'd hope right now, since it doesn't work for chunked encoding messages and
doesn't return actual bytes over the wire (headers, etc). */
StatsCollectors['request-bytes'] = function RequestBytesCollector() {
    var self = this;
    self.stats = new Accumulator();
    self.end = function(context, http) {
        if (http && http.req) {
            if (http.req._header) { self.stats.put(http.req._header.length); }
            if (http.req.body) { self.stats.put(http.req.body.length); }
        }
    };
};

/** Track the size of HTTP response bodies. It doesn't account for headers! */
StatsCollectors['response-bytes'] = function ResponseBytesCollector() {
    var self = this;
    self.stats = new Accumulator();
    self.end = function(context, http) {
        if (http && http.res) {
            http.res.on('data', function(chunk) {
                self.stats.put(chunk.length);
            });
        }
    };
};

/** Track unique URLs requested, storing stats in a stats.js#Uniques object. The client must call
Monitor.start({req: http.ClientRequest}). */
StatsCollectors['uniques'] = function UniquesCollector() {
    var self = this;
    self.stats = new Uniques();
    self.end = function(context, http) {
        if (http && http.req) { self.stats.put(http.req.path); }
    };
};
StatsCollectors['uniques'].disableIntervalCollection = true; // Per-interval stats should be not be collected

/** Track number HTTP response codes that are considered errors. Can also log request / response
information to disk when an error response is received. Specify the acceptable HTTP status codes in
params.successCodes. Specify the log file name in params.log, or leave undefined to disable logging. */
StatsCollectors['http-errors'] = function HttpErrorsCollector(params) {
    var self = this;
    self.stats = new Accumulator();
    self.successCodes = params.successCodes || [200];
    self.logfile = (typeof params.log === 'string') ? new LogFile(params.log) : params.log;
    self.end = function(context, http) {
        if (self.successCodes.indexOf(http.res.statusCode) < 0) {
            self.stats.put(1);

            if (self.logfile) {
                var body = '';
                http.res.on('data', function(chunk) { body += chunk; });
                http.res.on('end', function(chunk) {
                    self.logfile.put(JSON.stringify({
                        ts: new Date(),
                        req: {
                            // Use the _header "private" member of http.ClientRequest, available as of
                            // node v0.2.2 (9/30/10). This is the only way to reliably get all request
                            // headers, since ClientRequest adds headers beyond what the user specifies
                            // in certain conditions, like Connection and Transfer-Encoding.
                            headers: http.req._header,
                            body: http.req.body,
                        },
                        res: {
                            statusCode: http.res.statusCode,
                            headers: http.res.headers,
                            body: body
                        }
                    }) + '\n');
                });
            }
        }
    };
};
StatsCollectors['http-errors'].disableIntervalCollection = true; // Per-interval stats should be not be collected
<commit_msg>Adding slow-responses collector, adding logResBody option to http-errors<commit_after>//
// Define new statistics that Monitor can track by adding to this file. Each class should have:
//
// - stats, a member which implements the standard interface found in stats.js
// - start(context, args), optional, called when execution of the instrumented code is about to start
// - end(context, result), optional, called when the instrumented code finishes executing
//
// Defining .disableIntervalCollection and .disableCumulativeCollection to the collection of per-interval
// and overall statistics respectively.
//

/*jslint sub:true */
var BUILD_AS_SINGLE_FILE;
if (!BUILD_AS_SINGLE_FILE) {
var stats = require('../stats');
var Histogram = stats.Histogram;
var Peak = stats.Peak;
var ResultsCounter = stats.ResultsCounter;
var Uniques = stats.Uniques;
var Accumulator = stats.Accumulator;
var LogFile = stats.LogFile;
var StatsCollectors = exports;
} else {
var StatsCollectors = {};
}

/** Track the runtime of an operation, storing stats in a stats.js#Histogram  */
StatsCollectors['runtime'] = StatsCollectors['latency'] = function RuntimeCollector(params) {
    var self = this;
    self.stats = new Histogram(params);
    self.start = function(context) { context.start = new Date(); };
    self.end = function(context) { self.stats.put(new Date() - context.start); };
};

/** Track HTTP response codes, storing stats in a stats.js#ResultsCounter object. The client must call
.end({res: http.ClientResponse}). */
StatsCollectors['result-codes'] = function ResultCodesCollector() {
    var self = this;
    self.stats = new ResultsCounter();
    self.end = function(context, http) { self.stats.put(http.res.statusCode); };
};

/** Track the concurrent executions (ie. stuff between calls to .start() and .end()), storing in a
stats.js#Peak. */
StatsCollectors['concurrency'] = function ConcurrencyCollector() {
    var self = this, c = 0;
    self.stats = new Peak();
    self.start = function() { c++; };
    self.end = function() { self.stats.put(c--); };
};

/** Track the size of HTTP request bodies sent by adding up the content-length headers. This function
doesn't really work as you'd hope right now, since it doesn't work for chunked encoding messages and
doesn't return actual bytes over the wire (headers, etc). */
StatsCollectors['request-bytes'] = function RequestBytesCollector() {
    var self = this;
    self.stats = new Accumulator();
    self.end = function(context, http) {
        if (http && http.req) {
            if (http.req._header) { self.stats.put(http.req._header.length); }
            if (http.req.body) { self.stats.put(http.req.body.length); }
        }
    };
};

/** Track the size of HTTP response bodies. It doesn't account for headers! */
StatsCollectors['response-bytes'] = function ResponseBytesCollector() {
    var self = this;
    self.stats = new Accumulator();
    self.end = function(context, http) {
        if (http && http.res) {
            http.res.on('data', function(chunk) {
                self.stats.put(chunk.length);
            });
        }
    };
};

/** Track unique URLs requested, storing stats in a stats.js#Uniques object. The client must call
Monitor.start({req: http.ClientRequest}). */
StatsCollectors['uniques'] = function UniquesCollector() {
    var self = this;
    self.stats = new Uniques();
    self.end = function(context, http) {
        if (http && http.req) { self.stats.put(http.req.path); }
    };
};
StatsCollectors['uniques'].disableIntervalCollection = true; // Per-interval stats should be not be collected

/** Track number HTTP response codes that are considered errors. Can also log request / response
information to disk when an error response is received. Specify the acceptable HTTP status codes in
params.successCodes. Specify the log file name in params.log, or leave undefined to disable logging. */
StatsCollectors['http-errors'] = function HttpErrorsCollector(params) {
    var self = this;
    self.stats = new Accumulator();
    self.successCodes = params.successCodes || [200];
    self.logfile = (typeof params.log === 'string') ? new LogFile(params.log) : params.log;
    self.logResBody = ( params.hasOwnProperty('logResBody') ) ? params.logResBody : true;
    self.end = function(context, http) {
        if (self.successCodes.indexOf(http.res.statusCode) < 0) {
            self.stats.put(1);

            if (self.logfile) {
                var body = '';
                http.res.on('data', function(chunk) { body += chunk; });
                http.res.on('end', function(chunk) {
                    var logObj = { ts: new Date(),
                        req: {
                            // Use the _header "private" member of http.ClientRequest, available as of
                            // node v0.2.2 (9/30/10). This is the only way to reliably get all request
                            // headers, since ClientRequest adds headers beyond what the user specifies
                            // in certain conditions, like Connection and Transfer-Encoding.
                            headers: http.req._header,
                            body: http.req.body,
                        },
                        res: {
                            statusCode: http.res.statusCode,
                            headers: http.res.headers
                        }
                    };
                    if (self.logResBody) {
                        logObj.res.body = body;
                    }
                    self.logfile.put(JSON.stringify(logObj) + '\n');
                });
            }
        }
    };
};
StatsCollectors['http-errors'].disableIntervalCollection = true; // Per-interval stats should be not be collected

/** Track number HTTP response codes that are considered errors. Can also log request / response
information to disk when an error response is received. Specify the acceptable HTTP status codes in
params.successCodes. Specify the log file name in params.log, or leave undefined to disable logging. */
StatsCollectors['slow-responses'] = function HttpErrorsCollector(params) {
    var self = this;
    self.stats = new Accumulator();
    self.threshold = params.threshold || 1000;
    self.logfile = (typeof params.log === 'string') ? new LogFile(params.log) : params.log;
    self.logResBody = ( params.hasOwnProperty('logResBody') ) ? params.logResBody : true;
    self.start = function(context) { context.start = new Date(); };
    self.end = function(context, http) {
        var runTime = new Date() - context.start;
        if (runTime > self.threshold) {
            self.stats.put(1);

            if (self.logfile) {
                var body = '';
                http.res.on('data', function(chunk) { body += chunk; });
                http.res.on('end', function(chunk) {
                    var logObj = { ts: new Date(),
                        req: {
                            // Use the _header "private" member of http.ClientRequest, available as of
                            // node v0.2.2 (9/30/10). This is the only way to reliably get all request
                            // headers, since ClientRequest adds headers beyond what the user specifies
                            // in certain conditions, like Connection and Transfer-Encoding.
                            headers: http.req._header,
                            body: http.req.body,
                        },
                        res: {
                            statusCode: http.res.statusCode,
                            headers: http.res.headers
                        }
                    };
                    if (self.logResBody) {
                        logObj.res.body = body;
                    }
                    self.logfile.put(JSON.stringify(logObj) + '\n');
                });
            }
        }
    };
};
StatsCollectors['slow-responses'].disableIntervalCollection = true; // Per-interval stats should be not be collected<|endoftext|>


<commit_before>import React from 'react'
import {storiesOf} from '@storybook/react'
import {withKnobs, boolean} from '@storybook/addon-knobs'

import Panel from './index'
import Preview from '../Preview'
import Typography from '../Typography'

storiesOf('Tocco-UI | Layout / Panel', module)
  .addDecorator(withKnobs)
  .add(
    'PanelGroup',
    () => (
      <Panel.Group openPanelIndex={2}>
        {
          ['Group 1', 'Group 2', 'Group 3'].map((item, i) =>
            <Panel.Wrapper key={i}>
              <Panel.Header>
                <Typography.H4>{item}</Typography.H4>
              </Panel.Header>
              <Panel.Body>
                <Typography.Span>Body {item}</Typography.Span>
              </Panel.Body>
            </Panel.Wrapper>
          )
        }
      </Panel.Group>
    )
  )
  .add(
    'Panel with header',
    () => {
      const wrapperKnobs = {
        isFramed: boolean('isFramed', true),
        isToggleable: boolean('isToggleable', true)
      }
      return (
        <Panel.Wrapper isOpen={true} {...wrapperKnobs}>
          <Panel.Header>
            <Typography.H4>Header</Typography.H4>
          </Panel.Header>
          <Panel.Body>
            <Typography.Span>Body</Typography.Span>
          </Panel.Body>
        </Panel.Wrapper>
      )
    }
  )
  .add(
    'Panel with footer',
    () => {
      const wrapperKnobs = {
        isFramed: boolean('isFramed', true),
        isToggleable: boolean('isToggleable', true)
      }
      return (
        <Panel.Wrapper isOpen={true} {...wrapperKnobs}>
          <Panel.Body>
            <Preview
              alt="modern building captured from frog view"
              srcUrl="https://picsum.photos/500/500?image=1081"
              thumbnailUrl="https://picsum.photos/150/150?image=1081"
              maxDimensionY="130px"
              maxDimensionX="120px"
              caption="Consectetur adipisicing elit."
            />
          </Panel.Body>
          <Panel.Footer>
            <Typography.H4>Footer</Typography.H4>
          </Panel.Footer>
        </Panel.Wrapper>
      )
    }
  )
<commit_msg>refactor(tocco-ui): update panel story to new props<commit_after>import React from 'react'
import {storiesOf} from '@storybook/react'
import {withKnobs, boolean} from '@storybook/addon-knobs'

import Panel from './index'
import Preview from '../Preview'
import Typography from '../Typography'

storiesOf('Tocco-UI | Layout / Panel', module)
  .addDecorator(withKnobs)
  .add(
    'PanelGroup',
    () => (
      <Panel.Group openPanelIndex={2}>
        {
          ['Group 1', 'Group 2', 'Group 3'].map((item, i) =>
            <Panel.Wrapper key={i}>
              <Panel.Header>
                <Typography.H4>{item}</Typography.H4>
              </Panel.Header>
              <Panel.Body>
                <Typography.Span>Body {item}</Typography.Span>
              </Panel.Body>
            </Panel.Wrapper>
          )
        }
      </Panel.Group>
    )
  )
  .add(
    'Panel with header',
    () => {
      const wrapperKnobs = {
        isFramed: boolean('isFramed', true),
        isToggleable: boolean('isToggleable', true)
      }
      return (
        <Panel.Wrapper isOpenInitial={true} {...wrapperKnobs}>
          <Panel.Header>
            <Typography.H4>Header</Typography.H4>
          </Panel.Header>
          <Panel.Body>
            <Typography.Span>Body</Typography.Span>
          </Panel.Body>
        </Panel.Wrapper>
      )
    }
  )
  .add(
    'Panel with footer',
    () => {
      const wrapperKnobs = {
        isFramed: boolean('isFramed', true),
        isToggleable: boolean('isToggleable', true)
      }
      return (
        <Panel.Wrapper isOpenInitial={false} {...wrapperKnobs}>
          <Panel.Body>
            <Preview
              alt="modern building captured from frog view"
              srcUrl="https://picsum.photos/500/500?image=1081"
              thumbnailUrl="https://picsum.photos/150/150?image=1081"
              maxDimensionY="130px"
              maxDimensionX="120px"
              caption="Consectetur adipisicing elit."
            />
          </Panel.Body>
          <Panel.Footer>
            <Typography.H4>Footer</Typography.H4>
          </Panel.Footer>
        </Panel.Wrapper>
      )
    }
  )
<|endoftext|>