<commit_before>var stripe = require('stripe');
var q = require('q');
var request = require('request');
/**
 * A bridge connecting le-bank-service to Stripe
 * @class BankProvider
 * @param {string} secretKey the stripe secret key for your account
 * @param {StorageService} storage an instance of le-storage-service that is used to create records
 * @returns {service}
 */
var BankProvider = function (secretKey, storage) {
  if (!secretKey) { throw new Error('Secret key required'); }
  if (!storage) { throw new Error('Storage service required'); }
  var _provider = this;
  var _api = stripe(secretKey);
  /**
   * Creates a bank account
   *
   * Requires two tokens, one for crediting the bank account
   * and one for debiting the bank account. Simply generate
   * two tokens with the same bank account credentials.
   * @function createBankAccount
   * @memberof BankProvider
   * @instance
   * @param {string} countryCode the two letter country code of the bank's origin
   * @param {string} creditToken the tokenized bank account info
   * @param {string} debitToken the tokenized bank account info
   * @returns {promise} resolves with the newly created bankAccount record
   */
  this.createBankAccount = function (countryCode, creditToken, debitToken) {
    var bankAccount;
    var promises = [];
    promises.push(_api.accounts.create({
      managed: true,
      country: countryCode,
      bank_account: creditToken
    }));
    promises.push(_api.customers.create({
      bank_account: debitToken
    }));
    return q.all(promises)
    .spread(function (account, customer) {
      bankAccount = storage.createRecord('Bank Account');
      return bankAccount.update({
        _stripe: {
          customer_id: customer.id,
          account_id: account.id,
          bankAccount_id: customer.default_source
        }
      });
    })
    .then(function () { return bankAccount });
  };
  /**
   * Creates a credit card
   * @function createCreditCard
   * @memberof BankProvider
   * @instance
   * @param {string} token the tokenized credit card info
   * @returns {promise} resolves with the newly created creditCard record
   */
  this.createCreditCard = function (token) {
    return _api.customers.create({
      card: token
    })
    .then(function (customer) {
      var creditCard = storage.createRecord('Credit Card');
      return creditCard.update({
        _stripe: {
          customer_id: customer.id,
          creditCard_id: customer.default_source
        }
      })
      .then(function () {
        return creditCard;
      });
    });
  };
  /**
   * Verifies that the user has access to the bank account using micro-deposits
   *
   * @function verifyBankAccount
   * @memberof BankService
   * @instance
   * @param {record} bankAccount the record of the bank account to be verified
   * @param {array} amounts the micro-deposit verification amounts
   * @returns {promise}
   */
  this.verifyBankAccount = function (bankAccount, amounts) {
    return bankAccount.load()
    .then(function (data) {
      var customer = data._stripe.customer_id;
      var id = data._stripe.bankAccount_id;
      var deferred = q.defer();
      // node client doesn't support verifying bank accounts, so we must make the HTTP request ourselves :/
      var url = 'https://api.stripe.com/v1/customers/' + customer + '/bank_accounts/' + id + '/verify';
      var options = {
        url: url,
        auth: { 'bearer': secretKey },
        form: { amounts: amounts },
        qsStringifyOptions: { arrayFormat: 'brackets' }
      };
      request.post(options, function (err, resp) {
        if (!err && resp.statusCode == 200) {
          data.verifiedAt = new Date();
          bankAccount.update(data)
          .then(function () { deferred.resolve(); })
        } else {
          errMessage = JSON.parse(resp.body).error.message;
          deferred.reject(errMessage);
        }
      });
      return deferred.promise;
    });
  };
  /**
   * Charges a stripe customer
   * @function chargeCustomer
   * @memberof BankProvider
   * @instance
   * @param {string} customer the id of the stripe customer to charge
   * @param {number} cents the numbers of cents to charge
   * @param {string} account (optional) the id of the stripe account to credit
   * @returns {promise}
   */
  this.chargeCustomer = function (customer, cents, account) {
    var promise;
    if (account) {
      promise = _api.charges.create({
        amount: cents,
        currency: 'usd',
        customer: customer,
        destination: account,
        description: 'Castle'
      });
    } else {
      promise = _api.charges.create({
        amount: cents,
        currency: 'usd',
        customer: customer,
        description: 'Castle'
      });
    }
    return promise;
  };
  /**
   * Charges a bank account
   * @function chargeBankAccount
   * @memberof BankProvider
   * @instance
   * @param {record} bankAccount the record of the bank account to be charged
   * @param {number} cents the numbers of cents to charge
   * @returns {promise} resolves with the newly created payment record
   */
  this.chargeBankAccount = function (bankAccount, cents) {
    var customer;
    var payment;
    return bankAccount.load()
    .then(function (data) {
      if (data.verifiedAt) {
        customer = data._stripe.customer_id;
        return _provider.chargeCustomer(customer, cents);
      } else { return q.reject('Bank accounts must be verified before they can be charged'); }
    })
    .then(function (charge) {
      payment = storage.createRecord('Payment');
      return payment.update({
        cents: cents,
        _stripe: { customer_id: customer, charge_id: charge.id }
      });
    })
    .then(function () {
      return payment;
    });
  };
  /**
   * Charges a credit card
   * @function chargeCreditCard
   * @memberof BankProvider
   * @instance
   * @param {record} card the record of the credit card to be charged
   * @param {number} cents the numbers of cents to charge
   * @returns {promise} resolves with the newly created payment record
   */
  this.chargeCreditCard = function (card, cents) {
    var customer;
    var payment;
    return card.load()
    .then(function (data) {
      customer = data._stripe.customer_id;
      return _provider.chargeCustomer(customer, cents);
    })
    .then(function (charge) {
      payment = storage.createRecord('Payment');
      return payment.update({
        cents: cents,
        _stripe: { customer_id: customer, charge_id: charge.id }
      });
    })
    .then(function () {
      return payment;
    });
  };
  /**
   * Transfers money from a funding source to a bank account
   * @function transfer
   * @memberof BankProvider
   * @instance
   * @param {record} source the record of the credit card or bank account to be charged
   * @param {record} destination the record of the bank account to be credited
   * @param {number} cents the number of cents to transfer
   * @returns {promise} resolves with the newly created payment record
   */
  this.transfer = function (source, destination, cents) {
    var customer;
    var account;
    var payment;
    return source.load()
    .then(function (data) {
      customer = data._stripe.customer_id;
      return destination.load();
    })
    .then(function (data) {
      account = data._stripe.account_id;
      return _provider.chargeCustomer(customer, cents, account);
    })
    .then(function (charge) {
      payment = storage.createRecord('Payment');
      return payment.update({
        cents: cents,
        _stripe: { customer_id: customer, account_id: account, charge_id: charge.id }
      });
    })
    .then(function () {
      return payment;
    });
  };
  /**
   * Returns a bank account record, given the id
   * @function getBankAccount
   * @memberof BankProvider
   * @instance
   * @param {string} id the id of the bank account record
   * @returns {record}
   */
  this.getBankAccount = function (id) {
    return storage.createRecord('Bank Account', id);
  }
  /**
   * Returns a credit card record, given the id
   * @function getCreditCard
   * @memberof BankProvider
   * @instance
   * @param {string} id the id of the credit card record
   * @returns {record}
   */
  this.getCreditCard = function (id) {
    return storage.createRecord('Credit Card', id);
  }
};

module.exports = BankProvider;
<commit_msg>feat(BankProvider): implement verifyIdentity method<commit_after>var stripe = require('stripe');
var q = require('q');
var request = require('request');
/**
 * A bridge connecting le-bank-service to Stripe
 * @class BankProvider
 * @param {string} secretKey the stripe secret key for your account
 * @param {StorageService} storage an instance of le-storage-service that is used to create records
 * @returns {service}
 */
var BankProvider = function (secretKey, storage) {
  if (!secretKey) { throw new Error('Secret key required'); }
  if (!storage) { throw new Error('Storage service required'); }
  var _provider = this;
  var _api = stripe(secretKey);
  /**
   * Creates a bank account
   *
   * Requires two tokens, one for crediting the bank account
   * and one for debiting the bank account. Simply generate
   * two tokens with the same bank account credentials.
   * @function createBankAccount
   * @memberof BankProvider
   * @instance
   * @param {string} countryCode the two letter country code of the bank's origin
   * @param {string} creditToken the tokenized bank account info
   * @param {string} debitToken the tokenized bank account info
   * @returns {promise} resolves with the newly created bankAccount record
   */
  this.createBankAccount = function (countryCode, creditToken, debitToken) {
    var bankAccount;
    var promises = [];
    promises.push(_api.accounts.create({
      managed: true,
      country: countryCode,
      bank_account: creditToken
    }));
    promises.push(_api.customers.create({
      bank_account: debitToken
    }));
    return q.all(promises)
    .spread(function (account, customer) {
      bankAccount = storage.createRecord('Bank Account');
      return bankAccount.update({
        _stripe: {
          customer_id: customer.id,
          account_id: account.id,
          bankAccount_id: customer.default_source
        }
      });
    })
    .then(function () { return bankAccount });
  };
  /**
   * Creates a credit card
   * @function createCreditCard
   * @memberof BankProvider
   * @instance
   * @param {string} token the tokenized credit card info
   * @returns {promise} resolves with the newly created creditCard record
   */
  this.createCreditCard = function (token) {
    return _api.customers.create({
      card: token
    })
    .then(function (customer) {
      var creditCard = storage.createRecord('Credit Card');
      return creditCard.update({
        _stripe: {
          customer_id: customer.id,
          creditCard_id: customer.default_source
        }
      })
      .then(function () {
        return creditCard;
      });
    });
  };
  /**
   * Verifies that the user has access to the bank account using micro-deposits
   *
   * @function verifyBankAccount
   * @memberof BankProvider
   * @instance
   * @param {record} bankAccount the record of the bank account to be verified
   * @param {array} amounts the micro-deposit verification amounts
   * @returns {promise}
   */
  this.verifyBankAccount = function (bankAccount, amounts) {
    return bankAccount.load()
    .then(function (data) {
      var customer = data._stripe.customer_id;
      var id = data._stripe.bankAccount_id;
      var deferred = q.defer();
      // node client doesn't support verifying bank accounts, so we must make the HTTP request ourselves :/
      var url = 'https://api.stripe.com/v1/customers/' + customer + '/bank_accounts/' + id + '/verify';
      var options = {
        url: url,
        auth: { 'bearer': secretKey },
        form: { amounts: amounts },
        qsStringifyOptions: { arrayFormat: 'brackets' }
      };
      request.post(options, function (err, resp) {
        if (!err && resp.statusCode == 200) {
          data.verifiedAt = new Date();
          bankAccount.update(data)
          .then(function () { deferred.resolve(); })
        } else {
          errMessage = JSON.parse(resp.body).error.message;
          deferred.reject(errMessage);
        }
      });
      return deferred.promise;
    });
  };
  /**
   * Verifies that the user is who they say they are
   *
   * Basic verification requires the following properties on the identity object:
   * - legal_entity.first_name
   * - legal_entity.last_name
   * - legal_entity.dob.day
   * - legal_entity.dob.month
   * - legal_entity.dob.year
   * - legal_entity.type
   * - tos_acceptance.ip
   * - tos_acceptance.date
   * @function verifyIdentity
   * @memberof BankProvider
   * @instance
   * @param {record} bankAccount the record of the bank account to be verified
   * @param {Object} identity the map of identity fields
   * @returns {promise}
   */
  this.verifyIdentity = function (bankAccount, identity) {
    console.log('verifying identity');
    console.log(identity);
    return bankAccount.load()
    .then(function (data) {
      var account = data._stripe.account_id;
      return _api.accounts.update(account, identity);
    });
  };
  /**
   * Charges a stripe customer
   * @function chargeCustomer
   * @memberof BankProvider
   * @instance
   * @param {string} customer the id of the stripe customer to charge
   * @param {number} cents the numbers of cents to charge
   * @param {string} account (optional) the id of the stripe account to credit
   * @returns {promise}
   */
  this.chargeCustomer = function (customer, cents, account) {
    var promise;
    if (account) {
      promise = _api.charges.create({
        amount: cents,
        currency: 'usd',
        customer: customer,
        destination: account,
        description: 'Castle'
      });
    } else {
      promise = _api.charges.create({
        amount: cents,
        currency: 'usd',
        customer: customer,
        description: 'Castle'
      });
    }
    return promise;
  };
  /**
   * Charges a bank account
   * @function chargeBankAccount
   * @memberof BankProvider
   * @instance
   * @param {record} bankAccount the record of the bank account to be charged
   * @param {number} cents the numbers of cents to charge
   * @returns {promise} resolves with the newly created payment record
   */
  this.chargeBankAccount = function (bankAccount, cents) {
    var customer;
    var payment;
    return bankAccount.load()
    .then(function (data) {
      if (data.verifiedAt) {
        customer = data._stripe.customer_id;
        return _provider.chargeCustomer(customer, cents);
      } else { return q.reject('Bank accounts must be verified before they can be charged'); }
    })
    .then(function (charge) {
      payment = storage.createRecord('Payment');
      return payment.update({
        cents: cents,
        _stripe: { customer_id: customer, charge_id: charge.id }
      });
    })
    .then(function () {
      return payment;
    });
  };
  /**
   * Charges a credit card
   * @function chargeCreditCard
   * @memberof BankProvider
   * @instance
   * @param {record} card the record of the credit card to be charged
   * @param {number} cents the numbers of cents to charge
   * @returns {promise} resolves with the newly created payment record
   */
  this.chargeCreditCard = function (card, cents) {
    var customer;
    var payment;
    return card.load()
    .then(function (data) {
      customer = data._stripe.customer_id;
      return _provider.chargeCustomer(customer, cents);
    })
    .then(function (charge) {
      payment = storage.createRecord('Payment');
      return payment.update({
        cents: cents,
        _stripe: { customer_id: customer, charge_id: charge.id }
      });
    })
    .then(function () {
      return payment;
    });
  };
  /**
   * Transfers money from a funding source to a bank account
   * @function transfer
   * @memberof BankProvider
   * @instance
   * @param {record} source the record of the credit card or bank account to be charged
   * @param {record} destination the record of the bank account to be credited
   * @param {number} cents the number of cents to transfer
   * @returns {promise} resolves with the newly created payment record
   */
  this.transfer = function (source, destination, cents) {
    var customer;
    var account;
    var payment;
    return source.load()
    .then(function (data) {
      customer = data._stripe.customer_id;
      return destination.load();
    })
    .then(function (data) {
      account = data._stripe.account_id;
      return _provider.chargeCustomer(customer, cents, account);
    })
    .then(function (charge) {
      payment = storage.createRecord('Payment');
      return payment.update({
        cents: cents,
        _stripe: { customer_id: customer, account_id: account, charge_id: charge.id }
      });
    })
    .then(function () {
      return payment;
    });
  };
  /**
   * Returns a bank account record, given the id
   * @function getBankAccount
   * @memberof BankProvider
   * @instance
   * @param {string} id the id of the bank account record
   * @returns {record}
   */
  this.getBankAccount = function (id) {
    return storage.createRecord('Bank Account', id);
  }
  /**
   * Returns a credit card record, given the id
   * @function getCreditCard
   * @memberof BankProvider
   * @instance
   * @param {string} id the id of the credit card record
   * @returns {record}
   */
  this.getCreditCard = function (id) {
    return storage.createRecord('Credit Card', id);
  }
};

module.exports = BankProvider;
<|endoftext|>


<commit_before>(function (formBuilder) {
    /**
     * This directive is responsible for adding drag and drop functionality and sending the
     *  `builderDropzone` the required object in order to identify the dropped item.
     */
    formBuilder.directive('builderFieldType', ['$compile', function ($compile) {
        return {
            restrict: 'A',
            scope: {
                name: '@',
            },
            controller: function ($scope) {
                // check if it was defined.  If not - set a default
                $scope.item = $scope.item || { name: $scope.name };
            },
            compile: function (tElement, attrs, transclude) {
                tElement.removeAttr('builder-field-type');
                tElement.attr('dnd-draggable', 'item');
                tElement.attr('dnd-effect-allowed', 'move');
                tElement.attr('dnd-type', 'name');
                return {
                    pre: function preLink(scope, iElement, iAttrs, controller) { },
                    post: function postLink(scope, iElement, iAttrs, controller) {
                        $compile(iElement)(scope);
                    }
                };
            },

        };
    }]);

    formBuilder.directive('builderDropzone', ['$compile', 'builderConfig', function ($compile, builderConfig) {
        return {
            restrict: 'AE',
            scope: {
                builderList: '=?',
                builderName: '@',
                formFields: '=',
                shouldRender: '&?'
            },
            template: '<builder-dropzone-field ng-repeat="item in builderList" item="item" form-field="formFields[$index]" dnd-draggable="item" dnd-type="item.name"  dnd-moved="builderList.splice($index, 1)" on-field-removed="onItemRemoved($index)" dnd-effect-allowed="move" />',
            controller: function ($scope) {
                // check if it was defined.  If not - set a default
                $scope.builderList = $scope.builderList || [];
                $scope.formFields = new Array($scope.builderList.length);
                //  this adds the form field to `formFields` when the field is dropped to the dropzone
                //  the added form field is intitialized by undefined
                //  and the `builder-dropzone-field` is resposible for updating this value
                //  runs a callback to determine whether an item can be added to the drop zone. If it returns true then the item will be added
                $scope.onItemAdded = function (event, index, item, external, type) {
                    let canAddItem = true;
                    if (typeof $scope.shouldRender == "function")
                        canAddItem = $scope.shouldRender({ children: $scope.builderList, itemName: type });
                    if (canAddItem) {
                        $scope.formFields.splice(index, 0, undefined)
                        return item;
                    } else {
                        // stop Drop event propagation to higher drop zones
                        event.stopImmediatePropagation();
                        //stop drop event
                        return false;
                    }
                };

                /**
                 *this function is invoked when the `builder-dropzone-field` is destroyed
                 *the `builder-dropzone-field` is mainly destoryed when the item (field) is removed from the `builderList`
                 *`formFields` needs to be updated by removing the corresponding field from it
                 */
                $scope.onItemRemoved = function (index) {
                    $scope.formFields.splice(index, 1)
                };
            },
            compile: function (tElement, attrs, transclude) {
                tElement.removeAttr('builder-dropzone');
                tElement.attr('dnd-list', 'builderList');
                tElement.attr('dnd-drop', 'onItemAdded(event, index, item, external, type)');
                return {
                    pre: function preLink(scope, iElement, iAttrs, controller) {
                    },
                    post: function postLink(scope, iElement, iAttrs, controller) {
                        $compile(iElement)(scope);
                    }
                };
            },
        };
    }]);

    /**
     * This directive is responsible for rendering the droped field template  and
     *  add drag and drop functionality to it
     */
    formBuilder.directive('builderDropzoneField', ['$compile', 'builderConfig', '$q', '$templateCache', '$http', '$controller', function ($compile, builderConfig, $q, $templateCache, $http, $controller) {
        let check = apiCheck();

        function getFieldTemplate(component) {
            return getTemplate(component.template || component.templateUrl, !component.template);
        }

        //Takes a function injecting controller scope into it then invoke it. Simulating runtime directives
        function invokeController(controller, scope) {
            check.throw([check.func, check.object], arguments, { prefix: 'faild to invoke field controller this may happen if you are not passing a function', suffix: "check setType function" });
            $controller(controller, { $scope: scope })
        }

        //Takes a function injecting link function arguments into it then invoke it. Simulating runtime directives
        function invokeLink(fieldConfig, context, args) {
            let linkFunc = fieldConfig.link;
            return function () {
                if (!linkFunc)
                    return;
                else if (typeof linkFunc === "function")
                    linkFunc.apply(context, args);
                else
                    throw new Error('Link property must be a function check setType');
            }
        }

        // used to make an object property a const
        function freezObjectProperty(object, propertyName, value) {
            Object.defineProperty(object, propertyName, {
                value: value,
                writable: false,
                enumerable: true,
                configurable: true
            });
        }

        //resolvers html template and return a promise
        function getTemplate(template, isUrl) {
            let templatePromise = $q.when(template);
            if (!isUrl)
                return templatePromise;
            else {
                const httpOptions = { cache: $templateCache };
                return templatePromise.then((url) => $http.get(url, httpOptions))
                    .then(function (response) {
                        return response.data;
                    })
                    .catch(function (error) {
                        throw new Error('can not load template url ' + template + ' ' + error);
                    });
            }
        }

        //search dom tree for a element (transclude) then replaces that element with a new template then return a new html
        function doTransclusion(wrapper, template) {
            let superWrapper = angular.element('');
            superWrapper.append(wrapper);
            let transcludeEl = superWrapper.find('transclude');
            if (!transcludeEl.length)
                console.warn("can not find transclude tag check your wrapper template " + wrapper);
            transcludeEl.replaceWith(template);
            return superWrapper.html();
        }

        //Takes a set of html templates then wrap them over the original html template then return a promise
        function transcludeInWrappers(fieldConfig) {
            const wrappers = fieldConfig.wrapper;
            return function (template) {
                if (!wrappers || wrappers.length == 0)
                    return $q.when(template); // if no wrappers are provided just return the orginal template
                else if (Array.isArray(wrappers)) {
                    //try to resolve html templates will return array of promises
                    let templatesPromises = wrappers.map(function (wrapper) {
                        return getTemplate(wrapper, true);
                    });
                    return $q.all(templatesPromises).then(function (wrappersTemplates) {
                        wrappersTemplates.reverse();
                        let totalWrapper = wrappersTemplates.shift();
                        wrappersTemplates.forEach(function (wrapper) {
                            totalWrapper = doTransclusion(totalWrapper, wrapper);
                        });
                        return doTransclusion(totalWrapper, template);
                    });
                }
                else {
                    throw new Error("wrapper must be of type array check setType")
                }
            }
        }

        return {
            restrict: 'AE',
            scope: {
                item: '=',
                formField: '=',
                onFieldRemoved: '&'
            },
            link: function (scope, elem, attr) {
                freezObjectProperty(scope.item, "name", scope.item.name);
                let fieldConfig = builderConfig.getType(scope.item.name);
                let args = arguments;
                let that = this;

                //link template with scope and invoke custome link function
                getFieldTemplate(fieldConfig)
                    .then(transcludeInWrappers(fieldConfig))
                    .then(function (templateString) {
                        let compieldHtml = $compile(templateString)(scope);
                        elem.append(compieldHtml);
                    }).then(invokeLink(fieldConfig, that, args))
                    .catch(function (error) {
                        throw new Error('There was a problem setting the template for this field ' + error + " " + JSON.stringify(fieldConfig));
                    });

                scope.$on('$destroy', function () {
                    scope.onFieldRemoved();//callback
                });

            },
            controller: function ($scope) {
                $scope.formField = { "key": Math.random(), "type": $scope.item.name };
                let fieldConfig = builderConfig.getType($scope.item.name);
                if (fieldConfig.controller)
                    invokeController(fieldConfig.controller, $scope);
            },
        };

    }]);

    formBuilder.directive('builderFieldConfig', ['$compile', function ($compile) {
        return {
            restrict: 'A',
            scope: {
                name: '@',
            },
            controller: function ($scope) {
                // check if it was defined.  If not - set a default
                $scope.item = $scope.item || { name: $scope.name };
            },
            link: function (scope, elem, attr) {

            }

        };
    }]);

    //register and get application components.
    formBuilder.factory('builderConfig', function () {
        const typeMap = {}; // hashmap of components
        let check = apiCheck();

        function checkType(component) {
            if (angular.isUndefined(component.name))
                throw new Error('field name must be defined check setType ' + JSON.stringify(component));
            if (angular.isUndefined(component.template) && angular.isUndefined(component.templateUrl))
                throw new Error('field template or templateUrl must be defined check setType ' + JSON.stringify(component));
        }
        return {
            /*
            Takes directive definition object with extra properties and saves it to a hashmap.
            The object properties will be validated then registered,
            keys in the hashmap are lower case. If key exists its value will be overwritten.
             */
            setType: function (component) {
                check.throw([check.object], arguments, { prefix: 'faild to set new type this may happen if you are not passing an object', suffix: "check setType function" });
                checkType(component);
                if (typeMap[component.name.toLowerCase()])
                    console.warn(component.name + " is registered before it will be overwritten");
                typeMap[component.name.toLowerCase()] = component;
            },
            /*
            Takes component name and gets it from hashmap. name is case insensitive .
             */
            getType: function (name) {
                check.throw([check.string], arguments, { prefix: 'faild to get type this may happen if you are not passing an string', suffix: "check getType function" });
                let lowerCaseName = name.toLowerCase();
                if (typeMap[lowerCaseName])
                    return typeMap[lowerCaseName];
                else {
                    throw new Error('The field ' + name + ' is not registered');
                }
            }
        };
    });

})(angular.module('formBuilder', []));
<commit_msg>change shouldRender function name to shouldUpdate. change parameters of shouldUpdate to take item object instead of item name. add abbilaty to transorm from builder description to onther one<commit_after>(function (formBuilder) {
    /**
     * This directive is responsible for adding drag and drop functionality and sending the
     *  `builderDropzone` the required object in order to identify the dropped item.
     */
    formBuilder.directive('builderFieldType', ['$compile', function ($compile) {
        return {
            restrict: 'A',
            scope: {
                name: '@',
            },
            controller: function ($scope) {
                // check if it was defined.  If not - set a default
                $scope.item = $scope.item || { name: $scope.name };
            },
            compile: function (tElement, attrs, transclude) {
                tElement.removeAttr('builder-field-type');
                tElement.attr('dnd-draggable', 'item');
                tElement.attr('dnd-effect-allowed', 'move');
                tElement.attr('dnd-type', 'name');
                return {
                    pre: function preLink(scope, iElement, iAttrs, controller) { },
                    post: function postLink(scope, iElement, iAttrs, controller) {
                        $compile(iElement)(scope);
                    }
                };
            },

        };
    }]);

    formBuilder.directive('builderDropzone', ['$compile', 'builderConfig', function ($compile, builderConfig) {
        return {
            restrict: 'AE',
            scope: {
                builderList: '=?',
                builderName: '@',
                formFields: '=',
                shouldUpdate: '&?'
            },
            template: '<builder-dropzone-field ng-repeat="item in builderList" item="item" form-field="formFields[$index]" dnd-draggable="item" dnd-type="item.name"  dnd-moved="builderList.splice($index, 1)" on-field-removed="onItemRemoved($index)" dnd-effect-allowed="move" />',
            controller: function ($scope) {
                // check if it was defined.  If not - set a default
                $scope.builderList = $scope.builderList || [];
                $scope.formFields = new Array($scope.builderList.length);

                //  this adds the form field to `formFields` when the field is dropped to the dropzone
                //  the added form field is intitialized by undefined
                //  and the `builder-dropzone-field` is resposible for updating this value
                //  runs a callback to determine whether an item can be added to the drop zone. If it returns true then the item will be added
                $scope.onItemAdded = function (event, index, item, external, type) {
                    let canAddItem = true;
                    if (typeof $scope.shouldUpdate == "function")
                        canAddItem = $scope.shouldUpdate({ children: $scope.builderList, nextComponent: item });
                    if (canAddItem) {
                        $scope.formFields.splice(index, 0, undefined)
                        return item;
                    } else {
                        // stop Drop event propagation to higher drop zones
                        event.stopImmediatePropagation();
                        //stop drop event
                        return false;
                    }
                };

                /**
                 *this function is invoked when the `builder-dropzone-field` is destroyed
                 *the `builder-dropzone-field` is mainly destoryed when the item (field) is removed from the `builderList`
                 *`formFields` needs to be updated by removing the corresponding field from it
                 */
                $scope.onItemRemoved = function (index) {
                    $scope.formFields.splice(index, 1)
                };
            },
            compile: function (tElement, attrs, transclude) {
                tElement.removeAttr('builder-dropzone');
                tElement.attr('dnd-list', 'builderList');
                tElement.attr('dnd-drop', 'onItemAdded(event, index, item, external, type)');

                return {
                    pre: function preLink(scope, iElement, iAttrs, controller) {
                    },
                    post: function postLink(scope, iElement, iAttrs, controller) {
                        $compile(iElement)(scope);
                    }
                };
            },
        };
    }]);

    /**
     * This directive is responsible for rendering the droped field template  and
     *  add drag and drop functionality to it
     */
    formBuilder.directive('builderDropzoneField', ['$compile', 'builderConfig', '$q', '$templateCache', '$http', '$controller', function ($compile, builderConfig, $q, $templateCache, $http, $controller) {
        let check = apiCheck();

        function getFieldTemplate(component) {
            return getTemplate(component.template || component.templateUrl, !component.template);
        }

        //Takes a function injecting controller scope into it then invoke it. Simulating runtime directives
        function invokeController(controller, scope) {
            check.throw([check.func, check.object], arguments, { prefix: 'faild to invoke field controller this may happen if you are not passing a function', suffix: "check setType function" });
            $controller(controller, { $scope: scope })
        }

        //Takes a function injecting link function arguments into it then invoke it. Simulating runtime directives
        function invokeLink(fieldConfig, context, args) {
            let linkFunc = fieldConfig.link;
            return function () {
                if (!linkFunc)
                    return;
                else if (typeof linkFunc === "function")
                    linkFunc.apply(context, args);
                else
                    throw new Error('Link property must be a function check setType');
            }
        }

        // used to make an object property a const
        function freezObjectProperty(object, propertyName, value) {
            Object.defineProperty(object, propertyName, {
                value: value,
                writable: false,
                enumerable: true,
                configurable: true
            });
        }

        //resolvers html template and return a promise
        function getTemplate(template, isUrl) {
            let templatePromise = $q.when(template);
            if (!isUrl)
                return templatePromise;
            else {
                const httpOptions = { cache: $templateCache };
                return templatePromise.then((url) => $http.get(url, httpOptions))
                    .then(function (response) {
                        return response.data;
                    })
                    .catch(function (error) {
                        throw new Error('can not load template url ' + template + ' ' + error);
                    });
            }
        }

        //search dom tree for a element (transclude) then replaces that element with a new template then return a new html
        function doTransclusion(wrapper, template) {
            let superWrapper = angular.element('');
            superWrapper.append(wrapper);
            let transcludeEl = superWrapper.find('transclude');
            if (!transcludeEl.length)
                console.warn("can not find transclude tag check your wrapper template " + wrapper);
            transcludeEl.replaceWith(template);
            return superWrapper.html();
        }

        //Takes a set of html templates then wrap them over the original html template then return a promise
        function transcludeInWrappers(fieldConfig) {
            const wrappers = fieldConfig.wrapper;
            return function (template) {
                if (!wrappers || wrappers.length == 0)
                    return $q.when(template); // if no wrappers are provided just return the orginal template
                else if (Array.isArray(wrappers)) {
                    //try to resolve html templates will return array of promises
                    let templatesPromises = wrappers.map(function (wrapper) {
                        return getTemplate(wrapper, true);
                    });
                    return $q.all(templatesPromises).then(function (wrappersTemplates) {
                        wrappersTemplates.reverse();
                        let totalWrapper = wrappersTemplates.shift();
                        wrappersTemplates.forEach(function (wrapper) {
                            totalWrapper = doTransclusion(totalWrapper, wrapper);
                        });
                        return doTransclusion(totalWrapper, template);
                    });
                }
                else {
                    throw new Error("wrapper must be of type array check setType")
                }
            }
        }

        return {
            restrict: 'AE',
            scope: {
                item: '=',
                formField: '=',
                onFieldRemoved: '&'
            },
            link: function (scope, elem, attr) {
                freezObjectProperty(scope.item, "name", scope.item.name);
                let fieldConfig = builderConfig.getType(scope.item.name);
                let args = arguments;
                let that = this;

                //link template with scope and invoke custome link function
                getFieldTemplate(fieldConfig)
                    .then(transcludeInWrappers(fieldConfig))
                    .then(function (templateString) {
                        let compieldHtml = $compile(templateString)(scope);
                        elem.append(compieldHtml);
                    }).then(invokeLink(fieldConfig, that, args))
                    .catch(function (error) {
                        throw new Error('There was a problem setting the template for this field ' + error + " " + JSON.stringify(fieldConfig));
                    });

                scope.$on('$destroy', function () {
                    scope.onFieldRemoved();//callback
                });

            },
            controller: function ($scope) {
                let fieldConfig = builderConfig.getType($scope.item.name);
                $scope.formField = fieldConfig.transform($scope.item, $scope);
                if (fieldConfig.controller)
                    invokeController(fieldConfig.controller, $scope);
            },
        };

    }]);

    formBuilder.directive('builderFieldConfig', ['$compile', function ($compile) {
        return {
            restrict: 'A',
            scope: {
                name: '@',
            },
            controller: function ($scope) {
                // check if it was defined.  If not - set a default
                $scope.item = $scope.item || { name: $scope.name };
            },
            link: function (scope, elem, attr) {

            }

        };
    }]);

    //register and get application components.
    formBuilder.factory('builderConfig', function () {
        const typeMap = {}; // hashmap of components
        let check = apiCheck();

        function checkType(component) {
            if (angular.isUndefined(component.name))
                throw new Error('field name must be defined check setType of ' + JSON.stringify(component));
            else if (angular.isUndefined(component.template) && angular.isUndefined(component.templateUrl))
                throw new Error('field template or templateUrl must be defined check setType of ' + JSON.stringify(component));
            else if (typeof component.transform != "function")
                throw new Error("field must have a transform function check setType of " + JSON.stringify(component));
        }

        return {
            /*
            Takes directive definition object with extra properties and saves it to a hashmap.
            The object properties will be validated then registered,
            keys in the hashmap are lower case. If key exists its value will be overwritten.
             */
            setType: function (component) {
                check.throw([check.object], arguments, { prefix: 'faild to set new type this may happen if you are not passing an object', suffix: "check setType function" });
                checkType(component);
                if (typeMap[component.name.toLowerCase()])
                    console.warn(component.name + " is registered before it will be overwritten");
                typeMap[component.name.toLowerCase()] = component;
            },
            /*
            Takes component name and gets it from hashmap. name is case insensitive .
             */
            getType: function (name) {
                check.throw([check.string], arguments, { prefix: 'faild to get type this may happen if you are not passing an string', suffix: "check getType function" });
                let lowerCaseName = name.toLowerCase();
                if (typeMap[lowerCaseName])
                    return typeMap[lowerCaseName];
                else {
                    throw new Error('The field ' + name + ' is not registered');
                }
            }
        };
    });

})(angular.module('formBuilder', []));
<|endoftext|>


<commit_before>#!/usr/bin/env node

const prompt = require('prompt')
const fs = require('fs')
const os = require('os')
const updateNotifier = require('update-notifier')
const pkg = require('../package.json')
const Session = require('../lib/session')
const siding = require('../lib/siding')
const error = require('../lib/error')

prompt.colors = false

const userDataFolder = `${os.homedir()}/.sincding`

data = (data = {}) => {
  console.log('Let\'s update your user data')
  if (data !== {}) {
    console.log(
      ' For each prompt the value in () is the current one\n Press enter if you don\'t want to change it'
    )
  }
  const schema = {
    properties: {
      username: {
        pattern: /^[a-zA-Z\d]+$/,
        message: 'Username without @uc',
        required: true,
        default: data.username || ''
      },
      password: {
        required: true,
        hidden: true,
        replace: '*'
      },
      path: {
        required: true,
        default: data.path || '',
        conform: path => {
          if (!fs.existsSync(path)) {
            console.log('The provided path doesn\'t exist')
            return false
          }
          return true
        }
      },
      ignore: {
        pattern: /^[a-zA-Z\d ]+$/,
        message: 'Course acronyms separated by a space',
        default: (data.ignore || []).join(' ')
      }
    }
  }
  prompt.start()
  const saveData = (err, result) => {
    if (!result) {
      return exit()
    }
    const data = Object.assign({}, result, {
      ignore: result.ignore.split(' ').map(a => a.toUpperCase())
    })
    if (!fs.existsSync(`${userDataFolder}`)) {
      fs.mkdirSync(`${userDataFolder}`)
    }
    const path = `${userDataFolder}/data.json`
    const dataJson = JSON.stringify(data)
    fs.writeFile(path, dataJson, 'utf8', run)
    console.log('Updated user data')
  }
  prompt.get(schema, saveData)
}

sync = data => {
  const session = new Session(data.username, data.password)
  siding.coursesList(session, data.ignore).then(courses => {
    console.log('\nFound courses')
    courses.forEach(c => console.log(` - ${c.path()}`))
    console.log('')
    Promise.all(courses.map(course => course.scrap()))
      .then(courses => {
        console.log('\nFound:')
        const found = courses.map(c => ({
          name: c.name,
          folders: Object.keys(c.folders).length,
          files: Object.keys(c.files).length
        }))
        coursesSummary(found)
        console.log('\nDownload:')
        const downloads = courses.map(c => ({
          name: c.name,
          folders: Object.keys(c.folders)
            .filter(id => c.folders[id].shouldCreate(data.path))
            .map(id => c.folders[id]),
          files: Object.keys(c.files)
            .filter(id => c.files[id].shouldDownload(data.path))
            .map(id => c.files[id])
        }))
        const downloadNumbers = downloads.map(download => ({
          name: download.name,
          folders: download.folders.length,
          files: download.files.length
        }))
        coursesSummary(downloadNumbers)
        console.log('\nCreating missing folders...')
        courses.forEach(course => course.createFolder(data.path))
        downloads.forEach(d => d.folders.forEach(f => f.create(data.path)))
        console.log('\nStarting downloads, this may take a while...')
        const files = downloads
          .map(download => download.files)
          .reduce((total, arr) => total.concat(arr))
        Promise.all(files.map(file => file.download(data.path))).then(() =>
          console.log('\nFinished downloading!'))
      })
      .catch(err => error(err, 'Running sync'))
  })
}

coursesSummary = courses =>
  courses.forEach(c => {
    const log = `
- ${c.name}
  - folders: ${c.folders}
  - files: ${c.files}`
    console.log(log)
  })

exit = () => {
  console.log('\nTerminated sincding')
}

options = {
  data: data,
  sync: sync,
  exit: exit
}

optionsDescriptions = {
  data: 'Update your user data',
  sync: 'Download sincding files',
  exit: 'Exit sincding'
}

run = () => {
  console.log('')
  // Load user data
  const userData = require(`${userDataFolder}/data.json`) || {}
  if (userData === {}) {
    data(userData)
  }
  // Show user data
  console.log('Current user data')
  console.log(`user: ${userData.username}`)
  console.log(`path: ${userData.path}`)
  console.log(`ignore: ${userData.ignore.join(' ')}`)
  console.log('')
  // If a command was supplied in the call, execute it
  const commandLine = options[process.argv[2]]
  if (commandLine) {
    console.log(`Executing \'${process.argv[2]}\' command`)
    process.argv[2] = ''
    return commandLine(userData)
  }
  // Show available commands
  console.log('Commands:')
  Object.keys(options).forEach(key => {
    console.log(` - ${key}: ${optionsDescriptions[key]}`)
  })
  // Prompt user for command
  prompt.start()
  runCommand = (err, result) => {
    if (!result || err) {
      return exit()
    }
    const command = options[result.command]
    if (!command) {
      console.log('Not a valid command')
      return run()
    }
    // Execute selected command
    console.log(`Executing \'${result.command}\' command`)
    options[result.command](userData)
  }
  prompt.get(['command'], runCommand)
}

updateNotifier({pkg}).notify()
console.log('Welcome to sincding!')
console.log(`Version ${pkg.version}`)
run()
<commit_msg>Fix first user data setup<commit_after>#!/usr/bin/env node

const prompt = require('prompt')
const fs = require('fs')
const os = require('os')
const updateNotifier = require('update-notifier')
const pkg = require('../package.json')
const Session = require('../lib/session')
const siding = require('../lib/siding')
const error = require('../lib/error')

prompt.colors = false

const userDataFolder = `${os.homedir()}/.sincding`

data = (data = {}) => {
  console.log('Let\'s update your user data')
  if (data.username) {
    console.log(
      ' For each prompt the value in () is the current one\n Press enter if you don\'t want to change it'
    )
  }
  const schema = {
    properties: {
      username: {
        pattern: /^[a-zA-Z\d]+$/,
        message: 'Username without @uc',
        required: true,
        default: data.username || ''
      },
      password: {
        required: true,
        hidden: true,
        replace: '*'
      },
      path: {
        required: true,
        default: data.path || '',
        conform: path => {
          if (!fs.existsSync(path)) {
            console.log('The provided path doesn\'t exist')
            return false
          }
          return true
        }
      },
      ignore: {
        pattern: /^[a-zA-Z\d ]+$/,
        message: 'Course acronyms separated by a space',
        default: (data.ignore || []).join(' ')
      }
    }
  }
  prompt.start()
  const saveData = (err, result) => {
    if (!result) {
      return exit()
    }
    const data = Object.assign({}, result, {
      ignore: result.ignore.split(' ').map(a => a.toUpperCase())
    })
    if (!fs.existsSync(`${userDataFolder}`)) {
      fs.mkdirSync(`${userDataFolder}`)
    }
    const path = `${userDataFolder}/data.json`
    const dataJson = JSON.stringify(data)
    fs.writeFile(path, dataJson, 'utf8', run)
    console.log('Updated user data')
  }
  prompt.get(schema, saveData)
}

sync = data => {
  const session = new Session(data.username, data.password)
  siding.coursesList(session, data.ignore).then(courses => {
    console.log('\nFound courses')
    courses.forEach(c => console.log(` - ${c.path()}`))
    console.log('')
    Promise.all(courses.map(course => course.scrap()))
      .then(courses => {
        console.log('\nFound:')
        const found = courses.map(c => ({
          name: c.name,
          folders: Object.keys(c.folders).length,
          files: Object.keys(c.files).length
        }))
        coursesSummary(found)
        console.log('\nDownload:')
        const downloads = courses.map(c => ({
          name: c.name,
          folders: Object.keys(c.folders)
            .filter(id => c.folders[id].shouldCreate(data.path))
            .map(id => c.folders[id]),
          files: Object.keys(c.files)
            .filter(id => c.files[id].shouldDownload(data.path))
            .map(id => c.files[id])
        }))
        const downloadNumbers = downloads.map(download => ({
          name: download.name,
          folders: download.folders.length,
          files: download.files.length
        }))
        coursesSummary(downloadNumbers)
        console.log('\nCreating missing folders...')
        courses.forEach(course => course.createFolder(data.path))
        downloads.forEach(d => d.folders.forEach(f => f.create(data.path)))
        console.log('\nStarting downloads, this may take a while...')
        const files = downloads
          .map(download => download.files)
          .reduce((total, arr) => total.concat(arr))
        Promise.all(files.map(file => file.download(data.path))).then(() =>
          console.log('\nFinished downloading!'))
      })
      .catch(err => error(err, 'Running sync'))
  })
}

coursesSummary = courses =>
  courses.forEach(c => {
    const log = `
- ${c.name}
  - folders: ${c.folders}
  - files: ${c.files}`
    console.log(log)
  })

exit = () => {
  console.log('\nTerminated sincding')
}

options = {
  data: data,
  sync: sync,
  exit: exit
}

optionsDescriptions = {
  data: 'Update your user data',
  sync: 'Download sincding files',
  exit: 'Exit sincding'
}

run = () => {
  console.log('')
  // Load user data
  let userData
  try {
    userData = require(`${userDataFolder}/data.json`)
  } catch (err) {
    userData = null
  }
  if (!userData) {
    return data()
  }
  // Show user data
  console.log('Current user data')
  console.log(`user: ${userData.username}`)
  console.log(`path: ${userData.path}`)
  console.log(`ignore: ${(userData.ignore || []).join(' ')}`)
  console.log('')
  // If a command was supplied in the call, execute it
  const commandLine = options[process.argv[2]]
  if (commandLine) {
    console.log(`Executing \'${process.argv[2]}\' command`)
    process.argv[2] = ''
    return commandLine(userData)
  }
  // Show available commands
  console.log('Commands:')
  Object.keys(options).forEach(key => {
    console.log(` - ${key}: ${optionsDescriptions[key]}`)
  })
  // Prompt user for command
  prompt.start()
  runCommand = (err, result) => {
    if (!result || err) {
      return exit()
    }
    const command = options[result.command]
    if (!command) {
      console.log('Not a valid command')
      return run()
    }
    // Execute selected command
    console.log(`Executing \'${result.command}\' command`)
    options[result.command](userData)
  }
  prompt.get(['command'], runCommand)
}

updateNotifier({pkg}).notify()
console.log('Welcome to sincding!')
console.log(`Version ${pkg.version}`)
run()
<|endoftext|>


<commit_before>
import convert from './convert.js';     // GeoJSON conversion and preprocessing
import clip from './clip.js';           // stripe clipping algorithm
import wrap from './wrap.js';           // date line processing
import transform from './transform.js'; // coordinate transformation
import createTile from './tile.js';     // final simplified tile generation

export default function geojsonvt(data, options) {
    return new GeoJSONVT(data, options);
}

function GeoJSONVT(data, options) {
    options = this.options = extend(Object.create(this.options), options);

    const debug = options.debug;

    if (debug) console.time('preprocess data');

    if (options.maxZoom < 0 || options.maxZoom > 24) throw new Error('maxZoom should be in the 0-24 range');
    if (options.promoteId && options.generateId) throw new Error('promoteId and generateId cannot be used together.');

    // projects and adds simplification info
    let features = convert(data, options);

    // tiles and tileCoords are part of the public API
    this.tiles = {};
    this.tileCoords = [];

    if (debug) {
        console.timeEnd('preprocess data');
        console.log('index: maxZoom: %d, maxPoints: %d', options.indexMaxZoom, options.indexMaxPoints);
        console.time('generate tiles');
        this.stats = {};
        this.total = 0;
    }

    // wraps features (ie extreme west and extreme east)
    features = wrap(features, options);

    // start slicing from the top tile down
    if (features.length) this.splitTile(features, 0, 0, 0);

    if (debug) {
        if (features.length) console.log('features: %d, points: %d', this.tiles[0].numFeatures, this.tiles[0].numPoints);
        console.timeEnd('generate tiles');
        console.log('tiles generated:', this.total, JSON.stringify(this.stats));
    }
}

GeoJSONVT.prototype.options = {
    maxZoom: 14,            // max zoom to preserve detail on
    indexMaxZoom: 5,        // max zoom in the tile index
    indexMaxPoints: 100000, // max number of points per tile in the tile index
    tolerance: 3,           // simplification tolerance (higher means simpler)
    extent: 4096,           // tile extent
    buffer: 64,             // tile buffer on each side
    lineMetrics: false,     // whether to calculate line metrics
    promoteId: null,        // name of a feature property to be promoted to feature.id
    generateId: false,      // whether to generate feature ids. Cannot be used with promoteId
    debug: 0                // logging level (0, 1 or 2)
};

// splits features from a parent tile to sub-tiles.
// z, x, and y are the coordinates of the parent tile
// cz, cx, and cy are the coordinates of the target tile
//
// If no target tile is specified, splitting stops when we reach the maximum
// zoom or the number of points is low as specified in the options.
GeoJSONVT.prototype.splitTile = function (features, z, x, y, cz, cx, cy) {

    const stack = [features, z, x, y];
    const options = this.options;
    const debug = options.debug;

    // avoid recursion by using a processing queue
    while (stack.length) {
        y = stack.pop();
        x = stack.pop();
        z = stack.pop();
        features = stack.pop();

        const z2 = 1 << z;
        const id = toID(z, x, y);
        let tile = this.tiles[id];

        if (!tile) {
            if (debug > 1) console.time('creation');

            tile = this.tiles[id] = createTile(features, z, x, y, options);
            this.tileCoords.push({z, x, y});

            if (debug) {
                if (debug > 1) {
                    console.log('tile z%d-%d-%d (features: %d, points: %d, simplified: %d)',
                        z, x, y, tile.numFeatures, tile.numPoints, tile.numSimplified);
                    console.timeEnd('creation');
                }
                const key = `z${  z}`;
                this.stats[key] = (this.stats[key] || 0) + 1;
                this.total++;
            }
        }

        // save reference to original geometry in tile so that we can drill down later if we stop now
        tile.source = features;

        // if it's the first-pass tiling
        if (cz == null) {
            // stop tiling if we reached max zoom, or if the tile is too simple
            if (z === options.indexMaxZoom || tile.numPoints <= options.indexMaxPoints) continue;
        // if a drilldown to a specific tile
        } else if (z === options.maxZoom || z === cz) {
            // stop tiling if we reached base zoom or our target tile zoom
            continue;
        } else if (cz != null) {
            // stop tiling if it's not an ancestor of the target tile
            const zoomSteps = cz - z;
            if (x !== cx >> zoomSteps || y !== cy >> zoomSteps) continue;
        }

        // if we slice further down, no need to keep source geometry
        tile.source = null;

        if (features.length === 0) continue;

        if (debug > 1) console.time('clipping');

        // values we'll use for clipping
        const k1 = 0.5 * options.buffer / options.extent;
        const k2 = 0.5 - k1;
        const k3 = 0.5 + k1;
        const k4 = 1 + k1;

        let tl = null;
        let bl = null;
        let tr = null;
        let br = null;

        let left  = clip(features, z2, x - k1, x + k3, 0, tile.minX, tile.maxX, options);
        let right = clip(features, z2, x + k2, x + k4, 0, tile.minX, tile.maxX, options);
        features = null;

        if (left) {
            tl = clip(left, z2, y - k1, y + k3, 1, tile.minY, tile.maxY, options);
            bl = clip(left, z2, y + k2, y + k4, 1, tile.minY, tile.maxY, options);
            left = null;
        }

        if (right) {
            tr = clip(right, z2, y - k1, y + k3, 1, tile.minY, tile.maxY, options);
            br = clip(right, z2, y + k2, y + k4, 1, tile.minY, tile.maxY, options);
            right = null;
        }

        if (debug > 1) console.timeEnd('clipping');

        stack.push(tl || [], z + 1, x * 2,     y * 2);
        stack.push(bl || [], z + 1, x * 2,     y * 2 + 1);
        stack.push(tr || [], z + 1, x * 2 + 1, y * 2);
        stack.push(br || [], z + 1, x * 2 + 1, y * 2 + 1);
    }
};

GeoJSONVT.prototype.getTile = function (z, x, y) {
    z = +z;
    x = +x;
    y = +y;

    const options = this.options;
    const {extent, debug} = options;

    if (z < 0 || z > 24) return null;

    const z2 = 1 << z;
    x = (x + z2) & (z2 - 1); // wrap tile x coordinate

    const id = toID(z, x, y);
    if (this.tiles[id]) return transform(this.tiles[id], extent);

    if (debug > 1) console.log('drilling down to z%d-%d-%d', z, x, y);

    let z0 = z;
    let x0 = x;
    let y0 = y;
    let parent;

    while (!parent && z0 > 0) {
        z0--;
        x0 = x0 >> 1;
        y0 = y0 >> 1;
        parent = this.tiles[toID(z0, x0, y0)];
    }

    if (!parent || !parent.source) return null;

    // if we found a parent tile containing the original geometry, we can drill down from it
    if (debug > 1) {
        console.log('found parent tile z%d-%d-%d', z0, x0, y0);
        console.time('drilling down');
    }
    this.splitTile(parent.source, z0, x0, y0, z, x, y);
    if (debug > 1) console.timeEnd('drilling down');

    return this.tiles[id] ? transform(this.tiles[id], extent) : null;
};

function toID(z, x, y) {
    return (((1 << z) * y + x) * 32) + z;
}

function extend(dest, src) {
    for (const i in src) dest[i] = src[i];
    return dest;
}
<commit_msg>more idiomatic ES<commit_after>
import convert from './convert.js';     // GeoJSON conversion and preprocessing
import clip from './clip.js';           // stripe clipping algorithm
import wrap from './wrap.js';           // date line processing
import transform from './transform.js'; // coordinate transformation
import createTile from './tile.js';     // final simplified tile generation

const defaultOptions = {
    maxZoom: 14,            // max zoom to preserve detail on
    indexMaxZoom: 5,        // max zoom in the tile index
    indexMaxPoints: 100000, // max number of points per tile in the tile index
    tolerance: 3,           // simplification tolerance (higher means simpler)
    extent: 4096,           // tile extent
    buffer: 64,             // tile buffer on each side
    lineMetrics: false,     // whether to calculate line metrics
    promoteId: null,        // name of a feature property to be promoted to feature.id
    generateId: false,      // whether to generate feature ids. Cannot be used with promoteId
    debug: 0                // logging level (0, 1 or 2)
};

class GeoJSONVT {
    constructor(data, options) {
        options = this.options = extend(Object.create(defaultOptions), options);

        const debug = options.debug;

        if (debug) console.time('preprocess data');

        if (options.maxZoom < 0 || options.maxZoom > 24) throw new Error('maxZoom should be in the 0-24 range');
        if (options.promoteId && options.generateId) throw new Error('promoteId and generateId cannot be used together.');

        // projects and adds simplification info
        let features = convert(data, options);

        // tiles and tileCoords are part of the public API
        this.tiles = {};
        this.tileCoords = [];

        if (debug) {
            console.timeEnd('preprocess data');
            console.log('index: maxZoom: %d, maxPoints: %d', options.indexMaxZoom, options.indexMaxPoints);
            console.time('generate tiles');
            this.stats = {};
            this.total = 0;
        }

        // wraps features (ie extreme west and extreme east)
        features = wrap(features, options);

        // start slicing from the top tile down
        if (features.length) this.splitTile(features, 0, 0, 0);

        if (debug) {
            if (features.length) console.log('features: %d, points: %d', this.tiles[0].numFeatures, this.tiles[0].numPoints);
            console.timeEnd('generate tiles');
            console.log('tiles generated:', this.total, JSON.stringify(this.stats));
        }
    }

    // splits features from a parent tile to sub-tiles.
    // z, x, and y are the coordinates of the parent tile
    // cz, cx, and cy are the coordinates of the target tile
    //
    // If no target tile is specified, splitting stops when we reach the maximum
    // zoom or the number of points is low as specified in the options.
    splitTile(features, z, x, y, cz, cx, cy) {

        const stack = [features, z, x, y];
        const options = this.options;
        const debug = options.debug;

        // avoid recursion by using a processing queue
        while (stack.length) {
            y = stack.pop();
            x = stack.pop();
            z = stack.pop();
            features = stack.pop();

            const z2 = 1 << z;
            const id = toID(z, x, y);
            let tile = this.tiles[id];

            if (!tile) {
                if (debug > 1) console.time('creation');

                tile = this.tiles[id] = createTile(features, z, x, y, options);
                this.tileCoords.push({z, x, y});

                if (debug) {
                    if (debug > 1) {
                        console.log('tile z%d-%d-%d (features: %d, points: %d, simplified: %d)',
                            z, x, y, tile.numFeatures, tile.numPoints, tile.numSimplified);
                        console.timeEnd('creation');
                    }
                    const key = `z${  z}`;
                    this.stats[key] = (this.stats[key] || 0) + 1;
                    this.total++;
                }
            }

            // save reference to original geometry in tile so that we can drill down later if we stop now
            tile.source = features;

            // if it's the first-pass tiling
            if (cz == null) {
                // stop tiling if we reached max zoom, or if the tile is too simple
                if (z === options.indexMaxZoom || tile.numPoints <= options.indexMaxPoints) continue;
            // if a drilldown to a specific tile
            } else if (z === options.maxZoom || z === cz) {
                // stop tiling if we reached base zoom or our target tile zoom
                continue;
            } else if (cz != null) {
                // stop tiling if it's not an ancestor of the target tile
                const zoomSteps = cz - z;
                if (x !== cx >> zoomSteps || y !== cy >> zoomSteps) continue;
            }

            // if we slice further down, no need to keep source geometry
            tile.source = null;

            if (features.length === 0) continue;

            if (debug > 1) console.time('clipping');

            // values we'll use for clipping
            const k1 = 0.5 * options.buffer / options.extent;
            const k2 = 0.5 - k1;
            const k3 = 0.5 + k1;
            const k4 = 1 + k1;

            let tl = null;
            let bl = null;
            let tr = null;
            let br = null;

            let left  = clip(features, z2, x - k1, x + k3, 0, tile.minX, tile.maxX, options);
            let right = clip(features, z2, x + k2, x + k4, 0, tile.minX, tile.maxX, options);
            features = null;

            if (left) {
                tl = clip(left, z2, y - k1, y + k3, 1, tile.minY, tile.maxY, options);
                bl = clip(left, z2, y + k2, y + k4, 1, tile.minY, tile.maxY, options);
                left = null;
            }

            if (right) {
                tr = clip(right, z2, y - k1, y + k3, 1, tile.minY, tile.maxY, options);
                br = clip(right, z2, y + k2, y + k4, 1, tile.minY, tile.maxY, options);
                right = null;
            }

            if (debug > 1) console.timeEnd('clipping');

            stack.push(tl || [], z + 1, x * 2,     y * 2);
            stack.push(bl || [], z + 1, x * 2,     y * 2 + 1);
            stack.push(tr || [], z + 1, x * 2 + 1, y * 2);
            stack.push(br || [], z + 1, x * 2 + 1, y * 2 + 1);
        }
    }

    getTile(z, x, y) {
        z = +z;
        x = +x;
        y = +y;

        const options = this.options;
        const {extent, debug} = options;

        if (z < 0 || z > 24) return null;

        const z2 = 1 << z;
        x = (x + z2) & (z2 - 1); // wrap tile x coordinate

        const id = toID(z, x, y);
        if (this.tiles[id]) return transform(this.tiles[id], extent);

        if (debug > 1) console.log('drilling down to z%d-%d-%d', z, x, y);

        let z0 = z;
        let x0 = x;
        let y0 = y;
        let parent;

        while (!parent && z0 > 0) {
            z0--;
            x0 = x0 >> 1;
            y0 = y0 >> 1;
            parent = this.tiles[toID(z0, x0, y0)];
        }

        if (!parent || !parent.source) return null;

        // if we found a parent tile containing the original geometry, we can drill down from it
        if (debug > 1) {
            console.log('found parent tile z%d-%d-%d', z0, x0, y0);
            console.time('drilling down');
        }
        this.splitTile(parent.source, z0, x0, y0, z, x, y);
        if (debug > 1) console.timeEnd('drilling down');

        return this.tiles[id] ? transform(this.tiles[id], extent) : null;
    }
}

function toID(z, x, y) {
    return (((1 << z) * y + x) * 32) + z;
}

function extend(dest, src) {
    for (const i in src) dest[i] = src[i];
    return dest;
}

export default function geojsonvt(data, options) {
    return new GeoJSONVT(data, options);
}
<|endoftext|>


<commit_before>import EntitySchema from './EntitySchema';
import IterableSchema from './IterableSchema';
import UnionSchema from './UnionSchema';
import isEqual from 'lodash/isEqual';
import isObject from 'lodash/isObject';

function defaultAssignEntity(normalized, key, entity) {
  normalized[key] = entity;
}

function visitObject(obj, schema, bag, options, collectionKey) {
  const { assignEntity = defaultAssignEntity } = options;

  const defaults = schema && schema.getDefaults && schema.getDefaults();
  const schemaAssignEntity = schema && schema.getAssignEntity && schema.getAssignEntity();
  let normalized = isObject(defaults) ? { ...defaults } : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      const resolvedSchema = typeof schema[key] === 'function' ? schema[key].call(null, obj) : schema[key];
      const entity = visit(obj[key], resolvedSchema, bag, options, collectionKey);
      assignEntity.call(null, normalized, key, entity, obj, schema);
      if (schemaAssignEntity) {
        schemaAssignEntity.call(null, normalized, key, entity, obj, schema);
      }
    }
  }
  return normalized;
}

function defaultMapper(iterableSchema, itemSchema, bag, options) {
  return (obj, key) => visit(obj, itemSchema, bag, options, key);
}

function polymorphicMapper(iterableSchema, itemSchema, bag, options) {
  return (obj, key) => {
    const schemaKey = iterableSchema.getSchemaKey(obj);
    const result = visit(obj, itemSchema[schemaKey], bag, options, key);
    return { id: result, schema: schemaKey };
  };
}

function visitIterable(obj, iterableSchema, bag, options) {
  const itemSchema = iterableSchema.getItemSchema();
  const curriedItemMapper = defaultMapper(iterableSchema, itemSchema, bag, options);

  if (Array.isArray(obj)) {
    return obj.map(curriedItemMapper);
  } else {
    return Object.keys(obj).reduce(function (objMap, key) {
      objMap[key] = curriedItemMapper(obj[key], key);
      return objMap;
    }, {});
  }
}

function visitUnion(obj, unionSchema, bag, options) {
  const itemSchema = unionSchema.getItemSchema();
  return polymorphicMapper(unionSchema, itemSchema, bag, options)(obj);
}

function defaultMergeIntoEntity(entityA, entityB, entityKey) {
  for (let key in entityB) {
    if (!entityB.hasOwnProperty(key)) {
      continue;
    }

    if (!entityA.hasOwnProperty(key) || isEqual(entityA[key], entityB[key])) {
      entityA[key] = entityB[key];
      continue;
    }

    console.warn(
      'When merging two ' + entityKey + ', found unequal data in their "' + key + '" values. Using the earlier value.',
      entityA[key], entityB[key]
    );
  }
}

function visitEntity(entity, entitySchema, bag, options, collectionKey) {
  const { mergeIntoEntity = defaultMergeIntoEntity } = options;

  const entityKey = entitySchema.getKey();
  const id = entitySchema.getId(entity, collectionKey);

  if (!bag.hasOwnProperty(entityKey)) {
    bag[entityKey] = {};
  }

  if (!bag[entityKey].hasOwnProperty(id)) {
    bag[entityKey][id] = {};
  }

  let stored = bag[entityKey][id];
  let normalized = visitObject(entity, entitySchema, bag, options, collectionKey);
  mergeIntoEntity(stored, normalized, entityKey);

  return id;
}

function visit(obj, schema, bag, options, collectionKey) {
  if (!isObject(obj) || !isObject(schema)) {
    return obj;
  }

  if (schema instanceof EntitySchema) {
    return visitEntity(obj, schema, bag, options, collectionKey);
  } else if (schema instanceof IterableSchema) {
    return visitIterable(obj, schema, bag, options);
  } else if (schema instanceof UnionSchema) {
    return visitUnion(obj, schema, bag, options);
  } else {
    return visitObject(obj, schema, bag, options, collectionKey);
  }
}

function normalizeResult(result) {
  if (isObject(result) && isEqual(Object.keys(result), Object.keys(result).map(key => result[key]))) {
    return Object.keys(result);
  }
  return result;
}

export function arrayOf(schema, options) {
  return new IterableSchema(schema, options);
}

export function valuesOf(schema, options) {
  return new IterableSchema(schema, options);
}

export function unionOf(schema, options) {
  return new UnionSchema(schema, options);
}

export { EntitySchema as Schema };

export function normalize(obj, schema, options = {}) {
  if (!isObject(obj)) {
    throw new Error('Normalize accepts an object or an array as its input.');
  }

  if (!isObject(schema) || Array.isArray(schema)) {
    throw new Error('Normalize accepts an object for schema.');
  }

  let bag = {};
  let result = visit(obj, schema, bag, options);

  return {
    entities: bag,
    result: normalizeResult(result)
  };
}
<commit_msg>Add custom stuff<commit_after>import EntitySchema from './EntitySchema';
import IterableSchema from './IterableSchema';
import UnionSchema from './UnionSchema';
import isEqual from 'lodash/isEqual';
import isObject from 'lodash/isObject';
import isString from 'lodash/isString';

function defaultAssignEntity(normalized, key, entity) {
  normalized[key] = entity;
}

function visitObject(obj, schema, bag, options, collectionKey) {
  const { assignEntity = defaultAssignEntity } = options;

  const defaults = schema && schema.getDefaults && schema.getDefaults();
  const schemaAssignEntity = schema && schema.getAssignEntity && schema.getAssignEntity();
  let normalized = isObject(defaults) ? { ...defaults } : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      const resolvedSchema = (isObject(schema[key]) && !!schema[key].schema && schema[key].schema)
        || schema[key];
      const entity = visit(obj[key], resolvedSchema, bag, options, collectionKey);
      const resolvedKey = (isString(schema[key].resolve) ? schema[key].resolve : key);
      assignEntity.call(null, normalized, resolvedKey, entity, obj, schema);
      if (schemaAssignEntity) {
        schemaAssignEntity.call(null, normalized, key, entity, obj, schema);
      }
    }
  }
  return normalized;
}

function defaultMapper(iterableSchema, itemSchema, bag, options) {
  return (obj, key) => visit(obj, itemSchema, bag, options, key);
}

function polymorphicMapper(iterableSchema, itemSchema, bag, options) {
  return (obj, key) => {
    const schemaKey = iterableSchema.getSchemaKey(obj);
    const result = visit(obj, itemSchema[schemaKey], bag, options, key);
    return { id: result, schema: schemaKey };
  };
}

function visitIterable(obj, iterableSchema, bag, options) {
  const itemSchema = iterableSchema.getItemSchema();
  const curriedItemMapper = defaultMapper(iterableSchema, itemSchema, bag, options);

  if (Array.isArray(obj)) {
    return obj.map(curriedItemMapper);
  } else {
    return Object.keys(obj).reduce(function (objMap, key) {
      objMap[key] = curriedItemMapper(obj[key], key);
      return objMap;
    }, {});
  }
}

function visitUnion(obj, unionSchema, bag, options) {
  const itemSchema = unionSchema.getItemSchema();
  return polymorphicMapper(unionSchema, itemSchema, bag, options)(obj);
}

function defaultMergeIntoEntity(entityA, entityB, entityKey) {
  for (let key in entityB) {
    if (!entityB.hasOwnProperty(key)) {
      continue;
    }

    if (!entityA.hasOwnProperty(key) || isEqual(entityA[key], entityB[key])) {
      entityA[key] = entityB[key];
      continue;
    }

    console.warn(
      'When merging two ' + entityKey + ', found unequal data in their "' + key + '" values. Using the earlier value.',
      entityA[key], entityB[key]
    );
  }
}

function visitEntity(entity, entitySchema, bag, options, collectionKey) {
  const { mergeIntoEntity = defaultMergeIntoEntity } = options;

  const entityKey = entitySchema.getKey();
  const id = entitySchema.getId(entity, collectionKey);

  if (!bag.hasOwnProperty(entityKey)) {
    bag[entityKey] = {};
  }

  if (!bag[entityKey].hasOwnProperty(id)) {
    bag[entityKey][id] = {};
  }

  let stored = bag[entityKey][id];
  let normalized = visitObject(entity, entitySchema, bag, options, collectionKey);
  mergeIntoEntity(stored, normalized, entityKey);

  return id;
}

function visit(obj, schema, bag, options, collectionKey) {
  if (!isObject(obj) || !isObject(schema)) {
    return obj;
  }

  if (schema instanceof EntitySchema) {
    return visitEntity(obj, schema, bag, options, collectionKey);
  } else if (schema instanceof IterableSchema) {
    return visitIterable(obj, schema, bag, options);
  } else if (schema instanceof UnionSchema) {
    return visitUnion(obj, schema, bag, options);
  } else {
    return visitObject(obj, schema, bag, options, collectionKey);
  }
}

function normalizeResult(result) {
  if (isObject(result) && isEqual(Object.keys(result), Object.keys(result).map(key => result[key]))) {
    return Object.keys(result);
  }
  return result;
}

export function arrayOf(schema, options) {
  return new IterableSchema(schema, options);
}

export function valuesOf(schema, options) {
  return new IterableSchema(schema, options);
}

export function unionOf(schema, options) {
  return new UnionSchema(schema, options);
}

export { EntitySchema as Schema };

export function normalize(obj, schema, options = {}) {
  if (!isObject(obj)) {
    throw new Error('Normalize accepts an object or an array as its input.');
  }

  if (!isObject(schema) || Array.isArray(schema)) {
    throw new Error('Normalize accepts an object for schema.');
  }

  let bag = {};
  let result = visit(obj, schema, bag, options);

  return {
    entities: bag,
    result: normalizeResult(result)
  };
}
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';

import App from './components/app';
import formA from './components/form_A';
import formB from './components/form_B';
import formC from './components/form_C';
import formD from './components/form_D';

import reducers from './reducers';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';

import _  from 'lodash';
import * as StateChart from '../lib/statechart'
import Interpreter from 'js-interpreter';
import ControlFlow from './control_flow.json';

const createStoreWithMiddleware = applyMiddleware()(createStore);

function addGlobals(interpreter, scope) {

  interpreter['FLOW_DESC'] = ControlFlow;

  var wrapper = function(form_id, input_id) {
    var form = document.getElementById(form_id);
    if (form) {
      return interpreter.createPrimitive(form.elements[input_id].value);
    } else {
      return null;
    }
  };
  interpreter.setProperty(scope, 'getFormData', interpreter.createNativeFunction(wrapper));

  var wrapper = function(target, value) {
    return interpreter.setProperty(scope, target, value);
  };
  interpreter.setProperty(scope, 'setGlobal', interpreter.createNativeFunction(wrapper));

  var wrapper = function(obj, name) {
    // return this.properties[obj].properties[name].data
    // var parent_property = interpreter.getProperty(this, obj);

    var parent_property = interpreter.getValueFromScope(obj);

    if (arguments.length === 1) {
      return parent_property;
    } else {
      var child_property = interpreter.getProperty(parent_property, name);
      return child_property.data;
    }
  };
  interpreter.setProperty(scope, 'getGlobal', interpreter.createNativeFunction(wrapper));

  var wrapper = function(id) {
    return interpreter.createPrimitive(document.getElementById(id).value);
  };
  interpreter.setProperty(scope, 'getLocal', interpreter.createNativeFunction(wrapper));

  var wrapper = function(msg) {
    console.log(msg.data);
  };
  interpreter.setProperty(scope, 'log', interpreter.createNativeFunction(wrapper));

  // var wrapper = function() {

  //   var v = this.properties['vparams'].properties['name'].data;

  //   var age = interpreter.getGlobal('vparams', 'age');
  //   console.log(age);

  //   return true;
  // };
  // interpreter.setProperty(scope, 'canProceed', interpreter.createNativeFunction(wrapper));

}

window.stateMachine = _.extend({

    initialState: "formA",

    setVar: function(formula, data){
      return "setGlobal('initial_form', " + data + "); var initialForm = initial_form; log('initialFrom data: ' + initialForm.agreed); setGlobal('result', " + formula + ")";
    },

    states: {
        "formA": {
            "entry": function() {

                const initial_form = JSON.stringify({agreed: true});
                const code = this.setVar(ControlFlow.formA.canLand, initial_form);
                const myInterpreter = new Interpreter(code, addGlobals);

                myInterpreter.run();
                const result = myInterpreter.getValue('result');
                if (result) {
                  browserHistory.push('/formA');
                }
                console.log('result: ' + result);
            },
            "exit": function() {
              console.log("before exiting");
            },
            "next":  {
              // target: "formC",
              action: function(params) {
                let s = this.state("formC");
                if (params.age >= 18) {
                } else {
                  s = this.state('formB');
                }
                this.transition(s);
              },
              guard: function(params) {
                // console.log("data validation goes here");

                var json = {
                  canShow: "getFormValue('personal_info', 'age') > 18"
                }

                var string_params = JSON.stringify(params);
                console.log("json string: ", string_params);
                var input_string = "var age = getFormData('personal_info', 'age'); if (age > 18) { log('greater than 18'); } else { log('less than 18');}; var test = function(msg) { log('Got ' + msg)}; setGlobal('vparams', " + string_params + ");  var hash = vparams; log('--here start---'); log(hash.age); log('--here end---'); test(vparams.name); log('getGlobal result: ' + getGlobal('vparams', 'name')); log(vparams.age); log(vparams.name); //log('canProcess result: ' + canProceed());";

                var myInterpreter = new Interpreter(input_string, addGlobals);
                myInterpreter.run()

                if (params.work_or_student  && params.name && params.age) {
                  return true
                } else {
                  console.log("validation error");
                  return false;
                }
              }
            }
        },
        "formB": {
            "entry": function(params) {
              // console.log("Came to formB");
              // console.log(params);
              browserHistory.push('/formB');
            },
            "next":  { target: "formC"  },
            "previous": {
              target: "formA"
            }
        },
        "formC": {
            "entry": function(params) {
              // console.log("Came to formC");
              // console.log(params);
              browserHistory.push('/formC');
            },
            "next":  { target: "formD"  },
            "previous": { target: "formB" }
        }

    },
    valid: function() {
      return false;
    }

}, StateChart);

ReactDOM.render((
  <Router history={browserHistory}>
    <Route path="/" component={App} />
    <Route path="/formA" component={formA} />
    <Route path="/formB" component={formB} />
    <Route path="/formC" component={formC} />
    <Route path="/formD" component={formD} />
  </Router>)
  , document.querySelector('.container'));

window.stateMachine.run();

<commit_msg>WIP making generic function to accept params from state chart<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';

import App from './components/app';
import formA from './components/form_A';
import formB from './components/form_B';
import formC from './components/form_C';
import formD from './components/form_D';

import reducers from './reducers';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';

import _  from 'lodash';
import * as StateChart from '../lib/statechart'
import Interpreter from 'js-interpreter';
import ControlFlow from './control_flow.json';

const createStoreWithMiddleware = applyMiddleware()(createStore);

function addGlobals(interpreter, scope) {

  interpreter['FLOW_DESC'] = ControlFlow;

  var wrapper = function(form_id, input_id) {
    var form = document.getElementById(form_id);
    if (form) {
      return interpreter.createPrimitive(form.elements[input_id].value);
    } else {
      return null;
    }
  };
  interpreter.setProperty(scope, 'getFormData', interpreter.createNativeFunction(wrapper));

  var wrapper = function(target, value) {
    return interpreter.setProperty(scope, target, value);
  };
  interpreter.setProperty(scope, 'setGlobal', interpreter.createNativeFunction(wrapper));

  var wrapper = function(obj, name) {
    // return this.properties[obj].properties[name].data
    // var parent_property = interpreter.getProperty(this, obj);

    var parent_property = interpreter.getValueFromScope(obj);

    if (arguments.length === 1) {
      return parent_property;
    } else {
      var child_property = interpreter.getProperty(parent_property, name);
      return child_property.data;
    }
  };
  interpreter.setProperty(scope, 'getGlobal', interpreter.createNativeFunction(wrapper));

  var wrapper = function(id) {
    return interpreter.createPrimitive(document.getElementById(id).value);
  };
  interpreter.setProperty(scope, 'getLocal', interpreter.createNativeFunction(wrapper));

  var wrapper = function(msg) {
    console.log(msg.data);
  };
  interpreter.setProperty(scope, 'log', interpreter.createNativeFunction(wrapper));

  // var wrapper = function() {

  //   var v = this.properties['vparams'].properties['name'].data;

  //   var age = interpreter.getGlobal('vparams', 'age');
  //   console.log(age);

  //   return true;
  // };
  // interpreter.setProperty(scope, 'canProceed', interpreter.createNativeFunction(wrapper));

}

var dataA = {
  agreed: true
};

window.stateMachine = _.extend({

    initialState: "formA",

    config: ControlFlow,

    setConfig: function(json) {
      this.config = json;
    },

    setVar: function(formula, data){
      return "setGlobal('initial_form', " + data + "); var initialForm = initial_form; log('initialFrom data: ' + initialForm.agreed); setGlobal('result', " + formula + ")";
    },

    states: {
        "formA": {
            "entry": function(params) {

                var current_state = this.currentState().name;
                const initial_form = JSON.stringify(dataA);
                const code = this.setVar(ControlFlow.formA.canLand, initial_form);
                const myInterpreter = new Interpreter(code, addGlobals);

                myInterpreter.run();
                const result = myInterpreter.getValue('result');
                if (result) {
                  browserHistory.push('/formA');
                }
                console.log('result: ' + result);
            },
            "exit": function() {
              console.log("before exiting");
            },
            "next":  {
              // target: "formC",
              action: function(params) {
                let s = this.state("formC");
                if (params.age >= 18) {
                } else {
                  s = this.state('formB');
                }
                this.transition(s);
              },
              guard: function(params) {
                // console.log("data validation goes here");

                var json = {
                  canShow: "getFormValue('personal_info', 'age') > 18"
                }

                var string_params = JSON.stringify(params);
                console.log("json string: ", string_params);
                var input_string = "var age = getFormData('personal_info', 'age'); if (age > 18) { log('greater than 18'); } else { log('less than 18');}; var test = function(msg) { log('Got ' + msg)}; setGlobal('vparams', " + string_params + ");  var hash = vparams; log('--here start---'); log(hash.age); log('--here end---'); test(vparams.name); log('getGlobal result: ' + getGlobal('vparams', 'name')); log(vparams.age); log(vparams.name); //log('canProcess result: ' + canProceed());";

                var myInterpreter = new Interpreter(input_string, addGlobals);
                myInterpreter.run()

                if (params.work_or_student  && params.name && params.age) {
                  return true
                } else {
                  console.log("validation error");
                  return false;
                }
              }
            }
        },
        "formB": {
            "entry": function(params) {
              // console.log("Came to formB");
              // console.log(params);
              browserHistory.push('/formB');
            },
            "next":  { target: "formC"  },
            "previous": {
              target: "formA"
            }
        },
        "formC": {
            "entry": function(params) {
              // console.log("Came to formC");
              // console.log(params);
              browserHistory.push('/formC');
            },
            "next":  { target: "formD"  },
            "previous": { target: "formB" }
        }

    },
    valid: function() {
      return false;
    }

}, StateChart);

ReactDOM.render((
  <Router history={browserHistory}>
    <Route path="/" component={App} />
    <Route path="/formA" component={formA} />
    <Route path="/formB" component={formB} />
    <Route path="/formC" component={formC} />
    <Route path="/formD" component={formD} />
  </Router>)
  , document.querySelector('.container'));

window.stateMachine.run({foo: 'bar'});

<|endoftext|>