<commit_before>/* eslint-env browser */
import $ from 'jquery';

const SEND_COMMAND = 'send';
const EVENT_HIT_TYPE = 'event';
const EVENT_FIELDS = ['eventCategory', 'eventAction', 'eventLabel', 'eventValue', 'transport', 'hitCallback'];

/**
 * Compulsory fields for send event on clicking a link.
 */
function compulsoryFields(/* link */) {
  return {
    transport: 'beacon',
  };
}

/**
 * Returns the URL origin given a string representation of it. If the browser
 * does not support URL, it returns href itself.
 *
 * @param {String} href the string containing the URL.
 * @return {String} the origin of the URL.
 */
function getOrigin(href) {
  if (URL !== undefined) {
    return new URL(href).origin;
  }

  return href;
}

/**
 * Computes the default values for some keys to the send event command.
 *
 * @param {HTMLAnchorElement} link anchor clicked.
 * @return {Object} object with the default values.
 */
function defaultsForLink(link) {
  const sameOrigin = getOrigin(link) === window.location.origin;
  return {
    eventCategory: sameOrigin ? 'inbound' : 'outbound',
    eventAction: 'click',
    eventLabel: link.href,
  };
}

/**
 * Computes the data for a given anchor element
 *
 * @param {HTMLAnchorElement} link
 * @return {Object} object with the values to call ga send event command.
 */
function getData(link) {
  const data = Object.assign(
    defaultsForLink(link),
    link.dataset,
    compulsoryFields(link),
  );

  return EVENT_FIELDS.reduce((memo, key) => {
    /* eslint no-param-reassign: ["error", { "props": false }] */
    if (key in data) {
      memo[key] = data[key];
    }
    return memo;
  }, {});
}

/**
 * Returns the arguments to call ga command when the link has been clicked.
 *
 * @param {HTMLAnchorElement} link element to get the data from.
 * @return {Array<String, String, Object>} arguments for ga method.
 */
function commandArguments(link) {
  return [SEND_COMMAND, EVENT_HIT_TYPE, getData(link)];
}

// If ga is not installed, we have a fallback that both stores and logs the
// call.
const gaInternal = [];
const ga = window.ga || function (...args) {
  gaInternal.push(args);
  // eslint-disable-next-line no-console
  console.log(...args);

  const last = args.pop();

  if (typeof last.hitCallback === 'function') {
    setTimeout(last.hitCallback.bind(last), 0);
  }
};

/**
 * Promisify ga
 *
 * @param {...any} args
 * @return {Promise} a promise that resolves whenever the hitCallback is called.
 */
export function gaPromise(...args) {
  let obj = {};
  let oldCallback = () => { };

  if (typeof args[args.length - 1] === 'object') {
    obj = args.pop();
  }

  if (typeof obj.hitCallback === 'function') {
    oldCallback = obj.hitCallback;
  }

  return new Promise((resolve) => {
    obj.hitCallback = (...cbArgs) => {
      oldCallback(...cbArgs);
      resolve(...cbArgs);
    };
    ga(...args, obj);
  });
}

export function isScrollableLink(link) {
  const [url, id] = link.href.split('#');

  return window.location.href.indexOf(url) >= 0 && id;
}

/**
 * Scrolls to element anchored by the given link.
 *
 * @param {HTMLAnchorElement} link
 */
export function scrollTo(link) {
  const [, id] = link.href.split('#');
  $('body, html').animate(
    {
      scrollTop: $(`#${id}`).offset().top,
    },
    1000,
  );
}

/**
 * Same as window.setTimeout, but as a promise.
 *
 * @param {Number} ms
 */
function setTimeoutPromise(ms) {
  return new Promise(resolve => window.setTimeout(resolve, ms));
}

/**
 * Sends to Google Analytics the details from the link
 *
 * @param {HTMLAnchorElement} link anchor clicked
 * @return {Promise} a promise resolved whenever hitCallback is called.
 */
export function sendLinkDetails(link, ms = 500) {
  return Promise.race([
    setTimeoutPromise(ms),
    gaPromise(...commandArguments(link)),
  ]);
}

/**
 * Emulates the click on the link.
 *
 * @param {HTMLAnchorElement} link anchor to emulate
 */
export function goToLink(link) {
  if (link.target === '_blank') {
    window.open(link.href);
  } else {
    window.location.href = link.href;
  }
}

function trackableLink({ dataset, href }) {
  return 'tracked' in dataset && href;
}

/**
 * Sets up the tracking of links, inbound and outbound.
 *
 * For a link to be tracked it needs to:
 *
 * 1. Have a `data-tracked` attribute.
 * 2. Have a non-empty `href` attribute.
 *
 * Or satisfy the acceptableCondition predicate.
 */
export default function setupLinkTracking(acceptableCondition = () => false) {
  document.addEventListener('click', (evt) => {
    const { target } = evt;
    if (!(trackableLink(target) || acceptableCondition(target))) {
      return;
    }

    evt.preventDefault();

    if (isScrollableLink(target)) {
      sendLinkDetails(target);
      scrollTo(target);
      return;
    }

    sendLinkDetails(target)
      .then(() => goToLink(target));
  });
}
<commit_msg>open links in new tab when meta key (ctrl or command) is clicked<commit_after>/* eslint-env browser */
import $ from 'jquery';

const SEND_COMMAND = 'send';
const EVENT_HIT_TYPE = 'event';
const EVENT_FIELDS = ['eventCategory', 'eventAction', 'eventLabel', 'eventValue', 'transport', 'hitCallback'];

/**
 * Compulsory fields for send event on clicking a link.
 */
function compulsoryFields(/* link */) {
  return {
    transport: 'beacon',
  };
}

/**
 * Returns the URL origin given a string representation of it. If the browser
 * does not support URL, it returns href itself.
 *
 * @param {String} href the string containing the URL.
 * @return {String} the origin of the URL.
 */
function getOrigin(href) {
  if (URL !== undefined) {
    return new URL(href).origin;
  }

  return href;
}

/**
 * Computes the default values for some keys to the send event command.
 *
 * @param {HTMLAnchorElement} link anchor clicked.
 * @return {Object} object with the default values.
 */
function defaultsForLink(link) {
  const sameOrigin = getOrigin(link) === window.location.origin;
  return {
    eventCategory: sameOrigin ? 'inbound' : 'outbound',
    eventAction: 'click',
    eventLabel: link.href,
  };
}

/**
 * Computes the data for a given anchor element
 *
 * @param {HTMLAnchorElement} link
 * @return {Object} object with the values to call ga send event command.
 */
function getData(link) {
  const data = Object.assign(
    defaultsForLink(link),
    link.dataset,
    compulsoryFields(link),
  );

  return EVENT_FIELDS.reduce((memo, key) => {
    /* eslint no-param-reassign: ["error", { "props": false }] */
    if (key in data) {
      memo[key] = data[key];
    }
    return memo;
  }, {});
}

/**
 * Returns the arguments to call ga command when the link has been clicked.
 *
 * @param {HTMLAnchorElement} link element to get the data from.
 * @return {Array<String, String, Object>} arguments for ga method.
 */
function commandArguments(link) {
  return [SEND_COMMAND, EVENT_HIT_TYPE, getData(link)];
}

// If ga is not installed, we have a fallback that both stores and logs the
// call.
const gaInternal = [];
const ga = window.ga || function (...args) {
  gaInternal.push(args);
  // eslint-disable-next-line no-console
  console.log(...args);

  const last = args.pop();

  if (typeof last.hitCallback === 'function') {
    setTimeout(last.hitCallback.bind(last), 0);
  }
};

/**
 * Promisify ga
 *
 * @param {...any} args
 * @return {Promise} a promise that resolves whenever the hitCallback is called.
 */
export function gaPromise(...args) {
  let obj = {};
  let oldCallback = () => { };

  if (typeof args[args.length - 1] === 'object') {
    obj = args.pop();
  }

  if (typeof obj.hitCallback === 'function') {
    oldCallback = obj.hitCallback;
  }

  return new Promise((resolve) => {
    obj.hitCallback = (...cbArgs) => {
      oldCallback(...cbArgs);
      resolve(...cbArgs);
    };
    ga(...args, obj);
  });
}

export function isScrollableLink(link) {
  const [url, id] = link.href.split('#');

  return window.location.href.indexOf(url) >= 0 && id;
}

/**
 * Scrolls to element anchored by the given link.
 *
 * @param {HTMLAnchorElement} link
 */
export function scrollTo(link) {
  const [, id] = link.href.split('#');
  $('body, html').animate(
    {
      scrollTop: $(`#${id}`).offset().top,
    },
    1000,
  );
}

/**
 * Same as window.setTimeout, but as a promise.
 *
 * @param {Number} ms
 */
function setTimeoutPromise(ms) {
  return new Promise(resolve => window.setTimeout(resolve, ms));
}

/**
 * Sends to Google Analytics the details from the link
 *
 * @param {HTMLAnchorElement} link anchor clicked
 * @return {Promise} a promise resolved whenever hitCallback is called.
 */
export function sendLinkDetails(link, ms = 500) {
  return Promise.race([
    setTimeoutPromise(ms),
    gaPromise(...commandArguments(link)),
  ]);
}

/**
 * Emulates the click on the link.
 *
 * @param {HTMLAnchorElement} link anchor to emulate
 */
export function goToLink(link, evt) {
  if (link.target === '_blank'  // link to _blank
    || evt.metaKey              // command key
    || evt.shiftKey             // shift key
    || evt.ctrlKey              // control key
    || evt.button === 1) {      // middle button click
    window.open(link.href, '_blank');
  } else {
    window.location.href = link.href;
  }
}

function trackableLink({ dataset, href }) {
  return 'tracked' in dataset && href;
}

/**
 * Sets up the tracking of links, inbound and outbound.
 *
 * For a link to be tracked it needs to:
 *
 * 1. Have a `data-tracked` attribute.
 * 2. Have a non-empty `href` attribute.
 *
 * Or satisfy the acceptableCondition predicate.
 */
export default function setupLinkTracking(acceptableCondition = () => false) {
  document.addEventListener('click', (evt) => {
    const { target } = evt;
    if (!(trackableLink(target) || acceptableCondition(target))) {
      return;
    }

    evt.preventDefault();

    if (isScrollableLink(target)) {
      sendLinkDetails(target);
      scrollTo(target);
      return;
    }

    sendLinkDetails(target)
      .then(() => goToLink(target, evt));
  });
}
<|endoftext|>


<commit_before>var EventEmitter = require("events").EventEmitter;
var lazy = require("lazy.js");
var Util = require("../helpers/Util");

var AppDispatcher = require("../AppDispatcher");
var AppFormTransforms = require("./AppFormTransforms");
var AppFormValidators = require("./AppFormValidators");
var FormEvents = require("../events/FormEvents");

const defaultFieldValues = Object.freeze({
  cpus: 0.1,
  mem: 16,
  disk: 0,
  instances: 1
});

const validationRules = {
  "appId": [
    AppFormValidators.appIdNotEmpty,
    AppFormValidators.appIdNoWhitespaces
  ],
  "constraints": [AppFormValidators.constraints],
  "cpus": [AppFormValidators.cpus],
  "disk": [AppFormValidators.disk],
  "env": [AppFormValidators.env],
  "executor": [AppFormValidators.executor],
  "instances": [AppFormValidators.instances],
  "mem": [AppFormValidators.mem],
  "ports": [AppFormValidators.ports]
};

const transformationRules = {
  "constraints": AppFormTransforms.constraints,
  "cpus": AppFormTransforms.cpus,
  "disk": AppFormTransforms.disk,
  "env": AppFormTransforms.env,
  "instances": AppFormTransforms.instances,
  "mem": AppFormTransforms.mem,
  "ports": AppFormTransforms.ports,
  "uris": AppFormTransforms.uris
};

const resolveMap = {
  appId: "id",
  cmd: "cmd",
  constraints: "constraints",
  cpus: "cpus",
  disk: "disk",
  instances: "instances",
  env: "env",
  executor: "executor",
  mem: "mem",
  ports: "ports",
  uris: "uris"
};

function getValidationErrorIndex(fieldId, value) {
  if (validationRules[fieldId] == null) {
    return -1;
  }
  return validationRules[fieldId].findIndex((isValid) =>!isValid(value));
}

function insertField(fields, fieldId, index = null, value) {
  if (fieldId === "env") {
    Util.initKeyValue(fields, "env", []);
    if (index == null) {
      fields.env.push(value);
    } else {
      fields.env.splice(index, 0, value);
    }
  }
}

function updateField(fields, fieldId, index = null, value) {
  if (fieldId === "env") {
    Util.initKeyValue(fields, "env", []);
    fields.env[index] = value;
  } else {
    fields[fieldId] = value;
  }
}

function deleteField(fields, fieldId, index) {
  if (fieldId === "env") {
    fields.env.splice(index, 1);
  }
}

function getTransformedField(fieldId, value) {
  const transform = transformationRules[fieldId];
  if (transform == null) {
    return value;
  }
  return transform(value);
}

function rebuildModelFromFields(app, fields, fieldId) {
  const key = resolveMap[fieldId];
  if (key) {
    let field = getTransformedField(fieldId, fields[fieldId]);
    app[key] = field;
  }
}

var AppFormStore = lazy(EventEmitter.prototype).extend({
  app: {},
  fields: {},
  validationErrorIndices: {},
  initAndReset: function () {
    this.app = {};
    this.fields = {};
    this.validationErrorIndices = {};

    Object.keys(defaultFieldValues).forEach((fieldId) => {
      this.fields[fieldId] = defaultFieldValues[fieldId];
      rebuildModelFromFields(this.app, this.fields, fieldId);
    });
  }
}).value();

function executeAction(action, setFieldFunction) {
  const fieldId = action.fieldId;
  const value = action.value;
  const index = action.index;
  let errorIndex = -1;

  // This is not a delete-action
  if (value !== undefined || index == null) {
    errorIndex = getValidationErrorIndex(fieldId, value);

    if (errorIndex > -1) {
      if (index != null) {
        Util.initKeyValue(AppFormStore.validationErrorIndices, fieldId, []);
        AppFormStore.validationErrorIndices[fieldId][index] = errorIndex;
      } else {
        AppFormStore.validationErrorIndices[fieldId] = errorIndex;
      }
    } else {
      delete AppFormStore.validationErrorIndices[fieldId];
    }
  }

  setFieldFunction(AppFormStore.fields, fieldId, index, value);

  if (errorIndex === -1) {
    rebuildModelFromFields(AppFormStore.app, AppFormStore.fields, fieldId);
    AppFormStore.emit(FormEvents.CHANGE, fieldId);
  } else {
    AppFormStore.emit(FormEvents.FIELD_VALIDATION_ERROR);
  }
}

AppDispatcher.register(function (action) {
  switch (action.actionType) {
    case FormEvents.INSERT:
      executeAction(action, insertField);
      break;
    case FormEvents.UPDATE:
      executeAction(action, updateField);
      break;
    case FormEvents.DELETE:
      executeAction(action, deleteField);
      break;
  }
});

module.exports = AppFormStore;
<commit_msg>No need for a transformation rule mapping<commit_after>var EventEmitter = require("events").EventEmitter;
var lazy = require("lazy.js");
var Util = require("../helpers/Util");

var AppDispatcher = require("../AppDispatcher");
var AppFormTransforms = require("./AppFormTransforms");
var AppFormValidators = require("./AppFormValidators");
var FormEvents = require("../events/FormEvents");

const defaultFieldValues = Object.freeze({
  cpus: 0.1,
  mem: 16,
  disk: 0,
  instances: 1
});

const validationRules = {
  "appId": [
    AppFormValidators.appIdNotEmpty,
    AppFormValidators.appIdNoWhitespaces
  ],
  "constraints": [AppFormValidators.constraints],
  "cpus": [AppFormValidators.cpus],
  "disk": [AppFormValidators.disk],
  "env": [AppFormValidators.env],
  "executor": [AppFormValidators.executor],
  "instances": [AppFormValidators.instances],
  "mem": [AppFormValidators.mem],
  "ports": [AppFormValidators.ports]
};

const resolveMap = {
  appId: "id",
  cmd: "cmd",
  constraints: "constraints",
  cpus: "cpus",
  disk: "disk",
  instances: "instances",
  env: "env",
  executor: "executor",
  mem: "mem",
  ports: "ports",
  uris: "uris"
};

function getValidationErrorIndex(fieldId, value) {
  if (validationRules[fieldId] == null) {
    return -1;
  }
  return validationRules[fieldId].findIndex((isValid) =>!isValid(value));
}

function insertField(fields, fieldId, index = null, value) {
  if (fieldId === "env") {
    Util.initKeyValue(fields, "env", []);
    if (index == null) {
      fields.env.push(value);
    } else {
      fields.env.splice(index, 0, value);
    }
  }
}

function updateField(fields, fieldId, index = null, value) {
  if (fieldId === "env") {
    Util.initKeyValue(fields, "env", []);
    fields.env[index] = value;
  } else {
    fields[fieldId] = value;
  }
}

function deleteField(fields, fieldId, index) {
  if (fieldId === "env") {
    fields.env.splice(index, 1);
  }
}

function getTransformedField(fieldId, value) {
  const transform = AppFormTransforms[fieldId];
  if (transform == null) {
    return value;
  }
  return transform(value);
}

function rebuildModelFromFields(app, fields, fieldId) {
  const key = resolveMap[fieldId];
  if (key) {
    let field = getTransformedField(fieldId, fields[fieldId]);
    app[key] = field;
  }
}

var AppFormStore = lazy(EventEmitter.prototype).extend({
  app: {},
  fields: {},
  validationErrorIndices: {},
  initAndReset: function () {
    this.app = {};
    this.fields = {};
    this.validationErrorIndices = {};

    Object.keys(defaultFieldValues).forEach((fieldId) => {
      this.fields[fieldId] = defaultFieldValues[fieldId];
      rebuildModelFromFields(this.app, this.fields, fieldId);
    });
  }
}).value();

function executeAction(action, setFieldFunction) {
  const fieldId = action.fieldId;
  const value = action.value;
  const index = action.index;
  let errorIndex = -1;

  // This is not a delete-action
  if (value !== undefined || index == null) {
    errorIndex = getValidationErrorIndex(fieldId, value);

    if (errorIndex > -1) {
      if (index != null) {
        Util.initKeyValue(AppFormStore.validationErrorIndices, fieldId, []);
        AppFormStore.validationErrorIndices[fieldId][index] = errorIndex;
      } else {
        AppFormStore.validationErrorIndices[fieldId] = errorIndex;
      }
    } else {
      delete AppFormStore.validationErrorIndices[fieldId];
    }
  }

  setFieldFunction(AppFormStore.fields, fieldId, index, value);

  if (errorIndex === -1) {
    rebuildModelFromFields(AppFormStore.app, AppFormStore.fields, fieldId);
    AppFormStore.emit(FormEvents.CHANGE, fieldId);
  } else {
    AppFormStore.emit(FormEvents.FIELD_VALIDATION_ERROR);
  }
}

AppDispatcher.register(function (action) {
  switch (action.actionType) {
    case FormEvents.INSERT:
      executeAction(action, insertField);
      break;
    case FormEvents.UPDATE:
      executeAction(action, updateField);
      break;
    case FormEvents.DELETE:
      executeAction(action, deleteField);
      break;
  }
});

module.exports = AppFormStore;
<|endoftext|>


<commit_before>/*global require, module*/
var InvalidArgumentError = require("../error/invalid-argument-error"),
    InvalidOperationError = require("../error/invalid-operation-error"),
    ServiceAccessor = require('./service-accessor'),
    TimeEntry = require('./time-entry'),
    $ = require("jquery"),
    _ = require('lodash'),
    moment = require('moment'),
    Validator = require('validator'),
    StoreError = require('../constants/store-errors'),
    FilterType = {
        Match: "match",
        Equal: "equal"
    };

var DataManager = function (serviceAccessor) {
    "use strict";
    if (!(serviceAccessor instanceof ServiceAccessor)) {
        throw new InvalidArgumentError("Parameter serviceAccessor must be an instance of ServiceAccessor.");
    }

    this.serviceAccessor = serviceAccessor;

    this.taskCollection = [];
    this.activityCollection = [];
    this.activeTaskCollection = [];
    this.timePostedTaskCollection = null;
    this.resultCount = 10;
    this.urlFilterExpression = new RegExp("^" + this.serviceAccessor.serviceBaseUrl + "\\/issues\\/\\d{1,}$", 'gi');
    this.hashFilters = [
        {
            id: "#P",
            value: "project.name",
            filter: FilterType.Match
        },
        {
            id: "#ID",
            value: "id",
            filter: FilterType.Equal
        },
        {
            id: "#T",
            value: "tracker.name",
            filter: FilterType.Match
        },
        {
            id: "#A",
            value: "assigned_to.name",
            filter: FilterType.Match
        }
    ];
};

DataManager.prototype = (function () {
    "use strict";
    var getTaskUrl = function (id) {
            return this.serviceAccessor.serviceBaseUrl.concat("/issues/", id);
        },
        fetchData = function () {
            var promises = [],
                deferred = $.Deferred();
            promises.push(this.serviceAccessor.getTaskCollection(true));
            promises.push(this.serviceAccessor.getTimeEntryActivities());

            $.when.apply(this, promises).done(function (taskCollection, activityCollection) {
                this.taskCollection = taskCollection;
                this.activityCollection = activityCollection.time_entry_activities;
                deferred.resolve();
            }.bind(this)).fail(function () {
                deferred.reject(StoreError.DataLoadFailure);
            }.bind(this));
            return deferred.promise();
        },
        fetchLatest = function () {
            var deferred = $.Deferred(),
                oldActiveTask;
            $.when(this.serviceAccessor.getTaskCollection(false)).done(function (taskCollection) {
                taskCollection.map(function (task) {
                    var taskIndex = _.findIndex(this.taskCollection, { 'id' : task.id });
                    if (typeof taskIndex === "number") {
                        this.taskCollection[taskIndex] = task;
                        oldActiveTask = _.filter(this.activeTaskCollection, { 'issueId' : task.id });
                        if (oldActiveTask.length > 0) {
                            oldActiveTask.map(function (activeTask) {
                                activeTask.issueName = task.subject;
                                activeTask.projectName = task.project.name;
                            });
                        }
                    } else {
                        this.taskCollection.push(task);
                    }
                }.bind(this));
                deferred.resolve();
            }.bind(this)).fail(function () {
                deferred.reject(StoreError.DataLoadFailure);
            }.bind(this));
            return deferred.promise();
        },
        getHashProperty = function (value) {
            if (value) {
                return _.find(this.hashFilters, { 'id' : value });
            }

            return null;
        },
        applyMatchFilter = function (propertyName, query) {
            var hashFilterParts = query.toUpperCase().trim().split(' '),
                queryExpression = new RegExp("(?=.*" + hashFilterParts.join(')(?=.*') + ")", 'gi'),
                hashPropertyValue;
            return _.filter(this.taskCollection, function (task) {
                hashPropertyValue = _.get(task, propertyName);
                return (hashPropertyValue && hashPropertyValue.match(queryExpression));
            });
        },
        applyEqualFilter = function (propertyName, value) {
            if (!Validator.isInt(value)) {
                return [];
            }
            var parsedValue = parseInt(value, 10),
                task = _.find(this.taskCollection, function (task) {
                    var hashPropertyValue = _.get(task, propertyName);
                    return hashPropertyValue === parsedValue;
                });

            if (task) {
                return [ task ];
            }

            return [];
        },
        applySubjectFilter = function (taskCollection, queryParts) {
            var queryExpression = new RegExp("(?=.*" + queryParts.join(')(?=.*') + ")", 'gi'),
                match;
            return _.filter(taskCollection, function (task) {
                task.matchCount = 0;
                match = task.subject.match(queryExpression);
                if (match) {
                    task.matchCount += match.length;
                }

                return task.matchCount > 0;
            });
        },
        hashFilterTaskCollection = function (hashProperty, query) {
            switch (hashProperty.filter) {
            case FilterType.Match:
                return applyMatchFilter.call(this, hashProperty.value, query);
            case FilterType.Equal:
                return applyEqualFilter.call(this, hashProperty.value, query);
            }

        },
        applyUrlFilter = function (formattedQuery) {
            var match = formattedQuery.match(this.urlFilterExpression),
                filteredTasks;
            if (match) {
                filteredTasks = applyEqualFilter.call(this, "id", match[0].substr(match[0].lastIndexOf('/') + 1));
                filteredTasks.map(function (task) {
                    task.formattedTitle = task.subject;
                });

                return filteredTasks;
            }

            return [];
        },
        getTextHighlighter = function () {
            return "$1";
        },
        applyTitleHighlighter = function (taskCollection, upperQueryParts) {
            var selectorParts = _.filter(upperQueryParts, function (part) {
                    return part.length > 0;
                }),
                selectorExpression = new RegExp("(" + selectorParts.join('|') + ")", 'gi');
            taskCollection.map(function (task) {
                task.formattedTitle = task.subject;
                task.formattedTitle = task.formattedTitle.replace(selectorExpression, getTextHighlighter.call(this));
            });
        },
        filterTaskCollection = function (query) {
            var formattedQuery = _.trim(query.toUpperCase()),
                urlFilteredCollection = applyUrlFilter.call(this, formattedQuery),
                upperQueryParts,
                filterPartCount,
                hashProperty,
                taskCollection,
                parts,
                filteredTasks,
                sortedList;

            if (urlFilteredCollection.length === 1) {
                return urlFilteredCollection;
            }

            upperQueryParts = formattedQuery.split(' ');
            filterPartCount = upperQueryParts.length;

            if (filterPartCount === 0) {
                return [];
            }

            hashProperty = getHashProperty.call(this,  _.first(upperQueryParts));
            taskCollection = this.taskCollection;
            if (hashProperty) {
                if (filterPartCount < 2 || upperQueryParts[1].length === 0) {
                    return [];
                }

                taskCollection = hashFilterTaskCollection.call(this, hashProperty, upperQueryParts[1]);
                if (taskCollection.length === 0) {
                    return [];
                }

                upperQueryParts.splice(0, 2);
            }

            parts = _.filter(upperQueryParts, function (part) {
                return part.length > 1;
            });
            filteredTasks = applySubjectFilter.call(this, taskCollection, parts);

            if (filteredTasks.length === 0) {
                return [];
            }

            sortedList = _.take(_.sortByOrder(filteredTasks, ['matchCount'], [false]), this.resultCount);
            applyTitleHighlighter.call(this, sortedList, upperQueryParts);

            return sortedList;
        },
        createActiveTask = function (id) {
            var task = _.find(this.taskCollection, function (task) {
                return task.id === parseInt(id, 10);
            });

            if (!task) {
                throw new InvalidOperationError("Task not available.");
            }

            this.activeTaskCollection.push(TimeEntry.createInstance(task.id, task.subject, task.project.name, getTaskUrl.call(this)));
            this.activeTaskCollection = _.sortBy(this.activeTaskCollection, 'projectName');
        },
        removeActiveTask = function (timeEntryId) {
            _.remove(this.activeTaskCollection, function (entry) {
                return entry.id === timeEntryId;
            });
        },
        clearActiveTaskCollection = function () {
            this.activeTaskCollection = [];
        },
        updateActiveTaskHours = function (timeEntryId, hours) {
            var entry = _.find(this.activeTaskCollection, { 'id': timeEntryId });
            entry.setHours(hours);
        },
        updateActiveTaskActivityId = function (timeEntryId, activityId) {
            var entry = _.find(this.activeTaskCollection, { 'id': timeEntryId });
            entry.setActivityId(activityId);
        },
        updateActiveTaskComments = function (timeEntryId, comments) {
            var entry = _.find(this.activeTaskCollection, { 'id': timeEntryId });
            entry.setComments(comments);
        },
        postUpdatedActiveTaskCollection = function (spentOn) {
            if (!spentOn || !spentOn._isAMomentObject) {
                throw new InvalidArgumentError("Parameter spentOn must be a moment object.");
            }

            var deferred = $.Deferred();
            this.timePostedTaskCollection = _.filter(this.activeTaskCollection, function (entry) {
                return entry.updated;
            });
            $.when(this.serviceAccessor.createTimeEntries(this.timePostedTaskCollection, spentOn)).done(function () {
                this.timePostedTaskCollection.map(function (timeEntry) {
                    timeEntry.clearTimeEntry();
                });
                deferred.resolve();
            }.bind(this)).fail(function () {
                deferred.reject(StoreError.TimeEntryFailure);
            }.bind(this));
            return deferred.promise();
        },
        getPostedTaskCollection = function () {
            return _.pluck(this.timePostedTaskCollection, 'issueId');
        },
        createActiveTaskCollection = function (taskIdCollection) {
            if (typeof taskIdCollection === "Array") {
                throw new InvalidArgumentError("Parameter taskIdCollection must be an array.");
            }

            taskIdCollection.map(function (taskId) {
                var task = _.find(this.taskCollection, function (task) {
                    return task.id === parseInt(taskId, 10);
                });

                if (!task) {
                    return;
                }

                this.activeTaskCollection.push(TimeEntry.createInstance(taskId, task.subject, task.project.name, getTaskUrl.call(this)));
            }.bind(this));

            this.activeTaskCollection = _.sortBy(this.activeTaskCollection, 'projectName');
        };
    return {
        fetchData: fetchData,
        fetchLatest: fetchLatest,
        filterTaskCollection: filterTaskCollection,
        createActiveTask: createActiveTask,
        updateActiveTaskHours: updateActiveTaskHours,
        updateActiveTaskActivityId: updateActiveTaskActivityId,
        updateActiveTaskComments: updateActiveTaskComments,
        postUpdatedActiveTaskCollection: postUpdatedActiveTaskCollection,
        removeActiveTask: removeActiveTask,
        clearActiveTaskCollection: clearActiveTaskCollection,
        getPostedTaskCollection: getPostedTaskCollection,
        createActiveTaskCollection: createActiveTaskCollection
    };
}());

module.exports = DataManager;<commit_msg>Search filter defect fix<commit_after>/*global require, module*/
var InvalidArgumentError = require("../error/invalid-argument-error"),
    InvalidOperationError = require("../error/invalid-operation-error"),
    ServiceAccessor = require('./service-accessor'),
    TimeEntry = require('./time-entry'),
    $ = require("jquery"),
    _ = require('lodash'),
    moment = require('moment'),
    Validator = require('validator'),
    StoreError = require('../constants/store-errors'),
    FilterType = {
        Match: "match",
        Equal: "equal"
    };

var DataManager = function (serviceAccessor) {
    "use strict";
    if (!(serviceAccessor instanceof ServiceAccessor)) {
        throw new InvalidArgumentError("Parameter serviceAccessor must be an instance of ServiceAccessor.");
    }

    this.serviceAccessor = serviceAccessor;

    this.taskCollection = [];
    this.activityCollection = [];
    this.activeTaskCollection = [];
    this.timePostedTaskCollection = null;
    this.resultCount = 10;
    this.urlFilterExpression = new RegExp("^" + this.serviceAccessor.serviceBaseUrl + "\\/issues\\/\\d{1,}$", 'gi');
    this.hashFilters = [
        {
            id: "#P",
            value: "project.name",
            filter: FilterType.Match
        },
        {
            id: "#ID",
            value: "id",
            filter: FilterType.Equal
        },
        {
            id: "#T",
            value: "tracker.name",
            filter: FilterType.Match
        },
        {
            id: "#A",
            value: "assigned_to.name",
            filter: FilterType.Match
        }
    ];
};

DataManager.prototype = (function () {
    "use strict";
    var getTaskUrl = function (id) {
            return this.serviceAccessor.serviceBaseUrl.concat("/issues/", id);
        },
        fetchData = function () {
            var promises = [],
                deferred = $.Deferred();
            promises.push(this.serviceAccessor.getTaskCollection(true));
            promises.push(this.serviceAccessor.getTimeEntryActivities());

            $.when.apply(this, promises).done(function (taskCollection, activityCollection) {
                this.taskCollection = taskCollection;
                this.activityCollection = activityCollection.time_entry_activities;
                deferred.resolve();
            }.bind(this)).fail(function () {
                deferred.reject(StoreError.DataLoadFailure);
            }.bind(this));
            return deferred.promise();
        },
        fetchLatest = function () {
            var deferred = $.Deferred(),
                oldActiveTask;
            $.when(this.serviceAccessor.getTaskCollection(false)).done(function (taskCollection) {
                taskCollection.map(function (task) {
                    var taskIndex = _.findIndex(this.taskCollection, { 'id' : task.id });
                    if (typeof taskIndex === "number") {
                        this.taskCollection[taskIndex] = task;
                        oldActiveTask = _.filter(this.activeTaskCollection, { 'issueId' : task.id });
                        if (oldActiveTask.length > 0) {
                            oldActiveTask.map(function (activeTask) {
                                activeTask.issueName = task.subject;
                                activeTask.projectName = task.project.name;
                            });
                        }
                    } else {
                        this.taskCollection.push(task);
                    }
                }.bind(this));
                deferred.resolve();
            }.bind(this)).fail(function () {
                deferred.reject(StoreError.DataLoadFailure);
            }.bind(this));
            return deferred.promise();
        },
        getHashProperty = function (value) {
            if (value) {
                return _.find(this.hashFilters, { 'id' : value });
            }

            return null;
        },
        applyMatchFilter = function (propertyName, query) {
            var hashFilterParts = query.toUpperCase().trim().split(' '),
                queryExpression = new RegExp("(?=.*" + hashFilterParts.join(')(?=.*') + ")", 'gi'),
                hashPropertyValue;
            return _.filter(this.taskCollection, function (task) {
                hashPropertyValue = _.get(task, propertyName);
                return (hashPropertyValue && hashPropertyValue.match(queryExpression));
            });
        },
        applyEqualFilter = function (propertyName, value) {
            if (!Validator.isInt(value)) {
                return [];
            }
            var parsedValue = parseInt(value, 10),
                task = _.find(this.taskCollection, function (task) {
                    var hashPropertyValue = _.get(task, propertyName);
                    return hashPropertyValue === parsedValue;
                });

            if (task) {
                return [ task ];
            }

            return [];
        },
        applySubjectFilter = function (taskCollection, queryParts) {
            var queryExpression = new RegExp("(?=.*" + queryParts.join(')(?=.*') + ")", 'gi'),
                match;
            return _.filter(taskCollection, function (task) {
                task.matchCount = 0;
                match = task.subject.match(queryExpression);
                if (match) {
                    task.matchCount += match.length;
                }

                return task.matchCount > 0;
            });
        },
        hashFilterTaskCollection = function (hashProperty, query) {
            switch (hashProperty.filter) {
            case FilterType.Match:
                return applyMatchFilter.call(this, hashProperty.value, query);
            case FilterType.Equal:
                return applyEqualFilter.call(this, hashProperty.value, query);
            }

        },
        applyUrlFilter = function (formattedQuery) {
            var match = formattedQuery.match(this.urlFilterExpression),
                filteredTasks;
            if (match) {
                filteredTasks = applyEqualFilter.call(this, "id", match[0].substr(match[0].lastIndexOf('/') + 1));
                filteredTasks.map(function (task) {
                    task.formattedTitle = task.subject;
                });

                return filteredTasks;
            }

            return [];
        },
        getTextHighlighter = function () {
            return "$1";
        },
        applyTitleHighlighter = function (taskCollection, upperQueryParts) {
            var selectorParts = _.filter(upperQueryParts, function (part) {
                    return part.length > 0;
                }),
                selectorExpression = new RegExp("(" + selectorParts.join('|') + ")", 'gi');
            taskCollection.map(function (task) {
                task.formattedTitle = task.subject;
                task.formattedTitle = task.formattedTitle.replace(selectorExpression, getTextHighlighter.call(this));
            });
        },
        filterTaskCollection = function (query) {
            var formattedQuery = _.trim(query.toUpperCase()),
                urlFilteredCollection = applyUrlFilter.call(this, formattedQuery),
                upperQueryParts,
                filterPartCount,
                hashProperty,
                taskCollection,
                parts,
                filteredTasks,
                sortedList;

            if (urlFilteredCollection.length === 1) {
                return urlFilteredCollection;
            }

            upperQueryParts = _.escapeRegExp(formattedQuery).split(' ');
            filterPartCount = upperQueryParts.length;

            if (filterPartCount === 0) {
                return [];
            }

            hashProperty = getHashProperty.call(this,  _.first(upperQueryParts));
            taskCollection = this.taskCollection;
            if (hashProperty) {
                if (filterPartCount < 2 || upperQueryParts[1].length === 0) {
                    return [];
                }

                taskCollection = hashFilterTaskCollection.call(this, hashProperty, upperQueryParts[1]);
                if (taskCollection.length === 0) {
                    return [];
                }

                upperQueryParts.splice(0, 2);
            }

            parts = _.filter(upperQueryParts, function (part) {
                return part.length > 1;
            });
            filteredTasks = applySubjectFilter.call(this, taskCollection, parts);

            if (filteredTasks.length === 0) {
                return [];
            }

            sortedList = _.take(_.sortByOrder(filteredTasks, ['matchCount'], [false]), this.resultCount);
            applyTitleHighlighter.call(this, sortedList, upperQueryParts);

            return sortedList;
        },
        createActiveTask = function (id) {
            var task = _.find(this.taskCollection, function (task) {
                return task.id === parseInt(id, 10);
            });

            if (!task) {
                throw new InvalidOperationError("Task not available.");
            }

            this.activeTaskCollection.push(TimeEntry.createInstance(task.id, task.subject, task.project.name, getTaskUrl.call(this)));
            this.activeTaskCollection = _.sortBy(this.activeTaskCollection, 'projectName');
        },
        removeActiveTask = function (timeEntryId) {
            _.remove(this.activeTaskCollection, function (entry) {
                return entry.id === timeEntryId;
            });
        },
        clearActiveTaskCollection = function () {
            this.activeTaskCollection = [];
        },
        updateActiveTaskHours = function (timeEntryId, hours) {
            var entry = _.find(this.activeTaskCollection, { 'id': timeEntryId });
            entry.setHours(hours);
        },
        updateActiveTaskActivityId = function (timeEntryId, activityId) {
            var entry = _.find(this.activeTaskCollection, { 'id': timeEntryId });
            entry.setActivityId(activityId);
        },
        updateActiveTaskComments = function (timeEntryId, comments) {
            var entry = _.find(this.activeTaskCollection, { 'id': timeEntryId });
            entry.setComments(comments);
        },
        postUpdatedActiveTaskCollection = function (spentOn) {
            if (!spentOn || !spentOn._isAMomentObject) {
                throw new InvalidArgumentError("Parameter spentOn must be a moment object.");
            }

            var deferred = $.Deferred();
            this.timePostedTaskCollection = _.filter(this.activeTaskCollection, function (entry) {
                return entry.updated;
            });
            $.when(this.serviceAccessor.createTimeEntries(this.timePostedTaskCollection, spentOn)).done(function () {
                this.timePostedTaskCollection.map(function (timeEntry) {
                    timeEntry.clearTimeEntry();
                });
                deferred.resolve();
            }.bind(this)).fail(function () {
                deferred.reject(StoreError.TimeEntryFailure);
            }.bind(this));
            return deferred.promise();
        },
        getPostedTaskCollection = function () {
            return _.pluck(this.timePostedTaskCollection, 'issueId');
        },
        createActiveTaskCollection = function (taskIdCollection) {
            if (typeof taskIdCollection === "Array") {
                throw new InvalidArgumentError("Parameter taskIdCollection must be an array.");
            }

            taskIdCollection.map(function (taskId) {
                var task = _.find(this.taskCollection, function (task) {
                    return task.id === parseInt(taskId, 10);
                });

                if (!task) {
                    return;
                }

                this.activeTaskCollection.push(TimeEntry.createInstance(taskId, task.subject, task.project.name, getTaskUrl.call(this)));
            }.bind(this));

            this.activeTaskCollection = _.sortBy(this.activeTaskCollection, 'projectName');
        };
    return {
        fetchData: fetchData,
        fetchLatest: fetchLatest,
        filterTaskCollection: filterTaskCollection,
        createActiveTask: createActiveTask,
        updateActiveTaskHours: updateActiveTaskHours,
        updateActiveTaskActivityId: updateActiveTaskActivityId,
        updateActiveTaskComments: updateActiveTaskComments,
        postUpdatedActiveTaskCollection: postUpdatedActiveTaskCollection,
        removeActiveTask: removeActiveTask,
        clearActiveTaskCollection: clearActiveTaskCollection,
        getPostedTaskCollection: getPostedTaskCollection,
        createActiveTaskCollection: createActiveTaskCollection
    };
}());

module.exports = DataManager;<|endoftext|>


<commit_before>'use strict';

Darwinator.MeleeWeapon = function (game, coolDown, damage, owner) {
    Darwinator.Weapon.call(this, game, coolDown, damage, owner);

    // nothing permanent here, just some values to get started with implementation
    this.coolDown           = 250;
    this.damage             = this.owner.damage;
    this.knockBackDistance  = 100 + 2 * this.owner.attributes.strength;
    this.knockBackCooldown  = 2000;
    this.lastKnockBack      = 0;
};

Darwinator.MeleeWeapon.prototype = Object.create(Darwinator.Weapon.prototype);

Darwinator.MeleeWeapon.prototype.strike = function(target){
    var dmg = 0;
    if (!this.onCooldown()){
        var crit    = Math.random() - this.criticalStrike;
        dmg         = this.damage;

        if (crit < 0){
            dmg *= 2;
        }
        target.takeDamage(dmg);
        this.lastAttack = Date.now();
        this.tryKnockBack(target);
    }
    return dmg;
};

Darwinator.MeleeWeapon.prototype.tryKnockBack = function(target){
    if(Date.now() - this.lastKnockBack >= this.knockBackCooldown){
        var angle           = this.game.physics.arcade.angleBetween(this.owner, target);
        var targetFuturePos = Darwinator.Helpers.calculatePosition(target.x, target.y, this.knockBackDistance, angle);
        target.knockedBack  = true;
        target.knockBackPos = targetFuturePos;
        this.lastKnockBack  = Date.now();
    }
};<commit_msg>damageDone now correctly collected by melee weapons<commit_after>'use strict';

Darwinator.MeleeWeapon = function (game, coolDown, damage, owner) {
    Darwinator.Weapon.call(this, game, coolDown, damage, owner);

    // nothing permanent here, just some values to get started with implementation
    this.coolDown           = 250;
    this.damage             = this.owner.damage;
    this.knockBackDistance  = 100 + 2 * this.owner.attributes.strength;
    this.knockBackCooldown  = 2000;
    this.lastKnockBack      = 0;
};

Darwinator.MeleeWeapon.prototype = Object.create(Darwinator.Weapon.prototype);

Darwinator.MeleeWeapon.prototype.strike = function(target){
    var dmg = 0;
    if (!this.onCooldown()){
        var crit    = Math.random() - this.criticalStrike;
        dmg         = this.damage;

        if (crit < 0){
            dmg *= 2;
        }
        target.takeDamage(dmg);
        this.owner.damageDone += dmg;
        this.lastAttack = Date.now();
        this.tryKnockBack(target);
    }
    return dmg;
};

Darwinator.MeleeWeapon.prototype.tryKnockBack = function(target){
    if(Date.now() - this.lastKnockBack >= this.knockBackCooldown){
        var angle           = this.game.physics.arcade.angleBetween(this.owner, target);
        var targetFuturePos = Darwinator.Helpers.calculatePosition(target.x, target.y, this.knockBackDistance, angle);
        target.knockedBack  = true;
        target.knockBackPos = targetFuturePos;
        this.lastKnockBack  = Date.now();
    }
};<|endoftext|>


<commit_before>import * as array from 'd3-array'
// import * as scale from 'd3-scale'
// import * as shape from 'd3-shape'
import PropTypes from 'prop-types'
import React, { PureComponent } from 'react'
import { View } from 'react-native'
import Svg from 'react-native-svg'
import Path from '../animated-path'
import Chart from './chart'

class ChartGrouped extends PureComponent {

    state = {
        width: 0,
        height: 0,
    }

    _onLayout(event) {
        const { nativeEvent: { layout: { height, width } } } = event
        this.setState({ height, width })
    }

    createPaths() {
        throw 'Extending "ChartGrouped" requires you to override "createPaths'
    }

    render() {

        const {
            data,
            xAccessor,
            yAccessor,
            yScale,
            xScale,
            style,
            animate,
            animationDuration,
            numberOfTicks,
            contentInset: {
                top = 0,
                bottom = 0,
                left = 0,
                right = 0,
            },
            gridMax,
            gridMin,
            clampX,
            clampY,
            svg,
            children,
        } = this.props

        const { width, height } = this.state

        if (data.length === 0) {
            return <View style={ style } />
        }

        const mappedData = data.map((dataArray) => dataArray.data.map((item, index) => ({
            y: yAccessor({ item, index }),
            x: xAccessor({ item, index }),
        })))

        const yValues = array.merge(mappedData).map(item => item.y)
        const xValues = array.merge(mappedData).map(item => item.x)

        const yExtent = array.extent([ ...yValues, gridMin, gridMax ])
        const xExtent = array.extent([ ...xValues ])

        const {
            yMin = yExtent[0],
            yMax = yExtent[1],
            xMin = xExtent[0],
            xMax = xExtent[1],
        } = this.props

        //invert range to support svg coordinate system
        const y = yScale()
            .domain([ yMin, yMax ])
            .range([ height - bottom, top ])
            .clamp(clampY)

        const x = xScale()
            .domain([ xMin, xMax ])
            .range([ left, width - right ])
            .clamp(clampX)

        const paths = this.createPaths({
            data: mappedData,
            x,
            y,
        })

        const ticks = y.ticks(numberOfTicks)

        const extraProps = {
            x,
            y,
            data,
            ticks,
            width,
            height,
            ...paths,
        }

        return (
            <View style={ style }>
                <View style={{ flex: 1 }} onLayout={ event => this._onLayout(event) }>
                    {
                        height > 0 && width > 0 &&
                        <Svg style={{ height, width }}>
                            {
                                React.Children.map(children, child => {
                                    if (child && child.props.belowChart) {
                                        return React.cloneElement(child, extraProps)
                                    }
                                    return null
                                })
                            }
                            {paths.path.map((path, index) => {
                                const { svg: pathSvg } = data[index]
                                return (
                                    <Path
                                        key={ path }
                                        fill={ 'none' }
                                        { ...svg }
                                        { ...pathSvg }
                                        d={ path }
                                        animate={ animate }
                                        animationDuration={ animationDuration }
                                    />)
                            })}
                            {
                                React.Children.map(children, child => {
                                    if (child && !child.props.belowChart) {
                                        return React.cloneElement(child, extraProps)
                                    }
                                    return null
                                })
                            }
                        </Svg>
                    }
                </View>
            </View>
        )
    }
}

ChartGrouped.propTypes = {
    ...Chart.propTypes,
    data: PropTypes.arrayOf(PropTypes.shape({
        data: PropTypes.oneOfType([
            PropTypes.arrayOf(PropTypes.object),
            PropTypes.arrayOf(PropTypes.number),
            PropTypes.arrayOf(PropTypes.array),
        ]),
        svg: PropTypes.object,
    })).isRequired,
}

ChartGrouped.defaultProps = {
    ...Chart.defaultProps,
}

export default ChartGrouped
<commit_msg>Remove comments<commit_after>import * as array from 'd3-array'
import PropTypes from 'prop-types'
import React, { PureComponent } from 'react'
import { View } from 'react-native'
import Svg from 'react-native-svg'
import Path from '../animated-path'
import Chart from './chart'

class ChartGrouped extends PureComponent {

    state = {
        width: 0,
        height: 0,
    }

    _onLayout(event) {
        const { nativeEvent: { layout: { height, width } } } = event
        this.setState({ height, width })
    }

    createPaths() {
        throw 'Extending "ChartGrouped" requires you to override "createPaths'
    }

    render() {

        const {
            data,
            xAccessor,
            yAccessor,
            yScale,
            xScale,
            style,
            animate,
            animationDuration,
            numberOfTicks,
            contentInset: {
                top = 0,
                bottom = 0,
                left = 0,
                right = 0,
            },
            gridMax,
            gridMin,
            clampX,
            clampY,
            svg,
            children,
        } = this.props

        const { width, height } = this.state

        if (data.length === 0) {
            return <View style={ style } />
        }

        const mappedData = data.map((dataArray) => dataArray.data.map((item, index) => ({
            y: yAccessor({ item, index }),
            x: xAccessor({ item, index }),
        })))

        const yValues = array.merge(mappedData).map(item => item.y)
        const xValues = array.merge(mappedData).map(item => item.x)

        const yExtent = array.extent([ ...yValues, gridMin, gridMax ])
        const xExtent = array.extent([ ...xValues ])

        const {
            yMin = yExtent[0],
            yMax = yExtent[1],
            xMin = xExtent[0],
            xMax = xExtent[1],
        } = this.props

        //invert range to support svg coordinate system
        const y = yScale()
            .domain([ yMin, yMax ])
            .range([ height - bottom, top ])
            .clamp(clampY)

        const x = xScale()
            .domain([ xMin, xMax ])
            .range([ left, width - right ])
            .clamp(clampX)

        const paths = this.createPaths({
            data: mappedData,
            x,
            y,
        })

        const ticks = y.ticks(numberOfTicks)

        const extraProps = {
            x,
            y,
            data,
            ticks,
            width,
            height,
            ...paths,
        }

        return (
            <View style={ style }>
                <View style={{ flex: 1 }} onLayout={ event => this._onLayout(event) }>
                    {
                        height > 0 && width > 0 &&
                        <Svg style={{ height, width }}>
                            {
                                React.Children.map(children, child => {
                                    if (child && child.props.belowChart) {
                                        return React.cloneElement(child, extraProps)
                                    }
                                    return null
                                })
                            }
                            {paths.path.map((path, index) => {
                                const { svg: pathSvg } = data[index]
                                return (
                                    <Path
                                        key={ path }
                                        fill={ 'none' }
                                        { ...svg }
                                        { ...pathSvg }
                                        d={ path }
                                        animate={ animate }
                                        animationDuration={ animationDuration }
                                    />)
                            })}
                            {
                                React.Children.map(children, child => {
                                    if (child && !child.props.belowChart) {
                                        return React.cloneElement(child, extraProps)
                                    }
                                    return null
                                })
                            }
                        </Svg>
                    }
                </View>
            </View>
        )
    }
}

ChartGrouped.propTypes = {
    ...Chart.propTypes,
    data: PropTypes.arrayOf(PropTypes.shape({
        data: PropTypes.oneOfType([
            PropTypes.arrayOf(PropTypes.object),
            PropTypes.arrayOf(PropTypes.number),
            PropTypes.arrayOf(PropTypes.array),
        ]),
        svg: PropTypes.object,
    })).isRequired,
}

ChartGrouped.defaultProps = {
    ...Chart.defaultProps,
}

export default ChartGrouped
<|endoftext|>


<commit_before>"use strict";

/*eslint no-unused-vars: 0*/

var React = require("react");
var classNames = require("classnames");
var Container = require("./layout/Container");
var mixin = require("../util/mixin");

var Divider = React.createClass({
	// Component API
	propTypes: {
		width: React.PropTypes.oneOfType([
			React.PropTypes.number,
			React.PropTypes.string
		]),
		thickness: React.PropTypes.number,
		color: React.PropTypes.string,
		className: React.PropTypes.string,
		wrapper: React.PropTypes.shape({
			className: React.PropTypes.string,
			style: React.PropTypes.object
		})
	},
	getDefaultProps: function () {
		return {
			thickness: 1,
			color: "#eaeaea",
			width: "100%",
			wrapper: {}
		};
	},
	render: function () {
		var containerProps = this.getContainerProps();

		return (
			<Container {...containerProps} />
		);
	},
	// Private API
	getContainerProps: function () {
		return {
			cssPrefix: "divider",
			width: this.props.width,
			align: "center",
			className: this.props.className,
			wrapper: {
				align: "center",
				width: this.props.width,
				height: this.props.thickness,
				bgColor: this.props.color,
				className: classNames(this.props.wrapper.className),
				style: mixin({}, this.props.wrapper.style || {}, {
					fontSize: 1,
					lineHeight: 1,
					msoLineHeightRule: "exactly"
				})
			}
		};
	}
	// Public API
});

module.exports = Divider;
<commit_msg>Resolve line height style in Divider component<commit_after>"use strict";

/*eslint no-unused-vars: 0*/

var React = require("react");
var classNames = require("classnames");
var Container = require("./layout/Container");
var mixin = require("../util/mixin");

var Divider = React.createClass({
	// Component API
	propTypes: {
		width: React.PropTypes.oneOfType([
			React.PropTypes.number,
			React.PropTypes.string
		]),
		thickness: React.PropTypes.number,
		color: React.PropTypes.string,
		className: React.PropTypes.string,
		wrapper: React.PropTypes.shape({
			className: React.PropTypes.string,
			style: React.PropTypes.object
		})
	},
	getDefaultProps: function () {
		return {
			thickness: 1,
			color: "#eaeaea",
			width: "100%",
			wrapper: {}
		};
	},
	render: function () {
		var containerProps = this.getContainerProps();

		return (
			<Container {...containerProps} />
		);
	},
	// Private API
	getContainerProps: function () {
		return {
			cssPrefix: "divider",
			width: this.props.width,
			align: "center",
			className: this.props.className,
			wrapper: {
				align: "center",
				width: this.props.width,
				height: this.props.thickness,
				bgColor: this.props.color,
				className: classNames(this.props.wrapper.className),
				style: mixin({}, this.props.wrapper.style || {}, {
					fontSize: 1,
					lineHeight: '1px',
					msoLineHeightRule: "exactly"
				})
			}
		};
	}
	// Public API
});

module.exports = Divider;
<|endoftext|>


<commit_before>// Based on https://drafts.csswg.org/css-values-4/#value-defs

const primitives = new Map([
  ["custom-ident", {url: "https://drafts.csswg.org/css-values-4/#custom-idents"}],
  ["string", {url: "https://drafts.csswg.org/css-values-4/#strings"}],
  ["url", {url: "https://drafts.csswg.org/css-values-4/#urls"}],
  ["integer", {url: "https://drafts.csswg.org/css-values-4/#integers"}],
  ["number", {url: "https://drafts.csswg.org/css-values-4/#numbers"}],
  ["percentage", {url: "https://drafts.csswg.org/css-values-4/#percentages"}],
  ["length-percentage", {url: "https://drafts.csswg.org/css-values-4/#length-percentage"}],
  ["frequency-percentage", {url: "https://drafts.csswg.org/css-values-4/#frequency-percentage"}],
  ["angle-percentage", {url: "https://drafts.csswg.org/css-values-4/#angle-percentage"}],
  ["time-percentage", {url: "https://drafts.csswg.org/css-values-4/#time-percentage"}],
  ["dimension", {url: "https://drafts.csswg.org/css-values-4/#dimensions"}],
  ["length", {url: "https://drafts.csswg.org/css-values-4/#lengths"}],
  ["angle", {url: "https://drafts.csswg.org/css-values-4/#angles"}],
  ["time", {url: "https://drafts.csswg.org/css-values-4/#time"}],
  ["frequency", {url: "https://drafts.csswg.org/css-values-4/#frequency"}],
  ["resolution", {url: "https://drafts.csswg.org/css-values-4/#frequency"}],
  ["color", {url: "https://drafts.csswg.org/css-color-3/#valuea-def-color"}],
  ["image", {url: "https://drafts.csswg.org/css-images-3/#typedef-image"}],
  ["position", {url: "https://drafts.csswg.org/css-values-4/#typedef-position"}]
]);

const combinatorsMap = [['&&', 'allOf'],
                        ['||', 'anyOf'],
                        ['|', 'oneOf']];

const unquotedTokens = ['/', ',', '(', ')'];

const componentizeByCombinators = (parts, combinators = new Map(combinatorsMap)) => {
  const res = {};
  let combinatorFound = false;
  const combinatorIterator = combinators.entries();
  while (!combinatorFound) {
    const {value: entry, done} = combinatorIterator.next();
    if (done) break;
    const [c,t] = entry;
    if (Array.isArray(parts) && parts.includes(c)) {
      combinatorFound = true;
      // going down into the list of combinators by order of precedence
      const lowerCombinators = new Map(combinators);
      lowerCombinators.delete(c);
      let components = splitByCombinator(parts, c);
      res[t] = components.map(p => componentizeByCombinators(p, lowerCombinators));
    }
  }
  if (!combinatorFound) {
    if (Array.isArray(parts)) {
      if (parts.length > 1) {
        return {type: "array", items: parts.map(p => componentizeByCombinators(p))};
      } else {
        return componentizeByCombinators(parts[0]);
      }
    }
    if (parts && parts.type && parts.type === "array")
      return {...parts, items: componentizeByCombinators(parts.items)};
    return parts;
  }
  return res;
};

const splitByCombinator = (parts, combinator) => {
  const {components} = parts.reduce((a, b, i) => {
    if (b === combinator) {
      a.components.push(a.head.length === 1 ? a.head[0] : a.head);
      a.head = [];
    } else {
      if (Array.isArray(b)) {
        a.head.push(componentizeByCombinators(b));
      } else {
        a.head.push(b);
      }
    }
    if (i === parts.length - 1) {
      a.components.push(a.head.length === 1 ? a.head[0] : a.head);
    }
    return a;
  }, {head: [], components: []});
  return components;
};

const parseMultiplierRange = range => {
  if (range[0] !== '{')
    throw new Error(`Expected { at the start of multiplier range ${range}`);
  if (range[range.length - 1] !== '}')
    throw new Error(`Expected } at the end of multiplier range ${range}`);
  const values = range.slice(1,range.length - 1);
  if (values.match(/^[0-9]+$/)) {
    return {minItems: parseInt(values, 10), maxItems: parseInt(values, 10)};
  } else if (values.match(/^[0-9]+,[0-9]+$/)) {
    const [min,max] = values.split(',');
    return {minItems: parseInt(min, 10), maxItems: parseInt(max, 10)};
  } else {
    throw new Error(`Unrecognized range format in multiplier ${range}`);
  }
};

const parseMultiplier = (multiplier, modifiee) => {
  let ret;
  let multiplierLength = 1;
  if (multiplier === '') {
    return modifiee;
  } else if (multiplier[0] === '*') {
    ret = {
      type: "array",
      items: modifiee
    };
  } else if (multiplier[0] === '+') {
    ret = {
      type: "array",
      items: modifiee,
      minItems: 1
    };
  } else if (multiplier[0] === '#') {
      ret =  {
        type: "array",
        items: modifiee,
        separator: ","
      };
  } else if (multiplier.startsWith('{')) {
    multiplierLength = (multiplier.match(/^(\{[^\}]*\})/)[1] || "{").length;
    ret =  {
      type: "array",
      items: modifiee,
      ...parseMultiplierRange(multiplier)
    };
  } else if (multiplier[0] === '?') {
    if (Array.isArray(modifiee)) {
      ret =  {type: "array", items: modifiee, maxItems: 1};
    } else {
      ret = {...modifiee, optional: true};
    }
  } else if (multiplier[0] === '!') {
    if (Array.isArray(modifiee)) {
      ret =  {type: "array", items: modifiee, minItems: 1};
    } else {
      throw new Error(`Multiplier "!" applied to non-group ${modifiee}`);
    }
  } else {
    throw new Error(`Unrecognized multiplier ${multiplier}`);
  }
  return parseMultiplier(multiplier.slice(multiplierLength), ret);
};

const parseTerminals = s => {
  let m;
  let multiplier = '';
  let modifiee = s;
  if ((m = s.match(/([\+\?\*#\!]+)$/))) {
    multiplier = m[1];
    modifiee = s.slice(0, s.length - m[1].length);
  } else if ((m = s.match(/^(.*)(#?\{.*\})$/))) {
    multiplier = m[2];
    modifiee = m[1];
  }
  if ([...new Map(combinatorsMap).keys()].includes(s) || s === '[' || s.startsWith(']')) {
    return s;
  } else if (unquotedTokens.includes(s)) {
    return {type: "string", content: s};
  } else if ((m = s.match(/^\'([^\']*)\'$/))) {
    return {type: "string", content: m[1]};
  } else if ((m = modifiee.match(/^<\'([-_a-zA-Z][^\'>]*)\'>$/))) {
    return parseMultiplier(multiplier, {type: "propertyref", name: m[1]});
  } else if ([...primitives.keys()].map(p => "<" + p + ">").includes(modifiee)) {
    return parseMultiplier(multiplier, {type: "primitive", name: modifiee.slice(1, modifiee.length -1)});
  } else if ((m = modifiee.match(/^<[-_a-zA-Z]([^>]*)>$/))) {
    return parseMultiplier(multiplier, {type: "valuespace", name: modifiee.slice(1, modifiee.length -1)});
  } else if ((m = modifiee.match(/^[-_a-zA-Z]/))) {
    return parseMultiplier(multiplier, {type: "keyword", name: modifiee});
  } else {
    throw new Error(`Unrecognized token ${s}`);
  }
};

const parsePropDefValue = (value) => {
  value = value.trim();
  // TODO: whitespace normalization?

  // Not sure if combinators are supposed to be white-space separated from their content
  // but forcing it for now
  value = value.replace(/\[/g, '[ ')
    .replace(/\]/g, ' ]')
    .replace(/ #/g, '#')
    .replace(/>\|/g, '> |')
    .replace(/\],/g, '] ,')
    .replace(/\]\?,/g, ']? ,');

  let parts = value.split(' ').filter(x => x)
        .map(parseTerminals);

  // matching bracket-groups
  while(parts.lastIndexOf('[') !== -1) {
    const bracketIdx = parts.lastIndexOf('[');

    // closing bracket may be more than just ']'
    // since it can be accompanied with multipliers
    const matchingBracketIdx = parts.findIndex((p, i) => typeof(p)==="string" && p.startsWith(']') && i > bracketIdx);

    if (matchingBracketIdx <= bracketIdx) {
      throw new Error(`Unexpected closing bracket in ${value}`);
    }
    if (matchingBracketIdx === -1) {
      throw new Error(`Unterminated bracket-group in ${value}`);
    }
    const group = parts.slice(bracketIdx + 1, matchingBracketIdx);
    const multiplier = parts[matchingBracketIdx].slice(1);
    const multipliedGroup = multiplier ? parseMultiplier(multiplier, group) : [ group ] ;
    parts = parts.slice(0, bracketIdx)
      .concat(multipliedGroup)
      .concat(parts.slice(matchingBracketIdx + 1));
  }
  const res = componentizeByCombinators(parts);
  return res.length === 1 ? res[0] : res;
};

module.exports.parsePropDefValue =  parsePropDefValue;
<commit_msg>Fix bugs identified in parsing CSS definitions in the wild<commit_after>// Based on https://drafts.csswg.org/css-values-4/#value-defs

const primitives = new Map([
  ["custom-ident", {url: "https://drafts.csswg.org/css-values-4/#custom-idents"}],
  ["string", {url: "https://drafts.csswg.org/css-values-4/#strings"}],
  ["url", {url: "https://drafts.csswg.org/css-values-4/#urls"}],
  ["integer", {url: "https://drafts.csswg.org/css-values-4/#integers"}],
  ["number", {url: "https://drafts.csswg.org/css-values-4/#numbers"}],
  ["percentage", {url: "https://drafts.csswg.org/css-values-4/#percentages"}],
  ["length-percentage", {url: "https://drafts.csswg.org/css-values-4/#length-percentage"}],
  ["frequency-percentage", {url: "https://drafts.csswg.org/css-values-4/#frequency-percentage"}],
  ["angle-percentage", {url: "https://drafts.csswg.org/css-values-4/#angle-percentage"}],
  ["time-percentage", {url: "https://drafts.csswg.org/css-values-4/#time-percentage"}],
  ["dimension", {url: "https://drafts.csswg.org/css-values-4/#dimensions"}],
  ["length", {url: "https://drafts.csswg.org/css-values-4/#lengths"}],
  ["angle", {url: "https://drafts.csswg.org/css-values-4/#angles"}],
  ["time", {url: "https://drafts.csswg.org/css-values-4/#time"}],
  ["frequency", {url: "https://drafts.csswg.org/css-values-4/#frequency"}],
  ["resolution", {url: "https://drafts.csswg.org/css-values-4/#frequency"}],
  ["color", {url: "https://drafts.csswg.org/css-color-3/#valuea-def-color"}],
  ["image", {url: "https://drafts.csswg.org/css-images-3/#typedef-image"}],
  ["position", {url: "https://drafts.csswg.org/css-values-4/#typedef-position"}]
]);

const combinatorsMap = [['&&', 'allOf'],
                        ['||', 'anyOf'],
                        ['|', 'oneOf']];

const unquotedTokens = ['/', ',', '(', ')'];

const componentizeByCombinators = (parts, combinators = new Map(combinatorsMap)) => {
  const res = {};
  let combinatorFound = false;
  const combinatorIterator = combinators.entries();
  while (!combinatorFound) {
    const {value: entry, done} = combinatorIterator.next();
    if (done) break;
    const [c,t] = entry;
    if (Array.isArray(parts) && parts.includes(c)) {
      combinatorFound = true;
      // going down into the list of combinators by order of precedence
      const lowerCombinators = new Map(combinators);
      lowerCombinators.delete(c);
      let components = splitByCombinator(parts, c);
      res[t] = components.map(p => componentizeByCombinators(p, lowerCombinators));
    }
  }
  if (!combinatorFound) {
    if (Array.isArray(parts)) {
      if (parts.length > 1) {
        return {type: "array", items: parts.map(p => componentizeByCombinators(p))};
      } else {
        return componentizeByCombinators(parts[0]);
      }
    }
    if (parts && parts.type && parts.type === "array")
      return {...parts, items: componentizeByCombinators(parts.items)};
    return parts;
  }
  return res;
};

const splitByCombinator = (parts, combinator) => {
  const {components} = parts.reduce((a, b, i) => {
    if (b === combinator) {
      a.components.push(a.head.length === 1 ? a.head[0] : a.head);
      a.head = [];
    } else {
      if (Array.isArray(b)) {
        a.head.push(componentizeByCombinators(b));
      } else {
        a.head.push(b);
      }
    }
    if (i === parts.length - 1) {
      a.components.push(a.head.length === 1 ? a.head[0] : a.head);
    }
    return a;
  }, {head: [], components: []});
  return components;
};

const parseMultiplierRange = range => {
  if (range[0] !== '{')
    throw new Error(`Expected { at the start of multiplier range ${range}`);
  if (range[range.length - 1] !== '}')
    throw new Error(`Expected } at the end of multiplier range ${range}`);
  const values = range.slice(1,range.length - 1);
  if (values.match(/^[0-9]+$/)) {
    return {minItems: parseInt(values, 10), maxItems: parseInt(values, 10)};
  } else if (values.match(/^[0-9]+,[0-9]+$/)) {
    const [min,max] = values.split(',');
    return {minItems: parseInt(min, 10), maxItems: parseInt(max, 10)};
  } else {
    throw new Error(`Unrecognized range format in multiplier ${range}`);
  }
};

const parseMultiplier = (multiplier, modifiee) => {
  let ret;
  let multiplierLength = 1;
  if (multiplier === '') {
    return modifiee;
  } else if (multiplier[0] === '*') {
    ret = {
      type: "array",
      items: modifiee
    };
  } else if (multiplier[0] === '+') {
    ret = {
      type: "array",
      items: modifiee,
      minItems: 1
    };
  } else if (multiplier[0] === '#') {
      ret =  {
        type: "array",
        items: modifiee,
        separator: ","
      };
  } else if (multiplier.startsWith('{')) {
    multiplierLength = (multiplier.match(/^(\{[^\}]*\})/)[1] || "{").length;
    ret =  {
      type: "array",
      items: modifiee,
      ...parseMultiplierRange(multiplier)
    };
  } else if (multiplier[0] === '?') {
    if (Array.isArray(modifiee)) {
      ret =  {type: "array", items: modifiee, maxItems: 1};
    } else {
      ret = {...modifiee, optional: true};
    }
  } else if (multiplier[0] === '!') {
    if (Array.isArray(modifiee)) {
      ret =  {type: "array", items: modifiee, minItems: 1};
    } else {
      throw new Error(`Multiplier "!" applied to non-group ${modifiee}`);
    }
  } else {
    throw new Error(`Unrecognized multiplier ${multiplier}`);
  }
  return parseMultiplier(multiplier.slice(multiplierLength), ret);
};

const parseTerminals = s => {
  let m;
  let multiplier = '';
  let modifiee = s;
  if ((m = s.match(/([\+\?\*#\!]+)$/))) {
    multiplier = m[1];
    modifiee = s.slice(0, s.length - m[1].length);
  } else if ((m = s.match(/^([^#]*)(#?\{.*\})$/))) {
    multiplier = m[2];
    modifiee = m[1];
  }
  if ([...new Map(combinatorsMap).keys()].includes(s) || s === '[' || s.startsWith(']')) {
    return s;
  } else if (unquotedTokens.includes(s)) {
    return {type: "string", content: s};
  } else if ((m = modifiee.match(/^\'([^\']*)\'$/))) {
    return parseMultiplier(multiplier, {type: "string", content: m[1]});
  } else if ((m = modifiee.match(/^<\'([-_a-zA-Z][^\'>]*)\'>$/))) {
    return parseMultiplier(multiplier, {type: "propertyref", name: m[1]});
  } else if ([...primitives.keys()].map(p => "<" + p + ">").includes(modifiee)) {
    return parseMultiplier(multiplier, {type: "primitive", name: modifiee.slice(1, modifiee.length -1)});
  } else if ((m = modifiee.match(/^<[-_a-zA-Z]([^>]*)>$/))) {
    return parseMultiplier(multiplier, {type: "valuespace", name: modifiee.slice(1, modifiee.length -1)});
  } else if ((m = modifiee.match(/^[-_a-zA-Z][-_a-zA-Z0-9]*$/))) {
    return parseMultiplier(multiplier, {type: "keyword", name: modifiee});
  } else { // TODO: add support for functional notations https://drafts.csswg.org/css-values-4/#functional-notation even though they're not recognized as top-level items in the grammar
    throw new Error(`Unrecognized token ${s}`);
  }
};

const parsePropDefValue = (value) => {
  value = value.trim();
  // TODO: whitespace normalization?

  // This is a kludge as shortcut: instead of doing a proper tokenization,
  // we use white space as our token delimiter
  // and regexp-adapt cases that don't quite fit that assumption
  value = value.replace(/\]\]/g, ' ] ] ')
    .replace(/\[\[/g, ' [ [ ')
    .replace(/>\[/g, '> [ ')
    .replace(/\[([^'])/g, '[ $1')
    .replace(/([^'])\]/g, '$1 ]')
    .replace(/ #/g, '#')
    .replace(/>\|/g, '> |')
      .replace(/\|</g, '| <')
    .replace(/\],/g, '] ,')
    .replace(/\]\?,/g, ']? ,')
    .replace(/\] \?,/g, ']? ,');

  let parts = value.split(' ').filter(x => x)
        .map(parseTerminals);

  // matching bracket-groups
  while(parts.lastIndexOf('[') !== -1) {
    const bracketIdx = parts.lastIndexOf('[');

    // closing bracket may be more than just ']'
    // since it can be accompanied with multipliers
    const matchingBracketIdx = parts.findIndex((p, i) => typeof(p)==="string" && p.startsWith(']') && i > bracketIdx);

    if (matchingBracketIdx === -1) {
      console.log(value, bracketIdx, matchingBracketIdx);
      throw new Error(`Unterminated bracket-group in ${value}`);
    }
    const group = parts.slice(bracketIdx + 1, matchingBracketIdx);
    const multiplier = parts[matchingBracketIdx].slice(1);
    const multipliedGroup = multiplier && !multiplier.startsWith(']') ? parseMultiplier(multiplier, group) : [ group ] ;
    parts = parts.slice(0, bracketIdx)
      .concat(multipliedGroup)
      .concat(parts.slice(matchingBracketIdx + 1));
  }
  const res = componentizeByCombinators(parts);
  return res.length === 1 ? res[0] : res;
};

module.exports.parsePropDefValue =  parsePropDefValue;
<|endoftext|>


<commit_before>function Key(data) {
  //console.log("Initializing key");
  this.data = data;
  this.selectedCharacters = [];
  this.allowUnscored = true;
  this.unmatchedTaxa = [];
  this.matchedTaxa = this.data.taxa;
  this.characterTree = this.data.characterTree;
  this.descriptiveConcepts = this.data.descriptiveConcepts;
  this.imagePath = data.imagePath;
  this.taxonPath = data.taxonPath;
  this.updateUI = function(key) { };
  this.autoPrune = false;
}

Key.prototype.reset = function() {
    this.selectedCharacters = [];
    this.matchedTaxa = this.data.taxa;
    this.unmatchedTaxa = [];
};

Key.prototype.getCharacterTree = function() {
    return this.characterTree;
};

Key.prototype.setImagePath = function(imagePath) {
    this.imagePath = imagePath;
};

Key.prototype.setTaxonPath = function(taxonPath) {
    this.taxonPath = taxonPath;
};

Key.prototype.getImagePath = function() {
    return this.imagePath;
};

Key.prototype.getTaxonPath = function() {
    return this.taxonPath;
};

Key.prototype.setAllowUnscored = function(allow) {
    this.allowUnscored = allow;
};

Key.prototype.setAutoPrune = function(prune) {
    this.autoPrune = prune;
};

Key.prototype.getSelectedCharacters = function() {
    this.selectedCharacters.sort(function comparator(obj1, obj2){
      return obj1.id - obj2.id; });
    return this.selectedCharacters;
};

Key.prototype.getMatchedTaxa = function() {
    return this.matchedTaxa;
};

Key.prototype.getUnmatchedTaxa = function() {
    return this.unmatchedTaxa;
};

Key.prototype.calculate = function() {
    //console.log("Key.calculate");
    this.matchedTaxa = [];
    this.unmatchedTaxa = [];
    if ( this.selectedCharacters && this.selectedCharacters.length > 0 ) {
        this.calculateList();
        if(this.autoPrune) {
          this.calculateRedundant();
        }
    } else {
        //console.log("No Matched Taxa");
	this.matchedTaxa = this.data.taxa;
	this.unmatchedTaxa = [];
    }

    this.updateUI(this);
};

Key.prototype.pruneRedundants = function() {
    //console.log("Key.pruneRedundants");
    this.matchedTaxa = [];
    this.unmatchedTaxa = [];
    if ( this.selectedCharacters && this.selectedCharacters.length > 0 ) {
        this.calculateList();
        this.calculateRedundant();
    } else {
        //console.log("No Matched Taxa");
	    this.matchedTaxa = this.data.taxa;
	    this.unmatchedTaxa = [];
    }

    this.updateUI(this);
}

Key.prototype.calculateRedundant = function() {
    //console.log("Key.doPruneRedundant");
    if(this.matchedTaxa.length < 2) {
      return;
    }

    var unselectedCharacters = this.getUnselectedCharacters();
    for(var j=0; j < unselectedCharacters.length; j++) {
        var character = unselectedCharacters[j];
        //console.log("Checking " + character.name);
        var attributeValue1 = this.findTaxonAttribute(this.matchedTaxa[0], character);
        character.selectedValues = attributeValue1;
        var isRedundant = true;
        for(var i=1; i < this.matchedTaxa.length; i++) {
           var taxon = this.matchedTaxa[i];
           if (this.calculateOne(character, taxon)) {
		     isRedundant = false;
		     break;
	       }
        }
        character.selectedValues = [];
        if(isRedundant) {
          character.isRedundant = true;
        } else {
          character.isRedundant = false;
        }
    }
}

Key.prototype.getUnselectedCharacters = function() {
    var selectedCharacterHash = [];
    for (var i=0; i<this.selectedCharacters.length; i++) {
	var character = this.selectedCharacters[i];
	selectedCharacterHash[character.id] = character;
    }

    var unselectedCharacters = [];

    for (var i=0; i<this.data.characters.length; i++) {
	var character = this.data.characters[i];
	if ( selectedCharacterHash[character.id] ) {
	    continue;
	}
	unselectedCharacters.push(character);
    }
    return unselectedCharacters;
}

Key.prototype.setUpdateUI = function(updateFunction) {
	this.updateUI = updateFunction;
};

Key.prototype.getDescriptiveConcept = function(descriptiveConceptId) {
    var descriptiveConcept = this.data.descriptiveConcepts[descriptiveConceptId];
    return descriptiveConcept;
};

Key.prototype.getCharacter = function(characterId) {
    var character = this.data.characters[characterId];
    return character;
};

Key.prototype.getTaxon = function(taxonId) {
    var taxon = this.data.taxa[taxonId];
    return taxon;
};

Key.prototype.selectCharacter = function(characterId, value) {
	var character = this.data.characters[characterId];
        switch (character.type) {
	    case Key.Categorical:
            if(value.length == 0) {
                return;
            }
            character.selectedValues = value;

            break;
            // Quantitative
            default:
              if(value == "") {
                  return;
              }
              character.selectedValues = value;
            break;
            }
        this.selectedCharacters.push(character);

};

Key.prototype.unselectCharacter = function(characterId) {
    var newSelectedCharacters = [];
    for(var i = 0; i < this.selectedCharacters.length; i++) {
      var character = this.selectedCharacters[i];
      if(character.id != characterId) {
        newSelectedCharacters.push(character);
      } else {
  	character.selectedValues = null;
      }
    }
    this.selectedCharacters = newSelectedCharacters;
};

Key.prototype.calculateList = function() {
    //console.log("Key.calculateList");
    var numberOfTaxa = this.data.taxa.length;
    var characterLength = this.selectedCharacters.length;
	for (var i=0; i<numberOfTaxa; i++) {
		var taxon = this.data.taxa[i];
                //console.log("Checking " + taxon.name);
		var isMatch = true;
		for (var j=0; j<characterLength; j++) {
			var character = this.selectedCharacters[j];
			if ( !this.calculateOne(character, taxon)) {
				isMatch = false;
				break;
			}
		}
		if ( isMatch ) {
			this.matchedTaxa.push(taxon);
		} else {
			this.unmatchedTaxa.push(taxon);
		}
    }
};

Key.prototype.calculateOne = function(character, taxon) {
    //console.log("Key.calculateOne");
    var attributeValue = this.findTaxonAttribute(taxon, character);

    if ( !attributeValue ) {
    	if(this.allowUnscored) {
	        return true;
    	} else {
    		return false;
    	}
	}
	switch (character.type) {
	    case Key.Categorical:
           // console.log("Checking " + character.name + " " + character.selectedValues + " against " + taxon.name + " " + attributeValue);
	    var values;
	    if ( $.isArray(attributeValue) ) {
		    values = attributeValue;
	    } else {
		    values = [];
		    values[0] = attributeValue;
	    }
	    if ( attributeValue.length < 0) {
		    return false;
	    }

	    for (var i=0; i<character.selectedValues.length; i++) {
		   var selectedValue = character.selectedValues[i];
		   for (var j=0; j<values.length; j++) {
		       var attributeValue = values[j];
		       if ( selectedValue == attributeValue) {
			       return true;
		       }
		   }
	    }
	    break;
	    default:
        // Quantitative
	    var selectedValue = character.selectedValues;
	    if ( $.isArray(attributeValue) ) {
	        for (var i=0; i<attributeValue.length; i++) {
			    if ( selectedValue == attributeValue[i]) {
			        return true;
			    }
		    }
		} else if ( !Key.isUndefined(attributeValue.min)
                 && !Key.isUndefined(attributeValue.max)
                 && !Key.isUndefined(selectedValue.min)
                 && !Key.isUndefined(selectedValue.max) ) {
		    if ( selectedValue.min == attributeValue.min
                      && selectedValue.max == attributeValue.max) {
			    return true;
		    }
		} else if ( !Key.isUndefined(attributeValue.min)
                 && !Key.isUndefined(attributeValue.max) ) {
		    if ( selectedValue >= attributeValue.min
              && selectedValue <= attributeValue.max) {
			    return true;
		    }
		} else {
		    if ( Key.isString(attributeValue) ) {
			    if ( selectedValue.indexOf(attributeValue+'') > -1 ) {
				    return true;
			    }
		    } else {
			    if ( selectedValue == attributeValue ) {
			        return true;
			    }
		    }
		 }
		 break;
	}
	return false;
};

Key.isNull = function(value) {
	if (!value || value.length < 1) {
		return true;
	}
	return false;
};

Key.isUndefined = function(val) {
	return val === undefined;
};

Key.isString = function(val) {
	if (typeof val == "string") {
		return true;
	} else {
		return false;
	}
};

Key.parseFloat = function(val) {
	try {
		return parseFloat(val);
	} catch (e) {
	}
	return 0;
};

Key.prototype.findTaxonAttribute = function(taxon, character) {
    if ( Key.isNull(character.data) ) {
	    return;
    }

	var attributes = character.data;

	if (!attributes || attributes.length < 1 ) {
	    return null;
	}

	var index = taxon.id;
	var value = null;
	if ( index < attributes.length ) {
	    value = attributes[index];
	}
	if ( !value ) {
		return value;
	}

	if ( $.isArray(value) ) {
		return value;
	}
	if ( Key.isString(value) ) {
		if ( value.indexOf("-") > 0 ) {
			var words = value.split("-");
			if ( words && words.length > 1 ) {
				var object = new Object();
				object.min = Key.parseFloat(words[0]);
				object.max = Key.parseFloat(words[1]);
				return object;
			}
		}
	}
	return value;
};

Key.Categorical = "UM";
Key.Quantitative = "RN";
<commit_msg>Added jskey also<commit_after>function Key(data) {
  //console.log("Initializing key");
  this.data = data;
  this.selectedCharacters = [];
  this.allowUnscored = true;
  this.unmatchedTaxa = [];
  this.matchedTaxa = this.data.taxa;
  this.characterTree = this.data.characterTree;
  this.descriptiveConcepts = this.data.descriptiveConcepts;
  this.imagePath = data.imagePath;
  this.taxonPath = data.taxonPath;
  this.updateUI = function(key) { };
  this.autoPrune = false;
}

Key.prototype.reset = function() {
    this.selectedCharacters = [];
    this.matchedTaxa = this.data.taxa;
    this.unmatchedTaxa = [];
};

Key.prototype.getCharacterTree = function() {
    return this.characterTree;
};

Key.prototype.setImagePath = function(imagePath) {
    this.imagePath = imagePath;
};

Key.prototype.setTaxonPath = function(taxonPath) {
    this.taxonPath = taxonPath;
};

Key.prototype.getImagePath = function() {
    return this.imagePath;
};

Key.prototype.getTaxonPath = function() {
    return this.taxonPath;
};

Key.prototype.setAllowUnscored = function(allow) {
    this.allowUnscored = allow;
};

Key.prototype.setAutoPrune = function(prune) {
    this.autoPrune = prune;
};

Key.prototype.getSelectedCharacters = function() {
    this.selectedCharacters.sort(function comparator(obj1, obj2){
      return obj1.id - obj2.id; });
    return this.selectedCharacters;
};

Key.prototype.getMatchedTaxa = function() {
    return this.matchedTaxa;
};

Key.prototype.getUnmatchedTaxa = function() {
    return this.unmatchedTaxa;
};

Key.prototype.calculate = function() {
    //console.log("Key.calculate");
    this.matchedTaxa = [];
    this.unmatchedTaxa = [];
    if ( this.selectedCharacters && this.selectedCharacters.length > 0 ) {
        this.calculateList();
        if(this.autoPrune) {
          this.calculateRedundant();
        }
    } else {
        //console.log("No Matched Taxa");
	this.matchedTaxa = this.data.taxa;
	this.unmatchedTaxa = [];
    }

    this.updateUI(this);
};

Key.prototype.pruneRedundants = function() {
    //console.log("Key.pruneRedundants");
    this.matchedTaxa = [];
    this.unmatchedTaxa = [];
    if ( this.selectedCharacters && this.selectedCharacters.length > 0 ) {
        this.calculateList();
        this.calculateRedundant();
    } else {
        //console.log("No Matched Taxa");
	    this.matchedTaxa = this.data.taxa;
	    this.unmatchedTaxa = [];
    }

    this.updateUI(this);
};

Key.prototype.calculateRedundant = function() {
    //console.log("Key.doPruneRedundant");
    if(this.matchedTaxa.length < 2) {
      return;
    }

    var unselectedCharacters = this.getUnselectedCharacters();
    for(var j=0; j < unselectedCharacters.length; j++) {
        var character = unselectedCharacters[j];
        //console.log("Checking " + character.name);
        var attributeValue1 = this.findTaxonAttribute(this.matchedTaxa[0], character);
        character.selectedValues = attributeValue1;
        var isRedundant = true;
        for(var i=1; i < this.matchedTaxa.length; i++) {
           var taxon = this.matchedTaxa[i];
           if (this.calculateOne(character, taxon)) {
		     isRedundant = false;
		     break;
	       }
        }
        character.selectedValues = [];
        if(isRedundant) {
          character.isRedundant = true;
        } else {
          character.isRedundant = false;
        }
    }
};

Key.prototype.getUnselectedCharacters = function() {
    var selectedCharacterHash = [];
    for (var i=0; i<this.selectedCharacters.length; i++) {
	var character = this.selectedCharacters[i];
	selectedCharacterHash[character.id] = character;
    }

    var unselectedCharacters = [];

    for (var i=0; i<this.data.characters.length; i++) {
	var character = this.data.characters[i];
	if ( selectedCharacterHash[character.id] ) {
	    continue;
	}
	unselectedCharacters.push(character);
    }
    return unselectedCharacters;
};

Key.prototype.setUpdateUI = function(updateFunction) {
	this.updateUI = updateFunction;
};

Key.prototype.getDescriptiveConcept = function(descriptiveConceptId) {
    var descriptiveConcept = this.data.descriptiveConcepts[descriptiveConceptId];
    return descriptiveConcept;
};

Key.prototype.getCharacter = function(characterId) {
    var character = this.data.characters[characterId];
    return character;
};

Key.prototype.getTaxon = function(taxonId) {
    var taxon = this.data.taxa[taxonId];
    return taxon;
};

Key.prototype.selectCharacter = function(characterId, value) {
	var character = this.data.characters[characterId];
    switch (character.type) {
    case Key.Categorical:
        if(value.length == 0) {
            return;
        }
        character.selectedValues = value;

        break;
        // Quantitative
        default:
        if(value == "") {
              return;
        }
        character.selectedValues = value;
        break;
        }

        var selected = false;
        for(var i = 0; i < this.selectedCharacters.length; i++) {
        	if(this.selectedCharacters[i].id == character.id) {
        		this.selectedCharacters[i] = character;
        		selected = true;
        		break;
        	}
        }
        if(!selected) {
            this.selectedCharacters.push(character);
        }

};

Key.prototype.unselectCharacter = function(characterId) {
    var newSelectedCharacters = [];
    for(var i = 0; i < this.selectedCharacters.length; i++) {
      var character = this.selectedCharacters[i];
      if(character.id != characterId) {
        newSelectedCharacters.push(character);
      } else {
  	character.selectedValues = null;
      }
    }
    this.selectedCharacters = newSelectedCharacters;
};

Key.prototype.calculateList = function() {
    //console.log("Key.calculateList");
    var numberOfTaxa = this.data.taxa.length;
    var characterLength = this.selectedCharacters.length;
	for (var i=0; i<numberOfTaxa; i++) {
		var taxon = this.data.taxa[i];
                //console.log("Checking " + taxon.name);
		var isMatch = true;
		for (var j=0; j<characterLength; j++) {
			var character = this.selectedCharacters[j];
			if ( !this.calculateOne(character, taxon)) {
				isMatch = false;
				break;
			}
		}
		if ( isMatch ) {
			this.matchedTaxa.push(taxon);
		} else {
			this.unmatchedTaxa.push(taxon);
		}
    }
};

Key.prototype.calculateOne = function(character, taxon) {
    //console.log("Key.calculateOne");
    var attributeValue = this.findTaxonAttribute(taxon, character);

    if ( !attributeValue ) {
    	if(this.allowUnscored) {
	        return true;
    	} else {
    		return false;
    	}
	}
	switch (character.type) {
	    case Key.Categorical:
           // console.log("Checking " + character.name + " " + character.selectedValues + " against " + taxon.name + " " + attributeValue);
	    var values;
	    if ( $.isArray(attributeValue) ) {
		    values = attributeValue;
	    } else {
		    values = [];
		    values[0] = attributeValue;
	    }
	    if ( attributeValue.length < 0) {
		    return false;
	    }

	    for (var i=0; i<character.selectedValues.length; i++) {
		   var selectedValue = character.selectedValues[i];
		   for (var j=0; j<values.length; j++) {
		       var attributeValue = values[j];
		       if ( selectedValue == attributeValue) {
			       return true;
		       }
		   }
	    }
	    break;
	    default:
        // Quantitative
	    var selectedValue = character.selectedValues;
	    if ( $.isArray(attributeValue) ) {
	        for (var i=0; i<attributeValue.length; i++) {
			    if ( selectedValue == attributeValue[i]) {
			        return true;
			    }
		    }
		} else if ( !Key.isUndefined(attributeValue.min)
                 && !Key.isUndefined(attributeValue.max)
                 && !Key.isUndefined(selectedValue.min)
                 && !Key.isUndefined(selectedValue.max) ) {
		    if ( selectedValue.min == attributeValue.min
                      && selectedValue.max == attributeValue.max) {
			    return true;
		    }
		} else if ( !Key.isUndefined(attributeValue.min)
                 && !Key.isUndefined(attributeValue.max) ) {
		    if ( selectedValue >= attributeValue.min
              && selectedValue <= attributeValue.max) {
			    return true;
		    }
		} else {
		    if ( Key.isString(attributeValue) ) {
			    if ( selectedValue.indexOf(attributeValue+'') > -1 ) {
				    return true;
			    }
		    } else {
			    if ( selectedValue == attributeValue ) {
			        return true;
			    }
		    }
		 }
		 break;
	}
	return false;
};

Key.isNull = function(value) {
	if (!value || value.length < 1) {
		return true;
	}
	return false;
};

Key.isUndefined = function(val) {
	return val === undefined;
};

Key.isString = function(val) {
	if (typeof val == "string") {
		return true;
	} else {
		return false;
	}
};

Key.parseFloat = function(val) {
	try {
		return parseFloat(val);
	} catch (e) {
	}
	return 0;
};

Key.prototype.findTaxonAttribute = function(taxon, character) {
    if ( Key.isNull(character.data) ) {
	    return;
    }

	var attributes = character.data;

	if (!attributes || attributes.length < 1 ) {
	    return null;
	}

	var index = taxon.id;
	var value = null;
	if ( index < attributes.length ) {
	    value = attributes[index];
	}
	if ( !value ) {
		return value;
	}

	if ( $.isArray(value) ) {
		return value;
	}
	if ( Key.isString(value) ) {
		if ( value.indexOf("-") > 0 ) {
			var words = value.split("-");
			if ( words && words.length > 1 ) {
				var object = new Object();
				object.min = Key.parseFloat(words[0]);
				object.max = Key.parseFloat(words[1]);
				return object;
			}
		}
	}
	return value;
};

Key.Categorical = "UM";
Key.Quantitative = "RN";

define("jskey", function() {
	  return Key;
});<|endoftext|>