<commit_before>'use strict'

var ObjectId = require("mongodb").ObjectId;

require('mongodb-toolkit');

var DLModels = require('dl-models');
var map = DLModels.map;
var SuratJalan = DLModels.suratJalan.SuratJalan;

module.exports = class SuratJalanManager {

    constructor(db, user) {
        this.db = db;
        this.user = user;
        this.SuratJalanCollection = this.db.use('surat-jalan');
    }
    create(suratJalan) {
        return new Promise((resolve, reject) => {
            this._validate(suratJalan)
                .then(validSuratJalan => {
                    this.SuratJalanCollection.insert(validSuratJalan)
                        .then(id => {
                            resolve(id);
                        })
                        .catch(e => {
                            reject(e);
                        })
                })
                .catch(e => {
                    reject(e);
                })
        });
    }

    update(suratJalan) {
        return new Promise((resolve, reject) => {
            this._validate(suratJalan)
                .then(validSuratJalan => {
                    this.SuratJalanCollection.update(validSuratJalan)
                        .then(id => {
                            resolve(id);
                        })
                        .catch(e => {
                            reject(e);
                        });
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    post(suratJalan) {
        return new Promise((resolve, reject) => {
            this._validate(suratJalan)
                .then(validSuratJalan => {
                    validSuratJalan.isPosted = true;
                    this.SuratJalanCollection.update(validSuratJalan)
                        .then(id => {
                            resolve(id);
                        })
                        .catch(e => {
                            reject(e);
                        });
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    delete(suratJalan) {
        return new Promise((resolve, reject) => {
            this._validate(suratJalan)
                .then(validSuratJalan => {
                    validSuratJalan._deleted = true;
                    this.SuratJalanCollection.update(validSuratJalan)
                        .then(id => {
                            resolve(id);
                        })
                        .catch(e => {
                            reject(e);
                        });
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    _validate(suratJalan) {
        var errors = {};
        return new Promise((resolve, reject) => {
            var valid = suratJalan;

            var getSuratJalanPromise = this.SuratJalanCollection.singleOrDefault({
                "$and": [{
                    _id: {
                        '$ne': new ObjectId(valid._id)
                    }
                }, {
                        SJNo: valid.SJNo
                    }]
            });
            // 1. end: Declare promises.

            // 2. begin: Validation.
            Promise.all([getSuratJalanPromise])
                .then(results => {
                    var _module = results[0];
                    var now = new Date();
                    if (_module) {
                        if (_module.supplier.name == valid.supplier.name)
                            errors["SJNo"] = "Nomor surat jalan sudah terdaftar";
                    }
                    else if (!valid.SJNo || valid.SJNo == '')
                        errors["SJNo"] = "Nomor surat jalan tidak boleh kosong";

                    if (!valid.SJDate || valid.SJDate == '')
                        errors["SJDate"] = "Tanggal surat jalan tidak boleh kosong";
                    else if (valid.SJDate > now)
                        errors["SJDate"] = "Tanggal surat jalan tidak boleh lebih besar dari tanggal hari ini";

                    if (!valid.productArriveDate || valid.productArriveDate == '')
                        errors["productArriveDate"] = "Tanggal datang barang tidak boleh kosong";
                    else if (valid.productArriveDate > now)
                        errors["productArriveDate"] = "Tanggal datang barang tidak boleh lebih besar dari tanggal hari ini";
                    else if (valid.productArriveDate < valid.SJDate)
                        errors["productArriveDate"] = "Tanggal datang barang tidak boleh lebih kecil dari tanggal surat jalan";

                    if (!valid.supplier)
                        errors["supplier"] = "Supplier tidak boleh kosong";
                    else if (!valid.supplier.name || valid.supplier.name == '')
                        errors["supplier"] = "Supplier tidak boleh kosong";

                    if (!valid.deliveryType || valid.deliveryType == '')
                        errors["deliveryType"] = "Jenis Pengiriman tidak boleh kosong";

                    if (valid.deliveryType != 'Lokal')
                        if (!valid.deliveryNo || valid.deliveryNo == '')
                            errors["deliveryNo"] = "Nomor Penggiriman tidak boleh kosong";

                    if (!valid.items) {
                        errors["items"] = "Harus ada minimal 1 nomor PO"
                    }
                    else if (valid.items.length == 0) {
                        errors["items"] = "Harus ada minimal 1 nomor PO"
                    }
                    // 2c. begin: check if data has any error, reject if it has.
                    for (var prop in errors) {
                        var ValidationError = require('../../validation-error');
                        reject(new ValidationError('data does not pass validation', errors));
                    }

                    if (!valid.stamp)
                        valid = new SuratJalan(valid);

                    valid.stamp(this.user.username, 'manager');
                    resolve(valid);
                })
                .catch(e => {
                    reject(e);
                })
        });
    }

    read(paging) {
        var _paging = Object.assign({
            page: 1,
            size: 20,
            order: '_id',
            asc: true
        }, paging);

        return new Promise((resolve, reject) => {
            var deleted = {
                _deleted: false
            };
            var query = _paging.keyword ? {
                '$and': [deleted]
            } : deleted;

            if (_paging.keyword) {
                var regex = new RegExp(_paging.keyword, "i");
                var filterSJNo = {
                    'SJNo': {
                        '$regex': regex
                    }
                };
                var filterRefSJNo = {
                    'RefSJNo': {
                        '$regex': regex
                    }
                };
                var filterSupplier = {
                    'supplier.name': {
                        '$regex': regex
                    }
                };
                var $or = {
                    '$or': [filterSJNo, filterRefSJNo, filterSupplier]
                };

                query['$and'].push($or);
            }

            this.SuratJalanCollection
                .where(query)
                .page(_paging.page, _paging.size)
                .orderBy(_paging.order, _paging.asc)
                .execute()
                .then(suratJalans => {
                    resolve(suratJalans);
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    getById(id) {
        return new Promise((resolve, reject) => {
            if (id === '')
                resolve(null);
            var query = {
                _id: new ObjectId(id),
                _deleted: false
            };
            this.getSingleByQuery(query)
                .then(suratJalan => {
                    resolve(suratJalan);
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    getByIdOrDefault(id) {
        return new Promise((resolve, reject) => {
            if (id === '')
                resolve(null);
            var query = {
                _id: new ObjectId(id),
                _deleted: false
            };
            this.getSingleByQueryOrDefault(query)
                .then(suratJalan => {
                    resolve(suratJalan);
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    getSingleByQuery(query) {
        return new Promise((resolve, reject) => {
            this.SuratJalanCollection
                .single(query)
                .then(suratJalan => {
                    resolve(suratJalan);
                })
                .catch(e => {
                    reject(e);
                });
        })
    }

    getSingleByQueryOrDefault(query) {
        return new Promise((resolve, reject) => {
            this.SuratJalanCollection
                .singleOrDefault(query)
                .then(suratJalan => {
                    resolve(suratJalan);
                })
                .catch(e => {
                    reject(e);
                });
        })
    }
}<commit_msg>fix surat jalan<commit_after>'use strict'

var ObjectId = require("mongodb").ObjectId;

require('mongodb-toolkit');

var DLModels = require('dl-models');
var map = DLModels.map;
var SuratJalan = DLModels.suratJalan.SuratJalan;

module.exports = class SuratJalanManager {

    constructor(db, user) {
        this.db = db;
        this.user = user;
        this.SuratJalanCollection = this.db.use('surat-jalan');
    }
    create(suratJalan) {
        return new Promise((resolve, reject) => {
            this._validate(suratJalan)
                .then(validSuratJalan => {
                    this.SuratJalanCollection.insert(validSuratJalan)
                        .then(id => {
                            resolve(id);
                        })
                        .catch(e => {
                            reject(e);
                        })
                })
                .catch(e => {
                    reject(e);
                })
        });
    }

    update(suratJalan) {
        return new Promise((resolve, reject) => {
            this._validate(suratJalan)
                .then(validSuratJalan => {
                    this.SuratJalanCollection.update(validSuratJalan)
                        .then(id => {
                            resolve(id);
                        })
                        .catch(e => {
                            reject(e);
                        });
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    post(suratJalan) {
        return new Promise((resolve, reject) => {
            this._validate(suratJalan)
                .then(validSuratJalan => {
                    validSuratJalan.isPosted = true;
                    this.SuratJalanCollection.update(validSuratJalan)
                        .then(id => {
                            resolve(id);
                        })
                        .catch(e => {
                            reject(e);
                        });
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    delete(suratJalan) {
        return new Promise((resolve, reject) => {
            this._validate(suratJalan)
                .then(validSuratJalan => {
                    validSuratJalan._deleted = true;
                    this.SuratJalanCollection.update(validSuratJalan)
                        .then(id => {
                            resolve(id);
                        })
                        .catch(e => {
                            reject(e);
                        });
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    _validate(suratJalan) {
        var errors = {};
        return new Promise((resolve, reject) => {
            var valid = suratJalan;

            var getSuratJalanPromise = this.SuratJalanCollection.singleOrDefault({
                "$and": [{
                    _id: {
                        '$ne': new ObjectId(valid._id)
                    }
                }, {
                        SJNo: valid.SJNo
                    }]
            });
            // 1. end: Declare promises.

            // 2. begin: Validation.
            Promise.all([getSuratJalanPromise])
                .then(results => {
                    var _module = results[0];
                    var now = new Date();
                    if (_module) {
                        if (_module.supplier.name == valid.supplier.name)
                            errors["SJNo"] = "Nomor surat jalan sudah terdaftar";
                    }
                    else if (!valid.SJNo || valid.SJNo == '')
                        errors["SJNo"] = "Nomor surat jalan tidak boleh kosong";

                    if (!valid.SJDate || valid.SJDate == '')
                        errors["SJDate"] = "Tanggal surat jalan tidak boleh kosong";
                    else if (valid.SJDate > now)
                        errors["SJDate"] = "Tanggal surat jalan tidak boleh lebih besar dari tanggal hari ini";

                    if (!valid.productArriveDate || valid.productArriveDate == '')
                        errors["productArriveDate"] = "Tanggal datang barang tidak boleh kosong";
                    else if (valid.productArriveDate > now)
                        errors["productArriveDate"] = "Tanggal datang barang tidak boleh lebih besar dari tanggal hari ini";
                    else if (valid.productArriveDate < valid.SJDate)
                        errors["productArriveDate"] = "Tanggal datang barang tidak boleh lebih kecil dari tanggal surat jalan";

                    if (!valid.supplier)
                        errors["supplier"] = "Supplier tidak boleh kosong";
                    else if (!valid.supplier.name || valid.supplier.name == '')
                        errors["supplier"] = "Supplier tidak boleh kosong";

                    if (!valid.deliveryType || valid.deliveryType == '')
                        errors["deliveryType"] = "Jenis Pengiriman tidak boleh kosong";

                    if (valid.deliveryType != 'Lokal')
                        if (!valid.deliveryNo || valid.deliveryNo == '')
                            errors["deliveryNo"] = "Nomor Penggiriman tidak boleh kosong";

                    if (!valid.items) {
                        errors["po"] = "Harus ada minimal 1 nomor PO"
                    }
                    else if (valid.items.length == 0) {
                        errors["po"] = "Harus ada minimal 1 nomor PO"
                    }

                    for (var item of valid.items) {
                        if (!item.items) {
                            errors["po"] = "Harus ada minimal 1 barang setiap PO"
                        }
                        else if (item.items.length == 0) {
                            errors["po"] = "Harus ada minimal 1 barang setiap PO"
                        }

                        for (var poItem of item.items) {
                            if (poItem.dealQuantity < poItem.realizationQuantity)
                                errors["poItem"] = "Jumlah barang di SJ tidak boleh lebih besar dari jumlah barang di PO"
                        }
                    }

                    // 2c. begin: check if data has any error, reject if it has.
                    for (var prop in errors) {
                        var ValidationError = require('../../validation-error');
                        reject(new ValidationError('data does not pass validation', errors));
                    }

                    if (!valid.stamp)
                        valid = new SuratJalan(valid);

                    valid.stamp(this.user.username, 'manager');
                    resolve(valid);
                })
                .catch(e => {
                    reject(e);
                })
        });
    }

    read(paging) {
        var _paging = Object.assign({
            page: 1,
            size: 20,
            order: '_id',
            asc: true
        }, paging);

        return new Promise((resolve, reject) => {
            var deleted = {
                _deleted: false
            };
            var query = _paging.keyword ? {
                '$and': [deleted]
            } : deleted;

            if (_paging.keyword) {
                var regex = new RegExp(_paging.keyword, "i");
                var filterSJNo = {
                    'SJNo': {
                        '$regex': regex
                    }
                };
                var filterRefSJNo = {
                    'RefSJNo': {
                        '$regex': regex
                    }
                };
                var filterSupplier = {
                    'supplier.name': {
                        '$regex': regex
                    }
                };
                var $or = {
                    '$or': [filterSJNo, filterRefSJNo, filterSupplier]
                };

                query['$and'].push($or);
            }

            this.SuratJalanCollection
                .where(query)
                .page(_paging.page, _paging.size)
                .orderBy(_paging.order, _paging.asc)
                .execute()
                .then(suratJalans => {
                    resolve(suratJalans);
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    getById(id) {
        return new Promise((resolve, reject) => {
            if (id === '')
                resolve(null);
            var query = {
                _id: new ObjectId(id),
                _deleted: false
            };
            this.getSingleByQuery(query)
                .then(suratJalan => {
                    resolve(suratJalan);
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    getByIdOrDefault(id) {
        return new Promise((resolve, reject) => {
            if (id === '')
                resolve(null);
            var query = {
                _id: new ObjectId(id),
                _deleted: false
            };
            this.getSingleByQueryOrDefault(query)
                .then(suratJalan => {
                    resolve(suratJalan);
                })
                .catch(e => {
                    reject(e);
                });
        });
    }

    getSingleByQuery(query) {
        return new Promise((resolve, reject) => {
            this.SuratJalanCollection
                .single(query)
                .then(suratJalan => {
                    resolve(suratJalan);
                })
                .catch(e => {
                    reject(e);
                });
        })
    }

    getSingleByQueryOrDefault(query) {
        return new Promise((resolve, reject) => {
            this.SuratJalanCollection
                .singleOrDefault(query)
                .then(suratJalan => {
                    resolve(suratJalan);
                })
                .catch(e => {
                    reject(e);
                });
        })
    }
}<|endoftext|>


<commit_before>/*
  Version listing as a smart component since it involves binding a lot of
  actions.
*/
import classnames from 'classnames';
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';

import {fetchVersions} from '../actions/addon';
import {del as deleteVersion} from '../actions/version';
import {publish, reject} from '../actions/review';
import {fetchThreads, submitNote} from '../actions/comm';

import AddonVersion from '../components/version';
import {versionListSelector} from '../selectors/version';
import {PageSection} from '../../site/components/page';

export class AddonVersionListing extends React.Component {
  static propTypes = {
    className: React.PropTypes.string,
    deleteVersion: React.PropTypes.func,
    fetchThreads: React.PropTypes.func.isRequired,
    fetchVersions: React.PropTypes.func.isRequired,
    publish: React.PropTypes.func.isRequired,
    reject: React.PropTypes.func.isRequired,
    showDeveloperActions: React.PropTypes.bool,
    showReviewActions: React.PropTypes.bool,
    slug: React.PropTypes.func.isRequired,
    versions: React.PropTypes.array.isRequired,
  };

  constructor(props) {
    super(props);
    this.props.fetchThreads(this.props.slug);
    this.props.fetchVersions(this.props.slug);
  }

  renderVersion = version => {
    return (

        <AddonVersion {...this.props} {...version}
                      {...this.props.threads[version.id]}/>

    );
  }

  render() {
    return (
      <PageSection className={this.props.className} title="Versions">

{this.props.versions.map(this.renderVersion)}

      </PageSection>
    );
  }
}

export default connect(
  state => ({
    slug: state.router.params.slug,
    threads: state.addonThread.threads,
    versions: versionListSelector(
      (state.addon.addons[state.router.params.slug] || {}).versions
    )
  }),
  dispatch => bindActionCreators({
    deleteVersion,
    fetchThreads,
    fetchVersions,
    publish,
    reject,
    submitNote
  }, dispatch)
)(AddonVersionListing);
<commit_msg>add key in version listing loop<commit_after>/*
  Version listing as a smart component since it involves binding a lot of
  actions.
*/
import classnames from 'classnames';
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';

import {fetchVersions} from '../actions/addon';
import {del as deleteVersion} from '../actions/version';
import {publish, reject} from '../actions/review';
import {fetchThreads, submitNote} from '../actions/comm';

import AddonVersion from '../components/version';
import {versionListSelector} from '../selectors/version';
import {PageSection} from '../../site/components/page';

export class AddonVersionListing extends React.Component {
  static propTypes = {
    className: React.PropTypes.string,
    deleteVersion: React.PropTypes.func,
    fetchThreads: React.PropTypes.func.isRequired,
    fetchVersions: React.PropTypes.func.isRequired,
    publish: React.PropTypes.func.isRequired,
    reject: React.PropTypes.func.isRequired,
    showDeveloperActions: React.PropTypes.bool,
    showReviewActions: React.PropTypes.bool,
    slug: React.PropTypes.func.isRequired,
    versions: React.PropTypes.array.isRequired,
  };

  constructor(props) {
    super(props);
    this.props.fetchThreads(this.props.slug);
    this.props.fetchVersions(this.props.slug);
  }

  renderVersion = version => {
    return (

        <AddonVersion key={version.id} {...this.props} {...version}
                      {...this.props.threads[version.id]}/>

    );
  }

  render() {
    return (
      <PageSection className={this.props.className} title="Versions">

{this.props.versions.map(this.renderVersion)}

      </PageSection>
    );
  }
}

export default connect(
  state => ({
    slug: state.router.params.slug,
    threads: state.addonThread.threads,
    versions: versionListSelector(
      (state.addon.addons[state.router.params.slug] || {}).versions
    )
  }),
  dispatch => bindActionCreators({
    deleteVersion,
    fetchThreads,
    fetchVersions,
    publish,
    reject,
    submitNote
  }, dispatch)
)(AddonVersionListing);
<|endoftext|>


<commit_before>'use strict';

const {
  GraphQLObjectType,
  GraphQLInputObjectType,
  GraphQLList,
  GraphQLID,
  GraphQLInt,
  GraphQLFloat,
  GraphQLBoolean,
  GraphQLString,
  GraphQLNonNull,
} = require('graphql');
const { chain, omit, pick, defaults } = require('lodash');

const { EngineError } = require('../../../../error');
const { getTypeName, getOperationNameFromAttr } = require('./name');
const { getDescription, getDeprecationReason } = require('./description');
const { getResolver } = require('./resolver');
const { isMultiple, getSubDef, getModelName, isModel } = require('./utilities');

// Retrieves the GraphQL type for a given IDL definition
const getType = function (def, opts) {
  return getField(def, opts).type;
};

// Retrieves a GraphQL field info for a given IDL definition, i.e. an object that can be passed to new GraphQLObjectType({ fields })
// Includes return type, resolve function, arguments, etc.
const getField = function (def, opts) {
  // Retrieves correct field
  const fieldInfo = graphQLFieldsInfo.find(possibleType => possibleType.condition(def, opts));
  if (!fieldInfo) {
    throw new EngineError(`Could not parse property into a GraphQL type: ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }

  // Retrieves field information
  const field = fieldInfo.value(def, opts);

  // The following fields are type-agnostic, so are not inside `fieldInfo.value()`
  // Fields description|deprecation_reason are taken from IDL definition
  const description = getDescription({ def, opType: opts.opType, descriptionType: 'field' });
  const deprecationReason = getDeprecationReason({ def });
  Object.assign(field, defaults({ description, deprecationReason }, field));
	Object.assign(field, getResolver(def, Object.assign({ getType }, opts)));
  Object.assign(field.type, { def });

  // Can only assign default if fields are optional in input, but required by database
  if (canRequireAttributes(def, opts) && !def.required && opts.isInputObject && def.default !== undefined) {
    defaults(field, { defaultValue: def.default });
  }

  return field;
};

// Required field fieldsInfo
const graphQLRequiredFieldsInfo = function (def, opts) {
  // Goal is to avoid infinite recursion, i.e. without modification the same graphQLFieldsInfo would be hit again
  const modifiedDef = Object.assign({}, def, { required: false });
  const subType = getType(modifiedDef, opts);
  const type = new GraphQLNonNull(subType);
  return { type };
};

// Array field fieldsInfo
const graphQLArrayFieldsInfo = function (def, opts) {
  const subDef = getSubDef(def);
  const subType = getType(subDef, opts);
  const type = new GraphQLList(subType);
  return { type };
};

// Done so that children can get a cached reference of parent type, while avoiding infinite recursion
// Only cache schemas that have a model name, because they are the only one that can recurse
// Namespace by operation, because operations can have slightly different types
const memoizeObjectField = function (func) {
  return (def, opts) => {
    const modelName = getModelName(def);
    let key;
    if (modelName) {
      key = `field/${modelName}/${opts.opType}/${opts.isInputObject ? 'inputObject' : 'generic'}`;
      if (opts.cache.exists(key)) {
        return opts.cache.get(key);
      }
    }

    const fieldInfo = func(def, opts);
    if (key) {
      opts.cache.set(key, fieldInfo);
    }
    return fieldInfo;
  };
};

// Object field fieldsInfo
const graphQLObjectFieldsInfo = memoizeObjectField(function (def, opts) {
  const name = getTypeName({ def, opType: opts.opType, isInputObject: Boolean(opts.isInputObject) });
  const description = getDescription({ def, opType: opts.opType, descriptionType: 'type' });
	const constructor = opts.isInputObject ? GraphQLInputObjectType : GraphQLObjectType;
  const fields = getObjectFields(def, opts);

  const type = new constructor({ name, description, fields });
  return { type };
});

// Retrieve the fields of an object, using IDL definition
const getObjectFields = function (def, opts) {
  // This needs to be function, otherwise we run in an infinite recursion, if the children try to reference a parent type
  return () => {
		const fields = chain(def.properties)
      // Remove recursive value fields when used as inputObject (i.e. resolver argument)
      .pickBy(childDef => !(opts.isInputObject && isModel(childDef)))
			// Remove all return value fields for delete operations, except the recursive ones
      // And except for inputObject, since we use it to get the delete filters
			.pickBy(childDef => !(opts.opType === 'delete' && !isModel(childDef) && !opts.isInputObject))
      // Divide submodels fields between recursive fields (e.g. `model.createUser`) and non-recursive fields
      // (e.g. `model.user`)
      .transform((props, childDef, childDefName) => {
        // Not for 'Query' or 'Mutation' objects, nor models
        if (childDef.opType || !isModel(childDef)) {
          props[childDefName] = childDef;
          return;
        }

        const multiple = isMultiple(childDef);
        const subDef = getSubDef(childDef);

        // Retrieves `id` field definition of subfield
        const nonRecursiveAttrs = ['description', 'deprecation_reason', 'required'];
        const idDef = Object.assign({}, pick(subDef, nonRecursiveAttrs), omit(subDef.properties.id, nonRecursiveAttrs));

        // Assign `id` field definition to e.g. `model.user`
        const idsDef = multiple ? Object.assign({}, childDef, { items: idDef }) : idDef;
        props[childDefName] = idsDef;

        // Assign recursive field to e.g. `model.createUser`
        const newName = getOperationNameFromAttr({ name: childDefName, opType: opts.opType, asPlural: multiple });
        props[newName] = childDef;
      })
			// Recurse over children
			.mapValues(childDef => {
				// if 'Query' or 'Mutation' objects, pass current operation down to sub-fields
				if (childDef.opType) {
					opts = Object.assign({}, opts, { opType: childDef.opType });
				}

				return getField(childDef, opts);
			})
			.value();
    return fields;
  };
};

const canRequireAttributes = function (def, { opType, isInputObject }) {
  // Update operation does not require any attribute in input object, except `id`
	return !(opType === 'update' && isInputObject && !(def.type === 'integer' && def.format === 'id'))
    // Query inputObjects do not require any attribute
    && !(['find', 'delete'].includes(opType) && isInputObject);
};

/**
 * Maps an IDL definition into a GraphQL field information, including type
 * The first matching one will be used, i.e. order matters: required modifier, then array modifier come first
 */
const graphQLFieldsInfo = [

	// "Required" modifier type
  {
    condition: (def, opts) => def.required && canRequireAttributes(def, opts),
    value: graphQLRequiredFieldsInfo,
  },

	// "Array" modifier type
  {
    condition: def => def.type === 'array',
    value: graphQLArrayFieldsInfo,
  },

	// "Object" type
  {
    condition: def => def.type === 'object',
    value: graphQLObjectFieldsInfo,
  },

	// "Int" type
  {
    condition: def => def.type === 'integer',
    value: () => ({ type: GraphQLInt }),
  },

	// "Float" type
  {
    condition: def => def.type === 'number',
    value: () => ({ type: GraphQLFloat }),
  },

	// "ID" type
  {
    condition: def => def.type === 'string' && def.format === 'id',
    value: () => ({ type: GraphQLID }),
  },

	// "String" type
  {
    condition: def => def.type === 'string',
    value: () => ({ type: GraphQLString }),
  },

	// "Boolean" type
  {
    condition: def => def.type === 'boolean',
    value: () => ({ type: GraphQLBoolean }),
  },

];

module.exports = {
  getType,
};<commit_msg>Add non-recursive model fields to inputObjects<commit_after>'use strict';

const {
  GraphQLObjectType,
  GraphQLInputObjectType,
  GraphQLList,
  GraphQLID,
  GraphQLInt,
  GraphQLFloat,
  GraphQLBoolean,
  GraphQLString,
  GraphQLNonNull,
} = require('graphql');
const { chain, omit, pick, defaults } = require('lodash');

const { EngineError } = require('../../../../error');
const { getTypeName, getOperationNameFromAttr } = require('./name');
const { getDescription, getDeprecationReason } = require('./description');
const { getResolver } = require('./resolver');
const { isMultiple, getSubDef, getModelName, isModel } = require('./utilities');

// Retrieves the GraphQL type for a given IDL definition
const getType = function (def, opts) {
  return getField(def, opts).type;
};

// Retrieves a GraphQL field info for a given IDL definition, i.e. an object that can be passed to new GraphQLObjectType({ fields })
// Includes return type, resolve function, arguments, etc.
const getField = function (def, opts) {
  // Retrieves correct field
  const fieldInfo = graphQLFieldsInfo.find(possibleType => possibleType.condition(def, opts));
  if (!fieldInfo) {
    throw new EngineError(`Could not parse property into a GraphQL type: ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }

  // Retrieves field information
  const field = fieldInfo.value(def, opts);

  // The following fields are type-agnostic, so are not inside `fieldInfo.value()`
  // Fields description|deprecation_reason are taken from IDL definition
  const description = getDescription({ def, opType: opts.opType, descriptionType: 'field' });
  const deprecationReason = getDeprecationReason({ def });
  Object.assign(field, defaults({ description, deprecationReason }, field));
	Object.assign(field, getResolver(def, Object.assign({ getType }, opts)));
  Object.assign(field.type, { def });

  // Can only assign default if fields are optional in input, but required by database
  if (canRequireAttributes(def, opts) && !def.required && opts.isInputObject && def.default !== undefined) {
    defaults(field, { defaultValue: def.default });
  }

  return field;
};

// Required field fieldsInfo
const graphQLRequiredFieldsInfo = function (def, opts) {
  // Goal is to avoid infinite recursion, i.e. without modification the same graphQLFieldsInfo would be hit again
  const modifiedDef = Object.assign({}, def, { required: false });
  const subType = getType(modifiedDef, opts);
  const type = new GraphQLNonNull(subType);
  return { type };
};

// Array field fieldsInfo
const graphQLArrayFieldsInfo = function (def, opts) {
  const subDef = getSubDef(def);
  const subType = getType(subDef, opts);
  const type = new GraphQLList(subType);
  return { type };
};

// Done so that children can get a cached reference of parent type, while avoiding infinite recursion
// Only cache schemas that have a model name, because they are the only one that can recurse
// Namespace by operation, because operations can have slightly different types
const memoizeObjectField = function (func) {
  return (def, opts) => {
    const modelName = getModelName(def);
    let key;
    if (modelName) {
      key = `field/${modelName}/${opts.opType}/${opts.isInputObject ? 'inputObject' : 'generic'}`;
      if (opts.cache.exists(key)) {
        return opts.cache.get(key);
      }
    }

    const fieldInfo = func(def, opts);
    if (key) {
      opts.cache.set(key, fieldInfo);
    }
    return fieldInfo;
  };
};

// Object field fieldsInfo
const graphQLObjectFieldsInfo = memoizeObjectField(function (def, opts) {
  const name = getTypeName({ def, opType: opts.opType, isInputObject: Boolean(opts.isInputObject) });
  const description = getDescription({ def, opType: opts.opType, descriptionType: 'type' });
	const constructor = opts.isInputObject ? GraphQLInputObjectType : GraphQLObjectType;
  const fields = getObjectFields(def, opts);

  const type = new constructor({ name, description, fields });
  return { type };
});

// Retrieve the fields of an object, using IDL definition
const getObjectFields = function (def, opts) {
  // This needs to be function, otherwise we run in an infinite recursion, if the children try to reference a parent type
  return () => {
		const fields = chain(def.properties)
      // Divide submodels fields between recursive fields (e.g. `model.createUser`) and non-recursive fields
      // (e.g. `model.user`)
      .transform((props, childDef, childDefName) => {
        // Not for 'Query' or 'Mutation' objects, nor models
        if (childDef.opType || !isModel(childDef)) {
          props[childDefName] = childDef;
          return;
        }

        const multiple = isMultiple(childDef);
        const subDef = getSubDef(childDef);

        // Retrieves `id` field definition of subfield
        const nonRecursiveAttrs = ['description', 'deprecation_reason', 'required'];
        const idDef = Object.assign({}, pick(subDef, nonRecursiveAttrs), omit(subDef.properties.id, nonRecursiveAttrs));

        // Assign `id` field definition to e.g. `model.user`
        const idsDef = multiple ? Object.assign({}, childDef, { items: idDef }) : idDef;
        props[childDefName] = idsDef;

        // Assign recursive field to e.g. `model.createUser`
        const newName = getOperationNameFromAttr({ name: childDefName, opType: opts.opType, asPlural: multiple });
        props[newName] = childDef;
      })
      // Remove recursive value fields when used as inputObject (i.e. resolver argument)
      .pickBy(childDef => !(opts.isInputObject && isModel(childDef)))
			// Remove all return value fields for delete operations, except the recursive ones
      // And except for inputObject, since we use it to get the delete filters
			.pickBy(childDef => !(opts.opType === 'delete' && !isModel(childDef) && !opts.isInputObject))
			// Recurse over children
			.mapValues(childDef => {
				// if 'Query' or 'Mutation' objects, pass current operation down to sub-fields
				if (childDef.opType) {
					opts = Object.assign({}, opts, { opType: childDef.opType });
				}

				return getField(childDef, opts);
			})
			.value();
    return fields;
  };
};

const canRequireAttributes = function (def, { opType, isInputObject }) {
  // Update operation does not require any attribute in input object, except `id`
	return !(opType === 'update' && isInputObject && !(def.type === 'integer' && def.format === 'id'))
    // Query inputObjects do not require any attribute
    && !(['find', 'delete'].includes(opType) && isInputObject);
};

/**
 * Maps an IDL definition into a GraphQL field information, including type
 * The first matching one will be used, i.e. order matters: required modifier, then array modifier come first
 */
const graphQLFieldsInfo = [

	// "Required" modifier type
  {
    condition: (def, opts) => def.required && canRequireAttributes(def, opts),
    value: graphQLRequiredFieldsInfo,
  },

	// "Array" modifier type
  {
    condition: def => def.type === 'array',
    value: graphQLArrayFieldsInfo,
  },

	// "Object" type
  {
    condition: def => def.type === 'object',
    value: graphQLObjectFieldsInfo,
  },

	// "Int" type
  {
    condition: def => def.type === 'integer',
    value: () => ({ type: GraphQLInt }),
  },

	// "Float" type
  {
    condition: def => def.type === 'number',
    value: () => ({ type: GraphQLFloat }),
  },

	// "ID" type
  {
    condition: def => def.type === 'string' && def.format === 'id',
    value: () => ({ type: GraphQLID }),
  },

	// "String" type
  {
    condition: def => def.type === 'string',
    value: () => ({ type: GraphQLString }),
  },

	// "Boolean" type
  {
    condition: def => def.type === 'boolean',
    value: () => ({ type: GraphQLBoolean }),
  },

];

module.exports = {
  getType,
};<|endoftext|>


<commit_before>define('app/controllers/graphs', ['app/models/stats_request', 'ember'],
    //
    //  Graphs Controller
    //
    //  @returns Class
    //
    function (StatsRequest) {

        'use strict';

        var TIME_WINDOW_MAP = {
            minutes: 10 * TIME_MAP.MINUTE,
            hour: TIME_MAP.HOUR,
            day: TIME_MAP.DAY,
            week: TIME_MAP.WEEK,
            month: TIME_MAP.MONTH,
        };

        return Ember.ArrayController.extend(Ember.Evented, {

            //
            //
            //  Properties
            //
            //

            isOpen: null,
            content: [],
            resizeLock: null,
            pendingRequests: [],
            fetchingStats: null,
            fetchingStatsArgs: null,
            config: Ember.Object.create({
                requestMethod: 'Socket',
                timeWindow: TIME_WINDOW_MAP.minutes,
                measurementStep: 10 * TIME_MAP.SECOND,
                measurementOffset: 60 * TIME_MAP.SECOND,

                canModify: true,
                canControl: true,
                canMinimize: true,
                showGraphLegend: false,
                historyWidgetPosition: 'top',
            }),

            //
            //
            //  Initialization
            //
            //

            init: function () {
                this._super();
                this.stream.set('parent', this);
                this.history.set('parent', this);
                this.resolution.set('parent', this);
            },

            //
            //
            //  Methods
            //
            //

            open: function (args) {

                this._clear();

                this.setProperties({
                    'isOpen': true,
                    'content': args.graphs,
                });

                forIn(this, args.config, function (value, property) {
                    this.config.set(property, value);
                });

                Ember.run.next(this, function () {
                    this.stream.start();
                });

                if (args.config.timeWindow)
                    this.resolution.change(args.config.timeWindow, true);

                $(window).on('resize', this._handleWindowResize);
            },

            close: function () {
                this.stream.stop();
                this._clear();
                $(window).off('resize', this._handleWindowResize);
            },

            getGraph: function(id) {
                return this.content.findBy('id', id);
            },

            graphExists: function (id) {
                return !!this.getGraph(id);
            },

            //
            //
            //  Pseudo-Private Methods
            //
            //

            _clear: function () {
                this.setProperties({
                    'isOpen': false,
                    'content': [],
                });
                this.get('config').setProperties({
                    canModify: true,
                    canControl: true,
                    canMinimize: true,
                    timeWindow: TIME_WINDOW_MAP.minutes,
                    showGraphLegend: false,
                    historyWidgetPosition: 'top',
                });
                this._clearPendingRequests();
            },

            _fetchStats: function (args) {

                if (!this.get('isOpen')) return;

                if (DEBUG_STATS) {
                    info('Requesting stats from: ' +
                        new Date(args.from).getPrettyDateTime() +
                        ' until: ' +
                        new Date(args.until).getPrettyDateTime());
                }

                this._clearPendingRequests();
                this.setProperties({
                    fetchingStats: true,
                    fetchStatsArgs: args
                });

                var requests = this._generateRequests(args);
                requests.forEach(function (request) {
                    this.pendingRequests.push(request);
                    if (this.config.requestMethod == 'XHR')
                        this._fetchStatsFromXHR(request);
                    if (this.config.requestMethod == 'Socket')
                        this._fetchStatsFromSocket(request);
                }, this);
            },

            _clearPendingRequests: function () {
                this.set('pendingRequests', []);
                this.set('fetchingStats', false);
            },

            _generateRequests: function (args) {

                var requests = [];
                var offset = this.config.measurementOffset;
                this.get('content').forEach(function (graph) {
                    graph.datasources.forEach(function (datasource) {
                        var newRequest = StatsRequest.create({
                            from: args.from - offset,
                            until: args.until - offset,
                            datasources: [datasource],
                        });
                        // Try to merge this request with another
                        // one to reduce API calls
                        var didMerge = false;
                        requests.some(function (request) {
                            if (request.canMerge(newRequest)) {
                                request.merge(newRequest);
                                return didMerge = true;
                            }
                        });
                        if (!didMerge)
                            requests.push(newRequest);
                    });
                });
                return requests;
            },

            _fetchStatsFromXHR: function (request) {
                $.ajax({
                    type: 'GET',
                    url: request.url,
                    data: this._generatePayload(request),
                    complete: this._handleXHRResponse
                });
            },

            _fetchStatsFromSocket: function (request) {
                var data = this._generatePayload(request);
                var machine = request.datasources[0].machine;
                Mist.socket.emit('stats',
                    machine.backend.id,
                    machine.id,
                    data.start,
                    data.stop,
                    data.step,
                    request.id,
                    request.metrics
                );
            },

            _generatePayload: function (request) {
                return {
                    request_id: request.id,
                    start: parseInt(request.from / 1000) - 50,
                    stop: parseInt(request.until / 1000) + 50,
                    step: parseInt(this.config.measurementStep / 1000),
                    metrics: request.metrics
                };
            },

            _handleXHRResponse: function (jqXHR) {
                var that = Mist.graphsController;
                if (jqXHR.status == 200) {
                    var response = jqXHR.responseJSON;
                    var request = that.pendingRequests.findBy(
                        'id', parseInt(response.request_id));
                    if (request)
                        that._handleResponse(request, response);
                } else {
                    Ember.run.later(function () {
                        that._fetchStats(that.fetchStatsArgs);
                    }, that.config.measurementStep / 2);
                }
            },

            _handleSocketResponse: function (data) {
                var that = Mist.graphsController;
                var request = that.pendingRequests.findBy(
                    'id', parseInt(data.request_id));
                if (request)
                    that._handleResponse(request, data.metrics);
                that.trigger('onFetchStatsFromSocket', data);
            },

            _handleResponse: function (request, response) {

                request.datasources.forEach(function (datasource) {

                    if (!response[datasource.metric.id]) return;

                    var datapoints = this._processedDatapoints(request,
                        response[datasource.metric.id].datapoints);

                    if (this.stream.isStreaming)
                        datasource.update(datapoints);
                    else
                        datasource.overwrite(datapoints);

                }, this);

                var hadRequests = this.pendingRequests.length;
                this.pendingRequests.removeObject(request);

                if (!this.pendingRequests.length && hadRequests)
                    this._fetchStatsEnded(response);
            },

            _processedDatapoints: function (request, datapoints) {
                var newDatapoints = [];
                datapoints.forEach(function (datapoint) {
                    if (datapoint[1] >= parseInt(request.from / 1000) &&
                        datapoint[1] <= parseInt(request.until / 1000))
                            newDatapoints.push(datapoint);
                }, this);
                return newDatapoints;
            },

            _fetchStatsEnded: function (response) {
                Ember.run.next(this, function () {
                    this.get('content').forEach(function (graph) {
                        graph.view.draw();
                    });
                    this.set('fetchingStats', false);
                    if (this.fetchStatsArgs.callback instanceof Function)
                        this.fetchStatsArgs.callback();
                    this.trigger('onFetchStats', response);
                });
            },

            _restoreFetchStatsArgs: function () {

                // Restores fetch stats args to match currently
                // displayed datapoints. Used after closing a streaming
                // session.

                if (!this.get('content').length) return;
                var datasource = this.get('content')[0].datasources[0];

                this.set('fetchStatsArgs', {
                    from: datasource.getFirstTimestamp() || (Date.now() - this.config.timeWindow),
                    until: datasource.getLastTimestamp() || Date.now(),
                });
            },

            _handleWindowResize: function () {

                var that = Mist.graphsController;
                clearTimeout(that.resizeLock);
                that.set('resizeLock', setTimeout(resize, 500));
                function resize () {
                    that.content.forEach(function (graph) {
                        graph.view.autoResize();
                    });
                }
            },

            //
            //
            //  Resolution Object
            //
            //

            resolution: Ember.Object.create({

                //
                //
                //  Properties
                //
                //

                parent: null,

                //
                //
                //  Methods
                //
                //

                change: function (newTimeWindow, forceChange) {

                    var newTimeWindow = TIME_WINDOW_MAP[newTimeWindow];
                    var oldTimeWindow = this.parent.config.timeWindow;

                    if ((oldTimeWindow != newTimeWindow)|| forceChange) {

                        this.parent.stream.stop();
                        this.parent.config.setProperties({
                            timeWindow: newTimeWindow,
                            measurementStep: newTimeWindow /
                                DISPLAYED_DATAPOINTS,
                        });

                        if (forceChange) {
                            this.parent.stream.start();
                            return;
                        }

                        // Calculate new time boundaries
                        var oldFrom = this.parent.fetchStatsArgs.from;
                        var oldUntil = this.parent.fetchStatsArgs.until;

                        var middle = oldFrom + ((oldUntil - oldFrom) / 2);

                        var newFrom = middle - (newTimeWindow / 2);
                        var newUntil = middle + (newTimeWindow / 2);

                        if (this.parent.history.isInFuture(newUntil))
                            this.parent.stream.start();
                        else
                            this.parent._fetchStats({
                                from: newFrom,
                                until: newUntil,
                            });
                    }
                }
            }),

            //
            //
            //  History Object
            //
            //

            history: Ember.Object.create({

                //
                //
                //  Properties
                //
                //

                parent: null,

                //
                //
                //  Methods
                //
                //

                goBack: function () {
                    this.parent._clearPendingRequests();
                    this.parent.stream.stop();
                    this.parent._fetchStats({
                        from: this.parent.fetchStatsArgs.from -
                            this.parent.config.timeWindow,
                        until: this.parent.fetchStatsArgs.from
                    });
                },

                goForward: function () {
                    this.parent._clearPendingRequests();
                    if (this.isInFuture(this.parent.fetchStatsArgs.until))
                        this.parent.stream.start();
                    else
                        this.parent._fetchStats({
                            from: this.parent.fetchStatsArgs.until,
                            until: this.parent.fetchStatsArgs.until +
                                this.parent.config.timeWindow
                        });
                },

                isInFuture: function (until) {
                    return Date.now() <= until;
                },
            }),

            //
            //
            //  Streaming Object
            //
            //

            stream: Ember.Object.create({

                //
                //
                //  Properties
                //
                //

                parent: null,
                isStreaming: null,
                currentSessionId: 0,
                timeOfLastRequest: null,
                firstStreamingCall: null,

                //
                //
                //  Methods
                //
                //

                start: function () {
                    if (!this.isStreaming) {
                        this.parent._clearPendingRequests();
                        this.set('isStreaming', true);
                        this._stream(this.currentSessionId, true);
                    }
                },

                stop: function () {
                    if (this.isStreaming) {
                        this.parent._clearPendingRequests();
                        this.parent._restoreFetchStatsArgs();
                        this.setProperties({
                            isStreaming: false,
                            firstStreamingCall: false,
                            currentSessionId: this.currentSessionId + 1,
                        });
                    }
                },

                //
                //
                //  Pseudo-Private Methods
                //
                //

                _stream: function (sessionId, firstTime) {

                    if (this._sessionIsAlive(sessionId)) {

                        var now = Date.now();

                        // If this is the first time requesting stats for streaming
                        // we need to get enough stats to fill the entire graph
                        //
                        // Else, we request stats from where the previous request
                        // left off
                        var from = firstTime ?
                            now - this.parent.config.timeWindow :
                            this.timeOfLastRequest

                        this.parent._fetchStats({
                            from: from,
                            until: now,
                            callback: callback
                        });

                        if (firstTime)
                            this.set('firstStreamingCall', true);
                        this.set('timeOfLastRequest', now);
                    }

                    var that = this;
                    function callback () {
                        that.set('firstStreamingCall', false);
                        Ember.run.later(function () {
                            that._stream(sessionId);
                        }, that._getNextRequestDelay());
                    }
                },

                _getNextRequestDelay: function () {

                    // Client should make new stats requests every
                    // <measurementStep> milliseconds.
                    //
                    // We subtract the time elapsed on the previous
                    // request from the interval to make sure
                    // the next request is made on time.

                    return this.parent.config.measurementStep -
                        (Date.now() - this.timeOfLastRequest);
                },

                _sessionIsAlive: function (sessionId) {
                    return this.currentSessionId == sessionId;
                },
            }),
        });
    }
);
<commit_msg>Always stream when changing resolution<commit_after>define('app/controllers/graphs', ['app/models/stats_request', 'ember'],
    //
    //  Graphs Controller
    //
    //  @returns Class
    //
    function (StatsRequest) {

        'use strict';

        var TIME_WINDOW_MAP = {
            minutes: 10 * TIME_MAP.MINUTE,
            hour: TIME_MAP.HOUR,
            day: TIME_MAP.DAY,
            week: TIME_MAP.WEEK,
            month: TIME_MAP.MONTH,
        };

        return Ember.ArrayController.extend(Ember.Evented, {

            //
            //
            //  Properties
            //
            //

            isOpen: null,
            content: [],
            resizeLock: null,
            pendingRequests: [],
            fetchingStats: null,
            fetchingStatsArgs: null,
            config: Ember.Object.create({
                requestMethod: 'Socket',
                timeWindow: TIME_WINDOW_MAP.minutes,
                measurementStep: 10 * TIME_MAP.SECOND,
                measurementOffset: 60 * TIME_MAP.SECOND,

                canModify: true,
                canControl: true,
                canMinimize: true,
                showGraphLegend: false,
                historyWidgetPosition: 'top',
            }),

            //
            //
            //  Initialization
            //
            //

            init: function () {
                this._super();
                this.stream.set('parent', this);
                this.history.set('parent', this);
                this.resolution.set('parent', this);
            },

            //
            //
            //  Methods
            //
            //

            open: function (args) {

                this._clear();

                this.setProperties({
                    'isOpen': true,
                    'content': args.graphs,
                });

                forIn(this, args.config, function (value, property) {
                    this.config.set(property, value);
                });

                Ember.run.next(this, function () {
                    this.stream.start();
                });

                if (args.config.timeWindow)
                    this.resolution.change(args.config.timeWindow, true);

                $(window).on('resize', this._handleWindowResize);
            },

            close: function () {
                this.stream.stop();
                this._clear();
                $(window).off('resize', this._handleWindowResize);
            },

            getGraph: function(id) {
                return this.content.findBy('id', id);
            },

            graphExists: function (id) {
                return !!this.getGraph(id);
            },

            //
            //
            //  Pseudo-Private Methods
            //
            //

            _clear: function () {
                this.setProperties({
                    'isOpen': false,
                    'content': [],
                });
                this.get('config').setProperties({
                    canModify: true,
                    canControl: true,
                    canMinimize: true,
                    timeWindow: TIME_WINDOW_MAP.minutes,
                    showGraphLegend: false,
                    historyWidgetPosition: 'top',
                });
                this._clearPendingRequests();
            },

            _fetchStats: function (args) {

                if (!this.get('isOpen')) return;

                if (DEBUG_STATS) {
                    info('Requesting stats from: ' +
                        new Date(args.from).getPrettyDateTime() +
                        ' until: ' +
                        new Date(args.until).getPrettyDateTime());
                }

                this._clearPendingRequests();
                this.setProperties({
                    fetchingStats: true,
                    fetchStatsArgs: args
                });

                var requests = this._generateRequests(args);
                requests.forEach(function (request) {
                    this.pendingRequests.push(request);
                    if (this.config.requestMethod == 'XHR')
                        this._fetchStatsFromXHR(request);
                    if (this.config.requestMethod == 'Socket')
                        this._fetchStatsFromSocket(request);
                }, this);
            },

            _clearPendingRequests: function () {
                this.set('pendingRequests', []);
                this.set('fetchingStats', false);
            },

            _generateRequests: function (args) {

                var requests = [];
                var offset = this.config.measurementOffset;
                this.get('content').forEach(function (graph) {
                    graph.datasources.forEach(function (datasource) {
                        var newRequest = StatsRequest.create({
                            from: args.from - offset,
                            until: args.until - offset,
                            datasources: [datasource],
                        });
                        // Try to merge this request with another
                        // one to reduce API calls
                        var didMerge = false;
                        requests.some(function (request) {
                            if (request.canMerge(newRequest)) {
                                request.merge(newRequest);
                                return didMerge = true;
                            }
                        });
                        if (!didMerge)
                            requests.push(newRequest);
                    });
                });
                return requests;
            },

            _fetchStatsFromXHR: function (request) {
                $.ajax({
                    type: 'GET',
                    url: request.url,
                    data: this._generatePayload(request),
                    complete: this._handleXHRResponse
                });
            },

            _fetchStatsFromSocket: function (request) {
                var data = this._generatePayload(request);
                var machine = request.datasources[0].machine;
                Mist.socket.emit('stats',
                    machine.backend.id,
                    machine.id,
                    data.start,
                    data.stop,
                    data.step,
                    request.id,
                    request.metrics
                );
            },

            _generatePayload: function (request) {
                return {
                    request_id: request.id,
                    start: parseInt(request.from / 1000) - 50,
                    stop: parseInt(request.until / 1000) + 50,
                    step: parseInt(this.config.measurementStep / 1000),
                    metrics: request.metrics
                };
            },

            _handleXHRResponse: function (jqXHR) {
                var that = Mist.graphsController;
                if (jqXHR.status == 200) {
                    var response = jqXHR.responseJSON;
                    var request = that.pendingRequests.findBy(
                        'id', parseInt(response.request_id));
                    if (request)
                        that._handleResponse(request, response);
                } else {
                    Ember.run.later(function () {
                        that._fetchStats(that.fetchStatsArgs);
                    }, that.config.measurementStep / 2);
                }
            },

            _handleSocketResponse: function (data) {
                var that = Mist.graphsController;
                var request = that.pendingRequests.findBy(
                    'id', parseInt(data.request_id));
                if (request)
                    that._handleResponse(request, data.metrics);
                that.trigger('onFetchStatsFromSocket', data);
            },

            _handleResponse: function (request, response) {

                request.datasources.forEach(function (datasource) {

                    if (!response[datasource.metric.id]) return;

                    var datapoints = this._processedDatapoints(request,
                        response[datasource.metric.id].datapoints);

                    if (this.stream.isStreaming)
                        datasource.update(datapoints);
                    else
                        datasource.overwrite(datapoints);

                }, this);

                var hadRequests = this.pendingRequests.length;
                this.pendingRequests.removeObject(request);

                if (!this.pendingRequests.length && hadRequests)
                    this._fetchStatsEnded(response);
            },

            _processedDatapoints: function (request, datapoints) {
                var newDatapoints = [];
                datapoints.forEach(function (datapoint) {
                    if (datapoint[1] >= parseInt(request.from / 1000) &&
                        datapoint[1] <= parseInt(request.until / 1000))
                            newDatapoints.push(datapoint);
                }, this);
                return newDatapoints;
            },

            _fetchStatsEnded: function (response) {
                Ember.run.next(this, function () {
                    this.get('content').forEach(function (graph) {
                        graph.view.draw();
                    });
                    this.set('fetchingStats', false);
                    if (this.fetchStatsArgs.callback instanceof Function)
                        this.fetchStatsArgs.callback();
                    this.trigger('onFetchStats', response);
                });
            },

            _restoreFetchStatsArgs: function () {

                // Restores fetch stats args to match currently
                // displayed datapoints. Used after closing a streaming
                // session.

                if (!this.get('content').length) return;
                var datasource = this.get('content')[0].datasources[0];

                this.set('fetchStatsArgs', {
                    from: datasource.getFirstTimestamp() || (Date.now() - this.config.timeWindow),
                    until: datasource.getLastTimestamp() || Date.now(),
                });
            },

            _handleWindowResize: function () {

                var that = Mist.graphsController;
                clearTimeout(that.resizeLock);
                that.set('resizeLock', setTimeout(resize, 500));
                function resize () {
                    that.content.forEach(function (graph) {
                        graph.view.autoResize();
                    });
                }
            },

            //
            //
            //  Resolution Object
            //
            //

            resolution: Ember.Object.create({

                //
                //
                //  Properties
                //
                //

                parent: null,

                //
                //
                //  Methods
                //
                //

                change: function (newTimeWindow, forceChange) {

                    var newTimeWindow = TIME_WINDOW_MAP[newTimeWindow];
                    var oldTimeWindow = this.parent.config.timeWindow;

                    if ((oldTimeWindow != newTimeWindow)|| forceChange) {

                        this.parent.stream.stop();
                        this.parent.config.setProperties({
                            timeWindow: newTimeWindow,
                            measurementStep: newTimeWindow /
                                DISPLAYED_DATAPOINTS,
                        });

                        if (forceChange) {
                            this.parent.stream.start();
                            return;
                        }

                        this.parent.stream.start();
                    }
                }
            }),

            //
            //
            //  History Object
            //
            //

            history: Ember.Object.create({

                //
                //
                //  Properties
                //
                //

                parent: null,

                //
                //
                //  Methods
                //
                //

                goBack: function () {
                    this.parent._clearPendingRequests();
                    this.parent.stream.stop();
                    this.parent._fetchStats({
                        from: this.parent.fetchStatsArgs.from -
                            this.parent.config.timeWindow,
                        until: this.parent.fetchStatsArgs.from
                    });
                },

                goForward: function () {
                    this.parent._clearPendingRequests();
                    if (this.isInFuture(this.parent.fetchStatsArgs.until))
                        this.parent.stream.start();
                    else
                        this.parent._fetchStats({
                            from: this.parent.fetchStatsArgs.until,
                            until: this.parent.fetchStatsArgs.until +
                                this.parent.config.timeWindow
                        });
                },

                isInFuture: function (until) {
                    return Date.now() <= until;
                },
            }),

            //
            //
            //  Streaming Object
            //
            //

            stream: Ember.Object.create({

                //
                //
                //  Properties
                //
                //

                parent: null,
                isStreaming: null,
                currentSessionId: 0,
                timeOfLastRequest: null,
                firstStreamingCall: null,

                //
                //
                //  Methods
                //
                //

                start: function () {
                    if (!this.isStreaming) {
                        this.parent._clearPendingRequests();
                        this.set('isStreaming', true);
                        this._stream(this.currentSessionId, true);
                    }
                },

                stop: function () {
                    if (this.isStreaming) {
                        this.parent._clearPendingRequests();
                        this.parent._restoreFetchStatsArgs();
                        this.setProperties({
                            isStreaming: false,
                            firstStreamingCall: false,
                            currentSessionId: this.currentSessionId + 1,
                        });
                    }
                },

                //
                //
                //  Pseudo-Private Methods
                //
                //

                _stream: function (sessionId, firstTime) {

                    if (this._sessionIsAlive(sessionId)) {

                        var now = Date.now();

                        // If this is the first time requesting stats for streaming
                        // we need to get enough stats to fill the entire graph
                        //
                        // Else, we request stats from where the previous request
                        // left off
                        var from = firstTime ?
                            now - this.parent.config.timeWindow :
                            this.timeOfLastRequest

                        this.parent._fetchStats({
                            from: from,
                            until: now,
                            callback: callback
                        });

                        if (firstTime)
                            this.set('firstStreamingCall', true);
                        this.set('timeOfLastRequest', now);
                    }

                    var that = this;
                    function callback () {
                        that.set('firstStreamingCall', false);
                        Ember.run.later(function () {
                            that._stream(sessionId);
                        }, that._getNextRequestDelay());
                    }
                },

                _getNextRequestDelay: function () {

                    // Client should make new stats requests every
                    // <measurementStep> milliseconds.
                    //
                    // We subtract the time elapsed on the previous
                    // request from the interval to make sure
                    // the next request is made on time.

                    return this.parent.config.measurementStep -
                        (Date.now() - this.timeOfLastRequest);
                },

                _sessionIsAlive: function (sessionId) {
                    return this.currentSessionId == sessionId;
                },
            }),
        });
    }
);
<|endoftext|>


<commit_before>import Polymer from '../polymer';

export default new Polymer({
  is: 'more-info-alarm_control_panel',

  properties: {
    hass: {
      type: Object,
    },

    stateObj: {
      type: Object,
      observer: 'stateObjChanged',
    },
    enteredCode: {
      type: String,
      value: '',
    },
    disarmButtonVisible: {
      type: Boolean,
      value: false,
    },
    armHomeButtonVisible: {
      type: Boolean,
      value: false,
    },
    armAwayButtonVisible: {
      type: Boolean,
      value: false,
    },
    codeInputVisible: {
      type: Boolean,
      value: false,
    },
    codeInputEnabled: {
      type: Boolean,
      value: false,
    },
    codeFormat: {
      type: String,
      value: '',
    },
    codeValid: {
      type: Boolean,
      computed: 'validateCode(enteredCode, codeFormat)',
    },
  },

  validateCode(code, format) {
    const re = new RegExp(format);
    if (format === null) {
      return true;
    }
    return re.test(code);
  },

  stateObjChanged(newVal) {
    if (newVal) {
      this.codeFormat = newVal.attributes.code_format;
      this.codeInputVisible = this.codeFormat !== null;
      this.codeInputEnabled = (
        newVal.state === 'armed_home' ||
        newVal.state === 'armed_away' ||
        newVal.state === 'disarmed' ||
        newVal.state === 'pending' ||
        newVal.state === 'triggered');
      this.disarmButtonVisible = (
        newVal.state === 'armed_home' ||
        newVal.state === 'armed_away' ||
        newVal.state === 'pending' ||
        newVal.state === 'triggered');
      this.armHomeButtonVisible = newVal.state === 'disarmed';
      this.armAwayButtonVisible = newVal.state === 'disarmed';
    }
    this.async(() => this.fire('iron-resize'), 500);
  },

  handleDisarmTap() {
    this.callService('alarm_disarm', { code: this.enteredCode });
  },

  handleHomeTap() {
    this.callService('alarm_arm_home', { code: this.enteredCode });
  },

  handleAwayTap() {
    this.callService('alarm_arm_away', { code: this.enteredCode });
  },

  callService(service, data) {
    const serviceData = data || {};
    serviceData.entity_id = this.stateObj.entityId;
    this.hass.serviceActions.callService('alarm_control_panel', service, serviceData);
  },
});
<commit_msg>Empty alarm control panel code field after calling service<commit_after>import Polymer from '../polymer';

export default new Polymer({
  is: 'more-info-alarm_control_panel',

  properties: {
    hass: {
      type: Object,
    },

    stateObj: {
      type: Object,
      observer: 'stateObjChanged',
    },
    enteredCode: {
      type: String,
      value: '',
    },
    disarmButtonVisible: {
      type: Boolean,
      value: false,
    },
    armHomeButtonVisible: {
      type: Boolean,
      value: false,
    },
    armAwayButtonVisible: {
      type: Boolean,
      value: false,
    },
    codeInputVisible: {
      type: Boolean,
      value: false,
    },
    codeInputEnabled: {
      type: Boolean,
      value: false,
    },
    codeFormat: {
      type: String,
      value: '',
    },
    codeValid: {
      type: Boolean,
      computed: 'validateCode(enteredCode, codeFormat)',
    },
  },

  validateCode(code, format) {
    const re = new RegExp(format);
    if (format === null) {
      return true;
    }
    return re.test(code);
  },

  stateObjChanged(newVal) {
    if (newVal) {
      this.codeFormat = newVal.attributes.code_format;
      this.codeInputVisible = this.codeFormat !== null;
      this.codeInputEnabled = (
        newVal.state === 'armed_home' ||
        newVal.state === 'armed_away' ||
        newVal.state === 'disarmed' ||
        newVal.state === 'pending' ||
        newVal.state === 'triggered');
      this.disarmButtonVisible = (
        newVal.state === 'armed_home' ||
        newVal.state === 'armed_away' ||
        newVal.state === 'pending' ||
        newVal.state === 'triggered');
      this.armHomeButtonVisible = newVal.state === 'disarmed';
      this.armAwayButtonVisible = newVal.state === 'disarmed';
    }
    this.async(() => this.fire('iron-resize'), 500);
  },

  handleDisarmTap() {
    this.callService('alarm_disarm', { code: this.enteredCode });
  },

  handleHomeTap() {
    this.callService('alarm_arm_home', { code: this.enteredCode });
  },

  handleAwayTap() {
    this.callService('alarm_arm_away', { code: this.enteredCode });
  },

  callService(service, data) {
    const serviceData = data || {};
    serviceData.entity_id = this.stateObj.entityId;
    this.hass.serviceActions.callService('alarm_control_panel', service, serviceData)
      .then(() => { this.enteredCode = ''; });
  },
});
<|endoftext|>


<commit_before>/**
 * Demo.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*eslint no-console:0 */

define(
  'tinymce.plugins.imagetools.demo.Demo',
  [
    'global!console',
    'tinymce.core.dom.DomQuery',
    'tinymce.core.EditorManager',
    'tinymce.plugins.code.Plugin',
    'tinymce.plugins.imagetools.Plugin',
    'tinymce.themes.modern.Theme'
  ],
  function (console, DomQuery, EditorManager, CodePlugin, ImageToolsPlugin, ModernTheme) {
    return function () {
      var $ = DomQuery;

      CodePlugin();
      ImageToolsPlugin();
      ModernTheme();

      $(
        '<textarea class="tinymce">' +
        '
' +
        '' +
        '' +
        '' +
        '' +
        '' +
        '
' +
        '</textarea>'
      ).appendTo('#ephox-ui');

      EditorManager.init({
        //imagetools_cors_hosts: ["moxiecode.cachefly.net"],
        //imagetools_proxy: "proxy.php",
        //imagetools_api_key: '123',

        //images_upload_url: 'postAcceptor.php',
        //images_upload_base_path: 'base/path',
        //images_upload_credentials: true,

        selector: "textarea.tinymce",
        theme: "modern",
        skin_url: "../../../../../skins/lightgray/dist/lightgray",
        plugins: "imagetools code",
        add_unload_trigger: false,
        automatic_uploads: false,
        //images_replace_blob_uris: false,
        images_reuse_filename: true,
        paste_data_images: true,
        image_caption: true,
        height: 600,
        toolbar1: "undo redo | styleselect | alignleft aligncenter alignright alignjustify | link image | media | emoticons",
        images_upload_handler: function (data, success, failure, progress) {
          console.log('blob upload [started]', 'id:', data.id(), 'filename:', data.filename());
          progress(0);

          setTimeout(function () {
            console.log('blob upload [ended]', data.id());
            success(data.id() + '.png');
            progress(100);
          }, 1000);
        }
      });

      function send() {
        EditorManager.activeEditor.uploadImages(function () {
          console.log('saving:', EditorManager.activeEditor.getContent());
        });
      }

      function upload() {
        console.log('upload [started]');

        EditorManager.activeEditor.uploadImages(function (success) {
          console.log('upload [ended]', success);
        });
      }

      function dump() {
        var content = EditorManager.activeEditor.getContent();

        $('#view').html(content);
        console.log(content);
      }

      $('<button>send()</button>').appendTo('#ephox-ui').on('click', send);
      $('<button>upload()</button>').appendTo('#ephox-ui').on('click', upload);
      $('<button>dump()</button>').appendTo('#ephox-ui').on('click', dump);
    };
  }
);
<commit_msg>TINY-1095: comment out code that breaks linting<commit_after>/**
 * Demo.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*eslint no-console:0 */

define(
  'tinymce.plugins.imagetools.demo.Demo',
  [
    'global!console',
    'tinymce.core.dom.DomQuery',
    'tinymce.core.EditorManager',
    'tinymce.plugins.code.Plugin',
    'tinymce.plugins.imagetools.Plugin',
    'tinymce.themes.modern.Theme'
  ],
  function (console, DomQuery, EditorManager, CodePlugin, ImageToolsPlugin, ModernTheme) {
    return function () {
      var $ = DomQuery;

      CodePlugin();
      ImageToolsPlugin();
      ModernTheme();

      // $(
      //   '<textarea class="tinymce">' +
      //   '
' +
      //   '' +
      //   '' +
      //   '' +
      //   '' +
      //   '' +
      //   '
' +
      //   '</textarea>'
      // ).appendTo('#ephox-ui');

      EditorManager.init({
        //imagetools_cors_hosts: ["moxiecode.cachefly.net"],
        //imagetools_proxy: "proxy.php",
        //imagetools_api_key: '123',

        //images_upload_url: 'postAcceptor.php',
        //images_upload_base_path: 'base/path',
        //images_upload_credentials: true,

        selector: "textarea.tinymce",
        theme: "modern",
        skin_url: "../../../../../skins/lightgray/dist/lightgray",
        plugins: "imagetools code",
        add_unload_trigger: false,
        automatic_uploads: false,
        //images_replace_blob_uris: false,
        images_reuse_filename: true,
        paste_data_images: true,
        image_caption: true,
        height: 600,
        toolbar1: "undo redo | styleselect | alignleft aligncenter alignright alignjustify | link image | media | emoticons",
        images_upload_handler: function (data, success, failure, progress) {
          console.log('blob upload [started]', 'id:', data.id(), 'filename:', data.filename());
          progress(0);

          setTimeout(function () {
            console.log('blob upload [ended]', data.id());
            success(data.id() + '.png');
            progress(100);
          }, 1000);
        }
      });

      function send() {
        EditorManager.activeEditor.uploadImages(function () {
          console.log('saving:', EditorManager.activeEditor.getContent());
        });
      }

      function upload() {
        console.log('upload [started]');

        EditorManager.activeEditor.uploadImages(function (success) {
          console.log('upload [ended]', success);
        });
      }

      function dump() {
        var content = EditorManager.activeEditor.getContent();

        $('#view').html(content);
        console.log(content);
      }

      $('<button>send()</button>').appendTo('#ephox-ui').on('click', send);
      $('<button>upload()</button>').appendTo('#ephox-ui').on('click', upload);
      $('<button>dump()</button>').appendTo('#ephox-ui').on('click', dump);
    };
  }
);
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ReactNativeEventEmitter
 * @flow
 */
'use strict';

var EventPluginHub = require('EventPluginHub');
var EventPluginRegistry = require('EventPluginRegistry');
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
var ReactGenericBatching = require('ReactGenericBatching');

if (__DEV__) {
  var warning = require('fbjs/lib/warning');
}

/**
 * Version of `ReactBrowserEventEmitter` that works on the receiving side of a
 * serialized worker boundary.
 */

// Shared default empty native event - conserve memory.
var EMPTY_NATIVE_EVENT = {};

/**
 * Selects a subsequence of `Touch`es, without destroying `touches`.
 *
 * @param {Array<Touch>} touches Deserialized touch objects.
 * @param {Array<number>} indices Indices by which to pull subsequence.
 * @return {Array<Touch>} Subsequence of touch objects.
 */
var touchSubsequence = function(touches, indices) {
  var ret = [];
  for (var i = 0; i < indices.length; i++) {
    ret.push(touches[indices[i]]);
  }
  return ret;
};

/**
 * TODO: Pool all of this.
 *
 * Destroys `touches` by removing touch objects at indices `indices`. This is
 * to maintain compatibility with W3C touch "end" events, where the active
 * touches don't include the set that has just been "ended".
 *
 * @param {Array<Touch>} touches Deserialized touch objects.
 * @param {Array<number>} indices Indices to remove from `touches`.
 * @return {Array<Touch>} Subsequence of removed touch objects.
 */
var removeTouchesAtIndices = function(
  touches: Array<Object>,
  indices: Array<number>,
): Array<Object> {
  var rippedOut = [];
  // use an unsafe downcast to alias to nullable elements,
  // so we can delete and then compact.
  var temp: Array<?Object> = (touches: Array<any>);
  for (var i = 0; i < indices.length; i++) {
    var index = indices[i];
    rippedOut.push(touches[index]);
    temp[index] = null;
  }
  var fillAt = 0;
  for (var j = 0; j < temp.length; j++) {
    var cur = temp[j];
    if (cur !== null) {
      temp[fillAt++] = cur;
    }
  }
  temp.length = fillAt;
  return rippedOut;
};

var ReactNativeEventEmitter = {
  ...ReactEventEmitterMixin,

  registrationNames: EventPluginRegistry.registrationNameModules,

  getListener: EventPluginHub.getListener,

  /**
   * Internal version of `receiveEvent` in terms of normalized (non-tag)
   * `rootNodeID`.
   *
   * @see receiveEvent.
   *
   * @param {rootNodeID} rootNodeID React root node ID that event occurred on.
   * @param {TopLevelType} topLevelType Top level type of event.
   * @param {object} nativeEventParam Object passed from native.
   */
  _receiveRootNodeIDEvent: function(
    rootNodeID: number,
    topLevelType: string,
    nativeEventParam: Object,
  ) {
    var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
    var inst = ReactNativeComponentTree.getInstanceFromNode(rootNodeID);
    ReactGenericBatching.batchedUpdates(function() {
      ReactNativeEventEmitter.handleTopLevel(
        topLevelType,
        inst,
        nativeEvent,
        nativeEvent.target,
      );
    });
    // React Native doesn't use ReactControlledComponent but if it did, here's
    // where it would do it.
  },

  /**
   * Publicly exposed method on module for native objc to invoke when a top
   * level event is extracted.
   * @param {rootNodeID} rootNodeID React root node ID that event occurred on.
   * @param {TopLevelType} topLevelType Top level type of event.
   * @param {object} nativeEventParam Object passed from native.
   */
  receiveEvent: function(
    tag: number,
    topLevelType: string,
    nativeEventParam: Object,
  ) {
    var rootNodeID = tag;
    ReactNativeEventEmitter._receiveRootNodeIDEvent(
      rootNodeID,
      topLevelType,
      nativeEventParam,
    );
  },

  /**
   * Simple multi-wrapper around `receiveEvent` that is intended to receive an
   * efficient representation of `Touch` objects, and other information that
   * can be used to construct W3C compliant `Event` and `Touch` lists.
   *
   * This may create dispatch behavior that differs than web touch handling. We
   * loop through each of the changed touches and receive it as a single event.
   * So two `touchStart`/`touchMove`s that occur simultaneously are received as
   * two separate touch event dispatches - when they arguably should be one.
   *
   * This implementation reuses the `Touch` objects themselves as the `Event`s
   * since we dispatch an event for each touch (though that might not be spec
   * compliant). The main purpose of reusing them is to save allocations.
   *
   * TODO: Dispatch multiple changed touches in one event. The bubble path
   * could be the first common ancestor of all the `changedTouches`.
   *
   * One difference between this behavior and W3C spec: cancelled touches will
   * not appear in `.touches`, or in any future `.touches`, though they may
   * still be "actively touching the surface".
   *
   * Web desktop polyfills only need to construct a fake touch event with
   * identifier 0, also abandoning traditional click handlers.
   */
  receiveTouches: function(
    eventTopLevelType: string,
    touches: Array<Object>,
    changedIndices: Array<number>,
  ) {
    var changedTouches = eventTopLevelType === 'topTouchEnd' ||
      eventTopLevelType === 'topTouchCancel'
      ? removeTouchesAtIndices(touches, changedIndices)
      : touchSubsequence(touches, changedIndices);

    for (var jj = 0; jj < changedTouches.length; jj++) {
      var touch = changedTouches[jj];
      // Touch objects can fulfill the role of `DOM` `Event` objects if we set
      // the `changedTouches`/`touches`. This saves allocations.
      touch.changedTouches = changedTouches;
      touch.touches = touches;
      var nativeEvent = touch;
      var rootNodeID = null;
      var target = nativeEvent.target;
      if (target !== null && target !== undefined) {
        if (target < ReactNativeTagHandles.tagsStartAt) {
          if (__DEV__) {
            warning(
              false,
              'A view is reporting that a touch occurred on tag zero.',
            );
          }
        } else {
          rootNodeID = target;
        }
      }
      ReactNativeEventEmitter._receiveRootNodeIDEvent(
        rootNodeID,
        eventTopLevelType,
        nativeEvent,
      );
    }
  },
};

module.exports = ReactNativeEventEmitter;
<commit_msg>Removing unecessary assignment (#10528)<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ReactNativeEventEmitter
 * @flow
 */
'use strict';

var EventPluginHub = require('EventPluginHub');
var EventPluginRegistry = require('EventPluginRegistry');
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
var ReactGenericBatching = require('ReactGenericBatching');

if (__DEV__) {
  var warning = require('fbjs/lib/warning');
}

/**
 * Version of `ReactBrowserEventEmitter` that works on the receiving side of a
 * serialized worker boundary.
 */

// Shared default empty native event - conserve memory.
var EMPTY_NATIVE_EVENT = {};

/**
 * Selects a subsequence of `Touch`es, without destroying `touches`.
 *
 * @param {Array<Touch>} touches Deserialized touch objects.
 * @param {Array<number>} indices Indices by which to pull subsequence.
 * @return {Array<Touch>} Subsequence of touch objects.
 */
var touchSubsequence = function(touches, indices) {
  var ret = [];
  for (var i = 0; i < indices.length; i++) {
    ret.push(touches[indices[i]]);
  }
  return ret;
};

/**
 * TODO: Pool all of this.
 *
 * Destroys `touches` by removing touch objects at indices `indices`. This is
 * to maintain compatibility with W3C touch "end" events, where the active
 * touches don't include the set that has just been "ended".
 *
 * @param {Array<Touch>} touches Deserialized touch objects.
 * @param {Array<number>} indices Indices to remove from `touches`.
 * @return {Array<Touch>} Subsequence of removed touch objects.
 */
var removeTouchesAtIndices = function(
  touches: Array<Object>,
  indices: Array<number>,
): Array<Object> {
  var rippedOut = [];
  // use an unsafe downcast to alias to nullable elements,
  // so we can delete and then compact.
  var temp: Array<?Object> = (touches: Array<any>);
  for (var i = 0; i < indices.length; i++) {
    var index = indices[i];
    rippedOut.push(touches[index]);
    temp[index] = null;
  }
  var fillAt = 0;
  for (var j = 0; j < temp.length; j++) {
    var cur = temp[j];
    if (cur !== null) {
      temp[fillAt++] = cur;
    }
  }
  temp.length = fillAt;
  return rippedOut;
};

var ReactNativeEventEmitter = {
  ...ReactEventEmitterMixin,

  registrationNames: EventPluginRegistry.registrationNameModules,

  getListener: EventPluginHub.getListener,

  /**
   * Internal version of `receiveEvent` in terms of normalized (non-tag)
   * `rootNodeID`.
   *
   * @see receiveEvent.
   *
   * @param {rootNodeID} rootNodeID React root node ID that event occurred on.
   * @param {TopLevelType} topLevelType Top level type of event.
   * @param {object} nativeEventParam Object passed from native.
   */
  _receiveRootNodeIDEvent: function(
    rootNodeID: number,
    topLevelType: string,
    nativeEventParam: Object,
  ) {
    var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
    var inst = ReactNativeComponentTree.getInstanceFromNode(rootNodeID);
    ReactGenericBatching.batchedUpdates(function() {
      ReactNativeEventEmitter.handleTopLevel(
        topLevelType,
        inst,
        nativeEvent,
        nativeEvent.target,
      );
    });
    // React Native doesn't use ReactControlledComponent but if it did, here's
    // where it would do it.
  },

  /**
   * Publicly exposed method on module for native objc to invoke when a top
   * level event is extracted.
   * @param {rootNodeID} rootNodeID React root node ID that event occurred on.
   * @param {TopLevelType} topLevelType Top level type of event.
   * @param {object} nativeEventParam Object passed from native.
   */
  receiveEvent: function(
    rootNodeID: number,
    topLevelType: string,
    nativeEventParam: Object,
  ) {
    ReactNativeEventEmitter._receiveRootNodeIDEvent(
      rootNodeID,
      topLevelType,
      nativeEventParam,
    );
  },

  /**
   * Simple multi-wrapper around `receiveEvent` that is intended to receive an
   * efficient representation of `Touch` objects, and other information that
   * can be used to construct W3C compliant `Event` and `Touch` lists.
   *
   * This may create dispatch behavior that differs than web touch handling. We
   * loop through each of the changed touches and receive it as a single event.
   * So two `touchStart`/`touchMove`s that occur simultaneously are received as
   * two separate touch event dispatches - when they arguably should be one.
   *
   * This implementation reuses the `Touch` objects themselves as the `Event`s
   * since we dispatch an event for each touch (though that might not be spec
   * compliant). The main purpose of reusing them is to save allocations.
   *
   * TODO: Dispatch multiple changed touches in one event. The bubble path
   * could be the first common ancestor of all the `changedTouches`.
   *
   * One difference between this behavior and W3C spec: cancelled touches will
   * not appear in `.touches`, or in any future `.touches`, though they may
   * still be "actively touching the surface".
   *
   * Web desktop polyfills only need to construct a fake touch event with
   * identifier 0, also abandoning traditional click handlers.
   */
  receiveTouches: function(
    eventTopLevelType: string,
    touches: Array<Object>,
    changedIndices: Array<number>,
  ) {
    var changedTouches = eventTopLevelType === 'topTouchEnd' ||
      eventTopLevelType === 'topTouchCancel'
      ? removeTouchesAtIndices(touches, changedIndices)
      : touchSubsequence(touches, changedIndices);

    for (var jj = 0; jj < changedTouches.length; jj++) {
      var touch = changedTouches[jj];
      // Touch objects can fulfill the role of `DOM` `Event` objects if we set
      // the `changedTouches`/`touches`. This saves allocations.
      touch.changedTouches = changedTouches;
      touch.touches = touches;
      var nativeEvent = touch;
      var rootNodeID = null;
      var target = nativeEvent.target;
      if (target !== null && target !== undefined) {
        if (target < ReactNativeTagHandles.tagsStartAt) {
          if (__DEV__) {
            warning(
              false,
              'A view is reporting that a touch occurred on tag zero.',
            );
          }
        } else {
          rootNodeID = target;
        }
      }
      ReactNativeEventEmitter._receiveRootNodeIDEvent(
        rootNodeID,
        eventTopLevelType,
        nativeEvent,
      );
    }
  },
};

module.exports = ReactNativeEventEmitter;
<|endoftext|>