<commit_before>/**
 * Generate mutation and query fragments for a form based on the schema
 * TODO: refactor to mutualize more code with vulcan-core defaultFragment functions
 * TODO: move to lib when refactored
 */
import _uniq from 'lodash/uniq';
import _intersection from 'lodash/intersection';
import gql from 'graphql-tag';
import {
    getCreateableFields,
    getUpdateableFields,
    getFragmentFieldNames
} from 'meteor/vulcan:lib';
import {
    Utils,
} from 'meteor/vulcan:core';
const intlSuffix = '_intl';

// NewFoobarFormFragment || EditFoobarFormFragment
const getFragmentName = (formType, collectionName) => {
    const prefix = `${collectionName}${Utils.capitalize(
        formType
    )}`;
    const fragmentName = `${prefix}FormFragment`;
    return fragmentName;
};

// get modifiable fields in the query either for update or create operations
const getQueryFieldNames = ({
    schema,
    options
}) => {
    const queryFields = options.formType === 'new'
        ? getCreateableFields(schema)
        : getUpdateableFields(schema);
    return queryFields;
};
// add readable fields to mutation fields
const getMutationFieldNames = ({
    readableFieldNames,
    queryFieldNames
}) => {
    return _uniq(queryFieldNames.concat(readableFieldNames));
};

const getFieldFragment = ({ schema, fieldName, options }) => {
    const field = schema[fieldName];
    if (!(field && field.type)) return fieldName;
    const fieldType = field.type.singleType;
    const fieldTypeName =
        typeof fieldType === 'object' ? 'Object' : typeof fieldType === 'function' ? fieldType.name : fieldType;

    switch (fieldTypeName) {
        // recursive call for nested arrays and objects
        case 'Object':
            if (!field.blackbox && fieldType._schema) {
                return getSchemaFragment({
                    fragmentName: fieldName,
                    schema: fieldType._schema,
                    options
                }) || null;
            }
            return fieldName;
        case 'Array':
            const arrayItemFieldName = `${fieldName}.$`;
            const arrayItemField = schema[arrayItemFieldName];
            // note: make sure field has an associated array item field
            if (arrayItemField) {
                // child will either be native value or a an object (first case)
                const arrayItemFieldType = arrayItemField.type.singleType;
                if (!arrayItemField.blackbox && arrayItemFieldType._schema) {
                    return getSchemaFragment({
                        fragmentName: fieldName,
                        schema: arrayItemFieldType._schema,
                        options
                    }) || null;
                }
            }
            return fieldName;
        default:
            // handle intl or return fieldName
            return fieldName.slice(-5) === intlSuffix ? `${fieldName}{ locale value }` : fieldName;
    }
};

// get fragment for a whole schema (root schema or nested schema of an object or an array)
const getSchemaFragment = ({
    schema,
    fragmentName,
    options,
    fieldNames: providedFieldNames
}) => {
    // differentiate mutation/query and create/update cases
    // respect provided fieldNames if any (needed for the root schema)
    const fieldNames = providedFieldNames || (
        options.isMutation
            ? getMutationFieldNames({
                queryFieldNames: getQueryFieldNames({ schema, options }),
                readableFieldNames: getFragmentFieldNames({ schema, options: { onlyViewable: true } })
            })
            : getQueryFieldNames({ schema, options })
    );

    const childFragments = fieldNames.length && fieldNames.map(fieldName => getFieldFragment({
        schema,
        fieldName,
        options
    }))
        // remove empty values
        .filter(f => !!f);
    if (childFragments.length) {
        return `${fragmentName} { ${childFragments.join('\n')} }`;
    }
    return null;
};

/**
 * Generate query and mutation fragments for forms
*/
const getFormFragments = ({
    formType = 'new', // new || edit
    collectionName,
    typeName,
    schema,
    fields, // restrict on certain fields
    addFields, // add additional fields (eg to display static fields)
}) => {
    const fragmentName = getFragmentName(formType, collectionName);

    // get the root schema fieldNames
    let queryFieldNames = getQueryFieldNames({ schema, options: { formType } });
    let mutationFieldNames = getMutationFieldNames({
        queryFieldNames,
        readableFieldNames: getFragmentFieldNames({ schema, options: { onlyViewable: true } })
    });

    // if "fields" prop is specified, restrict list of fields to it
    if (typeof fields !== 'undefined' && fields.length > 0) {
        // add "_intl" suffix to all fields in case some of them are intl fields
        const fieldsWithIntlSuffix = fields.map(field => `${field}${intlSuffix}`);
        const allFields = [...fields, ...fieldsWithIntlSuffix];
        queryFieldNames = _intersection(queryFieldNames, allFields);
        mutationFieldNames = _intersection(mutationFieldNames, allFields);
    }

    // add "addFields" prop contents to list of fields
    if (addFields && addFields.length) {
        queryFieldNames = queryFieldNames.concat(addFields);
        mutationFieldNames = mutationFieldNames.concat(addFields);
    }

    queryFieldNames.unshift('_id');
    mutationFieldNames.unshift('_id');

    // check unicity (_id can be added twice)
    queryFieldNames = _uniq(queryFieldNames);
    mutationFieldNames = _uniq(mutationFieldNames);

    // generate query fragment based on the fields that can be edited. Note: always add _id.
    // TODO: support nesting
    const queryFragmentText = getSchemaFragment({
        schema,
        fragmentName: `fragment ${fragmentName} on ${typeName}`,
        options: { formType, isMutation: false },
        fieldNames: queryFieldNames
    });
    const generatedQueryFragment = gql(queryFragmentText);

    const mutationFragmentText = getSchemaFragment({
        schema,
        fragmentName: `fragment ${fragmentName} on ${typeName}`,
        options: { formType, isMutation: true },
        fieldNames: mutationFieldNames
    });
    // generate mutation fragment based on the fields that can be edited and/or viewed. Note: always add _id.
    // TODO: support nesting
    const generatedMutationFragment = gql(mutationFragmentText);

    // if any field specifies extra queries, add them
    const extraQueries = _.compact(
        getQueryFieldNames({ schema, options: { formType } }).map(fieldName => {
            const field = schema[fieldName];
            return field.query;
        })
    );
    // get query & mutation fragments from props or else default to same as generatedFragment
    return {
        queryFragment: generatedQueryFragment,
        mutationFragment: generatedMutationFragment,
        extraQueries
    };
};

export default getFormFragments;
<commit_msg>Intl fields have type Array, so move intl check before switch statement<commit_after>/**
 * Generate mutation and query fragments for a form based on the schema
 * TODO: refactor to mutualize more code with vulcan-core defaultFragment functions
 * TODO: move to lib when refactored
 */
import _uniq from 'lodash/uniq';
import _intersection from 'lodash/intersection';
import gql from 'graphql-tag';
import {
    getCreateableFields,
    getUpdateableFields,
    getFragmentFieldNames
} from 'meteor/vulcan:lib';
import {
    Utils,
} from 'meteor/vulcan:core';
const intlSuffix = '_intl';

// NewFoobarFormFragment || EditFoobarFormFragment
const getFragmentName = (formType, collectionName) => {
    const prefix = `${collectionName}${Utils.capitalize(
        formType
    )}`;
    const fragmentName = `${prefix}FormFragment`;
    return fragmentName;
};

// get modifiable fields in the query either for update or create operations
const getQueryFieldNames = ({
    schema,
    options
}) => {
    const queryFields = options.formType === 'new'
        ? getCreateableFields(schema)
        : getUpdateableFields(schema);
    return queryFields;
};
// add readable fields to mutation fields
const getMutationFieldNames = ({
    readableFieldNames,
    queryFieldNames
}) => {
    return _uniq(queryFieldNames.concat(readableFieldNames));
};

const getFieldFragment = ({ schema, fieldName, options }) => {
  let fieldFragment;
  const field = schema[fieldName];
  if (!(field && field.type)) return fieldName;
  const fieldType = field.type.singleType;
  const fieldTypeName =
    typeof fieldType === 'object'
      ? 'Object'
      : typeof fieldType === 'function'
      ? fieldType.name
      : fieldType;

  if (fieldName.slice(-5) === intlSuffix) {
    fieldFragment = `${fieldName}{ locale value }`;
  } else {
    switch (fieldTypeName) {
      // recursive call for nested arrays and objects
      case 'Object':
        if (!field.blackbox && fieldType._schema) {
          fieldFragment =
            getSchemaFragment({
              fragmentName: fieldName,
              schema: fieldType._schema,
              options,
            }) || null;
        }
        fieldFragment = fieldName;
        break;
      case 'Array':
        const arrayItemFieldName = `${fieldName}.$`;
        const arrayItemField = schema[arrayItemFieldName];
        // note: make sure field has an associated array item field
        if (arrayItemField) {
          // child will either be native value or a an object (first case)
          const arrayItemFieldType = arrayItemField.type.singleType;
          if (!arrayItemField.blackbox && arrayItemFieldType._schema) {
            fieldFragment =
              getSchemaFragment({
                fragmentName: fieldName,
                schema: arrayItemFieldType._schema,
                options,
              }) || null;
          }
        }
        fieldFragment = fieldName;
        break;
      default:
        // handle intl or return fieldName
        fieldFragment = fieldName;
        break;
    }
  }
  return fieldFragment;
};

// get fragment for a whole schema (root schema or nested schema of an object or an array)
const getSchemaFragment = ({
    schema,
    fragmentName,
    options,
    fieldNames: providedFieldNames
}) => {
    // differentiate mutation/query and create/update cases
    // respect provided fieldNames if any (needed for the root schema)
    const fieldNames = providedFieldNames || (
        options.isMutation
            ? getMutationFieldNames({
                queryFieldNames: getQueryFieldNames({ schema, options }),
                readableFieldNames: getFragmentFieldNames({ schema, options: { onlyViewable: true } })
            })
            : getQueryFieldNames({ schema, options })
    );

    const childFragments = fieldNames.length && fieldNames.map(fieldName => getFieldFragment({
        schema,
        fieldName,
        options
    }))
        // remove empty values
        .filter(f => !!f);
    if (childFragments.length) {
        return `${fragmentName} { ${childFragments.join('\n')} }`;
    }
    return null;
};

/**
 * Generate query and mutation fragments for forms
*/
const getFormFragments = ({
    formType = 'new', // new || edit
    collectionName,
    typeName,
    schema,
    fields, // restrict on certain fields
    addFields, // add additional fields (eg to display static fields)
}) => {
    const fragmentName = getFragmentName(formType, collectionName);

    // get the root schema fieldNames
    let queryFieldNames = getQueryFieldNames({ schema, options: { formType } });
    let mutationFieldNames = getMutationFieldNames({
        queryFieldNames,
        readableFieldNames: getFragmentFieldNames({ schema, options: { onlyViewable: true } })
    });

    // if "fields" prop is specified, restrict list of fields to it
    if (typeof fields !== 'undefined' && fields.length > 0) {
        // add "_intl" suffix to all fields in case some of them are intl fields
        const fieldsWithIntlSuffix = fields.map(field => `${field}${intlSuffix}`);
        const allFields = [...fields, ...fieldsWithIntlSuffix];
        queryFieldNames = _intersection(queryFieldNames, allFields);
        mutationFieldNames = _intersection(mutationFieldNames, allFields);
    }

    // add "addFields" prop contents to list of fields
    if (addFields && addFields.length) {
        queryFieldNames = queryFieldNames.concat(addFields);
        mutationFieldNames = mutationFieldNames.concat(addFields);
    }

    queryFieldNames.unshift('_id');
    mutationFieldNames.unshift('_id');

    // check unicity (_id can be added twice)
    queryFieldNames = _uniq(queryFieldNames);
    mutationFieldNames = _uniq(mutationFieldNames);

    // generate query fragment based on the fields that can be edited. Note: always add _id.
    // TODO: support nesting
    const queryFragmentText = getSchemaFragment({
        schema,
        fragmentName: `fragment ${fragmentName} on ${typeName}`,
        options: { formType, isMutation: false },
        fieldNames: queryFieldNames
    });
    const generatedQueryFragment = gql(queryFragmentText);

    const mutationFragmentText = getSchemaFragment({
        schema,
        fragmentName: `fragment ${fragmentName} on ${typeName}`,
        options: { formType, isMutation: true },
        fieldNames: mutationFieldNames
    });
    // generate mutation fragment based on the fields that can be edited and/or viewed. Note: always add _id.
    // TODO: support nesting
    const generatedMutationFragment = gql(mutationFragmentText);

    // if any field specifies extra queries, add them
    const extraQueries = _.compact(
        getQueryFieldNames({ schema, options: { formType } }).map(fieldName => {
            const field = schema[fieldName];
            return field.query;
        })
    );
    // get query & mutation fragments from props or else default to same as generatedFragment
    return {
        queryFragment: generatedQueryFragment,
        mutationFragment: generatedMutationFragment,
        extraQueries
    };
};

export default getFormFragments;
<|endoftext|>


<commit_before>// $Id$

psWFJTADefaultFormatter = {
  formatToken: function(data) {
    return data.label + ' %close%';
  },
  formatFeedItem: function(data) {
    return data.label;
  }
}

function psWidgetFormJQueryTokenAutocompleter(options) {

  // default options
  this.options = {
    formatter : 'psWFJTADefaultFormatter',
    min_chars : 1,
    urls : null,
    delay : 300,
    many : true
  };

  // constants
  this.DIRECTION = {
    PREVIOUS : -1,
    NEXT : -2
  };
  this.KEY = {
    DOWN : 40,
    UP : 38,
    BACKSPACE : 8,
    ESCAPE : 27,
    RETURN : 13
  };

  // elements handling
  this.element = null;
  this.input = null;
  this.select = null;
  this.feed = undefined;
  this.typeWatch = null;
  this.defaultsLoaded = false;
  this.formatter = null;

  // data handling
  this.selectedIds = Array();

  // tokens handling
  this.selectedTokenId = undefined;
  this.numTokens = 0;

  // feed handling
  this.ajaxQuery = undefined;
  this.feedData = Array();
  this.selectedFeedItem = null;
  this.numFeedItems = 0;

  this.init(options);
};

psWidgetFormJQueryTokenAutocompleter.prototype = {

  /**
   *
   */
  init : function(options) {
    var widget = this;

    if (options.min_chars) {
      widget.min_chars = Math.max(widget.min_chars, options.min_chars);
    }

    // formatter
    if (options.formatter) {
      widget.options.formatter = options.formatter;
    }
    eval('widget.formatter = ' + widget.options.formatter + ';');

    widget.options.urls = options.urls;

    widget.select = options.select;
    widget.element = widget.select.parent();

    // create list
    widget.itemsList = jQuery('
');
    widget.element.append(widget.itemsList);

    // create input tag
    widget.input = jQuery('<input type="text" />').bind('keydown',
        function(event) {

          var startTimer = false;

          switch (event.keyCode) {
            case widget.KEY.DOWN : // down
          event.preventDefault();
          widget.selectFeedItem(widget.DIRECTION.NEXT);
          break;
        case widget.KEY.UP : // up
          event.preventDefault();
          widget.selectFeedItem(widget.DIRECTION.PREVIOUS);
          break;
        case widget.KEY.BACKSPACE : // backspace
          if (widget.selectedTokenId != undefined) {
            widget.removeToken(widget.selectedTokenId);
            event.preventDefault();
          } else if (widget.input.val() == '' && widget.numTokens > 0) {
            var id = jQuery('li:last', widget.itemsList).prev().data('id');
            widget.selectToken(id);
            event.preventDefault();
          } else if (widget.input.val().length <= 1) {
            // input will be empty just after this event, delay removeFeed() for better responsiveness
            var removeTimer = setInterval( function() {
              clearInterval(removeTimer);
              widget.removeFeed();
            }, 10);
          } else {
            startTimer = true;
          }
          break;
        case widget.KEY.ESCAPE :
          if (widget.hasFeed()) {
            widget.removeFeed();
          } else {
            widget.selectToken();
          }

          break;
        case widget.KEY.RETURN : // return
          event.preventDefault();
          if (widget.selectedFeedItem != null) {
            widget.addToken(widget.feedData[widget.selectedFeedItem]);
          }
          break;

        default :
          if (widget.isCharacter(event.keyCode)) {
            startTimer = true;
          }
          break;
      }

      if (startTimer) {
        // add timer
        if (widget.typeWatch) {
          clearInterval(widget.typeWatch);
        }
        widget.typeWatch = setInterval( function() {
          clearInterval(widget.typeWatch);
          widget.createFeed();
        }, widget.options.delay);
      }
    });

    widget.input.bind('blur', function(event){
      widget.selectToken();
    });

    var li = widget.input.wrap('

');
    widget.itemsList.append(widget.input);
    widget.input.wrap('

');

    widget.element.append('
');

    // events are delegated to the div
    widget.element.bind('click', function(event) {
      var target = jQuery(event.target);
      if (target.is('a') && target.hasClass('ps-wfjta-close')) {
        event.preventDefault();
        var liTag = target.parents('li').eq(0);
        widget.removeToken(liTag.data('id'));
        widget.removeFeed();
      } else if (target.is('div') && target.hasClass('ps-wfjta')) {
        widget.selectToken();
      } else {
        var liTag = target.parents('li').eq(0);
        widget.selectToken(liTag.data('id'));
      }

      widget.input.focus();
    });

    // load defaults values
    var defaultValues = Array();
    var optionByValue = Array();
    jQuery('option', widget.select).each( function(index) {
      defaultValues.push(this.value);
      optionByValue[this.value] = this;
    });
    if (widget.options.urls.init != null && widget.options.urls.init != '') {
      jQuery.get(widget.options.urls.init, {
        'values[]' : defaultValues
      }, function(data) {
        jQuery.each(data, function(index, value) {
          widget.addToken(value, jQuery(optionByValue[value.id]));
        });
        widget.defaultsLoaded = true;
      }, 'json');
    } else {
      widget.defaultsLoaded = true;
    }
  },

  /**
   *
   */
  defaultFormatter : function(row) {
    return row.label + ' %close%';
  },

  /**
   *
   * @param keyCode
   * @return
   */
  isCharacter : function(keyCode) {
    if ((keyCode >= 48 && keyCode <= 90) // 0-1 a-z
        || (keyCode >= 96 && keyCode <= 111)) {
      return true;
    } else {
      return false;
    }
  },

  /**
   *
   */
  addToken : function(row, optionTag) {
    var widget = this;

    // don't add an existing entry
    if (widget.selectedIds[row.id] != undefined) {
      return;
    }

    var liTag = jQuery('
');
    liTag.data('id', row.id);

    // format li
    var html = widget.formatter.formatToken(row);

    // add close tag
    html = html.replace('%close%', 'x');

    liTag.html(html);
    var closeTag = jQuery('a', liTag);

    // add option to the select tag
    if (widget.defaultsLoaded) {
      var optionTag = jQuery('<option selected="selected" value="' + row.id
          + '">' + row.id + '</option>');
      widget.select.append(optionTag);
    }

    widget.selectedIds[row.id] = liTag;
    widget.numTokens++;

    // link option with the liTag
    if (optionTag) {
      liTag.data('optionTag', optionTag);
    }

    // add 'maximized' class
    if (!widget.options.many) {
      liTag.addClass('maximized');
    }

    // remove empty class
    widget.element.removeClass('ps-wfjta-empty');

    // insert the li tag before input
    liTag.hide();
    widget.input.parent().before(liTag);
    liTag.fadeIn('fast');

    // clear input
    widget.input.val('');

    // hide the input if no more tokens allowed
    if (!widget.options.many && widget.numTokens >= 1) {
      widget.input.parent().hide();
    }

    // hide feed
    widget.removeFeed();
  },

  /**
   *
   */
  removeToken : function(id) {
    var widget = this;

    if (widget.selectedIds[id] == undefined) {
      return;
    }

    if (widget.selectedTokenId == id) {
      widget.selectedTokenId = undefined;
    }

    var liTag = widget.selectedIds[id];

    // remove id from array
    widget.selectedIds[id] = undefined;
    widget.numTokens--;

    // remove option tag
    var optionTag = liTag.data('optionTag');
    optionTag.remove();

    // remove li tag
    liTag.fadeOut('fast', function() {
      liTag.remove();
      if (widget.numTokens == 0) {
        widget.element.addClass('ps-wfjta-empty');
      }
    });

    // show input
    if (!widget.options.many && widget.numTokens == 0) {
      widget.input.parent().show();
    }

    // remove feed
    widget.removeFeed();
  },

  killExistingQuery : function() {
    var widget = this;

    if (widget.ajaxQuery != undefined) {
      widget.ajaxQuery.abort();
      widget.ajaxQuery = undefined;
    }
  },

  /**
   *
   */
  createFeed : function() {
    var widget = this;

    var query = widget.input.val();
    if (query.length < widget.options.min_chars) {
      return;
    }

    widget.killExistingQuery();

    widget.ajaxQuery = jQuery.get(widget.options.urls.search, {
      q : query
    }, function(data) {

      // remove existing feed
        if (widget.feed) {
          widget.removeFeed();
        }
        widget.numFeedItems = 0;
        widget.feedData = Array();

        var feed = jQuery('
');

        // delegate events
        feed.bind('click', function(event) {
          if (target.is('li')) {
            widget.addToken(widget.feedData[target.data('itemIndex')]);
          }
          widget.removeFeed();
        });

        feed.bind('mouseover', function(event) {
          target = jQuery(event.target);
          if (target.is('li')) {
            widget.selectFeedItem($(target).data('itemIndex'));
          }
        });

        jQuery.each(
            data,
            function(index, row) {
              // filter selected items
              if (widget.selectedIds[row.id] != undefined) {
                return;
              }

              // highlight
              row.highlight = widget.highLightQuery(row.label, query);

              var line = jQuery('
');
              line.html(widget.formatter.formatFeedItem(row));
              widget.feedData[widget.numFeedItems] = row;
              line.data('itemIndex', widget.numFeedItems);
              feed.append(line);
              widget.numFeedItems++;
            });

        if (widget.numFeedItems>0) {
          var offset = widget.element.offset();
          feed.css('left', offset.left);

          var borderBottom = parseInt(widget.element.css('borderBottomWidth').replace(new RegExp('^([0-9]*)px$'), '$1'), 10);
          if (borderBottom == NaN) {
            borderBottom = 0;
          }
          feed.css('top', offset.top + widget.element.outerHeight() - borderBottom);

          var borderLeft = parseInt(feed.css('borderLeftWidth').replace(new RegExp('^([0-9]*)px$'), '$1'), 10);
          var borderRight = parseInt(feed.css('borderRightWidth').replace(new RegExp('^([0-9]*)px$'), '$1'), 10);
          if (borderLeft == NaN) {
            borderLeft = 0;
          }
          if (borderRight == NaN) {
            borderRight = 0;
          }
          var width = widget.element.outerWidth() - borderLeft - borderRight;
          feed.width(width);

          feed.appendTo(document.body);
          widget.feed = feed;

          // select first item
          widget.selectFeedItem(0);
        }
      }, 'json');
  },

  /**
   *
   */
  selectFeedItem : function(itemIndex) {
    var widget = this;

    // remove 'selected' class on previous selected item
    jQuery('li', widget.feed).eq(widget.selectedFeedItem).removeClass('ps-wfjta-selected');

    if (itemIndex == widget.DIRECTION.PREVIOUS
        || itemIndex == widget.DIRECTION.NEXT) {
      // handle state where no item is selected
      if (widget.selectedFeedItem == null) {
        if (itemIndex == widget.DIRECTION.PREVIOUS) {
          return;
        } else if (itemIndex == widget.DIRECTION.NEXT) {
          widget.selectedFeedItem = 0;
        }
      } else {
        // move to new selected item
        if (itemIndex == widget.DIRECTION.NEXT) {
          widget.selectedFeedItem++;
          widget.selectedFeedItem = Math.min(widget.selectedFeedItem,
              widget.numFeedItems - 1);
        } else if (itemIndex == widget.DIRECTION.PREVIOUS) {
          widget.selectedFeedItem--;
          if (widget.selectedFeedItem < 0) {
            widget.selectedFeedItem = null;
          }
        }
      }
    } else if (itemIndex >= 0 && itemIndex < widget.numFeedItems) {
      widget.selectedFeedItem = itemIndex;
    }

    // add 'selected' class on new selected item
    if (widget.selectedFeedItem != null) {
      jQuery('li', widget.feed).eq(widget.selectedFeedItem).addClass('ps-wfjta-selected');
    }
  },

  /**
   *
   */
  removeFeed : function() {
    var widget = this;

    widget.killExistingQuery();

    widget.selectedFeedItem = null;
    if (widget.feed != undefined) {
      widget.feed.remove();
    }
    widget.feed = undefined;
  },

  selectToken : function(tokenId) {
    var widget = this;

    // tokens are selectable in 'many' mode only
    if (!widget.options.many) {
      return;
    }

    if (widget.selectedTokenId != undefined) {
      widget.selectedIds[widget.selectedTokenId].removeClass('ps-wfjta-selected');
      widget.selectedTokenId = undefined;
    }

    if (widget.selectedIds[tokenId] != undefined) {
      widget.selectedIds[tokenId].addClass('ps-wfjta-selected');
      widget.selectedTokenId = tokenId;
    }
  },

  isInputEmpty : function() {
    var widget = this;

    if (widget.input.val() == '') {
      return true;
    } else {
      return false;
    }
  },

  hasFeed : function() {
    var widget = this;
    if (widget.feed == undefined) {
      return false;
    } else {
      return true;
    }
  },

  highLightQuery : function(text, query) {
    return text.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query
        + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1");
  }

};
<commit_msg>IE workaround<commit_after>// $Id$

psWFJTADefaultFormatter = {
  formatToken: function(data) {
    return data.label + ' %close%';
  },
  formatFeedItem: function(data) {
    return data.label;
  }
}

function psWidgetFormJQueryTokenAutocompleter(options) {

  // default options
  this.options = {
    formatter : 'psWFJTADefaultFormatter',
    min_chars : 1,
    urls : null,
    delay : 300,
    many : true
  };

  // constants
  this.DIRECTION = {
    PREVIOUS : -1,
    NEXT : -2
  };
  this.KEY = {
    DOWN : 40,
    UP : 38,
    BACKSPACE : 8,
    ESCAPE : 27,
    RETURN : 13
  };

  // elements handling
  this.element = null;
  this.input = null;
  this.select = null;
  this.feed = undefined;
  this.typeWatch = null;
  this.defaultsLoaded = false;
  this.formatter = null;

  // data handling
  this.selectedIds = Array();

  // tokens handling
  this.selectedTokenId = undefined;
  this.numTokens = 0;

  // feed handling
  this.ajaxQuery = undefined;
  this.feedData = Array();
  this.selectedFeedItem = null;
  this.numFeedItems = 0;

  this.init(options);
};

psWidgetFormJQueryTokenAutocompleter.prototype = {

  /**
   *
   */
  init : function(options) {
    var widget = this;

    if (options.min_chars) {
      widget.min_chars = Math.max(widget.min_chars, options.min_chars);
    }

    // formatter
    if (options.formatter) {
      widget.options.formatter = options.formatter;
    }
    eval('widget.formatter = ' + widget.options.formatter + ';');

    widget.options.urls = options.urls;

    widget.select = options.select;
    widget.element = widget.select.parent();

    // create list
    widget.itemsList = jQuery('
');
    widget.element.append(widget.itemsList);

    // create input tag
    widget.input = jQuery('<input type="text" />').bind('keydown',
        function(event) {

          var startTimer = false;

          switch (event.keyCode) {
            case widget.KEY.DOWN : // down
          event.preventDefault();
          widget.selectFeedItem(widget.DIRECTION.NEXT);
          break;
        case widget.KEY.UP : // up
          event.preventDefault();
          widget.selectFeedItem(widget.DIRECTION.PREVIOUS);
          break;
        case widget.KEY.BACKSPACE : // backspace
          if (widget.selectedTokenId != undefined) {
            widget.removeToken(widget.selectedTokenId);
            event.preventDefault();
          } else if (widget.input.val() == '' && widget.numTokens > 0) {
            var id = jQuery('li:last', widget.itemsList).prev().data('id');
            widget.selectToken(id);
            event.preventDefault();
          } else if (widget.input.val().length <= 1) {
            // input will be empty just after this event, delay removeFeed() for better responsiveness
            var removeTimer = setInterval( function() {
              clearInterval(removeTimer);
              widget.removeFeed();
            }, 10);
          } else {
            startTimer = true;
          }
          break;
        case widget.KEY.ESCAPE :
          if (widget.hasFeed()) {
            widget.removeFeed();
          } else {
            widget.selectToken();
          }

          break;
        case widget.KEY.RETURN : // return
          event.preventDefault();
          if (widget.selectedFeedItem != null) {
            widget.addToken(widget.feedData[widget.selectedFeedItem]);
          }
          break;

        default :
          if (widget.isCharacter(event.keyCode)) {
            startTimer = true;
          }
          break;
      }

      if (startTimer) {
        // add timer
        if (widget.typeWatch) {
          clearInterval(widget.typeWatch);
        }
        widget.typeWatch = setInterval( function() {
          clearInterval(widget.typeWatch);
          widget.createFeed();
        }, widget.options.delay);
      }
    });

    widget.input.bind('blur', function(event){
      widget.selectToken();
    });

    var li = widget.input.wrap('

');
    widget.itemsList.append(widget.input);
    widget.input.wrap('

');

    widget.element.append('
');

    // events are delegated to the div
    widget.element.bind('click', function(event) {
      var target = jQuery(event.target);
      if (target.is('a') && target.hasClass('ps-wfjta-close')) {
        event.preventDefault();
        var liTag = target.parents('li').eq(0);
        widget.removeToken(liTag.data('id'));
        widget.removeFeed();
      } else if (target.is('div') && target.hasClass('ps-wfjta')) {
        widget.selectToken();
      } else {
        var liTag = target.parents('li').eq(0);
        widget.selectToken(liTag.data('id'));
      }

      widget.input.focus();
    });

    // load defaults values
    var defaultValues = Array();
    var optionByValue = Array();
    jQuery('option', widget.select).each( function(index) {
      defaultValues.push(this.value);
      optionByValue[this.value] = this;
    });
    if (widget.options.urls.init != null && widget.options.urls.init != '') {
      jQuery.get(widget.options.urls.init, {
        'values[]' : defaultValues
      }, function(data) {
        jQuery.each(data, function(index, value) {
          widget.addToken(value, jQuery(optionByValue[value.id]));
        });
        widget.defaultsLoaded = true;
      }, 'json');
    } else {
      widget.defaultsLoaded = true;
    }
  },

  /**
   *
   */
  defaultFormatter : function(row) {
    return row.label + ' %close%';
  },

  /**
   *
   * @param keyCode
   * @return
   */
  isCharacter : function(keyCode) {
    if ((keyCode >= 48 && keyCode <= 90) // 0-1 a-z
        || (keyCode >= 96 && keyCode <= 111)) {
      return true;
    } else {
      return false;
    }
  },

  /**
   *
   */
  addToken : function(row, optionTag) {
    var widget = this;

    // don't add an existing entry
    if (widget.selectedIds[row.id] != undefined) {
      return;
    }

    var liTag = jQuery('
');
    liTag.data('id', row.id);

    // format li
    var html = widget.formatter.formatToken(row);

    // add close tag
    html = html.replace('%close%', 'x');

    liTag.html(html);
    var closeTag = jQuery('a', liTag);

    // add option to the select tag
    if (widget.defaultsLoaded) {
      var optionTag = jQuery('<option selected="selected" value="' + row.id
          + '">' + row.id + '</option>');
      widget.select.append(optionTag);
    }

    widget.selectedIds[row.id] = liTag;
    widget.numTokens++;

    // link option with the liTag
    if (optionTag) {
      liTag.data('optionTag', optionTag);
    }

    // add 'maximized' class
    if (!widget.options.many) {
      liTag.addClass('maximized');
    }

    // remove empty class
    widget.element.removeClass('ps-wfjta-empty');

    // insert the li tag before input
    liTag.hide();
    widget.input.parent().before(liTag);
    liTag.fadeIn('fast');

    // clear input
    widget.input.val('');

    // hide the input if no more tokens allowed
    if (!widget.options.many && widget.numTokens >= 1) {
      widget.input.parent().hide();
    }

    // hide feed
    widget.removeFeed();
  },

  /**
   *
   */
  removeToken : function(id) {
    var widget = this;

    if (widget.selectedIds[id] == undefined) {
      return;
    }

    if (widget.selectedTokenId == id) {
      widget.selectedTokenId = undefined;
    }

    var liTag = widget.selectedIds[id];

    // remove id from array
    widget.selectedIds[id] = undefined;
    widget.numTokens--;

    // remove option tag
    var optionTag = liTag.data('optionTag');
    optionTag.remove();

    // remove li tag
    liTag.fadeOut('fast', function() {
      liTag.remove();
      if (widget.numTokens == 0) {
        widget.element.addClass('ps-wfjta-empty');
      }
    });

    // show input
    if (!widget.options.many && widget.numTokens == 0) {
      widget.input.parent().show();
    }

    // remove feed
    widget.removeFeed();
  },

  killExistingQuery : function() {
    var widget = this;

    if (widget.ajaxQuery != undefined) {
      widget.ajaxQuery.abort();
      widget.ajaxQuery = undefined;
    }
  },

  /**
   *
   */
  createFeed : function() {
    var widget = this;

    var query = widget.input.val();
    if (query.length < widget.options.min_chars) {
      return;
    }

    widget.killExistingQuery();

    widget.ajaxQuery = jQuery.get(widget.options.urls.search, {
      q : query
    }, function(data) {

      // remove existing feed
        if (widget.feed) {
          widget.removeFeed();
        }
        widget.numFeedItems = 0;
        widget.feedData = Array();

        var feed = jQuery('
');

        // delegate events
        feed.bind('click', function(event) {
          if (target.is('li')) {
            widget.addToken(widget.feedData[target.data('itemIndex')]);
          }
          widget.removeFeed();
        });

        feed.bind('mouseover', function(event) {
          target = jQuery(event.target);
          if (target.is('li')) {
            widget.selectFeedItem($(target).data('itemIndex'));
          }
        });

        jQuery.each(
            data,
            function(index, row) {
              // filter selected items
              if (widget.selectedIds[row.id] != undefined) {
                return;
              }

              // highlight
              row.highlight = widget.highLightQuery(row.label, query);

              var line = jQuery('
');
              line.html(widget.formatter.formatFeedItem(row));
              widget.feedData[widget.numFeedItems] = row;
              line.data('itemIndex', widget.numFeedItems);
              feed.append(line);
              widget.numFeedItems++;
            });

        if (widget.numFeedItems>0) {
          var offset = widget.element.offset();
          feed.css('left', offset.left);

          var borderBottom = parseInt(widget.element.css('borderBottomWidth').replace(new RegExp('^([0-9]*)px$'), '$1'), 10) || 0;
          feed.css('top', offset.top + widget.element.outerHeight() - borderBottom);

          var borderLeft = parseInt(feed.css('borderLeftWidth').replace(new RegExp('^([0-9]*)px$'), '$1'), 10) || 0;
          var borderRight = parseInt(feed.css('borderRightWidth').replace(new RegExp('^([0-9]*)px$'), '$1'), 10) || 0;
          var width = widget.element.outerWidth() - borderLeft - borderRight;
          feed.width(width);

          feed.appendTo(document.body);
          widget.feed = feed;

          // select first item
          widget.selectFeedItem(0);
        }
      }, 'json');
  },

  /**
   *
   */
  selectFeedItem : function(itemIndex) {
    var widget = this;

    // remove 'selected' class on previous selected item
    jQuery('li', widget.feed).eq(widget.selectedFeedItem).removeClass('ps-wfjta-selected');

    if (itemIndex == widget.DIRECTION.PREVIOUS
        || itemIndex == widget.DIRECTION.NEXT) {
      // handle state where no item is selected
      if (widget.selectedFeedItem == null) {
        if (itemIndex == widget.DIRECTION.PREVIOUS) {
          return;
        } else if (itemIndex == widget.DIRECTION.NEXT) {
          widget.selectedFeedItem = 0;
        }
      } else {
        // move to new selected item
        if (itemIndex == widget.DIRECTION.NEXT) {
          widget.selectedFeedItem++;
          widget.selectedFeedItem = Math.min(widget.selectedFeedItem,
              widget.numFeedItems - 1);
        } else if (itemIndex == widget.DIRECTION.PREVIOUS) {
          widget.selectedFeedItem--;
          if (widget.selectedFeedItem < 0) {
            widget.selectedFeedItem = null;
          }
        }
      }
    } else if (itemIndex >= 0 && itemIndex < widget.numFeedItems) {
      widget.selectedFeedItem = itemIndex;
    }

    // add 'selected' class on new selected item
    if (widget.selectedFeedItem != null) {
      jQuery('li', widget.feed).eq(widget.selectedFeedItem).addClass('ps-wfjta-selected');
    }
  },

  /**
   *
   */
  removeFeed : function() {
    var widget = this;

    widget.killExistingQuery();

    widget.selectedFeedItem = null;
    if (widget.feed != undefined) {
      widget.feed.remove();
    }
    widget.feed = undefined;
  },

  selectToken : function(tokenId) {
    var widget = this;

    // tokens are selectable in 'many' mode only
    if (!widget.options.many) {
      return;
    }

    if (widget.selectedTokenId != undefined) {
      widget.selectedIds[widget.selectedTokenId].removeClass('ps-wfjta-selected');
      widget.selectedTokenId = undefined;
    }

    if (widget.selectedIds[tokenId] != undefined) {
      widget.selectedIds[tokenId].addClass('ps-wfjta-selected');
      widget.selectedTokenId = tokenId;
    }
  },

  isInputEmpty : function() {
    var widget = this;

    if (widget.input.val() == '') {
      return true;
    } else {
      return false;
    }
  },

  hasFeed : function() {
    var widget = this;
    if (widget.feed == undefined) {
      return false;
    } else {
      return true;
    }
  },

  highLightQuery : function(text, query) {
    return text.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query
        + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1");
  }

};
<|endoftext|>


<commit_before>/* globals window */
var Backbone     = require('backbone'),
    $            = require('jquery'),
    Query        = require('lib/query'),
    _            = require('underscore'),
    facilities   = require('collections/facilities').instance,
    searchParams = ["fr"],
    parseParams  = require('lib/query_param_parser');

function generateQueryParams(queryString) {
  var params       = parseParams(queryString),
      categories   = _.compact((params.categories || '').split(',')),
      demographics = _.compact((params.demographics || '').split(',')),
      gender       = params.gender || null,
      search       = decodeURIComponent(params.search || ''),
      sort         = params.sort,
      queryParams  = { search: search },
      filterParams = {};

  if (categories.length > 0) {
    filterParams.categories = categories;
  }

  if (demographics.length > 0) {
    filterParams.age = demographics;
  }

  if (params.gender && params.gender !== 'A') {
    filterParams.gender = params.gender;
  }

  if (params.sort) {
    queryParams.sort = params.sort;
  }

  queryParams.filter = filterParams;

  return queryParams;
}

function validCategory(category) {
  return category && (/[a-z]+/).test(category.toString());
}

function getData($elements, dataAttrName) {
  var result = [];
  $elements.each(function(n, el) {
    result.push($(el).data(dataAttrName));
  });
  return result;
}

var ListView = Backbone.View.extend({
  categories: require('lib/categories'),
  template: require('templates/list'),

  events: {
    "click #filter-button": 'goToFilter',
    "click #load-more":     'loadMore'
  },

  initialize: function() {
    this.listenTo(this.collection, 'reset', this.render);
  },

  reset: function() {
    this.offset = this.hideMore = null;
  },

  loadMore: function() {
    $('#loading-spinner').show();
    $('#load-more-container').hide();

    Query.submit(this.getFilterParams()).done(function(results) {
      $('#loading-spinner').hide();

      this.offset = results.offset;
      this.hideMore = (results.data.length < 10);

      this.collection.add(results.data);
      this.render();
    }.bind(this));
  },

  goToFilter: function() {
    var router = require('routers/router').instance;
    router.navigate("filter", {trigger: true});
  },

  generateQueryParams: generateQueryParams,

  getFilterParams: function () {
    var queryString  = window.location.hash.substring(window.location.hash.indexOf('?')+1),
        queryParams  = generateQueryParams(queryString);

    queryParams.offset = this.offset;

    this.options.categories = queryParams.filter.categories || [];

    return queryParams;
  },

  performQuery: function(params) {
    return Query.submit(params).done(function(results) {
      searchParams = params;
      // populate with results
      facilities.reset(results.data);
      this.offset = results.offset;
    }.bind(this));
  },

  resetFilters: function() {
    this.$(".query .selected").removeClass("selected");
    var self = this;

    this.options.categories.forEach(function(category) {
      self.$categoryOption(category).addClass("selected");
    });

    this.$(".query-option-gender [data-value='A']").addClass('selected');
  },

  $categoryOption: function(category) {
    if(validCategory(category)) {
      return this.$(".query-option-category [data-value=" + category + "]").addClass("selected");
    } else {
      return $();
    }

  },

  render: function() {
    var deepJson = this.deepToJson(this.collection);
    var templateJson = this.flattenServices(deepJson);

    // replace with template
    this.$el.html(this.template({
      facilities: templateJson,
      categories: this.categories,
      searchParams: this.filterSelectCategories(this.options.categories),
      navButtons: [
        {class: 'left', id: 'backNav-button', text: 'Back'},
        {class: 'right', id: 'filter-button', text: 'Filter'}
      ]
    }));
    this.$('.query').hide();
    this.$('.option-group-exclusive .query-option').click(function() {
      $(this).closest(".option-group-exclusive").find(".query-option").removeClass("selected");
      $(this).toggleClass("selected");
    });

    this.$('.option-group .query-option').click(function() {
      $(this).toggleClass("selected");
    });
    this.$('#backNav-button').click(function(){
      require('routers/router').instance.back();
    });

    if ( !this.hideMore ) {
      this.$('#load-more-container').show();
    }

    this.resetFilters();
    return this;
  },

  deepToJson: function(collection) {
    var json = [],
        modelJson;

    json = collection.models.map(function(model) {
      modelJson = model.toJSON();
      modelJson.services = [];

      model.attributes.services.forEach(function(service) {
        modelJson.services.push(service.toJSON());
      });

      return modelJson;
    });

    return json;
  },

  filterSelectCategories: function(queryParams) {
    var match, selectedCategories = [];

    queryParams.forEach(function(queryName) {
      var match = _.find(this.categories, function(e){ return e.key == queryName; });
      if (!_.contains(selectedCategories, match)) {
        selectedCategories.push(match);
      }
    });

    return selectedCategories;
  },

  flattenServices: function(jsonArray) {
    var serviceCategories,
        allNotes,
        flattened = [],
        self = this;

    jsonArray.forEach(function(jsonModel) {
      serviceCategories = [];
      allNotes = [];

      jsonModel.services.forEach(function(jsonService) {
        serviceCategories.push(jsonService.category);
        allNotes.push(jsonService.notes);
      });

      jsonModel.serviceCategories = self.filterSelectCategories(serviceCategories);
      // jsonModel.serviceCategories = serviceCategories.join(', ');
      jsonModel.allNotes = allNotes.join(' ');
      flattened.push(jsonModel);
    });

    return flattened;
  }
});

module.exports = ListView;
<commit_msg>unbreak admin<commit_after>/* globals window */
var Backbone     = require('backbone'),
    $            = require('jquery'),
    Query        = require('lib/query'),
    _            = require('underscore'),
    facilities   = require('collections/facilities').instance,
    searchParams = ["fr"],
    parseParams  = require('lib/query_param_parser');

function generateQueryParams(queryString) {
  var params       = parseParams(queryString),
      categories   = _.compact((params.categories || '').split(',')),
      demographics = _.compact((params.demographics || '').split(',')),
      gender       = params.gender || null,
      search       = decodeURIComponent(params.search || ''),
      sort         = params.sort,
      queryParams  = { search: search },
      filterParams = {};

  if (categories.length > 0) {
    filterParams.categories = categories;
  }

  if (demographics.length > 0) {
    filterParams.age = demographics;
  }

  if (params.gender && params.gender !== 'A') {
    filterParams.gender = params.gender;
  }

  if (params.sort) {
    queryParams.sort = params.sort;
  }

  queryParams.filter = filterParams;

  return queryParams;
}

function validCategory(category) {
  return category && (/[a-z]+/).test(category.toString());
}

function getData($elements, dataAttrName) {
  var result = [];
  $elements.each(function(n, el) {
    result.push($(el).data(dataAttrName));
  });
  return result;
}

var ListView = Backbone.View.extend({
  categories: require('lib/categories'),
  template: require('templates/list'),

  events: {
    "click #filter-button": 'goToFilter',
    "click #load-more":     'loadMore'
  },

  initialize: function() {
    this.listenTo(this.collection, 'reset', this.render);
  },

  reset: function() {
    this.offset = this.hideMore = null;
  },

  loadMore: function() {
    $('#loading-spinner').show();
    $('#load-more-container').hide();

    Query.submit(this.getFilterParams()).done(function(results) {
      $('#loading-spinner').hide();

      this.offset = results.offset;
      this.hideMore = (results.data.length < 10);

      this.collection.add(results.data);
      this.render();
    }.bind(this));
  },

  goToFilter: function() {
    var router = require('routers/router').instance;
    router.navigate("filter", {trigger: true});
  },

  generateQueryParams: generateQueryParams,

  getFilterParams: function () {
    var queryString  = window.location.hash.substring(window.location.hash.indexOf('?')+1),
        queryParams  = generateQueryParams(queryString);

    queryParams.offset = this.offset;

    this.options.categories = queryParams.filter.categories || [];

    return queryParams;
  },

  performQuery: function(params) {
    return Query.submit(params).done(function(results) {
      searchParams = params;
      // populate with results
      facilities.reset(results.data);
      this.offset = results.offset;
    }.bind(this));
  },

  resetFilters: function() {
    this.$(".query .selected").removeClass("selected");
    var self = this;

    if ( this.options.categories ) {
      this.options.categories.forEach(function(category) {
        self.$categoryOption(category).addClass("selected");
      });
    }

    this.$(".query-option-gender [data-value='A']").addClass('selected');
  },

  $categoryOption: function(category) {
    if(validCategory(category)) {
      return this.$(".query-option-category [data-value=" + category + "]").addClass("selected");
    } else {
      return $();
    }

  },

  render: function() {
    var deepJson = this.deepToJson(this.collection);
    var templateJson = this.flattenServices(deepJson);

    // replace with template
    this.$el.html(this.template({
      facilities: templateJson,
      categories: this.categories,
      searchParams: this.filterSelectCategories(this.options.categories),
      navButtons: [
        {class: 'left', id: 'backNav-button', text: 'Back'},
        {class: 'right', id: 'filter-button', text: 'Filter'}
      ]
    }));
    this.$('.query').hide();
    this.$('.option-group-exclusive .query-option').click(function() {
      $(this).closest(".option-group-exclusive").find(".query-option").removeClass("selected");
      $(this).toggleClass("selected");
    });

    this.$('.option-group .query-option').click(function() {
      $(this).toggleClass("selected");
    });
    this.$('#backNav-button').click(function(){
      require('routers/router').instance.back();
    });

    if ( !this.hideMore ) {
      this.$('#load-more-container').show();
    }

    this.resetFilters();
    return this;
  },

  deepToJson: function(collection) {
    var json = [],
        modelJson;

    json = collection.models.map(function(model) {
      modelJson = model.toJSON();
      modelJson.services = [];

      model.attributes.services.forEach(function(service) {
        modelJson.services.push(service.toJSON());
      });

      return modelJson;
    });

    return json;
  },

  filterSelectCategories: function(queryParams) {
    var match, selectedCategories = [];

    if ( queryParams ) {
      queryParams.forEach(function(queryName) {
        var match = _.find(this.categories, function(e){ return e.key == queryName; });
        if (!_.contains(selectedCategories, match)) {
          selectedCategories.push(match);
        }
      });
    }

    return selectedCategories;
  },

  flattenServices: function(jsonArray) {
    var serviceCategories,
        allNotes,
        flattened = [],
        self = this;

    jsonArray.forEach(function(jsonModel) {
      serviceCategories = [];
      allNotes = [];

      jsonModel.services.forEach(function(jsonService) {
        serviceCategories.push(jsonService.category);
        allNotes.push(jsonService.notes);
      });

      jsonModel.serviceCategories = self.filterSelectCategories(serviceCategories);
      // jsonModel.serviceCategories = serviceCategories.join(', ');
      jsonModel.allNotes = allNotes.join(' ');
      flattened.push(jsonModel);
    });

    return flattened;
  }
});

module.exports = ListView;
<|endoftext|>


<commit_before>// Load in dependencies
var fs = require('fs');
var path = require('path');
var _ = require('underscore');
var json2css = require('json2css');
var spritesmith = require('spritesmith');
var url = require('url2');

// Define class to contain different extension handlers
function ExtFormat() {
  this.formatObj = {};
}
ExtFormat.prototype = {
  'add': function (name, val) {
    this.formatObj[name] = val;
  },
  'get': function (filepath) {
    // Grab the extension from the filepath
    var ext = path.extname(filepath);
    var lowerExt = ext.toLowerCase();

    // Look up the file extenion from our format object
    var formatObj = this.formatObj;
    var format = formatObj[lowerExt];
    return format;
  }
};

// Create img and css formats
var imgFormats = new ExtFormat();
var cssFormats = new ExtFormat();

// Add our img formats
imgFormats.add('.png', 'png');
imgFormats.add('.jpg', 'jpeg');
imgFormats.add('.jpeg', 'jpeg');

// Add our css formats
cssFormats.add('.styl', 'stylus');
cssFormats.add('.stylus', 'stylus');
cssFormats.add('.sass', 'sass');
cssFormats.add('.scss', 'scss');
cssFormats.add('.less', 'less');
cssFormats.add('.json', 'json');
cssFormats.add('.css', 'css');

module.exports = function (grunt) {
  // Create a SpriteMaker function
  function SpriteMaker() {
    var data = this.data;
    var src = data.src;
    var destImg = data.destImg;
    var destCSS = data.destCSS;
    var cssTemplate = data.cssTemplate;
    var that = this;

    // Verify all properties are here
    if (!src || !destImg || !destCSS) {
      return grunt.fatal('grunt.sprite requires a src, destImg, and destCSS property');
    }

    // Load in all images from the src
    var srcFiles = grunt.file.expand(src);

    // Create an async callback
    var cb = this.async();

    // Determine the format of the image
    var imgOpts = data.imgOpts || {},
        imgFormat = imgOpts.format || imgFormats.get(destImg) || 'png';

    // Set up the defautls for imgOpts
    _.defaults(imgOpts, {'format': imgFormat});

    // Run through spritesmith
    var spritesmithParams = {
      src: srcFiles,
      engine: data.engine || 'auto',
      algorithm: data.algorithm || 'top-down',
      padding: data.padding || 0,
      algorithmOpts: data.algorithmOpts || {},
      engineOpts: data.engineOpts || {},
      exportOpts: imgOpts
    };
    spritesmith(spritesmithParams, function (err, result) {
      // If an error occurred, callback with it
      if (err) {
        grunt.fatal(err);
        return cb(err);
      }

      // Otherwise, write out the result to destImg
      var destImgDir = path.dirname(destImg);
      grunt.file.mkdir(destImgDir);
      fs.writeFileSync(destImg, result.image, 'binary');

      // Generate a listing of CSS variables
      var coordinates = result.coordinates;
      var properties = result.properties;
      var spritePath = data.imgPath || url.relative(destCSS, destImg);
      var cssVarMap = data.cssVarMap || function noop () {};
      var cleanCoords = [];

      // Clean up the file name of the file
      Object.getOwnPropertyNames(coordinates).sort().forEach(function (file) {
        // Extract the image name (exlcuding extension)
        var fullname = path.basename(file);
        var nameParts = fullname.split('.');

        // If there is are more than 2 parts, pop the last one
        if (nameParts.length >= 2) {
          nameParts.pop();
        }

        // Extract out our name
        var name = nameParts.join('.');
        var coords = coordinates[file];

        // Specify the image for the sprite
        coords.name = name;
        coords.source_image = file;
        coords.image = spritePath;
        coords.total_width = properties.width;
        coords.total_height = properties.height;

        // Map the coordinates through cssVarMap
        coords = cssVarMap(coords) || coords;

        // Save the cleaned name and coordinates
        cleanCoords.push(coords);
      });

      var cssFormat = 'spritesmith-custom',
          cssOptions = data.cssOpts || {};

      // If there's a custom template, use it
      if (cssTemplate) {
        if (typeof cssTemplate === 'function') {
          json2css.addTemplate(cssFormat, cssTemplate);
        } else {
          json2css.addMustacheTemplate(cssFormat, fs.readFileSync(cssTemplate, 'utf8'));
        }
      } else {
      // Otherwise, override the cssFormat and fallback to 'json'
        cssFormat = data.cssFormat || cssFormats.get(destCSS) || 'json';
      }

      // Render the variables via json2css
      var cssStr = json2css(cleanCoords, {format: cssFormat, formatOpts: cssOptions});

      // Write it out to the CSS file
      var destCSSDir = path.dirname(destCSS);
      grunt.file.mkdir(destCSSDir);
      fs.writeFileSync(destCSS, cssStr, 'utf8');

      // Fail task if errors were logged.
      if (that.errorCount) { cb(false); }

      // Otherwise, print a success message.
      grunt.log.writeln('Files "' + destCSS + '", "' + destImg + '" created.');

      // Callback
      cb(true);
    });
  }

  // Export the SpriteMaker function
  grunt.registerMultiTask('sprite', 'Spritesheet making utility', SpriteMaker);
};
<commit_msg>Fixed up all lint errors<commit_after>// Load in dependencies
var fs = require('fs');
var path = require('path');
var _ = require('underscore');
var json2css = require('json2css');
var spritesmith = require('spritesmith');
var url = require('url2');

// Define class to contain different extension handlers
function ExtFormat() {
  this.formatObj = {};
}
ExtFormat.prototype = {
  add: function (name, val) {
    this.formatObj[name] = val;
  },
  get: function (filepath) {
    // Grab the extension from the filepath
    var ext = path.extname(filepath);
    var lowerExt = ext.toLowerCase();

    // Look up the file extenion from our format object
    var formatObj = this.formatObj;
    var format = formatObj[lowerExt];
    return format;
  }
};

// Create img and css formats
var imgFormats = new ExtFormat();
var cssFormats = new ExtFormat();

// Add our img formats
imgFormats.add('.png', 'png');
imgFormats.add('.jpg', 'jpeg');
imgFormats.add('.jpeg', 'jpeg');

// Add our css formats
cssFormats.add('.styl', 'stylus');
cssFormats.add('.stylus', 'stylus');
cssFormats.add('.sass', 'sass');
cssFormats.add('.scss', 'scss');
cssFormats.add('.less', 'less');
cssFormats.add('.json', 'json');
cssFormats.add('.css', 'css');

module.exports = function (grunt) {
  // Create a SpriteMaker function
  function SpriteMaker() {
    var data = this.data;
    var src = data.src;
    var destImg = data.destImg;
    var destCSS = data.destCSS;
    var cssTemplate = data.cssTemplate;
    var that = this;

    // Verify all properties are here
    if (!src || !destImg || !destCSS) {
      return grunt.fatal('grunt.sprite requires a src, destImg, and destCSS property');
    }

    // Load in all images from the src
    var srcFiles = grunt.file.expand(src);

    // Create an async callback
    var cb = this.async();

    // Determine the format of the image
    var imgOpts = data.imgOpts || {};
    var imgFormat = imgOpts.format || imgFormats.get(destImg) || 'png';

    // Set up the defautls for imgOpts
    _.defaults(imgOpts, {format: imgFormat});

    // Run through spritesmith
    var spritesmithParams = {
      src: srcFiles,
      engine: data.engine || 'auto',
      algorithm: data.algorithm || 'top-down',
      padding: data.padding || 0,
      algorithmOpts: data.algorithmOpts || {},
      engineOpts: data.engineOpts || {},
      exportOpts: imgOpts
    };
    spritesmith(spritesmithParams, function (err, result) {
      // If an error occurred, callback with it
      if (err) {
        grunt.fatal(err);
        return cb(err);
      }

      // Otherwise, write out the result to destImg
      var destImgDir = path.dirname(destImg);
      grunt.file.mkdir(destImgDir);
      fs.writeFileSync(destImg, result.image, 'binary');

      // Generate a listing of CSS variables
      var coordinates = result.coordinates;
      var properties = result.properties;
      var spritePath = data.imgPath || url.relative(destCSS, destImg);
      var cssVarMap = data.cssVarMap || function noop () {};
      var cleanCoords = [];

      // Clean up the file name of the file
      Object.getOwnPropertyNames(coordinates).sort().forEach(function (file) {
        // Extract the image name (exlcuding extension)
        var fullname = path.basename(file);
        var nameParts = fullname.split('.');

        // If there is are more than 2 parts, pop the last one
        if (nameParts.length >= 2) {
          nameParts.pop();
        }

        // Extract out our name
        var name = nameParts.join('.');
        var coords = coordinates[file];

        // Specify the image for the sprite
        coords.name = name;
        coords.source_image = file;
        coords.image = spritePath;
        coords.total_width = properties.width;
        coords.total_height = properties.height;

        // Map the coordinates through cssVarMap
        coords = cssVarMap(coords) || coords;

        // Save the cleaned name and coordinates
        cleanCoords.push(coords);
      });

      var cssFormat = 'spritesmith-custom';
      var cssOptions = data.cssOpts || {};

      // If there's a custom template, use it
      if (cssTemplate) {
        if (typeof cssTemplate === 'function') {
          json2css.addTemplate(cssFormat, cssTemplate);
        } else {
          json2css.addMustacheTemplate(cssFormat, fs.readFileSync(cssTemplate, 'utf8'));
        }
      } else {
      // Otherwise, override the cssFormat and fallback to 'json'
        cssFormat = data.cssFormat || cssFormats.get(destCSS) || 'json';
      }

      // Render the variables via json2css
      var cssStr = json2css(cleanCoords, {format: cssFormat, formatOpts: cssOptions});

      // Write it out to the CSS file
      var destCSSDir = path.dirname(destCSS);
      grunt.file.mkdir(destCSSDir);
      fs.writeFileSync(destCSS, cssStr, 'utf8');

      // Fail task if errors were logged.
      if (that.errorCount) { cb(false); }

      // Otherwise, print a success message.
      grunt.log.writeln('Files "' + destCSS + '", "' + destImg + '" created.');

      // Callback
      cb(true);
    });
  }

  // Export the SpriteMaker function
  grunt.registerMultiTask('sprite', 'Spritesheet making utility', SpriteMaker);
};
<|endoftext|>


<commit_before>import R from 'ramda';
import { Maybe, Either } from 'ramda-fantasy';
import * as types from './types';

export * from './types';

const def = types.def;

/**
 * DEPRECATED: Use of this function lead to meaningless error messages that
 * are nearly impossible to debug. Use `explodeMaybe` and `explodeEither`
 * to convert monads to unhandled exceptions.
 *
 * Function to rapidly extract a value from Maybe or Either monad.
 * But it should be used only when we're sure that we should have a value,
 * otherwise it will throw an exception.
 *
 * @private
 * @function explode
 * @param {Maybe|Either}
 * @returns {*}
 * @throws Error
 */
export const explode = R.cond([
  [Maybe.isJust, R.unnest],
  [Maybe.isNothing, () => { throw new Error('Maybe is expected to be Just, but its Nothing.'); }],
  [Either.isRight, R.unnest],
  [Either.isLeft, (val) => { throw new Error(`Either expected to be Right, but its Left with value: ${val}`); }],
  [R.T, (input) => { throw new Error(`Maybe or Either should be passed into explode function. Passed: ${input}`); }],
]);

/**
 * Function to rapidly extract a value from Maybe.Just
 * or throw a defined error message on Maybe.Nothing
 *
 * @private
 * @function explodeMaybe
 * @param {String} errorMessage
 * @param {Maybe} maybeObject
 * @returns {*}
 * @throws Error
 */
export const explodeMaybe = def(
  'explodeMaybe :: String -> Maybe a -> a',
  (errorMessage, maybeObject) => {
    if (maybeObject.isJust) return maybeObject.value;
    throw new Error(errorMessage);
  }
);

/**
 * Function to extract value from `Either` by providing a function
 * to handle the types of values contained in both `Left` and `Right`.
 *
 * This is hack to prevent errors on using `Either.either` with
 * cross-package Eithers (`Either.either` checks for instanceof and
 * it has other constuctors for some reason).
 *
 * @private
 * @function foldEither
 * @param {Function} leftFn
 * @param {Function} rightFn
 * @param {Either} eitherObject
 * @returns {*}
 * @throws Error
 */
export const foldEither = def(
  'foldEither :: (a -> c) -> (b -> c) -> Either a b -> c',
  (leftFn, rightFn, eitherObject) => (
    eitherObject.isLeft ? leftFn(eitherObject.value) : rightFn(eitherObject.value)
  )
);

// :: Either a b -> Promise a b
export const eitherToPromise = foldEither(
  Promise.reject.bind(Promise),
  Promise.resolve.bind(Promise)
);

/**
 * Unwraps Either monad and returns it’s value if it is Right and throws an Error
 * if it is Left.
 */
export const explodeEither = def(
  'explodeMaybe :: Either a b -> b',
  foldEither(
    (err) => { throw new Error(`Explosion failed: ${err}`); },
    R.identity
  )
);

/**
 * Returns a result of calling iterator function over argument list.
 * Use this function if iterator function returns some Monad.
 * Monad constructor should be passed as first argument, cause
 * we haven't got type deduction in JS. :-(
 * E.G.
 * assocLink :: Link -> Patch -> Either Error Patch
 * upsertLinks = reduceEither(assocLink, patch, [link0, link1, link2]);
 */
export const reduceM = def(
  'reduceM :: (b -> m b) -> (b -> a -> m b) -> b -> [a] -> m b',
  (m, fn, initial, list) => R.reduce(
    (acc, a) => R.chain(val => fn(val, a), acc),
    m(initial),
    list
  )
);

/**
 * Returns a result of calling iterator function over argument list.
 * Use this function if iterator function returns Either.
 * @see reduceM for more details
 */
export const reduceEither = def(
  'reduceEither :: (b -> a -> Either c b) -> b -> [a] -> Either c b',
  reduceM(Either.of)
);

/**
 * Returns a result of calling iterator function over argument list.
 * Use this function if iterator function returns Either.
 * @see reduceM for more details
 */
export const reduceMaybe = def(
  'reduceEither :: (b -> a -> Maybe b) -> b -> [a] -> Maybe b',
  reduceM(Maybe.of)
);

/**
 * Returns an object provided with all `null` and `undefined` values omitted
 */
export const omitNilValues = def(
  'omitNilValues :: Object -> Object',
  R.reject(R.isNil)
);

/**
 * Returns an object provided with all empty (in sense of R.isEmpty) values
 * omitted
 */
export const omitEmptyValues = def(
  'omitEmptyValues :: Object -> Object',
  R.reject(R.isEmpty)
);

/**
 * Checks if an element is among elements of a list
 */
export const isAmong = def(
  'isAmong :: [a] -> a -> Boolean',
  R.flip(R.contains)
);

/**
 * Like `R.objOf` but returns empty object {} if value is `null` or `undefined`
 */
export const optionalObjOf = def(
  // 'optionalObjOf :: String -> a -> StrMap a', // TODO fix hm-def
  'optionalObjOf :: String -> a -> Object',
  (key, val) => (val == null ? {} : { [key]: val })
);

export const notNil = R.complement(R.isNil);
export const notEmpty = R.complement(R.isEmpty);
export const hasNo = R.complement(R.has);

// Returns a copy of second object with omitted
// fields that are equal to fields from first object.
// Kind of the reverse of R.merge.
//
//   subtractObject(
//     { foo: 1, bar: 2 },
//     { foo: 1, bar: 'not 2', baz: 3 }
//   ) // => { bar: "not 2", baz: 3}
export const subtractObject = def(
  'subtractObject :: Object -> Object -> Object',
  R.uncurryN(2, objToSubstract => R.converge(
    R.omit,
    [
      R.compose(
        R.keys,
        R.pickBy(R.both(
          (value, key) => R.has(key, objToSubstract),
          (value, key) => R.propEq(key, value, objToSubstract)
        ))
      ),
      R.identity,
    ]
  ))
);

/**
 * Like `R.tap` but works with Promises.
 * @param {Function} promiseFn Function that returns Promise
 * @returns {Function} Run promiseFn with argument and returns the same argument on resolve
 */
export const tapP = promiseFn => arg => promiseFn(arg).then(R.always(arg));

// :: ERROR_CODE -> Error -> Promise.Reject Error
export const rejectWithCode = R.curry(
  (code, err) => Promise.reject(Object.assign(err, { errorCode: code }))
);

// :: [Promise a] -> Promise a
export const allPromises = promises => Promise.all(promises);

/**
 * Concatenates a list of lists into one list.
 */
// :: [[a]] -> [a]
export const concatAll = R.reduce(R.concat, []);

export default Object.assign(
  {
    explode,
    explodeMaybe,
    foldEither,
    hasNo,
    omitNilValues,
    omitEmptyValues,
    isAmong,
    optionalObjOf,
    notNil,
    notEmpty,
    rejectWithCode,
    allPromises,
    concatAll,
  },
  types
);
<commit_msg>feat(xod-func-tools): ‘validateSanctuaryType’ util that is easy to compose and returns a proper Either<commit_after>import R from 'ramda';
import { Maybe, Either } from 'ramda-fantasy';
import * as types from './types';

export * from './types';

const def = types.def;

/**
 * DEPRECATED: Use of this function lead to meaningless error messages that
 * are nearly impossible to debug. Use `explodeMaybe` and `explodeEither`
 * to convert monads to unhandled exceptions.
 *
 * Function to rapidly extract a value from Maybe or Either monad.
 * But it should be used only when we're sure that we should have a value,
 * otherwise it will throw an exception.
 *
 * @private
 * @function explode
 * @param {Maybe|Either}
 * @returns {*}
 * @throws Error
 */
export const explode = R.cond([
  [Maybe.isJust, R.unnest],
  [Maybe.isNothing, () => { throw new Error('Maybe is expected to be Just, but its Nothing.'); }],
  [Either.isRight, R.unnest],
  [Either.isLeft, (val) => { throw new Error(`Either expected to be Right, but its Left with value: ${val}`); }],
  [R.T, (input) => { throw new Error(`Maybe or Either should be passed into explode function. Passed: ${input}`); }],
]);

/**
 * Function to rapidly extract a value from Maybe.Just
 * or throw a defined error message on Maybe.Nothing
 *
 * @private
 * @function explodeMaybe
 * @param {String} errorMessage
 * @param {Maybe} maybeObject
 * @returns {*}
 * @throws Error
 */
export const explodeMaybe = def(
  'explodeMaybe :: String -> Maybe a -> a',
  (errorMessage, maybeObject) => {
    if (maybeObject.isJust) return maybeObject.value;
    throw new Error(errorMessage);
  }
);

/**
 * Function to extract value from `Either` by providing a function
 * to handle the types of values contained in both `Left` and `Right`.
 *
 * This is hack to prevent errors on using `Either.either` with
 * cross-package Eithers (`Either.either` checks for instanceof and
 * it has other constuctors for some reason).
 *
 * @private
 * @function foldEither
 * @param {Function} leftFn
 * @param {Function} rightFn
 * @param {Either} eitherObject
 * @returns {*}
 * @throws Error
 */
export const foldEither = def(
  'foldEither :: (a -> c) -> (b -> c) -> Either a b -> c',
  (leftFn, rightFn, eitherObject) => (
    eitherObject.isLeft ? leftFn(eitherObject.value) : rightFn(eitherObject.value)
  )
);

// :: Either a b -> Promise a b
export const eitherToPromise = foldEither(
  Promise.reject.bind(Promise),
  Promise.resolve.bind(Promise)
);

export const sanctuaryDefEitherToRamdaFantasyEither = sdEither => (
  sdEither.isLeft
    ? Either.Left(sdEither.value)
    : Either.Right(sdEither.value)
);

export const validateSanctuaryType = R.uncurryN(2, SanctuaryType =>
  R.compose(
    sanctuaryDefEitherToRamdaFantasyEither,
    SanctuaryType.validate.bind(SanctuaryType)
  )
);

/**
 * Unwraps Either monad and returns it’s value if it is Right and throws an Error
 * if it is Left.
 */
export const explodeEither = def(
  'explodeMaybe :: Either a b -> b',
  foldEither(
    (err) => { throw new Error(`Explosion failed: ${err}`); },
    R.identity
  )
);

/**
 * Returns a result of calling iterator function over argument list.
 * Use this function if iterator function returns some Monad.
 * Monad constructor should be passed as first argument, cause
 * we haven't got type deduction in JS. :-(
 * E.G.
 * assocLink :: Link -> Patch -> Either Error Patch
 * upsertLinks = reduceEither(assocLink, patch, [link0, link1, link2]);
 */
export const reduceM = def(
  'reduceM :: (b -> m b) -> (b -> a -> m b) -> b -> [a] -> m b',
  (m, fn, initial, list) => R.reduce(
    (acc, a) => R.chain(val => fn(val, a), acc),
    m(initial),
    list
  )
);

/**
 * Returns a result of calling iterator function over argument list.
 * Use this function if iterator function returns Either.
 * @see reduceM for more details
 */
export const reduceEither = def(
  'reduceEither :: (b -> a -> Either c b) -> b -> [a] -> Either c b',
  reduceM(Either.of)
);

/**
 * Returns a result of calling iterator function over argument list.
 * Use this function if iterator function returns Either.
 * @see reduceM for more details
 */
export const reduceMaybe = def(
  'reduceEither :: (b -> a -> Maybe b) -> b -> [a] -> Maybe b',
  reduceM(Maybe.of)
);

/**
 * Returns an object provided with all `null` and `undefined` values omitted
 */
export const omitNilValues = def(
  'omitNilValues :: Object -> Object',
  R.reject(R.isNil)
);

/**
 * Returns an object provided with all empty (in sense of R.isEmpty) values
 * omitted
 */
export const omitEmptyValues = def(
  'omitEmptyValues :: Object -> Object',
  R.reject(R.isEmpty)
);

/**
 * Checks if an element is among elements of a list
 */
export const isAmong = def(
  'isAmong :: [a] -> a -> Boolean',
  R.flip(R.contains)
);

/**
 * Like `R.objOf` but returns empty object {} if value is `null` or `undefined`
 */
export const optionalObjOf = def(
  // 'optionalObjOf :: String -> a -> StrMap a', // TODO fix hm-def
  'optionalObjOf :: String -> a -> Object',
  (key, val) => (val == null ? {} : { [key]: val })
);

export const notNil = R.complement(R.isNil);
export const notEmpty = R.complement(R.isEmpty);
export const hasNo = R.complement(R.has);

// Returns a copy of second object with omitted
// fields that are equal to fields from first object.
// Kind of the reverse of R.merge.
//
//   subtractObject(
//     { foo: 1, bar: 2 },
//     { foo: 1, bar: 'not 2', baz: 3 }
//   ) // => { bar: "not 2", baz: 3}
export const subtractObject = def(
  'subtractObject :: Object -> Object -> Object',
  R.uncurryN(2, objToSubstract => R.converge(
    R.omit,
    [
      R.compose(
        R.keys,
        R.pickBy(R.both(
          (value, key) => R.has(key, objToSubstract),
          (value, key) => R.propEq(key, value, objToSubstract)
        ))
      ),
      R.identity,
    ]
  ))
);

/**
 * Like `R.tap` but works with Promises.
 * @param {Function} promiseFn Function that returns Promise
 * @returns {Function} Run promiseFn with argument and returns the same argument on resolve
 */
export const tapP = promiseFn => arg => promiseFn(arg).then(R.always(arg));

// :: ERROR_CODE -> Error -> Promise.Reject Error
export const rejectWithCode = R.curry(
  (code, err) => Promise.reject(Object.assign(err, { errorCode: code }))
);

// :: [Promise a] -> Promise a
export const allPromises = promises => Promise.all(promises);

/**
 * Concatenates a list of lists into one list.
 */
// :: [[a]] -> [a]
export const concatAll = R.reduce(R.concat, []);

export default Object.assign(
  {
    explode,
    explodeMaybe,
    foldEither,
    eitherToPromise,
    sanctuaryDefEitherToRamdaFantasyEither,
    validateSanctuaryType,
    hasNo,
    omitNilValues,
    omitEmptyValues,
    isAmong,
    optionalObjOf,
    notNil,
    notEmpty,
    rejectWithCode,
    allPromises,
    concatAll,
  },
  types
);
<|endoftext|>


<commit_before>import { Camera } from './Camera.js';
import { Object3D } from '../core/Object3D.js';

class OrthographicCamera extends Camera {

	constructor( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) {

		super();

		Object.defineProperty( this, 'isOrthographicCamera', { value: true } );

		this.type = 'OrthographicCamera';

		this.zoom = 1;
		this.view = null
    this.left = left;
    this.right = right;
    this.top = top;
    this.bottom = bottom;

    this.near = near;
    this.far = far;

		this.left = ( left !== undefined ) ? left : - 1;
		this.right = ( right !== undefined ) ? right : 1;
		this.top = ( top !== undefined ) ? top : 1;
		this.bottom = ( bottom !== undefined ) ? bottom : - 1;

		this.near = ( near !== undefined ) ? near : 0.1;
		this.far = ( far !== undefined ) ? far : 2000;

		this.updateProjectionMatrix();

	}

	copy( source, recursive ) {

		super.copy( source, recursive );

		this.left = source.left;
		this.right = source.right;
		this.top = source.top;
		this.bottom = source.bottom;
		this.near = source.near;
		this.far = source.far;

		this.zoom = source.zoom;
		this.view = source.view === null ? null : Object.assign( {}, source.view );

		return this;

	}

	setViewOffset( fullWidth, fullHeight, x, y, width, height ) {

		if ( this.view === null ) {

			this.view = {
				enabled: true,
				fullWidth: 1,
				fullHeight: 1,
				offsetX: 0,
				offsetY: 0,
				width: 1,
				height: 1
			};

		}

		this.view.enabled = true;
		this.view.fullWidth = fullWidth;
		this.view.fullHeight = fullHeight;
		this.view.offsetX = x;
		this.view.offsetY = y;
		this.view.width = width;
		this.view.height = height;

		this.updateProjectionMatrix();

	}

	clearViewOffset() {

		if ( this.view !== null ) {

			this.view.enabled = false;

		}

		this.updateProjectionMatrix();

	}

	updateProjectionMatrix() {

		const dx = ( this.right - this.left ) / ( 2 * this.zoom );
		const dy = ( this.top - this.bottom ) / ( 2 * this.zoom );
		const cx = ( this.right + this.left ) / 2;
		const cy = ( this.top + this.bottom ) / 2;

		let left = cx - dx;
		let right = cx + dx;
		let top = cy + dy;
		let bottom = cy - dy;

		if ( this.view !== null && this.view.enabled ) {

			const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom;
			const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom;

			left += scaleW * this.view.offsetX;
			right = left + scaleW * this.view.width;
			top -= scaleH * this.view.offsetY;
			bottom = top - scaleH * this.view.height;

		}

		this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far );

		this.projectionMatrixInverse.copy( this.projectionMatrix ).invert();

	}

	toJSON( meta ) {

		const data = Object3D.prototype.toJSON.call( this, meta );

		data.object.zoom = this.zoom;
		data.object.left = this.left;
		data.object.right = this.right;
		data.object.top = this.top;
		data.object.bottom = this.bottom;
		data.object.near = this.near;
		data.object.far = this.far;

		if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );

		return data;

	}

}

export { OrthographicCamera };
<commit_msg>Clean up for linting.<commit_after>import { Camera } from './Camera.js';
import { Object3D } from '../core/Object3D.js';

class OrthographicCamera extends Camera {

	constructor( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) {

		super();

		Object.defineProperty( this, 'isOrthographicCamera', { value: true } );

		this.type = 'OrthographicCamera';

		this.zoom = 1;
		this.view = null;
		this.left = left;
		this.right = right;
		this.top = top;
		this.bottom = bottom;

		this.near = near;
		this.far = far;

		this.left = ( left !== undefined ) ? left : - 1;
		this.right = ( right !== undefined ) ? right : 1;
		this.top = ( top !== undefined ) ? top : 1;
		this.bottom = ( bottom !== undefined ) ? bottom : - 1;

		this.near = ( near !== undefined ) ? near : 0.1;
		this.far = ( far !== undefined ) ? far : 2000;

		this.updateProjectionMatrix();

	}

	copy( source, recursive ) {

		super.copy( source, recursive );

		this.left = source.left;
		this.right = source.right;
		this.top = source.top;
		this.bottom = source.bottom;
		this.near = source.near;
		this.far = source.far;

		this.zoom = source.zoom;
		this.view = source.view === null ? null : Object.assign( {}, source.view );

		return this;

	}

	setViewOffset( fullWidth, fullHeight, x, y, width, height ) {

		if ( this.view === null ) {

			this.view = {
				enabled: true,
				fullWidth: 1,
				fullHeight: 1,
				offsetX: 0,
				offsetY: 0,
				width: 1,
				height: 1
			};

		}

		this.view.enabled = true;
		this.view.fullWidth = fullWidth;
		this.view.fullHeight = fullHeight;
		this.view.offsetX = x;
		this.view.offsetY = y;
		this.view.width = width;
		this.view.height = height;

		this.updateProjectionMatrix();

	}

	clearViewOffset() {

		if ( this.view !== null ) {

			this.view.enabled = false;

		}

		this.updateProjectionMatrix();

	}

	updateProjectionMatrix() {

		const dx = ( this.right - this.left ) / ( 2 * this.zoom );
		const dy = ( this.top - this.bottom ) / ( 2 * this.zoom );
		const cx = ( this.right + this.left ) / 2;
		const cy = ( this.top + this.bottom ) / 2;

		let left = cx - dx;
		let right = cx + dx;
		let top = cy + dy;
		let bottom = cy - dy;

		if ( this.view !== null && this.view.enabled ) {

			const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom;
			const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom;

			left += scaleW * this.view.offsetX;
			right = left + scaleW * this.view.width;
			top -= scaleH * this.view.offsetY;
			bottom = top - scaleH * this.view.height;

		}

		this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far );

		this.projectionMatrixInverse.copy( this.projectionMatrix ).invert();

	}

	toJSON( meta ) {

		const data = Object3D.prototype.toJSON.call( this, meta );

		data.object.zoom = this.zoom;
		data.object.left = this.left;
		data.object.right = this.right;
		data.object.top = this.top;
		data.object.bottom = this.bottom;
		data.object.near = this.near;
		data.object.far = this.far;

		if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );

		return data;

	}

}

export { OrthographicCamera };
<|endoftext|>


<commit_before>
/**
 * Globale Felder
 */
var infoPanelDistrictName = document.getElementById('info-panel-district-name'),
    infoPanel = document.getElementById('info-panel');

/**
 * Globale Variabeln
 */
var lastSelectetDistrictName    = '',
    lastSelectetDistrictId      = null;

/**
 * Wird aufgerufen wenn der User auf eine Region klickt
 * @param featureData das aktuelle "feature"
 */
function selectDistrict(featureData){

    // Setzen den aktullen wert in die Globalen Variabeln
    lastSelectetDistrictName    = featureData.properties.Stadtteilname;
    lastSelectetDistrictId      = featureData.properties.Stadtteilnummer;

    // In das HTML schreiben
    infoPanelDistrictName.innerHTML = "Stadtteil: " + lastSelectetDistrictName;
    console.log('District wurde geklickt',featureData);

    infoPanel.classList.add('isOpen');
} // end function

/**
 * Schließt das Infopanel
 */
function closeInfoPanel() {
    infoPanel.classList.remove('isOpen');
}

var KA_LAT = 49.00921;
var KA_LNG = 8.403951;

var TILES_URL = 'http://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png';

var map = new L.Map("map", {center: [KA_LAT, KA_LNG], zoom: 12})
    .addLayer(new L.TileLayer(TILES_URL));

var svg = d3.select(map.getPanes().overlayPane).append("svg"),
    g = svg.append("g").attr("class", "leaflet-zoom-hide");

var wahlbezirke = g.append('g')
    .classed('wahlbezirke', true);

var stadtteile = g.append('g')
    .classed('stadtteile', true);

/**
 * Adapter to use Leaflet's projection in D3.
 */
function projectPoint(x, y) {
    var point = map.latLngToLayerPoint(new L.LatLng(y, x));
    this.stream.point(point.x, point.y);
}
var transform = d3.geo.transform({point: projectPoint}),
    path = d3.geo.path().projection(transform);

/**
 * Create SVG paths from a GeoJSON file.
 */
function pathsFromGeoJSON(filename, group, callback) {

    d3.json(filename, function(error, collection) {
        if (error) return callback(error, null);

        var feature = group.selectAll("path")
            .data(collection.features)
            .enter().append("path");

        map.on("viewreset", reset);
        reset();

        return callback(null, feature);

        // Reposition the SVG to cover the features.
        function reset() {
            var bounds = path.bounds(collection);
            var topLeft = bounds[0];
            var bottomRight = bounds[1];

            svg.attr("width", bottomRight[0] - topLeft[0])
                .attr("height", bottomRight[1] - topLeft[1])
                .style("left", topLeft[0] + "px")
                .style("top", topLeft[1] + "px");

            group.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");

            feature.attr("d", path);
        }
    });
}

pathsFromGeoJSON("ka_stadtteile.geojson", stadtteile, function(error, paths) {
    paths
        .attr('class', 'district')
        .on('click', selectDistrict)
        .style('fill', 'rgba(255, 255, 255, 0.7)')
        .style('stroke', '#000')
        .style('stroke-width', 2);
});

pathsFromGeoJSON("statistiken-wahlbezirke.geojson", wahlbezirke, function(error, paths) {
    paths
        .attr('class', 'wahlbezirk')
        .style('fill', '#fff')
        .style('stroke', '#000')
        .style('stroke-width', 1);
});

<commit_msg>Wahlbezirke über die Stadtteile gelegt<commit_after>
/**
 * Globale Felder
 */
var infoPanelDistrictName = document.getElementById('info-panel-district-name'),
    infoPanel = document.getElementById('info-panel');

/**
 * Globale Variabeln
 */
var lastSelectetDistrictName    = '',
    lastSelectetDistrictId      = null;

/**
 * Wird aufgerufen wenn der User auf eine Region klickt
 * @param featureData das aktuelle "feature"
 */
function selectDistrict(featureData){

    // Setzen den aktullen wert in die Globalen Variabeln
    lastSelectetDistrictName    = featureData.properties.Stadtteilname;
    lastSelectetDistrictId      = featureData.properties.Stadtteilnummer;

    // In das HTML schreiben
    infoPanelDistrictName.innerHTML = "Stadtteil: " + lastSelectetDistrictName;
    console.log('District wurde geklickt',featureData);

    infoPanel.classList.add('isOpen');
} // end function

/**
 * Schließt das Infopanel
 */
function closeInfoPanel() {
    infoPanel.classList.remove('isOpen');
}

var KA_LAT = 49.00921;
var KA_LNG = 8.403951;

var TILES_URL = 'http://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png';

var map = new L.Map("map", {center: [KA_LAT, KA_LNG], zoom: 12})
    .addLayer(new L.TileLayer(TILES_URL));

var svg = d3.select(map.getPanes().overlayPane).append("svg"),
    g = svg.append("g").attr("class", "leaflet-zoom-hide");

var stadtteile = g.append('g')
    .classed('stadtteile', true);

var wahlbezirke = g.append('g')
    .classed('wahlbezirke', true);

/**
 * Adapter to use Leaflet's projection in D3.
 */
function projectPoint(x, y) {
    var point = map.latLngToLayerPoint(new L.LatLng(y, x));
    this.stream.point(point.x, point.y);
}
var transform = d3.geo.transform({point: projectPoint}),
    path = d3.geo.path().projection(transform);

/**
 * Create SVG paths from a GeoJSON file.
 */
function pathsFromGeoJSON(filename, group, callback) {

    d3.json(filename, function(error, collection) {
        if (error) return callback(error, null);

        var feature = group.selectAll("path")
            .data(collection.features)
            .enter().append("path");

        map.on("viewreset", reset);
        reset();

        return callback(null, feature);

        // Reposition the SVG to cover the features.
        function reset() {
            var bounds = path.bounds(collection);
            var topLeft = bounds[0];
            var bottomRight = bounds[1];

            svg.attr("width", bottomRight[0] - topLeft[0])
                .attr("height", bottomRight[1] - topLeft[1])
                .style("left", topLeft[0] + "px")
                .style("top", topLeft[1] + "px");

            group.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");

            feature.attr("d", path);
        }
    });
}

pathsFromGeoJSON("ka_stadtteile.geojson", stadtteile, function(error, paths) {
    paths
        .attr('class', 'district')
        .on('click', selectDistrict)
        .style('fill', 'rgba(255, 255, 255, 0.7)')
        .style('stroke', '#000')
        .style('stroke-width', 2);
});

pathsFromGeoJSON("statistiken-wahlbezirke.geojson", wahlbezirke, function(error, paths) {
    paths
        .attr('class', 'wahlbezirk')
        .style('fill', '#fff')
        .style('stroke', '#000')
        .style('stroke-width', 1);
});

<|endoftext|>