<commit_before>import Vulcan from './config.js';
import flatten from 'flat';

const getNestedProperty = function (obj, desc) {
  var arr = desc.split('.');
  while(arr.length && (obj = obj[arr.shift()]));
  return obj;
};

export const Settings = {};

export const getAllSettings = () => {

  const settingsObject = {};

  let rootSettings = _.clone(Meteor.settings);
  delete rootSettings.public;
  delete rootSettings.private;

  // root settings & private settings are both private
  rootSettings = flatten(rootSettings, {safe: true});
  const privateSettings = flatten(Meteor.settings.private || {}, {safe: true});

  // public settings
  const publicSettings = flatten(Meteor.settings.public || {}, {safe: true});

  // registered default values
  const registeredSettings = Settings;

  const allSettingKeys = _.union(_.keys(rootSettings), _.keys(publicSettings), _.keys(privateSettings), _.keys(registeredSettings));

  allSettingKeys.sort().forEach(key => {

    settingsObject[key] = {};

    if (typeof rootSettings[key] !== 'undefined') {
      settingsObject[key].value = rootSettings[key];
    } else if (typeof privateSettings[key] !== 'undefined') {
      settingsObject[key].value = privateSettings[key];
    } else if (typeof publicSettings[key] !== 'undefined') {
      settingsObject[key].value = publicSettings[key];
    }

    if (typeof publicSettings[key] !== 'undefined'){
      settingsObject[key].isPublic = true;
    }

    if (registeredSettings[key]) {
      if (registeredSettings[key].defaultValue !== null || registeredSettings[key].defaultValue !== undefined) settingsObject[key].defaultValue = registeredSettings[key].defaultValue;
      if (registeredSettings[key].description) settingsObject[key].description = registeredSettings[key].description;
    }

  });

  return _.map(settingsObject, (setting, key) => ({name: key, ...setting}));
};

Vulcan.showSettings = () => {
  return getAllSettings();
};

export const registerSetting = (settingName, defaultValue, description, isPublic) => {
  Settings[settingName] = { defaultValue, description, isPublic };
};

export const getSetting = (settingName, settingDefault) => {

  let setting;

  // if a default value has been registered using registerSetting, use it
  const defaultValue = settingDefault || Settings[settingName] && Settings[settingName].defaultValue;

  if (Meteor.isServer) {
    // look in public, private, and root
    const rootSetting = getNestedProperty(Meteor.settings, settingName);
    const privateSetting = Meteor.settings.private && getNestedProperty(Meteor.settings.private, settingName);
    const publicSetting = Meteor.settings.public && getNestedProperty(Meteor.settings.public, settingName);

    // if setting is an object, "collect" properties from all three places
    if (typeof rootSetting === 'object' || typeof privateSetting === 'object' || typeof publicSetting === 'object') {
      setting = {
        ...defaultValue,
        ...rootSetting,
        ...privateSetting,
        ...publicSetting,
      };
    } else {
      if (typeof rootSetting !== 'undefined') {
        setting = rootSetting;
      } else if (typeof privateSetting !== 'undefined') {
        setting = privateSetting;
      } else if (typeof publicSetting !== 'undefined') {
        setting = publicSetting;
      } else {
        setting = defaultValue;
      }
    }

  } else {
    // look only in public
    const publicSetting = Meteor.settings.public && getNestedProperty(Meteor.settings.public, settingName);
    setting = typeof publicSetting !== 'undefined' ? publicSetting : defaultValue;
  }

  // Settings[settingName] = {...Settings[settingName], settingValue: setting};

  return setting;

};

registerSetting('debug', false, 'Enable debug mode (more verbose logging)');
<commit_msg>Fix a falsy-vs-undefined bug in getSetting<commit_after>import Vulcan from './config.js';
import flatten from 'flat';

const getNestedProperty = function (obj, desc) {
  var arr = desc.split('.');
  while(arr.length && (obj = obj[arr.shift()]));
  return obj;
};

export const Settings = {};

export const getAllSettings = () => {

  const settingsObject = {};

  let rootSettings = _.clone(Meteor.settings);
  delete rootSettings.public;
  delete rootSettings.private;

  // root settings & private settings are both private
  rootSettings = flatten(rootSettings, {safe: true});
  const privateSettings = flatten(Meteor.settings.private || {}, {safe: true});

  // public settings
  const publicSettings = flatten(Meteor.settings.public || {}, {safe: true});

  // registered default values
  const registeredSettings = Settings;

  const allSettingKeys = _.union(_.keys(rootSettings), _.keys(publicSettings), _.keys(privateSettings), _.keys(registeredSettings));

  allSettingKeys.sort().forEach(key => {

    settingsObject[key] = {};

    if (typeof rootSettings[key] !== 'undefined') {
      settingsObject[key].value = rootSettings[key];
    } else if (typeof privateSettings[key] !== 'undefined') {
      settingsObject[key].value = privateSettings[key];
    } else if (typeof publicSettings[key] !== 'undefined') {
      settingsObject[key].value = publicSettings[key];
    }

    if (typeof publicSettings[key] !== 'undefined'){
      settingsObject[key].isPublic = true;
    }

    if (registeredSettings[key]) {
      if (registeredSettings[key].defaultValue !== null || registeredSettings[key].defaultValue !== undefined) settingsObject[key].defaultValue = registeredSettings[key].defaultValue;
      if (registeredSettings[key].description) settingsObject[key].description = registeredSettings[key].description;
    }

  });

  return _.map(settingsObject, (setting, key) => ({name: key, ...setting}));
};

Vulcan.showSettings = () => {
  return getAllSettings();
};

export const registerSetting = (settingName, defaultValue, description, isPublic) => {
  Settings[settingName] = { defaultValue, description, isPublic };
};

export const getSetting = (settingName, settingDefault) => {

  let setting;

  // if a default value has been registered using registerSetting, use it
  if (typeof settingDefault === 'undefined' && Settings[settingName])
    settingDefault = Settings[settingName].defaultValue;

  if (Meteor.isServer) {
    // look in public, private, and root
    const rootSetting = getNestedProperty(Meteor.settings, settingName);
    const privateSetting = Meteor.settings.private && getNestedProperty(Meteor.settings.private, settingName);
    const publicSetting = Meteor.settings.public && getNestedProperty(Meteor.settings.public, settingName);

    // if setting is an object, "collect" properties from all three places
    if (typeof rootSetting === 'object' || typeof privateSetting === 'object' || typeof publicSetting === 'object') {
      setting = {
        ...settingDefault,
        ...rootSetting,
        ...privateSetting,
        ...publicSetting,
      };
    } else {
      if (typeof rootSetting !== 'undefined') {
        setting = rootSetting;
      } else if (typeof privateSetting !== 'undefined') {
        setting = privateSetting;
      } else if (typeof publicSetting !== 'undefined') {
        setting = publicSetting;
      } else {
        setting = settingDefault;
      }
    }

  } else {
    // look only in public
    const publicSetting = Meteor.settings.public && getNestedProperty(Meteor.settings.public, settingName);
    setting = typeof publicSetting !== 'undefined' ? publicSetting : settingDefault;
  }

  // Settings[settingName] = {...Settings[settingName], settingValue: setting};

  return setting;

};

registerSetting('debug', false, 'Enable debug mode (more verbose logging)');
<|endoftext|>


<commit_before><commit_msg>Update project Autolinker.js to 0.20.0<commit_after><|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as actions from '../SessionActions';
import SessionListForm from '../components/SessionListForm';

class SessionListPage extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  componentWillMount() {
    this.props.actions.loadSessionListData(this.props.last_load_time);
  }

  render() {
    return (
      <SessionListForm {...this.props} />
    );
  }

}

SessionListPage.propTypes = {
  sessions: PropTypes.object.isRequired,
  time_slots: PropTypes.array.isRequired,
  meeting_spaces: PropTypes.array.isRequired,
  current_time_slot: PropTypes.object.isRequired,
  last_load_time: PropTypes.object,
  actions: PropTypes.object.isRequired
};

function mapStateToProps(state) {
  return {...state.sessions.viewSessionList};
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(actions, dispatch)
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(SessionListPage);
<commit_msg>auto refresh<commit_after>import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as actions from '../SessionActions';
import SessionListForm from '../components/SessionListForm';

class SessionListPage extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  mounted = false;

  componentWillMount() {
    console.log("mount");
    this.mounted = true;
    this.loadSessions();
    let autoRefreshSeconds = +this.props.location.query["auto-refresh"];
    if (autoRefreshSeconds)
      this.periodicallyLoadSessions(autoRefreshSeconds * 1000);
  }

  componentWillUnmount() {
    this.mounted = false;
    console.log("unmount");
  }

  periodicallyLoadSessions(waitTimeInMilliseconds)  {
    setTimeout(() => {
      if (!this.mounted)
        return;

      this.loadSessions();
      this.periodicallyLoadSessions(waitTimeInMilliseconds);
    }, waitTimeInMilliseconds);
  }

  loadSessions() {
    this.props.actions.loadSessionListData(this.props.last_load_time);
  }

  render() {
    return (
      <SessionListForm {...this.props} />
    );
  }

}

SessionListPage.propTypes = {
  sessions: PropTypes.array.isRequired,
  time_slots: PropTypes.array.isRequired,
  meeting_spaces: PropTypes.array.isRequired,
  current_time_slot: PropTypes.object.isRequired,
  last_load_time: PropTypes.object,
  actions: PropTypes.object.isRequired
};

function mapStateToProps(state) {
  return {...state.sessions.viewSessionList};
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(actions, dispatch)
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(SessionListPage);
<|endoftext|>


<commit_before>'use strict';

const Promise         = require('bluebird');
const _               = require('lodash');
const co              = require('co');
const fs              = Promise.promisifyAll(require('fs'));
const anymatch        = require('anymatch');
const beautifyHTML    = require('js-beautify').html;
const transform       = require('./transform');
const console         = require('../console');
const Source          = require('../source');
const resolve         = require('../context');
const AssetCollection = require('../assets/collection');

module.exports = class ComponentSource extends Source {

    constructor(items, app) {
        super('components', items, app);
        this.transform = transform;
    }

    assets() {
        let assets = [];
        for (let comp of this.flatten()) {
            assets = assets.concat(comp.assets().toArray());
        }
        return new AssetCollection({}, assets);
    }

    resolve(context) {
        return resolve(context, this);
    }

    renderString(str, context) {
        return this.engine().render(null, str, context);
    }

    renderPreview(entity, useLayout) {
        useLayout = useLayout !== false ? true : false;
        return this.render(entity, entity.context, { useLayout: true });
    }

    /**
     * Main render method. Accepts a component or variant
     * and renders them appropriately.
     *
     * Rendering a component results in the rendering of the components' default variant,
     * unless the collated option is 'true' - in this case it will return a collated rendering
     * of all it's variants.
     *
     * @param {Component/Variant} entity
     * @param {Object} context
     * @param {Object} opts
     * @return {Promise}
     * @api public
     */

    render(entity, context, opts) {

        opts           = opts || {};
        opts.useLayout = opts.useLayout || false;

        // opts.collated  = opts.collated  || false;

        const self = this;

        if (!entity) {
            return Promise.reject(null);
        }
        if (_.isString(entity)) {
            return fs.readFileAsync(entity, 'utf8').then(content => {
                return this.engine().render(entity, content, context);
            });
        }

        return co(function* () {
            const source = yield self.load();
            let rendered;
            if (_.includes(['component', 'variant'], entity.type)) {
                if (entity.type == 'component') {
                    if (entity.isCollated) {
                        rendered = yield self._renderCollatedComponent(entity, context);
                    } else {
                        entity = entity.variants().default();
                        rendered = yield self._renderVariant(entity, context);
                    }
                } else {
                    rendered = yield self._renderVariant(entity, context);
                }
                if (opts.useLayout && entity.preview) {
                    return yield self._wrapInLayout(rendered, entity.preview, {
                        _target: entity.toJSON()
                    });
                }
                return beautifyHTML(rendered, {
                    indent_size: 4,
                    preserve_newlines: true,
                    max_preserve_newlines: 1
                });
            } else {
                throw new Error(`Cannot render entity of type ${entity.type}`);
            }
        }).catch(err => {
            console.error(err);
        });
    }

    *_renderVariant(variant, context) {
        context = context || variant.context;
        const content = yield variant.getContent();
        const ctx     = yield this.resolve(context);
        ctx._self     = variant.toJSON();
        return this.engine().render(variant.viewPath, content, ctx);
    }

    *_renderCollatedComponent(component, context) {
        context = context || {};
        return (yield component.variants().toArray().map(variant => {
            return this.resolve(context[`@${variant.handle}`] || variant.context).then(ctx => {
                return this.render(variant, ctx).then(markup => {
                    const collator = this.setting('collator');
                    return _.isFunction(collator) ? collator(markup, variant) : markup;
                });
            });
        })).join('\n');
    }

    *_wrapInLayout(content, previewHandle, context) {
        let layout = this.find(previewHandle);
        if (!layout) {
            console.error(`Preview layout ${previewHandle} not found.`);
            return content;
        }
        if (layout.type === 'component') {
            layout = layout.variants().default();
        }
        let layoutContext = yield this.resolve(layout.context);
        let layoutContent = yield layout.getContent();
        layoutContext = _.defaults(layoutContext, context || {});
        layoutContext[this.setting('yield')] = content;
        return this.engine().render(layout.viewPath, layoutContent, layoutContext);
    }

    statusInfo(handle) {
        const statuses = this.setting('statuses');
        const defaultStatus = this.setting('default.status')
        if (_.isNull(handle)) {
            return null;
        }
        if (_.isUndefined(handle)) {
            return statuses[defaultStatus];
        }
        if (!statuses[handle]) {
            console.error(`Status ${handle} is not a known option.`);
            return statuses[defaultStatus];
        }
        return statuses[handle];
    }

    components() {
        return super.entities();
    }

    variants() {
        let items = [];
        for (let component of this.components()) {
            items = _.concat(items, component.variants().toArray());
        }
        return this.newSelf(items);
    }

    find() {
        if (this.size === 0 || arguments.length === 0) {
            return;
        }
        const isHandleFind = arguments.length == 1 && _.isString(arguments[0]) && arguments[0].startsWith('@');
        for (let item of this) {
            if (item.type === 'collection') {
                const search = item.find.apply(item, arguments);
                if (search) return search;
            } else if (item.type === 'component') {
                const matcher = isHandleFind ? this._makePredicate.apply(null, ['handle', arguments[0].replace('@', '')]) : this._makePredicate.apply(null, arguments);
                if (matcher(item)) return item;
            }
        }
        if (isHandleFind) {
            for (let item of this.entities()) {
                let variant = item.variants().find(arguments[0]);
                if (variant) return variant;
            }
        }
    }

    isView(file) {
        return anymatch([`**/*${this.setting('ext')}`, `!**/*${this.setting('splitter')}*${this.setting('ext')}`], file.path.toLowerCase());
    }

    isVarView(file) {
        return anymatch(`**/*${this.setting('splitter')}*${this.setting('ext')}`, file.path.toLowerCase());
    }

    isConfig(file) {
        return anymatch(`**/*.config.{js,json,yaml,yml}`, file.path.toLowerCase());
    }

    isReadme(file) {
        return anymatch(`**/readme.md`, file.path.toLowerCase());
    }

    isAsset(file) {
        return anymatch(['**/*.*', `!**/*${this.setting('ext')}`, `!**/*.config.{js,json,yaml,yml}`, `!**/readme.md`], file.path.toLowerCase());
    }

};
<commit_msg>feat(components): Defer layout rendering to adapters if available<commit_after>'use strict';

const Promise         = require('bluebird');
const _               = require('lodash');
const co              = require('co');
const fs              = Promise.promisifyAll(require('fs'));
const anymatch        = require('anymatch');
const beautifyHTML    = require('js-beautify').html;
const transform       = require('./transform');
const console         = require('../console');
const Source          = require('../source');
const resolve         = require('../context');
const AssetCollection = require('../assets/collection');

module.exports = class ComponentSource extends Source {

    constructor(items, app) {
        super('components', items, app);
        this.transform = transform;
    }

    assets() {
        let assets = [];
        for (let comp of this.flatten()) {
            assets = assets.concat(comp.assets().toArray());
        }
        return new AssetCollection({}, assets);
    }

    resolve(context) {
        return resolve(context, this);
    }

    renderString(str, context) {
        return this.engine().render(null, str, context);
    }

    renderPreview(entity, useLayout) {
        useLayout = useLayout !== false ? true : false;
        return this.render(entity, entity.context, { useLayout: true });
    }

    /**
     * Main render method. Accepts a component or variant
     * and renders them appropriately.
     *
     * Rendering a component results in the rendering of the components' default variant,
     * unless the collated option is 'true' - in this case it will return a collated rendering
     * of all it's variants.
     *
     * @param {Component/Variant} entity
     * @param {Object} context
     * @param {Object} opts
     * @return {Promise}
     * @api public
     */

    render(entity, context, opts) {

        opts           = opts || {};
        opts.useLayout = opts.useLayout || false;

        // opts.collated  = opts.collated  || false;

        const self = this;

        if (!entity) {
            return Promise.reject(null);
        }
        if (_.isString(entity)) {
            return fs.readFileAsync(entity, 'utf8').then(content => {
                return this.engine().render(entity, content, context);
            });
        }

        return co(function* () {
            const source = yield self.load();
            let rendered;
            if (_.includes(['component', 'variant'], entity.type)) {
                if (entity.type == 'component') {
                    if (entity.isCollated) {
                        rendered = yield self._renderCollatedComponent(entity, context);
                    } else {
                        entity = entity.variants().default();
                        rendered = yield self._renderVariant(entity, context);
                    }
                } else {
                    rendered = yield self._renderVariant(entity, context);
                }
                if (opts.useLayout && entity.preview) {
                    return yield self._wrapInLayout(rendered, entity.preview, {
                        _target: entity.toJSON()
                    });
                }
                return beautifyHTML(rendered, {
                    indent_size: 4,
                    preserve_newlines: true,
                    max_preserve_newlines: 1
                });
            } else {
                throw new Error(`Cannot render entity of type ${entity.type}`);
            }
        }).catch(err => {
            console.error(err);
        });
    }

    *_renderVariant(variant, context) {
        context = context || variant.context;
        const content = yield variant.getContent();
        const ctx     = yield this.resolve(context);
        ctx._self     = variant.toJSON();
        return this.engine().render(variant.viewPath, content, ctx);
    }

    *_renderCollatedComponent(component, context) {
        context = context || {};
        return (yield component.variants().toArray().map(variant => {
            return this.resolve(context[`@${variant.handle}`] || variant.context).then(ctx => {
                return this.render(variant, ctx).then(markup => {
                    const collator = this.setting('collator');
                    return _.isFunction(collator) ? collator(markup, variant) : markup;
                });
            });
        })).join('\n');
    }

    *_wrapInLayout(content, previewHandle, context) {
        let layout = this.find(previewHandle);
        if (!layout) {
            console.error(`Preview layout ${previewHandle} not found.`);
            return content;
        }
        if (layout.type === 'component') {
            layout = layout.variants().default();
        }
        let layoutContext = yield this.resolve(layout.context);
        let layoutContent = yield layout.getContent();
        layoutContext     = _.defaults(layoutContext, context || {});
        layoutContext[this.setting('yield')] = content;
        const renderMethod = (typeof this.engine().renderLayout === 'function') ? 'renderLayout' : 'render';
        return this.engine()[renderMethod](layout.viewPath, layoutContent, layoutContext);
    }

    statusInfo(handle) {
        const statuses = this.setting('statuses');
        const defaultStatus = this.setting('default.status')
        if (_.isNull(handle)) {
            return null;
        }
        if (_.isUndefined(handle)) {
            return statuses[defaultStatus];
        }
        if (!statuses[handle]) {
            console.error(`Status ${handle} is not a known option.`);
            return statuses[defaultStatus];
        }
        return statuses[handle];
    }

    components() {
        return super.entities();
    }

    variants() {
        let items = [];
        for (let component of this.components()) {
            items = _.concat(items, component.variants().toArray());
        }
        return this.newSelf(items);
    }

    find() {
        if (this.size === 0 || arguments.length === 0) {
            return;
        }
        const isHandleFind = arguments.length == 1 && _.isString(arguments[0]) && arguments[0].startsWith('@');
        for (let item of this) {
            if (item.type === 'collection') {
                const search = item.find.apply(item, arguments);
                if (search) return search;
            } else if (item.type === 'component') {
                const matcher = isHandleFind ? this._makePredicate.apply(null, ['handle', arguments[0].replace('@', '')]) : this._makePredicate.apply(null, arguments);
                if (matcher(item)) return item;
            }
        }
        if (isHandleFind) {
            for (let item of this.entities()) {
                let variant = item.variants().find(arguments[0]);
                if (variant) return variant;
            }
        }
    }

    isView(file) {
        return anymatch([`**/*${this.setting('ext')}`, `!**/*${this.setting('splitter')}*${this.setting('ext')}`], file.path.toLowerCase());
    }

    isVarView(file) {
        return anymatch(`**/*${this.setting('splitter')}*${this.setting('ext')}`, file.path.toLowerCase());
    }

    isConfig(file) {
        return anymatch(`**/*.config.{js,json,yaml,yml}`, file.path.toLowerCase());
    }

    isReadme(file) {
        return anymatch(`**/readme.md`, file.path.toLowerCase());
    }

    isAsset(file) {
        return anymatch(['**/*.*', `!**/*${this.setting('ext')}`, `!**/*.config.{js,json,yaml,yml}`, `!**/readme.md`], file.path.toLowerCase());
    }

};
<|endoftext|>


<commit_before>module.exports = function(grunt) {
  'use strict';

  require('time-grunt')(grunt);
  require('jit-grunt')(grunt)({
    customTasksDir: 'helpers/grunt/tasks'
  });

  var path = require('path'),
      cwd =  process.cwd();

  // Initialize config
  grunt.initConfig({
    pkg: require('./package.json'),
    mainPkg: grunt.file.readJSON(path.join(cwd, '../package.json'))
  });

  // load tasks config per file
  grunt.loadTasks('helpers/grunt/config');

  //--- grunt tasks

  grunt.registerTask('default', ['jshint']);

  grunt.registerTask('init', [
    'jshint',
    'clean:gh_pages_dir',
    'gitclone:target'
  ]);

  grunt.registerTask('publish',  function(target) { // default: prod workflow
    var message = 'publish ',
        tasks = [
          'jshint',
          'clean:gh_pages_content',
          'copy:gitignore'
        ];

    if(target === 'dev') {
      message += 'dev code';
      tasks = tasks.concat([
        'shell:projectBuildDev',
        'copy:projectDev',
        'shell:projectClean',
        'githubPages:devCode'
      ]);
    } else { // prod
      message += 'prod code';
      tasks = tasks.concat([
        'shell:projectBuildProd',
        'copy:projectProd',
        'shell:projectClean',
        'githubPages:prodCode'
      ]);
    }

    grunt.log.writeln(message);
    //console.log(tasks);
    return grunt.task.run(tasks);
  });

};
<commit_msg>#4 /publisher grunt added jit-grunt support<commit_after>module.exports = function(grunt) {
  'use strict';

  require('time-grunt')(grunt);
  require('jit-grunt')(grunt, {
    gitclone: 'grunt-git'
  })({
    customTasksDir: 'helpers/grunt/tasks'
  });

  var path = require('path'),
      cwd =  process.cwd();

  // Initialize config
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    mainPkg: grunt.file.readJSON(path.join(cwd, '../package.json'))
  });

  // load tasks config per file
  grunt.loadTasks('helpers/grunt/config');

  //--- grunt tasks

  grunt.registerTask('default', ['jshint']);

  grunt.registerTask('init', [
    'jshint',
    'clean:gh_pages_dir',
    'gitclone:target'
  ]);

  grunt.registerTask('publish',  function(target) { // default: prod workflow
    var message = 'publish ',
        tasks = [
          'jshint',
          'clean:gh_pages_content',
          'copy:gitignore'
        ];

    if(target === 'dev') {
      message += 'dev code';
      tasks = tasks.concat([
        'shell:projectBuildDev',
        'copy:projectDev',
        'shell:projectClean',
        'githubPages:devCode'
      ]);
    } else { // prod
      message += 'prod code';
      tasks = tasks.concat([
        'shell:projectBuildProd',
        'copy:projectProd',
        'shell:projectClean',
        'githubPages:prodCode'
      ]);
    }

    grunt.log.writeln(message);
    //console.log(tasks);
    return grunt.task.run(tasks);
  });

};
<|endoftext|>


<commit_before>const Promise = require(`bluebird`)
const {
  GraphQLObjectType,
  GraphQLBoolean,
  GraphQLString,
  GraphQLInt,
  GraphQLFloat,
  GraphQLEnumType,
} = require(`graphql`)
const qs = require(`qs`)
const base64Img = require(`base64-img`)
const _ = require(`lodash`)

const ImageFormatType = new GraphQLEnumType({
  name: `ContentfulImageFormat`,
  values: {
    NO_CHANGE: { value: `` },
    JPG: { value: `jpg` },
    PNG: { value: `png` },
    WEBP: { value: `webp` },
  },
})

const ImageResizingBehavior = new GraphQLEnumType({
  name: `ImageResizingBehavior`,
  values: {
    NO_CHANGE: {
      value: ``,
    },
    PAD: {
      value: `pad`,
      description: `Same as the default resizing, but adds padding so that the generated image has the specified dimensions.`,
    },

    CROP: {
      value: `crop`,
      description: `Crop a part of the original image to match the specified size.`,
    },
    FILL: {
      value: `fill`,
      description: `Crop the image to the specified dimensions, if the original image is smaller than these dimensions, then the image will be upscaled.`,
    },
    THUMB: {
      value: `thumb`,
      description: `When used in association with the f parameter below, creates a thumbnail from the image based on a focus area.`,
    },
    SCALE: {
      value: `scale`,
      description: `Scale the image regardless of the original aspect ratio.`,
    },
  },
})

const ImageCropFocusType = new GraphQLEnumType({
  name: `ContentfulImageCropFocus`,
  values: {
    TOP: { value: `top` },
    TOP_LEFT: { value: `top_left` },
    TOP_RIGHT: { value: `top_right` },
    BOTTOM: { value: `bottom` },
    BOTTOM_RIGHT: { value: `bottom_left` },
    BOTTOM_LEFT: { value: `bottom_right` },
    RIGHT: { value: `right` },
    LEFT: { value: `left` },
    FACES: { value: `faces` },
  },
})

const isImage = image =>
  _.includes(
    [`image/jpeg`, `image/jpg`, `image/png`, `image/webp`, `image/gif`],
    image.file.contentType
  )

const getBase64Image = (imgUrl, args = {}) => {
  const requestUrl = `https:${imgUrl}?w=20`
  // TODO add caching.
  const urlArgs = { width: 20, height: 20 / args.aspectRatio }
  return new Promise(resolve => {
    base64Img.requestBase64(requestUrl, (a, b, body) => {
      resolve(body)
    })
  })
}

const getBase64ImageAndBasicMeasurements = (image, args) =>
  new Promise(resolve => {
    getBase64Image(image.file.url, args).then(base64Str => {
      let aspectRatio
      if (args.width && args.height) {
        aspectRatio = args.width / args.height
      } else {
        aspectRatio =
          image.file.details.image.width / image.file.details.image.height
      }

      resolve({
        contentType: image.file.contentType,
        base64Str,
        aspectRatio,
        width: image.file.details.image.width,
        height: image.file.details.image.height,
      })
    })
  })
const createUrl = (imgUrl, options = {}) => {
  // Convert to Contentful names and filter out undefined/null values.
  const args = _.pickBy(
    {
      w: options.width,
      h: options.height,
      fl: options.jpegProgressive ? `progressive` : null,
      q: options.quality,
      fm: options.toFormat ? options.toFormat : ``,
      fit: options.resizingBehavior ? options.resizingBehavior : ``,
      f: options.cropFocus ? options.cropFocus : ``,
    },
    _.identity
  )
  return `${imgUrl}?${qs.stringify(args)}`
}
exports.createUrl = createUrl

const resolveResponsiveResolution = (image, options) => {
  if (isImage(image)) {
    return new Promise(resolve => {
      getBase64ImageAndBasicMeasurements(
        image,
        options
      ).then(({ contentType, base64Str, width, height, aspectRatio }) => {
        // Special case handling gifs. Contentful's image api can't handle
        // them so we just return them as is.
        if (contentType === `image/gif`) {
          return resolve({
            base64: ``,
            aspectRatio: aspectRatio,
            width: Math.round(options.width),
            height: Math.round(pickedHeight),
            src: image.file.url,
            srcSet: ``,
          })
        }

        let desiredAspectRatio = aspectRatio

        // If we're cropping, calculate the specified aspect ratio.
        if (options.height) {
          desiredAspectRatio = options.width / options.height
        }

        // If the user selected a height (so cropping) and options for focus
        // and fit aren't set, we'll set our defaults
        if (options.height) {
          if (!options.resizingBehavior) {
            options.resizingBehavior = `fill`
          }
          if (!options.cropFocus) {
            options.cropFocus = `faces`
          }
        }

        // Create sizes (in width) for the image. If the width of the
        // image is 800px, the sizes would then be: 800, 1200, 1600,
        // 2400.
        //
        // This is enough sizes to provide close to the optimal image size for every
        // device size / screen resolution
        let sizes = []
        sizes.push(options.width)
        sizes.push(options.width * 1.5)
        sizes.push(options.width * 2)
        sizes.push(options.width * 3)
        sizes = sizes.map(Math.round)

        // Filter out sizes larger than the image's width.
        const filteredSizes = sizes.filter(size => size < width)

        // Sort sizes for prettiness.
        const sortedSizes = _.sortBy(filteredSizes)

        // Create the srcSet.
        const srcSet = sortedSizes
          .map((size, i) => {
            let resolution
            switch (i) {
              case 0:
                resolution = `1x`
                break
              case 1:
                resolution = `1.5x`
                break
              case 2:
                resolution = `2x`
                break
              case 3:
                resolution = `3x`
                break
              default:
            }
            const h = Math.round(size / desiredAspectRatio)
            return `${createUrl(image.file.url, {
              ...options,
              width: size,
              height: h,
            })} ${resolution}`
          })
          .join(`,\n`)

        let pickedHeight
        if (options.height) {
          pickedHeight = options.height
        } else {
          pickedHeight = options.width / desiredAspectRatio
        }

        return resolve({
          base64: base64Str,
          aspectRatio: aspectRatio,
          width: Math.round(options.width),
          height: Math.round(pickedHeight),
          src: createUrl(image.file.url, {
            ...options,
            width: options.width,
          }),
          srcSet,
        })
      })
    })
    return null
  }
}
exports.resolveResponsiveResolution = resolveResponsiveResolution

const resolveResponsiveSizes = (image, options) => {
  if (isImage(image)) {
    return new Promise(resolve => {
      getBase64ImageAndBasicMeasurements(
        image,
        options
      ).then(({ contentType, base64Str, width, height, aspectRatio }) => {
        // Special case handling gifs. Contentful's image api can't handle
        // them so we just return them as is.
        if (contentType === `image/gif`) {
          return resolve({
            base64: ``,
            srcSet: ``,
            sizes: ``,
            aspectRatio: aspectRatio,
            width: Math.round(options.width),
            height: Math.round(pickedHeight),
            src: image.file.url,
          })
        }

        let desiredAspectRatio = aspectRatio

        // If we're cropping, calculate the specified aspect ratio.
        if (options.maxHeight) {
          desiredAspectRatio = options.maxWidth / options.maxHeight
        }

        // If the users didn't set a default sizes, we'll make one.
        if (!options.sizes) {
          options.sizes = `(max-width: ${options.maxWidth}px) 100vw, ${options.maxWidth}px`
        }

        // Create sizes (in width) for the image. If the max width of the container
        // for the rendered markdown file is 800px, the sizes would then be: 200,
        // 400, 800, 1200, 1600, 2400.
        //
        // This is enough sizes to provide close to the optimal image size for every
        // device size / screen resolution
        let sizes = []
        sizes.push(options.maxWidth / 4)
        sizes.push(options.maxWidth / 2)
        sizes.push(options.maxWidth)
        sizes.push(options.maxWidth * 1.5)
        sizes.push(options.maxWidth * 2)
        sizes.push(options.maxWidth * 3)
        sizes = sizes.map(Math.round)

        // Filter out sizes larger than the image's maxWidth.
        const filteredSizes = sizes.filter(size => size < width)

        // Add the original image to ensure the largest image possible
        // is available for small images.
        filteredSizes.push(width)

        // Sort sizes for prettiness.
        const sortedSizes = _.sortBy(filteredSizes)

        // Create the srcSet.
        const srcSet = sortedSizes
          .map(width => {
            const h = Math.round(width * desiredAspectRatio)
            return `${createUrl(image.file.url, {
              ...options,
              width,
              height: h,
            })} ${Math.round(width)}w`
          })
          .join(`,\n`)

        return resolve({
          base64: base64Str,
          aspectRatio: aspectRatio,
          src: createUrl(image.file.url, {
            ...options,
            width: options.maxWidth,
            height: options.maxHeight,
          }),
          srcSet,
          sizes: options.sizes,
        })
      })
    })
    return null
  }
}
exports.resolveResponsiveSizes = resolveResponsiveSizes

const resolveResize = (image, options) =>
  new Promise(resolve => {
    if (isImage(image)) {
      getBase64ImageAndBasicMeasurements(
        image,
        options
      ).then(({ contentType, base64Str, width, height, aspectRatio }) => {
        // Special case handling gifs. Contentful's image api can't handle
        // them so we just return them as is.
        if (contentType === `image/gif`) {
          return resolve({
            base64: ``,
            aspectRatio: aspectRatio,
            width: Math.round(options.width),
            height: Math.round(pickedHeight),
            src: image.file.url,
          })
        }

        // If the user selected a height (so cropping) and options for focus
        // and fit aren't set, we'll set our defaults
        if (options.height) {
          if (!options.resizingBehavior) {
            options.resizingBehavior = `fill`
          }
          if (!options.cropFocus) {
            options.cropFocus = `faces`
          }
        }

        if (options.base64) {
          return resolve(base64Str)
        } else {
          const pickedWidth = options.width
          let pickedHeight
          if (options.height) {
            pickedHeight = options.height
          } else {
            pickedHeight = pickedWidth / aspectRatio
          }
          resolve({
            src: createUrl(image.file.url, options),
            width: Math.round(pickedWidth),
            height: Math.round(pickedHeight),
            aspectRatio,
            base64: base64Str,
          })
        }
      })
    } else {
      resolve()
    }
  })

exports.resolveResize = resolveResize

exports.extendNodeType = ({ type }) => {
  if (type.name !== `ContentfulAsset`) {
    return {}
  }

  return {
    responsiveResolution: {
      type: new GraphQLObjectType({
        name: `ContentfulResponsiveResolution`,
        fields: {
          base64: { type: GraphQLString },
          aspectRatio: { type: GraphQLFloat },
          width: { type: GraphQLFloat },
          height: { type: GraphQLFloat },
          src: { type: GraphQLString },
          srcSet: { type: GraphQLString },
        },
      }),
      args: {
        width: {
          type: GraphQLInt,
          defaultValue: 400,
        },
        height: {
          type: GraphQLInt,
        },
        quality: {
          type: GraphQLInt,
          defaultValue: 50,
        },
        toFormat: {
          type: ImageFormatType,
          defaultValue: ``,
        },
        resizingBehavior: {
          type: ImageResizingBehavior,
        },
        cropFocus: {
          type: ImageCropFocusType,
          defaultValue: null,
        },
      },
      resolve(image, options, context) {
        return resolveResponsiveResolution(image, options)
      },
    },
    responsiveSizes: {
      type: new GraphQLObjectType({
        name: `ContentfulResponsiveSizes`,
        fields: {
          base64: { type: GraphQLString },
          aspectRatio: { type: GraphQLFloat },
          src: { type: GraphQLString },
          srcSet: { type: GraphQLString },
          sizes: { type: GraphQLString },
        },
      }),
      args: {
        maxWidth: {
          type: GraphQLInt,
          defaultValue: 800,
        },
        maxHeight: {
          type: GraphQLInt,
        },
        quality: {
          type: GraphQLInt,
          defaultValue: 50,
        },
        toFormat: {
          type: ImageFormatType,
          defaultValue: ``,
        },
        resizingBehavior: {
          type: ImageResizingBehavior,
        },
        cropFocus: {
          type: ImageCropFocusType,
          defaultValue: null,
        },
        sizes: {
          type: GraphQLString,
        },
      },
      resolve(image, options, context) {
        return resolveResponsiveSizes(image, options)
      },
    },
    resize: {
      type: new GraphQLObjectType({
        name: `ContentfulResize`,
        fields: {
          src: { type: GraphQLString },
          width: { type: GraphQLInt },
          height: { type: GraphQLInt },
          aspectRatio: { type: GraphQLFloat },
        },
      }),
      args: {
        width: {
          type: GraphQLInt,
          defaultValue: 400,
        },
        height: {
          type: GraphQLInt,
        },
        quality: {
          type: GraphQLInt,
          defaultValue: 50,
        },
        jpegProgressive: {
          type: GraphQLBoolean,
          defaultValue: true,
        },
        resizingBehavior: {
          type: ImageResizingBehavior,
        },
        base64: {
          type: GraphQLBoolean,
          defaultValue: false,
        },
        toFormat: {
          type: ImageFormatType,
          defaultValue: ``,
        },
        cropFocus: {
          type: ImageCropFocusType,
          defaultValue: null,
        },
      },
      resolve(image, options, context) {
        return resolveResize(image, options)
      },
    },
  }
}
<commit_msg>Contentful does support resizing gifs from Image API fixes #2120 (#2126)<commit_after>const Promise = require(`bluebird`)
const {
  GraphQLObjectType,
  GraphQLBoolean,
  GraphQLString,
  GraphQLInt,
  GraphQLFloat,
  GraphQLEnumType,
} = require(`graphql`)
const qs = require(`qs`)
const base64Img = require(`base64-img`)
const _ = require(`lodash`)

const ImageFormatType = new GraphQLEnumType({
  name: `ContentfulImageFormat`,
  values: {
    NO_CHANGE: { value: `` },
    JPG: { value: `jpg` },
    PNG: { value: `png` },
    WEBP: { value: `webp` },
  },
})

const ImageResizingBehavior = new GraphQLEnumType({
  name: `ImageResizingBehavior`,
  values: {
    NO_CHANGE: {
      value: ``,
    },
    PAD: {
      value: `pad`,
      description: `Same as the default resizing, but adds padding so that the generated image has the specified dimensions.`,
    },

    CROP: {
      value: `crop`,
      description: `Crop a part of the original image to match the specified size.`,
    },
    FILL: {
      value: `fill`,
      description: `Crop the image to the specified dimensions, if the original image is smaller than these dimensions, then the image will be upscaled.`,
    },
    THUMB: {
      value: `thumb`,
      description: `When used in association with the f parameter below, creates a thumbnail from the image based on a focus area.`,
    },
    SCALE: {
      value: `scale`,
      description: `Scale the image regardless of the original aspect ratio.`,
    },
  },
})

const ImageCropFocusType = new GraphQLEnumType({
  name: `ContentfulImageCropFocus`,
  values: {
    TOP: { value: `top` },
    TOP_LEFT: { value: `top_left` },
    TOP_RIGHT: { value: `top_right` },
    BOTTOM: { value: `bottom` },
    BOTTOM_RIGHT: { value: `bottom_left` },
    BOTTOM_LEFT: { value: `bottom_right` },
    RIGHT: { value: `right` },
    LEFT: { value: `left` },
    FACES: { value: `faces` },
  },
})

const isImage = image =>
  _.includes(
    [`image/jpeg`, `image/jpg`, `image/png`, `image/webp`, `image/gif`],
    image.file.contentType
  )

const getBase64Image = (imgUrl, args = {}) => {
  const requestUrl = `https:${imgUrl}?w=20`
  // TODO add caching.
  const urlArgs = { width: 20, height: 20 / args.aspectRatio }
  return new Promise(resolve => {
    base64Img.requestBase64(requestUrl, (a, b, body) => {
      resolve(body)
    })
  })
}

const getBase64ImageAndBasicMeasurements = (image, args) =>
  new Promise(resolve => {
    getBase64Image(image.file.url, args).then(base64Str => {
      let aspectRatio
      if (args.width && args.height) {
        aspectRatio = args.width / args.height
      } else {
        aspectRatio =
          image.file.details.image.width / image.file.details.image.height
      }

      resolve({
        contentType: image.file.contentType,
        base64Str,
        aspectRatio,
        width: image.file.details.image.width,
        height: image.file.details.image.height,
      })
    })
  })
const createUrl = (imgUrl, options = {}) => {
  // Convert to Contentful names and filter out undefined/null values.
  const args = _.pickBy(
    {
      w: options.width,
      h: options.height,
      fl: options.jpegProgressive ? `progressive` : null,
      q: options.quality,
      fm: options.toFormat ? options.toFormat : ``,
      fit: options.resizingBehavior ? options.resizingBehavior : ``,
      f: options.cropFocus ? options.cropFocus : ``,
    },
    _.identity
  )
  return `${imgUrl}?${qs.stringify(args)}`
}
exports.createUrl = createUrl

const resolveResponsiveResolution = (image, options) => {
  if (isImage(image)) {
    return new Promise(resolve => {
      getBase64ImageAndBasicMeasurements(
        image,
        options
      ).then(({ contentType, base64Str, width, height, aspectRatio }) => {
        let desiredAspectRatio = aspectRatio

        // If we're cropping, calculate the specified aspect ratio.
        if (options.height) {
          desiredAspectRatio = options.width / options.height
        }

        // If the user selected a height (so cropping) and options for focus
        // and fit aren't set, we'll set our defaults
        if (options.height) {
          if (!options.resizingBehavior) {
            options.resizingBehavior = `fill`
          }
          if (!options.cropFocus) {
            options.cropFocus = `faces`
          }
        }

        // Create sizes (in width) for the image. If the width of the
        // image is 800px, the sizes would then be: 800, 1200, 1600,
        // 2400.
        //
        // This is enough sizes to provide close to the optimal image size for every
        // device size / screen resolution
        let sizes = []
        sizes.push(options.width)
        sizes.push(options.width * 1.5)
        sizes.push(options.width * 2)
        sizes.push(options.width * 3)
        sizes = sizes.map(Math.round)

        // Filter out sizes larger than the image's width.
        const filteredSizes = sizes.filter(size => size < width)

        // Sort sizes for prettiness.
        const sortedSizes = _.sortBy(filteredSizes)

        // Create the srcSet.
        const srcSet = sortedSizes
          .map((size, i) => {
            let resolution
            switch (i) {
              case 0:
                resolution = `1x`
                break
              case 1:
                resolution = `1.5x`
                break
              case 2:
                resolution = `2x`
                break
              case 3:
                resolution = `3x`
                break
              default:
            }
            const h = Math.round(size / desiredAspectRatio)
            return `${createUrl(image.file.url, {
              ...options,
              width: size,
              height: h,
            })} ${resolution}`
          })
          .join(`,\n`)

        let pickedHeight
        if (options.height) {
          pickedHeight = options.height
        } else {
          pickedHeight = options.width / desiredAspectRatio
        }

        return resolve({
          base64: base64Str,
          aspectRatio: aspectRatio,
          width: Math.round(options.width),
          height: Math.round(pickedHeight),
          src: createUrl(image.file.url, {
            ...options,
            width: options.width,
          }),
          srcSet,
        })
      })
    })
    return null
  }
}
exports.resolveResponsiveResolution = resolveResponsiveResolution

const resolveResponsiveSizes = (image, options) => {
  if (isImage(image)) {
    return new Promise(resolve => {
      getBase64ImageAndBasicMeasurements(
        image,
        options
      ).then(({ contentType, base64Str, width, height, aspectRatio }) => {
        let desiredAspectRatio = aspectRatio

        // If we're cropping, calculate the specified aspect ratio.
        if (options.maxHeight) {
          desiredAspectRatio = options.maxWidth / options.maxHeight
        }

        // If the users didn't set a default sizes, we'll make one.
        if (!options.sizes) {
          options.sizes = `(max-width: ${options.maxWidth}px) 100vw, ${options.maxWidth}px`
        }

        // Create sizes (in width) for the image. If the max width of the container
        // for the rendered markdown file is 800px, the sizes would then be: 200,
        // 400, 800, 1200, 1600, 2400.
        //
        // This is enough sizes to provide close to the optimal image size for every
        // device size / screen resolution
        let sizes = []
        sizes.push(options.maxWidth / 4)
        sizes.push(options.maxWidth / 2)
        sizes.push(options.maxWidth)
        sizes.push(options.maxWidth * 1.5)
        sizes.push(options.maxWidth * 2)
        sizes.push(options.maxWidth * 3)
        sizes = sizes.map(Math.round)

        // Filter out sizes larger than the image's maxWidth.
        const filteredSizes = sizes.filter(size => size < width)

        // Add the original image to ensure the largest image possible
        // is available for small images.
        filteredSizes.push(width)

        // Sort sizes for prettiness.
        const sortedSizes = _.sortBy(filteredSizes)

        // Create the srcSet.
        const srcSet = sortedSizes
          .map(width => {
            const h = Math.round(width * desiredAspectRatio)
            return `${createUrl(image.file.url, {
              ...options,
              width,
              height: h,
            })} ${Math.round(width)}w`
          })
          .join(`,\n`)

        return resolve({
          base64: base64Str,
          aspectRatio: aspectRatio,
          src: createUrl(image.file.url, {
            ...options,
            width: options.maxWidth,
            height: options.maxHeight,
          }),
          srcSet,
          sizes: options.sizes,
        })
      })
    })
    return null
  }
}
exports.resolveResponsiveSizes = resolveResponsiveSizes

const resolveResize = (image, options) =>
  new Promise(resolve => {
    if (isImage(image)) {
      getBase64ImageAndBasicMeasurements(
        image,
        options
      ).then(({ contentType, base64Str, width, height, aspectRatio }) => {
        // If the user selected a height (so cropping) and options for focus
        // and fit aren't set, we'll set our defaults
        if (options.height) {
          if (!options.resizingBehavior) {
            options.resizingBehavior = `fill`
          }
          if (!options.cropFocus) {
            options.cropFocus = `faces`
          }
        }

        if (options.base64) {
          return resolve(base64Str)
        } else {
          const pickedWidth = options.width
          let pickedHeight
          if (options.height) {
            pickedHeight = options.height
          } else {
            pickedHeight = pickedWidth / aspectRatio
          }
          resolve({
            src: createUrl(image.file.url, options),
            width: Math.round(pickedWidth),
            height: Math.round(pickedHeight),
            aspectRatio,
            base64: base64Str,
          })
        }
      })
    } else {
      resolve()
    }
  })

exports.resolveResize = resolveResize

exports.extendNodeType = ({ type }) => {
  if (type.name !== `ContentfulAsset`) {
    return {}
  }

  return {
    responsiveResolution: {
      type: new GraphQLObjectType({
        name: `ContentfulResponsiveResolution`,
        fields: {
          base64: { type: GraphQLString },
          aspectRatio: { type: GraphQLFloat },
          width: { type: GraphQLFloat },
          height: { type: GraphQLFloat },
          src: { type: GraphQLString },
          srcSet: { type: GraphQLString },
        },
      }),
      args: {
        width: {
          type: GraphQLInt,
          defaultValue: 400,
        },
        height: {
          type: GraphQLInt,
        },
        quality: {
          type: GraphQLInt,
          defaultValue: 50,
        },
        toFormat: {
          type: ImageFormatType,
          defaultValue: ``,
        },
        resizingBehavior: {
          type: ImageResizingBehavior,
        },
        cropFocus: {
          type: ImageCropFocusType,
          defaultValue: null,
        },
      },
      resolve(image, options, context) {
        return resolveResponsiveResolution(image, options)
      },
    },
    responsiveSizes: {
      type: new GraphQLObjectType({
        name: `ContentfulResponsiveSizes`,
        fields: {
          base64: { type: GraphQLString },
          aspectRatio: { type: GraphQLFloat },
          src: { type: GraphQLString },
          srcSet: { type: GraphQLString },
          sizes: { type: GraphQLString },
        },
      }),
      args: {
        maxWidth: {
          type: GraphQLInt,
          defaultValue: 800,
        },
        maxHeight: {
          type: GraphQLInt,
        },
        quality: {
          type: GraphQLInt,
          defaultValue: 50,
        },
        toFormat: {
          type: ImageFormatType,
          defaultValue: ``,
        },
        resizingBehavior: {
          type: ImageResizingBehavior,
        },
        cropFocus: {
          type: ImageCropFocusType,
          defaultValue: null,
        },
        sizes: {
          type: GraphQLString,
        },
      },
      resolve(image, options, context) {
        return resolveResponsiveSizes(image, options)
      },
    },
    resize: {
      type: new GraphQLObjectType({
        name: `ContentfulResize`,
        fields: {
          src: { type: GraphQLString },
          width: { type: GraphQLInt },
          height: { type: GraphQLInt },
          aspectRatio: { type: GraphQLFloat },
        },
      }),
      args: {
        width: {
          type: GraphQLInt,
          defaultValue: 400,
        },
        height: {
          type: GraphQLInt,
        },
        quality: {
          type: GraphQLInt,
          defaultValue: 50,
        },
        jpegProgressive: {
          type: GraphQLBoolean,
          defaultValue: true,
        },
        resizingBehavior: {
          type: ImageResizingBehavior,
        },
        base64: {
          type: GraphQLBoolean,
          defaultValue: false,
        },
        toFormat: {
          type: ImageFormatType,
          defaultValue: ``,
        },
        cropFocus: {
          type: ImageCropFocusType,
          defaultValue: null,
        },
      },
      resolve(image, options, context) {
        return resolveResize(image, options)
      },
    },
  }
}
<|endoftext|>


<commit_before>moduleAid.VERSION = '2.1.0';
moduleAid.LAZY = true;

// xmlHttpRequest(url, callback, method, async) - aid for quickly using the nsIXMLHttpRequest interface
//	url - (string) to send the request
//	callback - (function) to be called after request is completed; expects callback(xmlhttp, e) where xmlhttp = xmlhttprequest return object and e = event object
//	(optional) method - either (string) "POST" or (string) "GET"
//	(optional) async - (bool) defines whether to perform the operation asynchronously, defaults to true
this.xmlHttpRequest = function(url, callback, method, async) {
	if(!method) { method = "GET"; }
	if(async !== false) { async = true; }

	var xmlhttp = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
	xmlhttp.open(method, url, async);
	if(async) {
		xmlhttp.onreadystatechange = function(e) { callback(xmlhttp, e); };
	}
	xmlhttp.send();
	if(!async) {
		callback(xmlhttp);
	}
	return xmlhttp;
};

// aSync(aFunc, aDelay) - lets me run aFunc asynchronously, basically it's a one shot timer with a delay of aDelay msec
//	aFunc - (function) to be called asynchronously
//	(optional) aDelay - (int) msec to set the timer, defaults to 0msec
this.aSync = function(aFunc, aDelay) {
	var newTimer = {
		timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
		handler: aFunc.bind(self),
		cancel: function() {
			this.timer.cancel();
		}
	};
	newTimer.timer.init(newTimer.handler, (!aDelay) ? 0 : aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
	return newTimer;
};

// dispatch(obj, properties) - creates and dispatches an event and returns (bool) whether preventDefault was called on it
//	obj - (xul element) object to dispatch the event from, it will be e.target
//	properties - (obj) expecting the following sub properties defining the following event characteristics:
//		type - (str) the event type
//		(optional) bubbles - (bool) defaults to true
//		(optional) cancelable - (bool) defaults to true
this.dispatch = function(obj, properties) {
	if(!obj || (!obj.ownerDocument && !obj.document) || !obj.dispatchEvent || !properties || !properties.type) { return false; }

	var bubbles = properties.bubbles || true;
	var cancelable = properties.cancelable || true;

	var event = (obj.ownerDocument) ? obj.ownerDocument.createEvent('Event') : obj.document.createEvent('Event');
	event.initEvent(properties.type, bubbles, cancelable);
	return obj.dispatchEvent(event);
};

// compareFunction(a, b, strict) - returns (bool) if a === b
//	a - (function) to compare
//	b - (function) to compare
//	(optional) strict - false compares function source as (string), true does not, defaults to false
this.compareFunction = function(a, b, strict) {
	if(a === b || (!strict && a.toSource() == b.toSource())) {
		return true;
	}
	return false;
};

// isAncestor(aNode, aParent) - Checks if aNode decends from aParent
//	aNode - (xul element) node to check for ancestry
//	aParent - (xul element) node to check if ancestor of aNode
//	(dont set) aWindow - to be used internally by isAncestor()
this.isAncestor = function(aNode, aParent, aWindow) {
	if(!aNode || !aParent) { return false; };

	if(aNode == aParent) { return true; }

	var ownDocument = aNode.ownerDocument || aNode.document;
	if(ownDocument && ownDocument == aParent) { return true; }
	if(aNode.compareDocumentPosition && (aNode.compareDocumentPosition(aParent) & aNode.DOCUMENT_POSITION_CONTAINS)) { return true; }

	var browsers = aParent.getElementsByTagName('browser');
	for(var i=0; i<browsers.length; i++) {
		if(isAncestor(aNode, browsers[i].contentDocument, browsers[i].contentWindow)) { return true; }
	}

	if(!aWindow) { return false; }
	for(var i=0; i<aWindow.frames.length; i++) {
		if(isAncestor(aNode, aWindow.frames[i].document, aWindow.frames[i])) { return true; }
	}
	return false;
};

// hideIt(aNode, show) - in theory this should collapse whatever I want
//	aNode - (xul element) node to collapse
//	(optional) show - false collapses aNode, true 'un'collapses it, defaults to false
this.hideIt = function(aNode, show) {
	toggleAttribute(aNode, 'collapsed', !show);
};

// trim(str) - trims whitespaces from a string (found in http://blog.stevenlevithan.com/archives/faster-trim-javascript -> trim3())
//	str - (string) to trim
this.trim = function(str) {
	if(typeof(str) != 'string') {
		return '';
	}

	return str.substring(Math.max(str.search(/\S/), 0), str.search(/\S\s*$/) + 1);
};

// closeCustomize() - useful for when you want to close the customize toolbar dialogs for whatever reason
this.closeCustomize = function() {
	windowMediator.callOnAll(function(aWindow) { try { aWindow.close(); } catch(ex) {} }, null, "chrome://global/content/customizeToolbar.xul");
};

// replaceObjStrings(node) - replace all objName and objPathString references in the node attributes and its children with the proper names
//	node - (xul element) to replace the strings in
this.replaceObjStrings = function(node) {
	if(!node) { return; }

	if(node.attributes) {
		for(var a=0; a<node.attributes.length; a++) {
			// Replace objName with this objName in every attribute
			while(node.attributes[a].value.indexOf('objName') > -1) {
				node.attributes[a].value = node.attributes[a].value.replace('objName', objName);
			}
			while(node.attributes[a].value.indexOf('objPathString') > -1) {
				node.attributes[a].value = node.attributes[a].value.replace('objPathString', objPathString);
			}
		}
	}

	var curChild = node.firstChild;
	while(curChild) {
		replaceObjStrings(curChild);
		curChild = curChild.nextSibling;
	}
};
<commit_msg>expanded dispatch() for customEvent with detail<commit_after>moduleAid.VERSION = '2.1.1';
moduleAid.LAZY = true;

// xmlHttpRequest(url, callback, method, async) - aid for quickly using the nsIXMLHttpRequest interface
//	url - (string) to send the request
//	callback - (function) to be called after request is completed; expects callback(xmlhttp, e) where xmlhttp = xmlhttprequest return object and e = event object
//	(optional) method - either (string) "POST" or (string) "GET"
//	(optional) async - (bool) defines whether to perform the operation asynchronously, defaults to true
this.xmlHttpRequest = function(url, callback, method, async) {
	if(!method) { method = "GET"; }
	if(async !== false) { async = true; }

	var xmlhttp = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
	xmlhttp.open(method, url, async);
	if(async) {
		xmlhttp.onreadystatechange = function(e) { callback(xmlhttp, e); };
	}
	xmlhttp.send();
	if(!async) {
		callback(xmlhttp);
	}
	return xmlhttp;
};

// aSync(aFunc, aDelay) - lets me run aFunc asynchronously, basically it's a one shot timer with a delay of aDelay msec
//	aFunc - (function) to be called asynchronously
//	(optional) aDelay - (int) msec to set the timer, defaults to 0msec
this.aSync = function(aFunc, aDelay) {
	var newTimer = {
		timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
		handler: aFunc.bind(self),
		cancel: function() {
			this.timer.cancel();
		}
	};
	newTimer.timer.init(newTimer.handler, (!aDelay) ? 0 : aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
	return newTimer;
};

// dispatch(obj, properties) - creates and dispatches an event and returns (bool) whether preventDefault was called on it
//	obj - (xul element) object to dispatch the event from, it will be e.target
//	properties - (obj) expecting the following sub properties defining the following event characteristics:
//		type - (str) the event type
//		(optional) bubbles - (bool) defaults to true
//		(optional) cancelable - (bool) defaults to true
//		(optional) detail - to be passed to the event
this.dispatch = function(obj, properties) {
	if(!obj || (!obj.ownerDocument && !obj.document) || !obj.dispatchEvent || !properties || !properties.type) { return false; }

	var bubbles = properties.bubbles || true;
	var cancelable = properties.cancelable || true;
	var detail = properties.detail || undefined;

	var event = (obj.ownerDocument) ? obj.ownerDocument.createEvent('CustomEvent') : obj.document.createEvent('CustomEvent');
	event.initCustomEvent(properties.type, bubbles, cancelable, detail);
	return obj.dispatchEvent(event);
};

// compareFunction(a, b, strict) - returns (bool) if a === b
//	a - (function) to compare
//	b - (function) to compare
//	(optional) strict - false compares function source as (string), true does not, defaults to false
this.compareFunction = function(a, b, strict) {
	if(a === b || (!strict && a.toSource() == b.toSource())) {
		return true;
	}
	return false;
};

// isAncestor(aNode, aParent) - Checks if aNode decends from aParent
//	aNode - (xul element) node to check for ancestry
//	aParent - (xul element) node to check if ancestor of aNode
//	(dont set) aWindow - to be used internally by isAncestor()
this.isAncestor = function(aNode, aParent, aWindow) {
	if(!aNode || !aParent) { return false; };

	if(aNode == aParent) { return true; }

	var ownDocument = aNode.ownerDocument || aNode.document;
	if(ownDocument && ownDocument == aParent) { return true; }
	if(aNode.compareDocumentPosition && (aNode.compareDocumentPosition(aParent) & aNode.DOCUMENT_POSITION_CONTAINS)) { return true; }

	var browsers = aParent.getElementsByTagName('browser');
	for(var i=0; i<browsers.length; i++) {
		if(isAncestor(aNode, browsers[i].contentDocument, browsers[i].contentWindow)) { return true; }
	}

	if(!aWindow) { return false; }
	for(var i=0; i<aWindow.frames.length; i++) {
		if(isAncestor(aNode, aWindow.frames[i].document, aWindow.frames[i])) { return true; }
	}
	return false;
};

// hideIt(aNode, show) - in theory this should collapse whatever I want
//	aNode - (xul element) node to collapse
//	(optional) show - false collapses aNode, true 'un'collapses it, defaults to false
this.hideIt = function(aNode, show) {
	toggleAttribute(aNode, 'collapsed', !show);
};

// trim(str) - trims whitespaces from a string (found in http://blog.stevenlevithan.com/archives/faster-trim-javascript -> trim3())
//	str - (string) to trim
this.trim = function(str) {
	if(typeof(str) != 'string') {
		return '';
	}

	return str.substring(Math.max(str.search(/\S/), 0), str.search(/\S\s*$/) + 1);
};

// closeCustomize() - useful for when you want to close the customize toolbar dialogs for whatever reason
this.closeCustomize = function() {
	windowMediator.callOnAll(function(aWindow) { try { aWindow.close(); } catch(ex) {} }, null, "chrome://global/content/customizeToolbar.xul");
};

// replaceObjStrings(node) - replace all objName and objPathString references in the node attributes and its children with the proper names
//	node - (xul element) to replace the strings in
this.replaceObjStrings = function(node) {
	if(!node) { return; }

	if(node.attributes) {
		for(var a=0; a<node.attributes.length; a++) {
			// Replace objName with this objName in every attribute
			while(node.attributes[a].value.indexOf('objName') > -1) {
				node.attributes[a].value = node.attributes[a].value.replace('objName', objName);
			}
			while(node.attributes[a].value.indexOf('objPathString') > -1) {
				node.attributes[a].value = node.attributes[a].value.replace('objPathString', objPathString);
			}
		}
	}

	var curChild = node.firstChild;
	while(curChild) {
		replaceObjStrings(curChild);
		curChild = curChild.nextSibling;
	}
};
<|endoftext|>


<commit_before>pausemenu.inventory = {
    cursor: { x: 0, y: 0 }, inventoryWidth: 3,
    layersToClear: ["menuA", "menutext", "tutorial", "menuOverBlack", "menutextOverBlack"],
    actualIndexes: [], selectedCrop: -1, trashInfo: [], trashIdx: 0,
    setup: function() {
        this.cursor = { x: 0, y: 0 };
        this.cursors = new CursorAnimSet([
            { key: "main", x: this.cursor.x, y: this.cursor.y, w: 0, h: 0, type: "cursor", layer: "menucursorA" },
            { key: "alt", x: -1, y: -1, w: 0, h: 0, type: "xcursor", layer: "menucursorB" }
        ]);
        this.selectedCrop = -1;
        this.trashInfo = [];
        this.trashIdx = setInterval(this.HandleTrashCan, 50);
        this.DrawAll();
        this.cursors.Start();
    },
    DrawAll: function() {
        gfx.clearSome(this.layersToClear);
        this.actualIndexes = [];
        let j = 0;
        if(this.selectedCrop < 0) { this.cursors.MoveCursor("alt", -1, -1); }
        for(let i = 0; i < player.inventory.length; i++) {
            if(player.inventory[i][0][0] === "_" || player.inventory[i][0][0] === "!") { continue; }
            gfx.drawInventoryItem(player.inventory[i], j % this.inventoryWidth + 0.25, Math.floor(j / this.inventoryWidth) + 0.5, "menuA");
            this.actualIndexes.push(i);
            if(i === this.selectedCrop) {
                this.cursors.MoveCursor("alt", j % this.inventoryWidth + 0.25, Math.floor(j / this.inventoryWidth) + 0.5);
            }
            j++;
        }
        if(this.cursor.x < this.inventoryWidth) {
            this.cursors.MoveCursor("main", this.cursor.x + 0.25, this.cursor.y + 0.5);
        } else {
            this.cursors.MoveCursor("main", this.cursor.x + 0.5, this.cursor.y + 0.5);
        }
        if(this.selectedCrop >= 0) { this.DrawSelectInfo(); }
        this.HandleTrashCan(true);
        this.setCrop();
    },
    clean: function() {
        clearInterval(this.trashIdx);
        this.cursors.Perish();
        gfx.clearAll(true);
    },
    cancel: function() {
        if(this.selectedCrop < 0) { game.innerTransition(this, pausemenu); }
        else { this.selectedCrop = -1; if(this.cursor.x >= this.inventoryWidth) { this.cursor.x = this.inventoryWidth - 1; } this.DrawAll(); }
    },
    mouseMove: function(pos) {
        if(pos.x < 0 || pos.y < 0) { return false; }
        if(this.selectedCrop < 0) {
            if(pos.x >= this.inventoryWidth) { return false; }
            const idx = pos.y * this.inventoryWidth + pos.x;
            if(idx >= this.actualIndexes.length) { return false; }
        } else {
            if(pos.x > this.inventoryWidth) { return false; }
        }
        this.cursor = { x: pos.x, y: pos.y };
        this.DrawAll();
        return true;
    },
    HandleTrashCan: function(fromDrawAll) {
        gfx.clearLayer("tutorial");
        if(pausemenu.inventory.selectedCrop < 0 && pausemenu.inventory.trashInfo.length === 0) { return; }
        if(pausemenu.inventory.selectedCrop >= 0) { gfx.drawTileToGrid("animBin0", 3.5, pausemenu.inventory.cursor.y + 0.5, "tutorial"); }
        if(pausemenu.inventory.trashInfo.length > 0) {
            for(let i = pausemenu.inventory.trashInfo.length - 1; i >= 0; i--) {
                const ti = pausemenu.inventory.trashInfo[i];
                const trashFrame = ti.frame > 5 ? 0 : ti.frame;
                gfx.drawTileToGrid("animBin" + trashFrame, ti.x, ti.y, "tutorial");
                if(!fromDrawAll) { ti.frame++; }
                if(ti.numCoins > 0 && ti.frame % 3 === 0) {
                    ti.numCoins -= 1;
                    ti.coinStates.push({ frame: 0, x: ti.x, y: ti.y, done: false });
                }
                let allCoinsDone = true;
                for(let j = 0; j < ti.coinStates.length; j++) {
                    const coin = ti.coinStates[j];
                    if(coin.done) { continue; }
                    allCoinsDone = false;
                    const coinFrame = coin.frame > 3 ? 0 : coin.frame;
                    coin.done = (coin.frame > 4);
                    coin.y -= 0.25;
                    coin.frame++;
                    gfx.drawTileToGrid("animCoin" + coinFrame, coin.x, coin.y, "tutorial");
                }
                if(allCoinsDone && ti.frame > 6) { pausemenu.inventory.trashInfo.splice(i, 1); }
            }
        }
    },
    DrawSelectInfo: function() {
        const idx = this.cursor.y * this.inventoryWidth + this.cursor.x;
        const actIdx = this.actualIndexes[idx];
        let text = "";
        if(actIdx === this.selectedCrop) {
            text = GetText("inv.unselect");
        } else if(this.cursor.x === this.inventoryWidth) {
            text = GetText("inv.drop");
            const invfo = player.inventory[this.selectedCrop];
            const price = Math.ceil(GetCrop(invfo[0]).price * invfo[1] * 0.1);
            text = text.replace(/\{0\}/g, price);
        } else {
            text = GetText("inv.swap");
        }

        const y = this.cursor.y + 0.5, x = 4.5;
        let xi = 1;
        let width = gfx.getTextWidth(text) + 20;
        let xiimax = x + Math.ceil(width / 64);
        while(xiimax > 14) { x -= 1; xiimax = x + Math.ceil(width / 64); }
        gfx.drawTile("recSelL", x * 16, 2 + y * 16, "menuOverBlack");
        while(width > 128) {
            width -= 64;
            gfx.drawTile("recSelM", x * 16 + 16 * xi++, 2 + y * 16, "menuOverBlack");
        }
        gfx.drawTile("recSelR", x * 16 + 16 * xi, 2 + y * 16, "menuOverBlack");
        gfx.drawText(text, 7 + x * 16, 10.5 + y * 16, undefined, undefined, "menutextOverBlack");
    },
    click: function(pos) {
        if(this.cursor.x === this.inventoryWidth) {
            const invfo = player.inventory[this.selectedCrop];
            const price = Math.ceil(GetCrop(invfo[0]).price * invfo[1] * 0.1);
            player.monies += price;
            player.inventory.splice(this.selectedCrop, 1);
            this.trashInfo.push({ frame: 0, coinStates: [], x: 3.5, y: (pausemenu.inventory.cursor.y + 0.5), numCoins: Math.min(10, Math.ceil(price / 30)) });
            this.selectedCrop = -1;
            this.cursor.x = this.inventoryWidth - 1;
        } else {
            const idx = this.cursor.y * this.inventoryWidth + this.cursor.x;
            const actIdx = this.actualIndexes[idx];
            if(actIdx === undefined) { return false; }
            if(this.selectedCrop < 0) {
                this.selectedCrop = actIdx;
            } else if(this.selectedCrop === this.actIdx) {
                this.selectedCrop = -1;
                if(this.cursor.x >= this.inventoryWidth) { this.cursor.x = this.inventoryWidth - 1; }
            } else {
                const temp = player.inventory[actIdx];
                player.inventory[actIdx] = player.inventory[this.selectedCrop];
                player.inventory[this.selectedCrop] = temp;
                this.selectedCrop = -1;
            }
        }
        this.DrawAll();
        return true;
    },
    keyPress: function(key) {
        const pos = { x: this.cursor.x, y: this.cursor.y };
        let isEnter = false;
        switch(key) {
            case player.controls.up: pos.y--; break;
            case player.controls.left: pos.x--; break;
            case player.controls.down: pos.y++; break;
            case player.controls.right: pos.x++; break;
            case player.controls.confirm:
            case player.controls.pause: isEnter = true; break;
            case player.controls.cancel: return this.cancel();
        }
        if(pos.y < 0 || pos.x < 0) { return false; }
        if(isEnter) {
            return this.click(pos);
        } else {
            return this.mouseMove(pos);
        }
    },
    setCrop: function() {
        const rowYs = [0.25, 1.5, 2.75];
        const rowTextYs = [16, 32, 57, 72];
        const leftMostX = 4.75;
        const rightMostX = 14;
        const leftMostTextX = 92;

        gfx.drawInfobox(12, 10, 0);

        const idx = this.cursor.y * this.inventoryWidth + this.cursor.x;
        const actIdx = this.actualIndexes[idx];
        const item = player.inventory[actIdx];
        if(item === undefined) { return; }
        const crop = GetCrop(item[0]);

        // Row 0
        gfx.drawText(crop.displayname, leftMostTextX, rowTextYs[0], undefined, 32);
        gfx.drawTileToGrid(crop.showSeed ? crop.name + "seed" : crop.name, leftMostX, rowYs[0], "menutext");

        let cropSprite = "dirt";
        switch(crop.type) {
            case "bee": cropSprite = "_beehive"; break;
            case "spear":
            case "water":
            case "rod": cropSprite = "_lake"; break;
            case "mush": cropSprite = "_log"; break;
            case "egg": cropSprite = "_coop"; break;
            case "food": cropSprite = "_cow"; break;
            case "rice": cropSprite = "_paddy"; break;
            case "tech": cropSprite = "_hotspot"; break;
            case "sickle2": cropSprite = "_charger"; break;
        }
        gfx.drawTileToGrid(cropSprite, leftMostX + 9.25, rowYs[0], "menutext");
        gfx.drawItemNumber(crop.size, leftMostX + 9.5, rowYs[0], "menutext", true);

        // Row 1
        const seasons = ["spring", "summer", "autumn", "winter"];
        for(let i = 0; i < 4; i++) {
            gfx.drawTileToGrid(seasons[i] + crop.seasons[i], leftMostX + 6.75 + i, rowYs[1], "menutext");
        }
        this.DrawCropPower(crop, leftMostX, rowYs[1], "menutext");

        // Row 2
        gfx.drawTileToGrid("inv_time", leftMostX, rowYs[2], "menutext");
        if(crop.time === 999 || crop.time === -1) { // TODO: -1 vs 999 what is the diff?
            gfx.drawTileToGrid("bigNum?", leftMostX + 1, rowYs[2], "menutext");
        } else {
            gfx.drawBigNumber(crop.time, leftMostX + 1, rowYs[2], "menutext");
        }
        if(crop.respawn > 0) {
            gfx.drawTileToGrid("inv_regrow", leftMostX + 2, rowYs[2], "menutext");
            if(crop.respawn === 999 || crop.respawn === -1) {
                gfx.drawTileToGrid("bigNum?", leftMostX + 3, rowYs[2], "menutext");
            }  else {
                gfx.drawBigNumber(crop.respawn, leftMostX + 3, rowYs[2], "menutext");
            }
        }

        let bonusesToPush = [];
        if(crop.waterResist) { bonusesToPush.push("waterIco" + crop.waterResist); }
        if(crop.fireResist) { bonusesToPush.push("fireIco" + crop.fireResist); }
        if(crop.stickChance) { bonusesToPush.push("stunIco" + crop.stickChance); }
        if(crop.saltResist) { bonusesToPush.push("saltIco" + crop.saltResist); }
        if(crop.saltClean) { bonusesToPush.push("saltIcoX"); }
        if(crop.animal) { bonusesToPush.push("animal" + crop.animal); }
        for(let i = 0; i < bonusesToPush.length; i++) {
            gfx.drawTileToGrid(bonusesToPush[i], rightMostX - 0.25 - i, rowYs[2], "menutext");
        }

        // Row 3
        gfx.drawWrappedText(GetText(crop.name), leftMostTextX - 16, rowTextYs[3], 170);
    },
    DrawCropPower: function(crop, x, y, layer, ignoreSun) {
        if(!ignoreSun) { gfx.drawTileToGrid("inv_power", x, y, layer); }
        const numStars = crop.power / 2, starDx = 1;
        if(numStars > 5) {
            for(let i = 0; i < 5; i++) {
                gfx.drawTileToGrid("starMax", x + 1 + i * starDx, y, layer);
            }
        } else {
            for(let i = 0; i < numStars; i++) {
                gfx.drawTileToGrid("starFull", x + 1 + i * starDx, y, layer);
            }
            if(numStars % 1 !== 0) { gfx.drawTileToGrid("starHalf", x + 1 + (numStars - 0.5) * starDx, y, layer); }
            for(let i = Math.ceil(numStars); i < 5; i++) {
                gfx.drawTileToGrid("starNone", x + 1 + i * starDx, y, layer);
            }
        }
    }
};<commit_msg>moved item title text over a wee bit<commit_after>pausemenu.inventory = {
    cursor: { x: 0, y: 0 }, inventoryWidth: 3,
    layersToClear: ["menuA", "menutext", "tutorial", "menuOverBlack", "menutextOverBlack"],
    actualIndexes: [], selectedCrop: -1, trashInfo: [], trashIdx: 0,
    setup: function() {
        this.cursor = { x: 0, y: 0 };
        this.cursors = new CursorAnimSet([
            { key: "main", x: this.cursor.x, y: this.cursor.y, w: 0, h: 0, type: "cursor", layer: "menucursorA" },
            { key: "alt", x: -1, y: -1, w: 0, h: 0, type: "xcursor", layer: "menucursorB" }
        ]);
        this.selectedCrop = -1;
        this.trashInfo = [];
        this.trashIdx = setInterval(this.HandleTrashCan, 50);
        this.DrawAll();
        this.cursors.Start();
    },
    DrawAll: function() {
        gfx.clearSome(this.layersToClear);
        this.actualIndexes = [];
        let j = 0;
        if(this.selectedCrop < 0) { this.cursors.MoveCursor("alt", -1, -1); }
        for(let i = 0; i < player.inventory.length; i++) {
            if(player.inventory[i][0][0] === "_" || player.inventory[i][0][0] === "!") { continue; }
            gfx.drawInventoryItem(player.inventory[i], j % this.inventoryWidth + 0.25, Math.floor(j / this.inventoryWidth) + 0.5, "menuA");
            this.actualIndexes.push(i);
            if(i === this.selectedCrop) {
                this.cursors.MoveCursor("alt", j % this.inventoryWidth + 0.25, Math.floor(j / this.inventoryWidth) + 0.5);
            }
            j++;
        }
        if(this.cursor.x < this.inventoryWidth) {
            this.cursors.MoveCursor("main", this.cursor.x + 0.25, this.cursor.y + 0.5);
        } else {
            this.cursors.MoveCursor("main", this.cursor.x + 0.5, this.cursor.y + 0.5);
        }
        if(this.selectedCrop >= 0) { this.DrawSelectInfo(); }
        this.HandleTrashCan(true);
        this.setCrop();
    },
    clean: function() {
        clearInterval(this.trashIdx);
        this.cursors.Perish();
        gfx.clearAll(true);
    },
    cancel: function() {
        if(this.selectedCrop < 0) { game.innerTransition(this, pausemenu); }
        else { this.selectedCrop = -1; if(this.cursor.x >= this.inventoryWidth) { this.cursor.x = this.inventoryWidth - 1; } this.DrawAll(); }
    },
    mouseMove: function(pos) {
        if(pos.x < 0 || pos.y < 0) { return false; }
        if(this.selectedCrop < 0) {
            if(pos.x >= this.inventoryWidth) { return false; }
            const idx = pos.y * this.inventoryWidth + pos.x;
            if(idx >= this.actualIndexes.length) { return false; }
        } else {
            if(pos.x > this.inventoryWidth) { return false; }
        }
        this.cursor = { x: pos.x, y: pos.y };
        this.DrawAll();
        return true;
    },
    HandleTrashCan: function(fromDrawAll) {
        gfx.clearLayer("tutorial");
        if(pausemenu.inventory.selectedCrop < 0 && pausemenu.inventory.trashInfo.length === 0) { return; }
        if(pausemenu.inventory.selectedCrop >= 0) { gfx.drawTileToGrid("animBin0", 3.5, pausemenu.inventory.cursor.y + 0.5, "tutorial"); }
        if(pausemenu.inventory.trashInfo.length > 0) {
            for(let i = pausemenu.inventory.trashInfo.length - 1; i >= 0; i--) {
                const ti = pausemenu.inventory.trashInfo[i];
                const trashFrame = ti.frame > 5 ? 0 : ti.frame;
                gfx.drawTileToGrid("animBin" + trashFrame, ti.x, ti.y, "tutorial");
                if(!fromDrawAll) { ti.frame++; }
                if(ti.numCoins > 0 && ti.frame % 3 === 0) {
                    ti.numCoins -= 1;
                    ti.coinStates.push({ frame: 0, x: ti.x, y: ti.y, done: false });
                }
                let allCoinsDone = true;
                for(let j = 0; j < ti.coinStates.length; j++) {
                    const coin = ti.coinStates[j];
                    if(coin.done) { continue; }
                    allCoinsDone = false;
                    const coinFrame = coin.frame > 3 ? 0 : coin.frame;
                    coin.done = (coin.frame > 4);
                    coin.y -= 0.25;
                    coin.frame++;
                    gfx.drawTileToGrid("animCoin" + coinFrame, coin.x, coin.y, "tutorial");
                }
                if(allCoinsDone && ti.frame > 6) { pausemenu.inventory.trashInfo.splice(i, 1); }
            }
        }
    },
    DrawSelectInfo: function() {
        const idx = this.cursor.y * this.inventoryWidth + this.cursor.x;
        const actIdx = this.actualIndexes[idx];
        let text = "";
        if(actIdx === this.selectedCrop) {
            text = GetText("inv.unselect");
        } else if(this.cursor.x === this.inventoryWidth) {
            text = GetText("inv.drop");
            const invfo = player.inventory[this.selectedCrop];
            const price = Math.ceil(GetCrop(invfo[0]).price * invfo[1] * 0.1);
            text = text.replace(/\{0\}/g, price);
        } else {
            text = GetText("inv.swap");
        }

        const y = this.cursor.y + 0.5, x = 4.5;
        let xi = 1;
        let width = gfx.getTextWidth(text) + 20;
        let xiimax = x + Math.ceil(width / 64);
        while(xiimax > 14) { x -= 1; xiimax = x + Math.ceil(width / 64); }
        gfx.drawTile("recSelL", x * 16, 2 + y * 16, "menuOverBlack");
        while(width > 128) {
            width -= 64;
            gfx.drawTile("recSelM", x * 16 + 16 * xi++, 2 + y * 16, "menuOverBlack");
        }
        gfx.drawTile("recSelR", x * 16 + 16 * xi, 2 + y * 16, "menuOverBlack");
        gfx.drawText(text, 7 + x * 16, 10.5 + y * 16, undefined, undefined, "menutextOverBlack");
    },
    click: function(pos) {
        if(this.cursor.x === this.inventoryWidth) {
            const invfo = player.inventory[this.selectedCrop];
            const price = Math.ceil(GetCrop(invfo[0]).price * invfo[1] * 0.1);
            player.monies += price;
            player.inventory.splice(this.selectedCrop, 1);
            this.trashInfo.push({ frame: 0, coinStates: [], x: 3.5, y: (pausemenu.inventory.cursor.y + 0.5), numCoins: Math.min(10, Math.ceil(price / 30)) });
            this.selectedCrop = -1;
            this.cursor.x = this.inventoryWidth - 1;
        } else {
            const idx = this.cursor.y * this.inventoryWidth + this.cursor.x;
            const actIdx = this.actualIndexes[idx];
            if(actIdx === undefined) { return false; }
            if(this.selectedCrop < 0) {
                this.selectedCrop = actIdx;
            } else if(this.selectedCrop === this.actIdx) {
                this.selectedCrop = -1;
                if(this.cursor.x >= this.inventoryWidth) { this.cursor.x = this.inventoryWidth - 1; }
            } else {
                const temp = player.inventory[actIdx];
                player.inventory[actIdx] = player.inventory[this.selectedCrop];
                player.inventory[this.selectedCrop] = temp;
                this.selectedCrop = -1;
            }
        }
        this.DrawAll();
        return true;
    },
    keyPress: function(key) {
        const pos = { x: this.cursor.x, y: this.cursor.y };
        let isEnter = false;
        switch(key) {
            case player.controls.up: pos.y--; break;
            case player.controls.left: pos.x--; break;
            case player.controls.down: pos.y++; break;
            case player.controls.right: pos.x++; break;
            case player.controls.confirm:
            case player.controls.pause: isEnter = true; break;
            case player.controls.cancel: return this.cancel();
        }
        if(pos.y < 0 || pos.x < 0) { return false; }
        if(isEnter) {
            return this.click(pos);
        } else {
            return this.mouseMove(pos);
        }
    },
    setCrop: function() {
        const rowYs = [0.25, 1.5, 2.75];
        const rowTextYs = [16, 32, 57, 72];
        const leftMostX = 4.75;
        const rightMostX = 14;
        const leftMostTextX = 92;

        gfx.drawInfobox(12, 10, 0);

        const idx = this.cursor.y * this.inventoryWidth + this.cursor.x;
        const actIdx = this.actualIndexes[idx];
        const item = player.inventory[actIdx];
        if(item === undefined) { return; }
        const crop = GetCrop(item[0]);

        // Row 0
        gfx.drawText(crop.displayname, leftMostTextX + 4, rowTextYs[0], undefined, 32);
        gfx.drawTileToGrid(crop.showSeed ? crop.name + "seed" : crop.name, leftMostX, rowYs[0], "menutext");

        let cropSprite = "dirt";
        switch(crop.type) {
            case "bee": cropSprite = "_beehive"; break;
            case "spear":
            case "water":
            case "rod": cropSprite = "_lake"; break;
            case "mush": cropSprite = "_log"; break;
            case "egg": cropSprite = "_coop"; break;
            case "food": cropSprite = "_cow"; break;
            case "rice": cropSprite = "_paddy"; break;
            case "tech": cropSprite = "_hotspot"; break;
            case "sickle2": cropSprite = "_charger"; break;
        }
        gfx.drawTileToGrid(cropSprite, leftMostX + 9.25, rowYs[0], "menutext");
        gfx.drawItemNumber(crop.size, leftMostX + 9.5, rowYs[0], "menutext", true);

        // Row 1
        const seasons = ["spring", "summer", "autumn", "winter"];
        for(let i = 0; i < 4; i++) {
            gfx.drawTileToGrid(seasons[i] + crop.seasons[i], leftMostX + 6.75 + i, rowYs[1], "menutext");
        }
        this.DrawCropPower(crop, leftMostX, rowYs[1], "menutext");

        // Row 2
        gfx.drawTileToGrid("inv_time", leftMostX, rowYs[2], "menutext");
        if(crop.time === 999 || crop.time === -1) { // TODO: -1 vs 999 what is the diff?
            gfx.drawTileToGrid("bigNum?", leftMostX + 1, rowYs[2], "menutext");
        } else {
            gfx.drawBigNumber(crop.time, leftMostX + 1, rowYs[2], "menutext");
        }
        if(crop.respawn > 0) {
            gfx.drawTileToGrid("inv_regrow", leftMostX + 2, rowYs[2], "menutext");
            if(crop.respawn === 999 || crop.respawn === -1) {
                gfx.drawTileToGrid("bigNum?", leftMostX + 3, rowYs[2], "menutext");
            }  else {
                gfx.drawBigNumber(crop.respawn, leftMostX + 3, rowYs[2], "menutext");
            }
        }

        let bonusesToPush = [];
        if(crop.waterResist) { bonusesToPush.push("waterIco" + crop.waterResist); }
        if(crop.fireResist) { bonusesToPush.push("fireIco" + crop.fireResist); }
        if(crop.stickChance) { bonusesToPush.push("stunIco" + crop.stickChance); }
        if(crop.saltResist) { bonusesToPush.push("saltIco" + crop.saltResist); }
        if(crop.saltClean) { bonusesToPush.push("saltIcoX"); }
        if(crop.animal) { bonusesToPush.push("animal" + crop.animal); }
        for(let i = 0; i < bonusesToPush.length; i++) {
            gfx.drawTileToGrid(bonusesToPush[i], rightMostX - 0.25 - i, rowYs[2], "menutext");
        }

        // Row 3
        gfx.drawWrappedText(GetText(crop.name), leftMostTextX - 16, rowTextYs[3], 170);
    },
    DrawCropPower: function(crop, x, y, layer, ignoreSun) {
        if(!ignoreSun) { gfx.drawTileToGrid("inv_power", x, y, layer); }
        const numStars = crop.power / 2, starDx = 1;
        if(numStars > 5) {
            for(let i = 0; i < 5; i++) {
                gfx.drawTileToGrid("starMax", x + 1 + i * starDx, y, layer);
            }
        } else {
            for(let i = 0; i < numStars; i++) {
                gfx.drawTileToGrid("starFull", x + 1 + i * starDx, y, layer);
            }
            if(numStars % 1 !== 0) { gfx.drawTileToGrid("starHalf", x + 1 + (numStars - 0.5) * starDx, y, layer); }
            for(let i = Math.ceil(numStars); i < 5; i++) {
                gfx.drawTileToGrid("starNone", x + 1 + i * starDx, y, layer);
            }
        }
    }
};<|endoftext|>


<commit_before>// Load modules

var Lab = require('lab');
var Code = require('code');
var Hodgepodge = require('..');

// Test shortcuts

var lab = exports.lab = Lab.script();
var describe = lab.describe;
var it = lab.it;
var expect = Code.expect;

describe('Hodgepodge', function () {

    // Plugin A

    var pluginA = function () {};
    pluginA.attributes = {
        name: 'plugin-a',
        dependencies: ['plugin-b', 'plugin-c']
    };

    // Plugin B

    var pluginB = function () {};
    pluginB.attributes = {
        name: 'plugin-b',
        hodgepodge: true,
        dependencies: 'plugin-c'
    };

    pluginB.restore = function () {

        this.attributes.hodgepodge = true;
    };

    // Plugin C

    var pluginC = function () {};

    pluginC.attributes = {
        name: 'plugin-c'
    };

    // Finally the tests

    it('consumes falsey values.', function (done) {

        expect(Hodgepodge(false)).to.deep.equal([]);
        expect(Hodgepodge(null)).to.deep.equal([]);
        expect(Hodgepodge(undefined)).to.deep.equal([]);

        done();
    });

    it('consumes a plain plugin.', function (done) {

        expect(Hodgepodge(pluginA)).to.deep.equal([pluginA]);

        done();
    });

    it('consumes unfamiliar objects.', function (done) {

        var ufos = [
            1,
            'two',
            { three: 3 }
        ];

        var degenerates = [
            function () {}
        ];

        expect(Hodgepodge(ufos)).to.deep.equal(ufos);
        expect(Hodgepodge(degenerates)).to.deep.equal(degenerates);
        expect(Hodgepodge(1)).to.deep.equal([1]);

        done();
    });

    it('consumes a list of plugins using the function format.', function (done) {

        expect(Hodgepodge([pluginA])).to.deep.equal([pluginA]);

        done();
    });

    it('consumes a list of plugins using the object format.', function (done) {

        var plugins = [
            {
                register: pluginA,
                options: {}
            }
        ];

        expect(Hodgepodge(plugins)).to.deep.equal(plugins);

        done();
    });

    it('eats-up the hodgepodge attribute.', function (done) {

        expect(pluginB.attributes.hodgepodge).to.equal(true);

        var plugins = Hodgepodge([pluginB]);

        expect(plugins[0]).to.equal(pluginB);
        expect(plugins[0].attributes.hodgepodge).to.not.exist();

        pluginB.restore();

        done();
    });

    it('reorders a list of plugins, respecting their dependencies.', function (done) {

        var plugins = [pluginA, pluginB, pluginC];

        expect(Hodgepodge(plugins)).to.deep.equal([pluginC, pluginB, pluginA]);

        done();
    });

});
<commit_msg>Add circular dep test.<commit_after>// Load modules

var Lab = require('lab');
var Code = require('code');
var Hodgepodge = require('..');

// Test shortcuts

var lab = exports.lab = Lab.script();
var describe = lab.describe;
var it = lab.it;
var expect = Code.expect;

describe('Hodgepodge', function () {

    // Plugin A

    var pluginA = function () {};
    pluginA.attributes = {
        name: 'plugin-a',
        dependencies: ['plugin-b', 'plugin-c']
    };

    // Plugin B

    var pluginB = function () {};
    pluginB.attributes = {
        name: 'plugin-b',
        hodgepodge: true,
        dependencies: 'plugin-c'
    };

    pluginB.restore = function () {

        this.attributes.hodgepodge = true;
    };

    // Plugin C

    var pluginC = function () {};

    pluginC.attributes = {
        name: 'plugin-c'
    };

    // Finally the tests

    it('consumes falsey values.', function (done) {

        expect(Hodgepodge(false)).to.deep.equal([]);
        expect(Hodgepodge(null)).to.deep.equal([]);
        expect(Hodgepodge(undefined)).to.deep.equal([]);

        done();
    });

    it('consumes a plain plugin.', function (done) {

        expect(Hodgepodge(pluginA)).to.deep.equal([pluginA]);

        done();
    });

    it('consumes unfamiliar objects.', function (done) {

        var ufos = [
            1,
            'two',
            { three: 3 }
        ];

        var degenerates = [
            function () {}
        ];

        expect(Hodgepodge(ufos)).to.deep.equal(ufos);
        expect(Hodgepodge(degenerates)).to.deep.equal(degenerates);
        expect(Hodgepodge(1)).to.deep.equal([1]);

        done();
    });

    it('consumes a list of plugins using the function format.', function (done) {

        expect(Hodgepodge([pluginA])).to.deep.equal([pluginA]);

        done();
    });

    it('consumes a list of plugins using the object format.', function (done) {

        var plugins = [
            {
                register: pluginA,
                options: {}
            }
        ];

        expect(Hodgepodge(plugins)).to.deep.equal(plugins);

        done();
    });

    it('eats-up the hodgepodge attribute.', function (done) {

        expect(pluginB.attributes.hodgepodge).to.equal(true);

        var plugins = Hodgepodge([pluginB]);

        expect(plugins[0]).to.equal(pluginB);
        expect(plugins[0].attributes.hodgepodge).to.not.exist();

        pluginB.restore();

        done();
    });

    it('reorders a list of plugins, respecting their dependencies.', function (done) {

        var plugins = [pluginA, pluginB, pluginC];

        expect(Hodgepodge(plugins)).to.deep.equal([pluginC, pluginB, pluginA]);

        pluginB.restore();

        done();
    });

    it('throws on circular dependencies.', function (done) {

        var wantsOne = function () {};
        wantsOne.attributes = {
            name: 'two',
            dependencies: 'one'
        };

        var wantsTwo = function () {};
        wantsTwo.attributes = {
            name: 'one',
            dependencies: 'two'
        };

        var hodgepodging = function () {

            Hodgepodge([wantsOne, wantsTwo]);
        };

        expect(hodgepodging).to.throw();

        done();
    });

});
<|endoftext|>