<commit_before>import { PLAN_SERVICES, PLAN_TYPES } from 'proton-shared/lib/constants';
import { hasBit } from 'proton-shared/lib/helpers/bitset';
import { c, msgid } from 'ttag';
import { isEquivalent } from 'proton-shared/lib/helpers/object';

const { PLAN, ADDON } = PLAN_TYPES;
const { MAIL, VPN } = PLAN_SERVICES;

const I18N = {
    included: c('Option').t`included`,
    address(value) {
        return c('Option').ngettext(msgid`1 address`, `${value} addresses`, value);
    },
    space(value) {
        return c('Option').ngettext(msgid`1 GB storage`, `${value} GB storage`, value);
    },
    domain(value) {
        return c('Option').ngettext(msgid`1 custom domain`, `${value} custom domains`, value);
    },
    member(value) {
        return c('Option').ngettext(msgid`1 user`, `${value} users`, value);
    },
    vpn(value) {
        return c('Option').ngettext(msgid`1 VPN connection`, `${value} VPN connections`, value);
    }
};

/**
 * Build plansMap from current subscription and user demand
 * @param {Object} plansMap user demand
 * Possible entries for plansMap
 * {}
 * { vpnplus: 1 }
 * { vpnbasic: 1 }
 * { plus: 1 }, { vpnplus: 1, plus: 1 }
 * { professional: 1 }, { vpnplus: 1, professional: 1 }
 * { visionary: 1 }
 * @param {Array} subscription.Plans
 * @returns {Object} plansMap
 */
export const mergePlansMap = (plansMap = {}, { Plans = [] }) => {
    // Free user subscribing
    if (!Plans.length) {
        return plansMap;
    }

    // Has an existing subscription

    // Visionary includes mail and vpn service with no addons
    if (plansMap.visionary) {
        return plansMap;
    }

    const currentPlansMap = toPlanNames(Plans);

    if (isEquivalent(plansMap, { vpnplus: 1 }) || isEquivalent(plansMap, { vpnbasic: 1 })) {
        return {
            ...plansMap,
            plus: currentPlansMap.plus,
            ['1vpn']: currentPlansMap['1vpn']
        };
    }

    if (isEquivalent(plansMap, { vpnplus: 1, plus: 1 })) {
        return {
            ...plansMap,
            ['1domain']: currentPlansMap['1domain'],
            ['5address']: currentPlansMap['5address'],
            ['1gb']: currentPlansMap['1gb'],
            ['1vpn']: currentPlansMap['1vpn']
        };
    }

    if (isEquivalent(plansMap, { vpnplus: 1, professional: 1 })) {
        return {
            ...plansMap,
            ['1domain']: currentPlansMap['1domain'] > 1 ? currentPlansMap['1domain'] : undefined, // pro starts with 2 custom domain
            ['1member']: currentPlansMap['1member'],
            ['1vpn']: currentPlansMap['1vpn']
        };
    }

    return plansMap;
};

export const getTextOption = (type, value, index) => {
    return `${I18N[type](value)} ${index ? '' : `(${I18N.included})`}`.trim();
};

/**
 * Calculate total for a specific subscription configuration
 * @param {Object} param.plansMap
 * @param {Number} param.cycle
 * @param {Array} param.plans
 * @param {Number} param.services optional
 * @returns {Number} amount
 */
export const getSubTotal = ({ plansMap, cycle, plans, services }) => {
    return Object.entries(plansMap).reduce((acc, [planName, quantity]) => {
        if (quantity) {
            const { Pricing = {} } =
                plans.find(({ Name, Services }) => {
                    if (services) {
                        return Name === planName && hasBit(Services, services);
                    }
                    return Name === planName;
                }) || {};
            const amount = Pricing[cycle] || 0;
            return acc + quantity * amount;
        }
        return acc;
    }, 0);
};

/**
 * Convert subscription plans to PlanIDs format required by API requests
 * @param {Array} plans coming from Subscription API
 * @returns {Object}
 */
export const toPlanMap = (plans = [], key = 'ID') => {
    return plans.reduce((acc, plan) => {
        acc[plan[key]] = acc[plan[key]] || 0;
        acc[plan[key]] += 1;
        return acc;
    }, Object.create(null));
};

export const toPlanIDs = toPlanMap;
export const toPlanNames = (plans = []) => toPlanMap(plans, 'Name');

/**
 * Merge addon to addition parameters
 * @param {Number} oldAddon.Quantity
 * @param {Number} oldAddon.Amount
 * @param {Number} oldAddon.MaxDomains
 * @param {Number} oldAddon.MaxAddresses
 * @param {Number} oldAddon.MaxSpace
 * @param {Number} oldAddon.MaxMembers
 * @param {Number} oldAddon.MaxVPN
 * @param {Object} addon to merge with
 * @returns {Object} new addon
 */
const mergeAddons = (
    { Quantity = 0, Amount = 0, MaxDomains = 0, MaxAddresses = 0, MaxSpace = 0, MaxMembers = 0, MaxVPN = 0 } = {},
    addon
) => ({
    ...addon,
    MaxAddresses: MaxAddresses + addon.MaxAddresses,
    MaxSpace: MaxSpace + addon.MaxSpace,
    MaxMembers: MaxMembers + addon.MaxMembers,
    MaxVPN: MaxVPN + addon.MaxVPN,
    MaxDomains: MaxDomains + addon.MaxDomains,
    Quantity: Quantity + addon.Quantity,
    Amount: Amount + addon.Amount
});

/**
 * Format plans to returns essential structure
 * @param {Array} plans coming from Subscription API
 * @returns {Object} { mailPlan, vpnPlan, addressAddon, vpnAddon, domainAddon, memberAddon, spaceAddon }
 */
export const formatPlans = (plans = []) => {
    return plans.reduce((acc, plan) => {
        if (plan.Type === PLAN) {
            // visionary is a special case because it contains mail and vpn services
            // we consider it as a mail plan
            if (plan.Name === 'visionary') {
                acc.mailPlan = plan;
                return acc;
            }
            if (hasBit(plan.Services, MAIL)) {
                acc.mailPlan = plan;
            }
            if (hasBit(plan.Services, VPN)) {
                acc.vpnPlan = plan;
            }
            return acc;
        }

        if (plan.Type === ADDON) {
            if (plan.Name === '1domain') {
                acc.domainAddon = mergeAddons(acc.domainAddon, plan);
            }
            if (plan.Name === '1member') {
                acc.memberAddon = mergeAddons(acc.memberAddon, plan);
            }
            if (plan.Name === '1vpn') {
                acc.vpnAddon = mergeAddons(acc.vpnAddon, plan);
            }
            if (plan.Name === '5address') {
                acc.addressAddon = mergeAddons(acc.addressAddon, plan);
            }
            if (plan.Name === '1gb') {
                acc.spaceAddon = mergeAddons(acc.spaceAddon, plan);
            }
            return acc;
        }

        return acc;
    }, {});
};

/**
 * Helper to find plans from Subscription.Plans or Plans
 * @param {Array} plans from Subscription.Plans or Plans
 * @param {String} params.planName examples: 'plus', 'vpnplus', 'visionary', '5address', '1member'
 * @param {String} params.id plan ID
 * @param {Number} params.type default: plan
 * @returns {Object} plan Object
 */
export const getPlan = (plans = [], { type = PLAN, name = '', id = '' }) => {
    if (!plans.length) {
        throw new Error('plans not defined');
    }

    if (!name && !id) {
        throw new Error('name or id not defined to get plan');
    }

    if (name) {
        return plans.find(({ Type, Name }) => Type === type && Name === name);
    }

    return plans.find(({ Type, ID }) => Type === type && ID === id);
};

/**
 * Alias to getPlan with Type = ADDON
 * @param {Array} plans
 * @param {Object} params
 */
export const getAddon = (plans, params) => getPlan(plans, { ...params, type: ADDON });

/**
 * Prepare params for /api/subscription/check request
 * @param {Array} param.plans
 * @param {Object} param.plansMap
 * @param {String} param.coupon
 * @param {String} param.currency
 * @param {Number} param.cycle
 * @returns {Object} parameters
 */
export const getCheckParams = ({
    plans = [],
    plansMap = {},
    coupon: CouponCode,
    currency: Currency,
    cycle: Cycle,
    ...rest
}) => {
    if (!plans.length) {
        throw new Error('plans not defined');
    }

    const PlanIDs = Object.entries(plansMap).reduce((acc, [planName, quantity]) => {
        if (quantity) {
            const { ID } = plans.find((plan) => plan.Name === planName);
            acc[ID] = quantity;
        }
        return acc;
    }, Object.create(null));

    return {
        PlanIDs,
        CouponCode,
        Currency,
        Cycle,
        ...rest
    };
};

/**
 * Check if a subscription is eligible to BUNDLE coupon
 * @param {Array} Subscription.Plans
 * @param {String} Subscription.CouponCode
 * @returns {Boolean} is eligible to BUNDLE
 */
export const isBundleEligible = ({ Plans, CouponCode } = {}) => {
    if (CouponCode) {
        return false;
    }

    const { plus, professional, visionary, vpnplus, vpnbasic } = toPlanNames(Plans);

    if (visionary) {
        return false;
    }

    const mailPlan = plus || professional;
    const vpnPlan = vpnplus || vpnbasic;

    return (mailPlan && !vpnPlan) || (!mailPlan && vpnplus);
};
<commit_msg>Compare plans map with plans (and not add-on)<commit_after>import { PLAN_SERVICES, PLAN_TYPES } from 'proton-shared/lib/constants';
import { hasBit } from 'proton-shared/lib/helpers/bitset';
import { c, msgid } from 'ttag';
import { isEquivalent, pick } from 'proton-shared/lib/helpers/object';

const { PLAN, ADDON } = PLAN_TYPES;
const { MAIL, VPN } = PLAN_SERVICES;

const I18N = {
    included: c('Option').t`included`,
    address(value) {
        return c('Option').ngettext(msgid`1 address`, `${value} addresses`, value);
    },
    space(value) {
        return c('Option').ngettext(msgid`1 GB storage`, `${value} GB storage`, value);
    },
    domain(value) {
        return c('Option').ngettext(msgid`1 custom domain`, `${value} custom domains`, value);
    },
    member(value) {
        return c('Option').ngettext(msgid`1 user`, `${value} users`, value);
    },
    vpn(value) {
        return c('Option').ngettext(msgid`1 VPN connection`, `${value} VPN connections`, value);
    }
};

/**
 * Check if a plans map contains at least b plans map
 * @param {Object} a plans map
 * @param {Object} b plans map
 * @returns {Boolean}
 */
const containsSamePlans = (a, b) => isEquivalent(pick(a, Object.keys(b)), b);

/**
 * Build plansMap from current subscription and user demand
 * @param {Object} plansMap user demand
 * Possible entries for plansMap
 * {}
 * { vpnplus: 1 }
 * { vpnbasic: 1 }
 * { plus: 1 }, { vpnplus: 1, plus: 1 }
 * { professional: 1 }, { vpnplus: 1, professional: 1 }
 * { visionary: 1 }
 * @param {Array} subscription.Plans
 * @returns {Object} plansMap
 */
export const mergePlansMap = (plansMap = {}, { Plans = [] }) => {
    // Free user subscribing
    if (!Plans.length) {
        return plansMap;
    }

    // Has an existing subscription

    // Visionary includes mail and vpn service with no addons
    if (plansMap.visionary) {
        return plansMap;
    }

    const currentPlansMap = toPlanNames(Plans);

    if (containsSamePlans(plansMap, { vpnplus: 1 }) || containsSamePlans(plansMap, { vpnbasic: 1 })) {
        return {
            ...plansMap,
            plus: currentPlansMap.plus,
            ['1vpn']: currentPlansMap['1vpn']
        };
    }

    if (containsSamePlans(plansMap, { vpnplus: 1, plus: 1 })) {
        return {
            ...plansMap,
            ['1domain']: currentPlansMap['1domain'],
            ['5address']: currentPlansMap['5address'],
            ['1gb']: currentPlansMap['1gb'],
            ['1vpn']: currentPlansMap['1vpn']
        };
    }

    if (containsSamePlans(plansMap, { vpnplus: 1, professional: 1 })) {
        return {
            ...plansMap,
            ['1domain']: currentPlansMap['1domain'] > 1 ? currentPlansMap['1domain'] : undefined, // pro starts with 2 custom domain
            ['1member']: currentPlansMap['1member'],
            ['1vpn']: currentPlansMap['1vpn']
        };
    }

    return plansMap;
};

export const getTextOption = (type, value, index) => {
    return `${I18N[type](value)} ${index ? '' : `(${I18N.included})`}`.trim();
};

/**
 * Calculate total for a specific subscription configuration
 * @param {Object} param.plansMap
 * @param {Number} param.cycle
 * @param {Array} param.plans
 * @param {Number} param.services optional
 * @returns {Number} amount
 */
export const getSubTotal = ({ plansMap, cycle, plans, services }) => {
    return Object.entries(plansMap).reduce((acc, [planName, quantity]) => {
        if (quantity) {
            const { Pricing = {} } =
                plans.find(({ Name, Services }) => {
                    if (services) {
                        return Name === planName && hasBit(Services, services);
                    }
                    return Name === planName;
                }) || {};
            const amount = Pricing[cycle] || 0;
            return acc + quantity * amount;
        }
        return acc;
    }, 0);
};

/**
 * Convert subscription plans to PlanIDs format required by API requests
 * @param {Array} plans coming from Subscription API
 * @returns {Object}
 */
export const toPlanMap = (plans = [], key = 'ID') => {
    return plans.reduce((acc, plan) => {
        acc[plan[key]] = acc[plan[key]] || 0;
        acc[plan[key]] += 1;
        return acc;
    }, Object.create(null));
};

export const toPlanIDs = toPlanMap;
export const toPlanNames = (plans = []) => toPlanMap(plans, 'Name');

/**
 * Merge addon to addition parameters
 * @param {Number} oldAddon.Quantity
 * @param {Number} oldAddon.Amount
 * @param {Number} oldAddon.MaxDomains
 * @param {Number} oldAddon.MaxAddresses
 * @param {Number} oldAddon.MaxSpace
 * @param {Number} oldAddon.MaxMembers
 * @param {Number} oldAddon.MaxVPN
 * @param {Object} addon to merge with
 * @returns {Object} new addon
 */
const mergeAddons = (
    { Quantity = 0, Amount = 0, MaxDomains = 0, MaxAddresses = 0, MaxSpace = 0, MaxMembers = 0, MaxVPN = 0 } = {},
    addon
) => ({
    ...addon,
    MaxAddresses: MaxAddresses + addon.MaxAddresses,
    MaxSpace: MaxSpace + addon.MaxSpace,
    MaxMembers: MaxMembers + addon.MaxMembers,
    MaxVPN: MaxVPN + addon.MaxVPN,
    MaxDomains: MaxDomains + addon.MaxDomains,
    Quantity: Quantity + addon.Quantity,
    Amount: Amount + addon.Amount
});

/**
 * Format plans to returns essential structure
 * @param {Array} plans coming from Subscription API
 * @returns {Object} { mailPlan, vpnPlan, addressAddon, vpnAddon, domainAddon, memberAddon, spaceAddon }
 */
export const formatPlans = (plans = []) => {
    return plans.reduce((acc, plan) => {
        if (plan.Type === PLAN) {
            // visionary is a special case because it contains mail and vpn services
            // we consider it as a mail plan
            if (plan.Name === 'visionary') {
                acc.mailPlan = plan;
                return acc;
            }
            if (hasBit(plan.Services, MAIL)) {
                acc.mailPlan = plan;
            }
            if (hasBit(plan.Services, VPN)) {
                acc.vpnPlan = plan;
            }
            return acc;
        }

        if (plan.Type === ADDON) {
            if (plan.Name === '1domain') {
                acc.domainAddon = mergeAddons(acc.domainAddon, plan);
            }
            if (plan.Name === '1member') {
                acc.memberAddon = mergeAddons(acc.memberAddon, plan);
            }
            if (plan.Name === '1vpn') {
                acc.vpnAddon = mergeAddons(acc.vpnAddon, plan);
            }
            if (plan.Name === '5address') {
                acc.addressAddon = mergeAddons(acc.addressAddon, plan);
            }
            if (plan.Name === '1gb') {
                acc.spaceAddon = mergeAddons(acc.spaceAddon, plan);
            }
            return acc;
        }

        return acc;
    }, {});
};

/**
 * Helper to find plans from Subscription.Plans or Plans
 * @param {Array} plans from Subscription.Plans or Plans
 * @param {String} params.planName examples: 'plus', 'vpnplus', 'visionary', '5address', '1member'
 * @param {String} params.id plan ID
 * @param {Number} params.type default: plan
 * @returns {Object} plan Object
 */
export const getPlan = (plans = [], { type = PLAN, name = '', id = '' }) => {
    if (!plans.length) {
        throw new Error('plans not defined');
    }

    if (!name && !id) {
        throw new Error('name or id not defined to get plan');
    }

    if (name) {
        return plans.find(({ Type, Name }) => Type === type && Name === name);
    }

    return plans.find(({ Type, ID }) => Type === type && ID === id);
};

/**
 * Alias to getPlan with Type = ADDON
 * @param {Array} plans
 * @param {Object} params
 */
export const getAddon = (plans, params) => getPlan(plans, { ...params, type: ADDON });

/**
 * Prepare params for /api/subscription/check request
 * @param {Array} param.plans
 * @param {Object} param.plansMap
 * @param {String} param.coupon
 * @param {String} param.currency
 * @param {Number} param.cycle
 * @returns {Object} parameters
 */
export const getCheckParams = ({
    plans = [],
    plansMap = {},
    coupon: CouponCode,
    currency: Currency,
    cycle: Cycle,
    ...rest
}) => {
    if (!plans.length) {
        throw new Error('plans not defined');
    }

    const PlanIDs = Object.entries(plansMap).reduce((acc, [planName, quantity]) => {
        if (quantity) {
            const { ID } = plans.find((plan) => plan.Name === planName);
            acc[ID] = quantity;
        }
        return acc;
    }, Object.create(null));

    return {
        PlanIDs,
        CouponCode,
        Currency,
        Cycle,
        ...rest
    };
};

/**
 * Check if a subscription is eligible to BUNDLE coupon
 * @param {Array} Subscription.Plans
 * @param {String} Subscription.CouponCode
 * @returns {Boolean} is eligible to BUNDLE
 */
export const isBundleEligible = ({ Plans, CouponCode } = {}) => {
    if (CouponCode) {
        return false;
    }

    const { plus, professional, visionary, vpnplus, vpnbasic } = toPlanNames(Plans);

    if (visionary) {
        return false;
    }

    const mailPlan = plus || professional;
    const vpnPlan = vpnplus || vpnbasic;

    return (mailPlan && !vpnPlan) || (!mailPlan && vpnplus);
};
<|endoftext|>


<commit_before>import React from 'react'
import {intlShape} from 'react-intl'
import classNames from 'classnames'
import {Button} from 'tocco-ui'
import SearchField from './SearchField'

const SearchForm = props => {
  const handleResetClick = () => {
    props.reset()
  }

  const handleSubmit = e => {
    e.preventDefault()
    props.setSearchInput()
  }

  const msg = id => (props.intl.formatMessage({id}))

  const renderField = name => (
    !isHidden(name) && (props.disableSimpleSearch || props.simpleSearchFields.includes(name)
    || props.showExtendedSearchForm)
  )

  const toggleExtendedSearchForm = () => {
    props.setShowExtendedSearchForm(!props.showExtendedSearchForm)
  }

  const isHidden = name => {
    const field = props.preselectedSearchFields.find(f => f.id === name)
    return field !== undefined && field.hidden
  }

  return (
    <form onSubmit={handleSubmit}>
      {
      props.searchFormDefinition.map((definition, idx) => (
        renderField(definition.name)
        &&

          {definition.label}

            <SearchField
              value={props.searchInputs ? props.searchInputs[definition.name] : undefined}
              type={definition.type}
              name={definition.name}
              relationEntities={props.relationEntities}
              entityModel={props.entityModel}
              setSearchInput={props.setSearchInput}
            />

        ))
      }
      {!props.disableSimpleSearch
        &&

          <Button
            type="button"
            label={msg('client.entity-browser.extendedSearch')}
            onClick={toggleExtendedSearchForm}
          />

      }

            <Button
              type="submit"
              icon="glyphicon-search"
              label={msg('client.entity-browser.search')}
              primary
            />
            <Button
              type="button"
              icon="glyphicon-repeat"
              label={msg('client.entity-browser.reset')}
              onClick={handleResetClick}
            />

    </form>
  )
}

SearchForm.propTypes = {
  intl: intlShape.isRequired,
  entityModel: React.PropTypes.objectOf(
    React.PropTypes.shape({
      type: React.PropTypes.string.isRequired,
      targetEntity: React.PropTypes.string
    })
  ).isRequired,
  searchFormDefinition: React.PropTypes.arrayOf(
    React.PropTypes.shape({
      name: React.PropTypes.string.isRequired,
      type: React.PropTypes.string.isRequired,
      displayType: React.PropTypes.string.isRequired,
      label: React.PropTypes.string.isRequired,
      useLabel: React.PropTypes.string.isRequired
    })
  ).isRequired,
  setSearchInput: React.PropTypes.func.isRequired,
  relationEntities: React.PropTypes.objectOf(
    React.PropTypes.arrayOf(
      React.PropTypes.shape({
        displayName: React.PropTypes.string.isRequired,
        value: React.PropTypes.string.isRequired
      })
    )).isRequired,
  searchInputs: React.PropTypes.objectOf(React.PropTypes.any),
  reset: React.PropTypes.func.isRequired,
  disableSimpleSearch: React.PropTypes.bool,
  simpleSearchFields: React.PropTypes.arrayOf(
    React.PropTypes.string
  ),
  showExtendedSearchForm: React.PropTypes.bool,
  setShowExtendedSearchForm: React.PropTypes.func,
  preselectedSearchFields: React.PropTypes.arrayOf(
    React.PropTypes.shape({
      id: React.PropTypes.string.isRequired,
      value: React.PropTypes.any.isRequired,
      hidden: React.PropTypes.bool.isRequired
    })
  )
}

export default SearchForm
<commit_msg>refactor(entity-browser): remove unused class<commit_after>import React from 'react'
import {intlShape} from 'react-intl'
import {Button} from 'tocco-ui'
import SearchField from './SearchField'

const SearchForm = props => {
  const handleResetClick = () => {
    props.reset()
  }

  const handleSubmit = e => {
    e.preventDefault()
    props.setSearchInput()
  }

  const msg = id => (props.intl.formatMessage({id}))

  const isHidden = name => {
    const field = props.preselectedSearchFields.find(f => f.id === name)
    return field !== undefined && field.hidden
  }

  const renderField = name => (
    !isHidden(name) && (props.disableSimpleSearch || props.simpleSearchFields.includes(name)
    || props.showExtendedSearchForm)
  )

  const toggleExtendedSearchForm = () => {
    props.setShowExtendedSearchForm(!props.showExtendedSearchForm)
  }

  return (
    <form onSubmit={handleSubmit}>
      {
      props.searchFormDefinition.map((definition, idx) => (
        renderField(definition.name)
        &&

          {definition.label}

            <SearchField
              value={props.searchInputs ? props.searchInputs[definition.name] : undefined}
              type={definition.type}
              name={definition.name}
              relationEntities={props.relationEntities}
              entityModel={props.entityModel}
              setSearchInput={props.setSearchInput}
            />

        ))
      }
      {!props.disableSimpleSearch
        &&

          <Button
            type="button"
            label={msg('client.entity-browser.extendedSearch')}
            onClick={toggleExtendedSearchForm}
          />

      }

            <Button
              type="submit"
              icon="glyphicon-search"
              label={msg('client.entity-browser.search')}
              primary
            />
            <Button
              type="button"
              icon="glyphicon-repeat"
              label={msg('client.entity-browser.reset')}
              onClick={handleResetClick}
            />

    </form>
  )
}

SearchForm.propTypes = {
  intl: intlShape.isRequired,
  entityModel: React.PropTypes.objectOf(
    React.PropTypes.shape({
      type: React.PropTypes.string.isRequired,
      targetEntity: React.PropTypes.string
    })
  ).isRequired,
  searchFormDefinition: React.PropTypes.arrayOf(
    React.PropTypes.shape({
      name: React.PropTypes.string.isRequired,
      type: React.PropTypes.string.isRequired,
      displayType: React.PropTypes.string.isRequired,
      label: React.PropTypes.string.isRequired,
      useLabel: React.PropTypes.string.isRequired
    })
  ).isRequired,
  setSearchInput: React.PropTypes.func.isRequired,
  relationEntities: React.PropTypes.objectOf(
    React.PropTypes.arrayOf(
      React.PropTypes.shape({
        displayName: React.PropTypes.string.isRequired,
        value: React.PropTypes.string.isRequired
      })
    )).isRequired,
  searchInputs: React.PropTypes.objectOf(React.PropTypes.any),
  reset: React.PropTypes.func.isRequired,
  disableSimpleSearch: React.PropTypes.bool,
  simpleSearchFields: React.PropTypes.arrayOf(
    React.PropTypes.string
  ),
  showExtendedSearchForm: React.PropTypes.bool,
  setShowExtendedSearchForm: React.PropTypes.func,
  preselectedSearchFields: React.PropTypes.arrayOf(
    React.PropTypes.shape({
      id: React.PropTypes.string.isRequired,
      value: React.PropTypes.any.isRequired,
      hidden: React.PropTypes.bool.isRequired
    })
  )
}

export default SearchForm
<|endoftext|>


<commit_before>"use strict";

window.cls || (window.cls = {});

/**
 * @constructor
 */
cls.ElementLayout = function()
{
  this._es_debugger = window.services['ecmascript-debugger'];
  this._tag_manager = cls.TagManager.get_instance();
  this._stylesheets = window.stylesheets;
  this._css_index_map = null;
  this._selected_element = null;
  this._comp_style = null;
  this._offset_values = "";
  this._views = ['css-layout'];

  var OFFSET_TOP = 1;
  var OFFSET_LEFT = 2;
  var OFFSET_WIDTH = 3;
  var OFFSET_HEIGHT = 4;
  var SCROLL_TOP = 5;
  var SCROLL_LEFT = 6;
  var SCROLL_WIDTH = 7;
  var SCROLL_HEIGHT = 8;
  var CLIENT_TOP = 9;
  var CLIENT_LEFT = 10;
  var CLIENT_WIDTH = 11;
  var CLIENT_HEIGHT = 12;
  var OFFSETS = cls.ElementLayout.OFFSETS;
  var OFFSETS_SCRIPT = "\
    (function(ele)\
    {\
      var \
      parent = ele,\
      log = ele.nodeName + '|0;',\
      offsetParent = ele.offsetParent,\
      offsets =\
      [\
        '" + OFFSETS[OFFSET_TOP] + "',\
        '" + OFFSETS[OFFSET_LEFT] + "',\
        '" + OFFSETS[OFFSET_WIDTH] + "',\
        '" + OFFSETS[OFFSET_HEIGHT] + "',\
        '" + OFFSETS[SCROLL_TOP] + "',\
        '" + OFFSETS[SCROLL_LEFT] + "',\
        '" + OFFSETS[SCROLL_WIDTH] + "',\
        '" + OFFSETS[SCROLL_HEIGHT] + "',\
        '" + OFFSETS[CLIENT_TOP] + "',\
        '" + OFFSETS[CLIENT_LEFT] + "',\
        '" + OFFSETS[CLIENT_WIDTH] + "',\
        '" + OFFSETS[CLIENT_HEIGHT] + "'\
      ],\
      offset = '',\
      i = 0;\
      while ((parent = parent.parentNode) && parent.nodeType == 1)\
      {\
        if (parent == offsetParent)\
        {\
          log = parent.nodeName + '|1,' + log;\
          offsetParent = parent.offsetParent;\
        }\
        else\
        {\
          log = parent.nodeName + '|0,' + log;\
        }\
      };\
      for ( ; offset = offsets[i]; i++)\
      {\
        log += ele[offset] + ';';\
      }\
      return log;\
    })(ele)\
    ";

  this._on_element_selected = function(msg)
  {
    this._comp_style = null;
    this._offset_values = "";
    this._selected_element = msg.rt_id && msg.obj_id
                           ? {rt_id: msg.rt_id,
                              obj_id: msg.obj_id,
                              model: msg.model}
                           : null;

    for (var i = 0, view_id; view_id = this._views[i]; i++)
    {
      window.views[view_id].update();
    }
  };

  this.has_selected_element = function()
  {
    return Boolean(this._selected_element);
  };

  this.get_layout_values = function(callback)
  {
    if (!this._selected_element)
    {
      callback(null);
      return;
    }

    if (this._comp_style)
    {
      callback(this._comp_style);
      return;
    }

    var rt_id = this._selected_element.rt_id;
    var obj_id = this._selected_element.obj_id;

    if (this._stylesheets.has_stylesheets_runtime(rt_id))
    {
      var tag = this._tag_manager.set_callback(this, this._handle_get_metrics_data, [rt_id, obj_id, callback]);
      this._es_debugger.requestCssGetStyleDeclarations(tag, [rt_id, obj_id]);
    }
    else
    {
      this._stylesheets.get_stylesheets(this._selected_element.rt_id, this.get_layout_values.bind(this, callback));
    }
  };

  this._handle_get_metrics_data = function(status, message, rt_id, obj_id, callback, index_map)
  {
    var COMPUTED_STYLE_LIST = 0;
    var NODE_STYLE_LIST = 1;

    this._comp_style = message[COMPUTED_STYLE_LIST];
    if (!this._css_index_map)
    {
      if (!index_map)
      {
        window.stylesheets.get_css_index_map(this._handle_get_metrics_data.bind(this, status, message, rt_id, obj_id, callback));
        return;
      }
      else
      {
        this._css_index_map = index_map;
      }
    }

    if (callback)
      callback(this._comp_style);
  };

  this.get_offset_values = function(cb)
  {
    if (!this._selected_element)
    {
      cb(null);
    }
    else if (this._offset_values)
    {
      cb(window.helpers.copy_object(this._offset_values));
    }
    else
    {
      var rt_id = this._selected_element.rt_id;
      var obj_id = this._selected_element.obj_id;
      var tag = this._tag_manager.set_callback(null, this._handle_get_offset_data_bound, [rt_id, obj_id, cb]);
      this._es_debugger.requestEval(tag, [rt_id, 0, 0, OFFSETS_SCRIPT, [['ele', obj_id]]]);
    }
  };

  this._handle_get_offset_data = function(status, message, rt_id, obj_id, cb)
  {
    var STATUS = 0;
    var VALUE = 2;
    if (message[STATUS] == 'completed')
    {
      this._offset_values = this._parse_offset_values(message[VALUE]);
      if (cb)
        cb(window.helpers.copy_object(this._offset_values));
    }
    else
    {
      opera.postError(ui_strings.S_DRAGONFLY_INFO_MESSAGE +
        '_handle_get_offset_data failed')
    }
  };

  this._handle_get_offset_data_bound = this._handle_get_offset_data.bind(this);

  this._parse_offset_values = function(offset_values)
  {
    var data = offset_values.slice(0, -1).split(';'); // Remove the last ';' before splitting
    data[0] = data[0].split(',').map(function(item){
      return item.split('|');
    });
    return data;
  };

  this.get_metrics_template = function(comp_style)
  {
    var index_map = this._css_index_map;
    var is_positioned = comp_style[index_map.indexOf("position")] != "static";
    var is_border_box = comp_style[index_map.indexOf("box-sizing")] == "border-box";
    var width = this._get_dimension("width", comp_style, is_border_box);
    var height = this._get_dimension("height", comp_style, is_border_box);
    return (
      ['div',
        ['ul',
          ['li',
            ['ul',
              ['li',
                ['p',
                  '\u00A0',
                  ['span',
                    is_positioned
                    ? 'position'
                    : '\u00A0'
                  ]
                ]
              ],
              ['li',
                is_positioned
                ? this._convert_to_unitless(comp_style[index_map.indexOf("top")], true)
                : '\u00A0'
              ],
              ['li']
            ],
            ['ul',
              ['li',
                is_positioned
                ? this._convert_to_unitless(comp_style[index_map.indexOf("left")], true)
                : '\u00A0'
              ],
              ['li',
                ['ul',
                  ['li',
                    ['p',
                      '\u00a0',
                      ['span',
                        'margin'
                      ]
                    ]
                  ],
                  ['li',
                    this._convert_to_unitless(comp_style[index_map.indexOf("margin-top")])
                  ],
                  ['li']
                ],
                ['ul',
                  ['li',
                    this._convert_to_unitless(comp_style[index_map.indexOf("margin-left")])
                  ],
                  ['li',
                    ['ul',
                      ['li',
                        ['p',
                          '\u00a0',
                          ['span',
                            'border'
                          ]
                        ]
                      ],
                      ['li',
                        this._convert_to_unitless(comp_style[index_map.indexOf("border-top-width")])
                      ],
                      ['li']
                    ],
                    ['ul',
                      ['li',
                        this._convert_to_unitless(comp_style[index_map.indexOf("border-left-width")])
                      ],
                      ['li',
                        ['ul',
                          ['li',
                            ['p',
                              '\u00a0',
                              ['span',
                                'padding'
                              ]
                            ]
                          ],
                          ['li',
                            this._convert_to_unitless(comp_style[index_map.indexOf("padding-top")])
                          ],
                          ['li']
                        ],
                        ['ul',
                          ['li',
                            this._convert_to_unitless(comp_style[index_map.indexOf("padding-left")])
                          ],
                          ['li',
                            ['ul',
                              ['li',
                                '\u00a0'
                              ]
                            ],
                            ['ul',
                              ['li',
                                this._convert_to_unitless(width) +
                                ' × ' +
                                this._convert_to_unitless(height)
                              ]
                            ],
                            ['ul',
                              ['li',
                                '\u00a0'
                              ]
                            ],
                           'class', 'dimension'
                          ],
                          ['li',
                            this._convert_to_unitless(comp_style[index_map.indexOf("padding-right")])
                          ]
                        ],
                        ['ul',
                          ['li'],
                          ['li',
                            this._convert_to_unitless(comp_style[index_map.indexOf("padding-bottom")])
                          ],
                          ['li']
                        ],
                      'class', 'padding'
                      ],
                      ['li',
                        this._convert_to_unitless(comp_style[index_map.indexOf("border-right-width")])
                      ]
                    ],
                    ['ul',
                      ['li'],
                      ['li',
                        this._convert_to_unitless(comp_style[index_map.indexOf("border-bottom-width")])
                      ],
                      ['li']
                    ],
                   'class', 'border'
                  ],
                  ['li',
                    this._convert_to_unitless(comp_style[index_map.indexOf("margin-right")])
                  ]
                ],
                ['ul',
                  ['li'],
                  ['li',
                    this._convert_to_unitless(comp_style[index_map.indexOf("margin-bottom")])
                  ],
                  ['li']
                ],
               'class', 'margin'
              ],
              ['li',
                is_positioned
                ? this._convert_to_unitless(comp_style[index_map.indexOf("right")], true)
                : '\u00A0'
              ]
            ],
            ['ul',
              ['li'],
              ['li',
                is_positioned
                ? this._convert_to_unitless(comp_style[index_map.indexOf("bottom")], true)
                : '\u00A0'
              ],
              ['li']
            ],
           'class', is_positioned ? 'position' : ''
          ],
         'class', comp_style[index_map.indexOf("box-sizing")]
        ],
        ['table',
          ['tr',
            ['th',
              'position:'
            ],
            ['td',
              comp_style[index_map.indexOf("position")] || "–"
            ],
           'data-spec', 'css#position'
          ],
          ['tr',
            ['th',
              'z-index:'
            ],
            ['td',
              comp_style[index_map.indexOf("z-index")] || "–"
            ],
           'data-spec', 'css#z-index'
          ],
          ['tr',
            ['th',
              'box-sizing:'
            ],
            ['td',
              comp_style[index_map.indexOf("box-sizing")] || "–"
            ],
           'data-spec', 'css#box-sizing'
          ],
         'id', 'layout-info'
        ]
      ]
    );
  };

  this._get_dimension = function(direction, comp_style, is_border_box)
  {
    var index_map = this._css_index_map;
    var dim = parseInt(comp_style[index_map.indexOf(direction)]);
    var props = {
      "width": [
        "border-left",
        "border-right",
        "padding-left",
        "padding-right"
      ],
      "height": [
        "border-top",
        "border-bottom",
        "padding-top",
        "padding-bottom"
      ]
    };

    if (is_border_box)
    {
      dim += props[direction].reduce(function(prev, curr) {
        return prev - parseInt(comp_style[index_map.indexOf(curr)]);
      }, 0);
    }

    return dim;
  };

  this._convert_to_unitless = function(value, no_replace)
  {
    switch (value)
    {
    case "auto":
    case "":
      return "–";
    case "0px":
      return no_replace ? "0" : "–";
    default:
      return String(parseInt(value))
    }
  };

  window.messages.addListener('element-selected', this._on_element_selected.bind(this));
};

cls.ElementLayout.OFFSETS = [
  '',
  'offsetTop',
  'offsetLeft',
  'offsetWidth',
  'offsetHeight',
  'scrollTop',
  'scrollLeft',
  'scrollWidth',
  'scrollHeight',
  'clientTop',
  'clientLeft',
  'clientWidth',
  'clientHeight'
];

<commit_msg>Small refactorization.<commit_after>"use strict";

window.cls || (window.cls = {});

/**
 * @constructor
 */
cls.ElementLayout = function()
{
  this._es_debugger = window.services['ecmascript-debugger'];
  this._tag_manager = cls.TagManager.get_instance();
  this._stylesheets = window.stylesheets;
  this._css_index_map = null;
  this._selected_element = null;
  this._comp_style = null;
  this._offset_values = "";
  this._views = ['css-layout'];

  var OFFSET_TOP = 1;
  var OFFSET_LEFT = 2;
  var OFFSET_WIDTH = 3;
  var OFFSET_HEIGHT = 4;
  var SCROLL_TOP = 5;
  var SCROLL_LEFT = 6;
  var SCROLL_WIDTH = 7;
  var SCROLL_HEIGHT = 8;
  var CLIENT_TOP = 9;
  var CLIENT_LEFT = 10;
  var CLIENT_WIDTH = 11;
  var CLIENT_HEIGHT = 12;
  var OFFSETS = cls.ElementLayout.OFFSETS;
  var OFFSETS_SCRIPT = "\
    (function(ele)\
    {\
      var \
      parent = ele,\
      log = ele.nodeName + '|0;',\
      offsetParent = ele.offsetParent,\
      offsets =\
      [\
        '" + OFFSETS[OFFSET_TOP] + "',\
        '" + OFFSETS[OFFSET_LEFT] + "',\
        '" + OFFSETS[OFFSET_WIDTH] + "',\
        '" + OFFSETS[OFFSET_HEIGHT] + "',\
        '" + OFFSETS[SCROLL_TOP] + "',\
        '" + OFFSETS[SCROLL_LEFT] + "',\
        '" + OFFSETS[SCROLL_WIDTH] + "',\
        '" + OFFSETS[SCROLL_HEIGHT] + "',\
        '" + OFFSETS[CLIENT_TOP] + "',\
        '" + OFFSETS[CLIENT_LEFT] + "',\
        '" + OFFSETS[CLIENT_WIDTH] + "',\
        '" + OFFSETS[CLIENT_HEIGHT] + "'\
      ],\
      offset = '',\
      i = 0;\
      while ((parent = parent.parentNode) && parent.nodeType == 1)\
      {\
        if (parent == offsetParent)\
        {\
          log = parent.nodeName + '|1,' + log;\
          offsetParent = parent.offsetParent;\
        }\
        else\
        {\
          log = parent.nodeName + '|0,' + log;\
        }\
      };\
      for ( ; offset = offsets[i]; i++)\
      {\
        log += ele[offset] + ';';\
      }\
      return log;\
    })(ele)\
    ";

  this._on_element_selected = function(msg)
  {
    this._comp_style = null;
    this._offset_values = "";
    this._selected_element = msg.rt_id && msg.obj_id
                           ? {rt_id: msg.rt_id,
                              obj_id: msg.obj_id,
                              model: msg.model}
                           : null;

    for (var i = 0, view_id; view_id = this._views[i]; i++)
    {
      window.views[view_id].update();
    }
  };

  this.has_selected_element = function()
  {
    return Boolean(this._selected_element);
  };

  this.get_layout_values = function(callback)
  {
    if (!this._selected_element)
    {
      callback(null);
      return;
    }

    if (this._comp_style)
    {
      callback(this._comp_style);
      return;
    }

    var rt_id = this._selected_element.rt_id;
    var obj_id = this._selected_element.obj_id;

    if (this._stylesheets.has_stylesheets_runtime(rt_id))
    {
      var tag = this._tag_manager.set_callback(this, this._handle_get_metrics_data, [rt_id, obj_id, callback]);
      this._es_debugger.requestCssGetStyleDeclarations(tag, [rt_id, obj_id]);
    }
    else
    {
      this._stylesheets.get_stylesheets(this._selected_element.rt_id, this.get_layout_values.bind(this, callback));
    }
  };

  this._handle_get_metrics_data = function(status, message, rt_id, obj_id, callback, index_map)
  {
    var COMPUTED_STYLE_LIST = 0;
    var NODE_STYLE_LIST = 1;

    this._comp_style = message[COMPUTED_STYLE_LIST];
    if (!this._css_index_map)
    {
      if (!index_map)
      {
        window.stylesheets.get_css_index_map(this._handle_get_metrics_data.bind(this, status, message, rt_id, obj_id, callback));
        return;
      }
      else
      {
        this._css_index_map = index_map;
      }
    }

    if (callback)
      callback(this._comp_style);
  };

  this.get_offset_values = function(cb)
  {
    if (!this._selected_element)
    {
      cb(null);
    }
    else if (this._offset_values)
    {
      cb(window.helpers.copy_object(this._offset_values));
    }
    else
    {
      var rt_id = this._selected_element.rt_id;
      var obj_id = this._selected_element.obj_id;
      var tag = this._tag_manager.set_callback(null, this._handle_get_offset_data_bound, [rt_id, obj_id, cb]);
      this._es_debugger.requestEval(tag, [rt_id, 0, 0, OFFSETS_SCRIPT, [['ele', obj_id]]]);
    }
  };

  this._handle_get_offset_data = function(status, message, rt_id, obj_id, cb)
  {
    var STATUS = 0;
    var VALUE = 2;
    if (message[STATUS] == 'completed')
    {
      this._offset_values = this._parse_offset_values(message[VALUE]);
      if (cb)
        cb(window.helpers.copy_object(this._offset_values));
    }
    else
    {
      opera.postError(ui_strings.S_DRAGONFLY_INFO_MESSAGE +
        '_handle_get_offset_data failed')
    }
  };

  this._handle_get_offset_data_bound = this._handle_get_offset_data.bind(this);

  this._parse_offset_values = function(offset_values)
  {
    var data = offset_values.slice(0, -1).split(';'); // Remove the last ';' before splitting
    data[0] = data[0].split(',').map(function(item){
      return item.split('|');
    });
    return data;
  };

  this.get_metrics_template = function(comp_style)
  {
    var index_map = this._css_index_map;
    var is_positioned = comp_style[index_map.indexOf("position")] != "static";
    var width = this._get_dimension("width", comp_style);
    var height = this._get_dimension("height", comp_style);
    return (
      ['div',
        ['ul',
          ['li',
            ['ul',
              ['li',
                ['p',
                  '\u00A0',
                  ['span',
                    is_positioned
                    ? 'position'
                    : '\u00A0'
                  ]
                ]
              ],
              ['li',
                is_positioned
                ? this._convert_to_unitless(comp_style[index_map.indexOf("top")], true)
                : '\u00A0'
              ],
              ['li']
            ],
            ['ul',
              ['li',
                is_positioned
                ? this._convert_to_unitless(comp_style[index_map.indexOf("left")], true)
                : '\u00A0'
              ],
              ['li',
                ['ul',
                  ['li',
                    ['p',
                      '\u00a0',
                      ['span',
                        'margin'
                      ]
                    ]
                  ],
                  ['li',
                    this._convert_to_unitless(comp_style[index_map.indexOf("margin-top")])
                  ],
                  ['li']
                ],
                ['ul',
                  ['li',
                    this._convert_to_unitless(comp_style[index_map.indexOf("margin-left")])
                  ],
                  ['li',
                    ['ul',
                      ['li',
                        ['p',
                          '\u00a0',
                          ['span',
                            'border'
                          ]
                        ]
                      ],
                      ['li',
                        this._convert_to_unitless(comp_style[index_map.indexOf("border-top-width")])
                      ],
                      ['li']
                    ],
                    ['ul',
                      ['li',
                        this._convert_to_unitless(comp_style[index_map.indexOf("border-left-width")])
                      ],
                      ['li',
                        ['ul',
                          ['li',
                            ['p',
                              '\u00a0',
                              ['span',
                                'padding'
                              ]
                            ]
                          ],
                          ['li',
                            this._convert_to_unitless(comp_style[index_map.indexOf("padding-top")])
                          ],
                          ['li']
                        ],
                        ['ul',
                          ['li',
                            this._convert_to_unitless(comp_style[index_map.indexOf("padding-left")])
                          ],
                          ['li',
                            ['ul',
                              ['li',
                                '\u00a0'
                              ]
                            ],
                            ['ul',
                              ['li',
                                this._convert_to_unitless(width) +
                                ' × ' +
                                this._convert_to_unitless(height)
                              ]
                            ],
                            ['ul',
                              ['li',
                                '\u00a0'
                              ]
                            ],
                           'class', 'dimension'
                          ],
                          ['li',
                            this._convert_to_unitless(comp_style[index_map.indexOf("padding-right")])
                          ]
                        ],
                        ['ul',
                          ['li'],
                          ['li',
                            this._convert_to_unitless(comp_style[index_map.indexOf("padding-bottom")])
                          ],
                          ['li']
                        ],
                      'class', 'padding'
                      ],
                      ['li',
                        this._convert_to_unitless(comp_style[index_map.indexOf("border-right-width")])
                      ]
                    ],
                    ['ul',
                      ['li'],
                      ['li',
                        this._convert_to_unitless(comp_style[index_map.indexOf("border-bottom-width")])
                      ],
                      ['li']
                    ],
                   'class', 'border'
                  ],
                  ['li',
                    this._convert_to_unitless(comp_style[index_map.indexOf("margin-right")])
                  ]
                ],
                ['ul',
                  ['li'],
                  ['li',
                    this._convert_to_unitless(comp_style[index_map.indexOf("margin-bottom")])
                  ],
                  ['li']
                ],
               'class', 'margin'
              ],
              ['li',
                is_positioned
                ? this._convert_to_unitless(comp_style[index_map.indexOf("right")], true)
                : '\u00A0'
              ]
            ],
            ['ul',
              ['li'],
              ['li',
                is_positioned
                ? this._convert_to_unitless(comp_style[index_map.indexOf("bottom")], true)
                : '\u00A0'
              ],
              ['li']
            ],
           'class', is_positioned ? 'position' : ''
          ],
         'class', comp_style[index_map.indexOf("box-sizing")]
        ],
        ['table',
          ['tr',
            ['th',
              'position:'
            ],
            ['td',
              comp_style[index_map.indexOf("position")] || "–"
            ],
           'data-spec', 'css#position'
          ],
          ['tr',
            ['th',
              'z-index:'
            ],
            ['td',
              comp_style[index_map.indexOf("z-index")] || "–"
            ],
           'data-spec', 'css#z-index'
          ],
          ['tr',
            ['th',
              'box-sizing:'
            ],
            ['td',
              comp_style[index_map.indexOf("box-sizing")] || "–"
            ],
           'data-spec', 'css#box-sizing'
          ],
         'id', 'layout-info'
        ]
      ]
    );
  };

  this._get_dimension = function(direction, comp_style)
  {
    var index_map = this._css_index_map;
    var dim = parseInt(comp_style[index_map.indexOf(direction)]);
    var is_border_box = comp_style[index_map.indexOf("box-sizing")] == "border-box";
    var props = {
      "width": [
        "border-left",
        "border-right",
        "padding-left",
        "padding-right"
      ],
      "height": [
        "border-top",
        "border-bottom",
        "padding-top",
        "padding-bottom"
      ]
    };

    if (is_border_box)
    {
      dim += props[direction].reduce(function(prev, curr) {
        return prev - parseInt(comp_style[index_map.indexOf(curr)]);
      }, 0);
    }

    return dim;
  };

  this._convert_to_unitless = function(value, no_replace)
  {
    switch (value)
    {
    case "auto":
    case "":
      return "–";
    case "0px":
      return no_replace ? "0" : "–";
    default:
      return String(parseInt(value))
    }
  };

  window.messages.addListener('element-selected', this._on_element_selected.bind(this));
};

cls.ElementLayout.OFFSETS = [
  '',
  'offsetTop',
  'offsetLeft',
  'offsetWidth',
  'offsetHeight',
  'scrollTop',
  'scrollLeft',
  'scrollWidth',
  'scrollHeight',
  'clientTop',
  'clientLeft',
  'clientWidth',
  'clientHeight'
];

<|endoftext|>


<commit_before>// This module handles the management of modules required by the runtime and flows.
// Essentially this means keeping track of what extra modules a flow requires,
// ensuring those modules are installed and providing a standard way for nodes
// to require those modules safely.

const fs = require("fs-extra");
const registryUtil = require("./util");
const path = require("path");
const clone = require("clone");
const exec = require("@node-red/util").exec;
const log = require("@node-red/util").log;
const hooks = require("@node-red/util").hooks;

const BUILTIN_MODULES = require('module').builtinModules;

// TODO: outsource running npm to a plugin
const NPM_COMMAND = (process.platform === "win32") ? "npm.cmd" : "npm";

let registeredTypes = {};
let subflowTypes = {};
let settings;

let knownExternalModules = {};

let installEnabled = true;
let installAllowList = ['*'];
let installDenyList = [];

let IMPORT_SUPPORTED = true;
const nodeVersionParts = process.versions.node.split(".").map(v => parseInt(v));
if (nodeVersionParts[0] < 12 || (nodeVersionParts[0] === 12 && nodeVersionParts[1] < 17)) {
    IMPORT_SUPPORTED = false;
}

function getInstallDir() {
    return path.resolve(settings.userDir || process.env.NODE_RED_HOME || ".");
}

let loggedLegacyWarning = false;
async function refreshExternalModules() {

    if (!loggedLegacyWarning) {
        loggedLegacyWarning = true;
        const oldExternalModulesDir = path.join(path.resolve(settings.userDir || process.env.NODE_RED_HOME || "."),"externalModules");
        if (fs.existsSync(oldExternalModulesDir)) {
            try {
                log.warn(log._("server.install.old-ext-mod-dir-warning",{oldDir:oldExternalModulesDir, newDir:getInstallDir()}))
            } catch(err) {console.log(err)}
        }

    }

    const externalModuleDir = getInstallDir();
    try {
        const pkgFile = JSON.parse(await fs.readFile(path.join(externalModuleDir,"package.json"),"utf-8"));
        knownExternalModules = pkgFile.dependencies || {};
    } catch(err) {
        knownExternalModules = {};
    }
}

function init(_settings) {
    settings = _settings;
    knownExternalModules = {};
    installEnabled = true;

    if (settings.externalModules && settings.externalModules.modules) {
        if (settings.externalModules.modules.allowList || settings.externalModules.modules.denyList) {
            installAllowList = settings.externalModules.modules.allowList;
            installDenyList = settings.externalModules.modules.denyList;
        }
        if (settings.externalModules.modules.hasOwnProperty("allowInstall")) {
            installEnabled = settings.externalModules.modules.allowInstall
        }
    }
    installAllowList = registryUtil.parseModuleList(installAllowList);
    installDenyList = registryUtil.parseModuleList(installDenyList);
}

function register(type, dynamicModuleListProperty) {
    registeredTypes[type] = dynamicModuleListProperty;
}

function registerSubflow(type, subflowConfig) {
    subflowTypes[type] = subflowConfig;
}

function requireModule(module) {
    if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }

    const parsedModule = parseModuleName(module);

    if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) {
        return require(parsedModule.module);
    }
    if (!knownExternalModules[parsedModule.module]) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }
    const externalModuleDir = getInstallDir();
    const moduleDir = path.join(externalModuleDir,"node_modules",module);
    return require(moduleDir);
}
function importModule(module) {
    if (!IMPORT_SUPPORTED) {
        // On Node < 12.17 - fall back to try a require
        return new Promise((resolve, reject) => {
            try {
                const mod = requireModule(module);
                resolve(mod);
            } catch(err) {
                reject(err);
            }
        });
    }

    if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }

    const parsedModule = parseModuleName(module);

    if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) {
        return import(parsedModule.module);
    }
    if (!knownExternalModules[parsedModule.module]) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }
    const externalModuleDir = getInstallDir();
    const moduleDir = path.join(externalModuleDir,"node_modules",module);
    // Import needs the full path to the module's main .js file
    const moduleFile = require.resolve(moduleDir);
    return import(moduleFile);
}

function parseModuleName(module) {
    var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module);
    if (match) {
        return {
            spec: module,
            module: match[1],
            version: match[2],
            builtin: BUILTIN_MODULES.indexOf(match[1]) !== -1,
            known: !!knownExternalModules[match[1]]
        }
    }
    return null;
}

function isInstalled(moduleDetails) {
    return moduleDetails.builtin || moduleDetails.known;
}

async function checkFlowDependencies(flowConfig) {
    let nodes = clone(flowConfig);
    await refreshExternalModules();

    const checkedModules = {};
    const promises = [];
    const errors = [];
    const checkedSubflows = {};
    while (nodes.length > 0) {
        let n = nodes.shift();
        if (subflowTypes[n.type] && !checkedSubflows[n.type]) {
            checkedSubflows[n.type] = true;
            nodes = nodes.concat(subflowTypes[n.type].flow)
        } else if (registeredTypes[n.type]) {
            let nodeModules = n[registeredTypes[n.type]] || [];
            if (!Array.isArray(nodeModules)) {
                nodeModules = [nodeModules]
            }
            nodeModules.forEach(module => {
                if (typeof module !== 'string') {
                    module = module.module || "";
                }
                if (module) {
                    let moduleDetails = parseModuleName(module)
                    if (moduleDetails && checkedModules[moduleDetails.module] === undefined) {
                        checkedModules[moduleDetails.module] = isInstalled(moduleDetails)
                        if (!checkedModules[moduleDetails.module]) {
                            if (installEnabled) {
                                promises.push(installModule(moduleDetails).catch(err => {
                                    errors.push({module: moduleDetails,error:err});
                                }))
                            } else if (!installEnabled) {
                                const e = new Error("Module install disabled - externalModules.modules.allowInstall=false");
                                e.code = "install_not_allowed";
                                errors.push({module: moduleDetails,error:e});
                            }
                        } else if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) {
                            const e = new Error("Module not allowed");
                            e.code = "module_not_allowed";
                            errors.push({module: moduleDetails,error:e});
                        }
                    }
                }
            })
        }
    }
    return Promise.all(promises).then(refreshExternalModules).then(() => {
        if (errors.length > 0) {
            throw errors;
        }
    })
}

async function ensureModuleDir() {
    const installDir = getInstallDir();

    if (!fs.existsSync(installDir)) {
        await fs.ensureDir(installDir);
    }
    const pkgFile = path.join(installDir,"package.json");
    if (!fs.existsSync(pkgFile)) {
        await fs.writeFile(path.join(installDir,"package.json"),`{
    "name": "Node-RED-External-Modules",
    "description": "These modules are automatically installed by Node-RED to use in Function nodes.",
    "version": "1.0.0",
    "private": true,
    "dependencies": {}
}`)
    }
}

async function installModule(moduleDetails) {
    let installSpec = moduleDetails.module;
    if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) {
        const e = new Error("Install not allowed");
        e.code = "install_not_allowed";
        throw e;
    }
    if (moduleDetails.version) {
        installSpec = installSpec+"@"+moduleDetails.version;
    }
    log.info(log._("server.install.installing",{name: moduleDetails.module,version: moduleDetails.version||"latest"}));
    const installDir = getInstallDir();

    await ensureModuleDir();

    let triggerPayload = {
        "module": moduleDetails.module,
        "version": moduleDetails.version,
        "dir": installDir,
        "args": ["--production","--engine-strict"]
    }
    return hooks.trigger("preInstall", triggerPayload).then((result) => {
        // preInstall passed
        // - run install
        if (result !== false) {
            let extraArgs = triggerPayload.args || [];
            let args = ['install', ...extraArgs, installSpec]
            log.trace(NPM_COMMAND + JSON.stringify(args));
            return exec.run(NPM_COMMAND, args, { cwd: installDir },true)
        } else {
            log.trace("skipping npm install");
        }
    }).then(() => {
        return hooks.trigger("postInstall", triggerPayload)
    }).then(() => {
        log.info(log._("server.install.installed", { name: installSpec }));
        const runtimeInstalledModules = settings.get("modules") || {};
        runtimeInstalledModules[moduleDetails.module] = moduleDetails;
        settings.set("modules",runtimeInstalledModules)
    }).catch(result => {
        var output = result.stderr || result.toString();
        var e;
        if (/E404/.test(output) || /ETARGET/.test(output)) {
            log.error(log._("server.install.install-failed-not-found",{name:installSpec}));
            e = new Error("Module not found");
            e.code = 404;
            throw e;
        } else {
            log.error(log._("server.install.install-failed-long",{name:installSpec}));
            log.error("------------------------------------------");
            log.error(output);
            log.error("------------------------------------------");
            e = new Error(log._("server.install.install-failed"));
            e.code = "unexpected_error";
            throw e;
        }
    })
}

module.exports = {
    init,
    register,
    registerSubflow,
    checkFlowDependencies,
    require: requireModule,
    import: importModule
}
<commit_msg>Use file:// url with dynamic import<commit_after>// This module handles the management of modules required by the runtime and flows.
// Essentially this means keeping track of what extra modules a flow requires,
// ensuring those modules are installed and providing a standard way for nodes
// to require those modules safely.

const fs = require("fs-extra");
const registryUtil = require("./util");
const path = require("path");
const clone = require("clone");
const exec = require("@node-red/util").exec;
const log = require("@node-red/util").log;
const hooks = require("@node-red/util").hooks;
const url = require("url");

const BUILTIN_MODULES = require('module').builtinModules;

// TODO: outsource running npm to a plugin
const NPM_COMMAND = (process.platform === "win32") ? "npm.cmd" : "npm";

let registeredTypes = {};
let subflowTypes = {};
let settings;

let knownExternalModules = {};

let installEnabled = true;
let installAllowList = ['*'];
let installDenyList = [];

let IMPORT_SUPPORTED = true;
const nodeVersionParts = process.versions.node.split(".").map(v => parseInt(v));
if (nodeVersionParts[0] < 12 || (nodeVersionParts[0] === 12 && nodeVersionParts[1] < 17)) {
    IMPORT_SUPPORTED = false;
}

function getInstallDir() {
    return path.resolve(settings.userDir || process.env.NODE_RED_HOME || ".");
}

let loggedLegacyWarning = false;
async function refreshExternalModules() {

    if (!loggedLegacyWarning) {
        loggedLegacyWarning = true;
        const oldExternalModulesDir = path.join(path.resolve(settings.userDir || process.env.NODE_RED_HOME || "."),"externalModules");
        if (fs.existsSync(oldExternalModulesDir)) {
            try {
                log.warn(log._("server.install.old-ext-mod-dir-warning",{oldDir:oldExternalModulesDir, newDir:getInstallDir()}))
            } catch(err) {console.log(err)}
        }

    }

    const externalModuleDir = getInstallDir();
    try {
        const pkgFile = JSON.parse(await fs.readFile(path.join(externalModuleDir,"package.json"),"utf-8"));
        knownExternalModules = pkgFile.dependencies || {};
    } catch(err) {
        knownExternalModules = {};
    }
}

function init(_settings) {
    settings = _settings;
    knownExternalModules = {};
    installEnabled = true;

    if (settings.externalModules && settings.externalModules.modules) {
        if (settings.externalModules.modules.allowList || settings.externalModules.modules.denyList) {
            installAllowList = settings.externalModules.modules.allowList;
            installDenyList = settings.externalModules.modules.denyList;
        }
        if (settings.externalModules.modules.hasOwnProperty("allowInstall")) {
            installEnabled = settings.externalModules.modules.allowInstall
        }
    }
    installAllowList = registryUtil.parseModuleList(installAllowList);
    installDenyList = registryUtil.parseModuleList(installDenyList);
}

function register(type, dynamicModuleListProperty) {
    registeredTypes[type] = dynamicModuleListProperty;
}

function registerSubflow(type, subflowConfig) {
    subflowTypes[type] = subflowConfig;
}

function requireModule(module) {
    if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }

    const parsedModule = parseModuleName(module);

    if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) {
        return require(parsedModule.module);
    }
    if (!knownExternalModules[parsedModule.module]) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }
    const externalModuleDir = getInstallDir();
    const moduleDir = path.join(externalModuleDir,"node_modules",module);
    return require(moduleDir);
}
function importModule(module) {
    if (!IMPORT_SUPPORTED) {
        // On Node < 12.17 - fall back to try a require
        return new Promise((resolve, reject) => {
            try {
                const mod = requireModule(module);
                resolve(mod);
            } catch(err) {
                reject(err);
            }
        });
    }

    if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }

    const parsedModule = parseModuleName(module);

    if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) {
        return import(parsedModule.module);
    }
    if (!knownExternalModules[parsedModule.module]) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }
    const externalModuleDir = getInstallDir();
    const moduleDir = path.join(externalModuleDir,"node_modules",module);
    // Import needs the full path to the module's main .js file
    // It also needs to be a file:// url for Windows
    const moduleFile = url.pathToFileURL(require.resolve(moduleDir));
    return import(moduleFile);
}

function parseModuleName(module) {
    var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module);
    if (match) {
        return {
            spec: module,
            module: match[1],
            version: match[2],
            builtin: BUILTIN_MODULES.indexOf(match[1]) !== -1,
            known: !!knownExternalModules[match[1]]
        }
    }
    return null;
}

function isInstalled(moduleDetails) {
    return moduleDetails.builtin || moduleDetails.known;
}

async function checkFlowDependencies(flowConfig) {
    let nodes = clone(flowConfig);
    await refreshExternalModules();

    const checkedModules = {};
    const promises = [];
    const errors = [];
    const checkedSubflows = {};
    while (nodes.length > 0) {
        let n = nodes.shift();
        if (subflowTypes[n.type] && !checkedSubflows[n.type]) {
            checkedSubflows[n.type] = true;
            nodes = nodes.concat(subflowTypes[n.type].flow)
        } else if (registeredTypes[n.type]) {
            let nodeModules = n[registeredTypes[n.type]] || [];
            if (!Array.isArray(nodeModules)) {
                nodeModules = [nodeModules]
            }
            nodeModules.forEach(module => {
                if (typeof module !== 'string') {
                    module = module.module || "";
                }
                if (module) {
                    let moduleDetails = parseModuleName(module)
                    if (moduleDetails && checkedModules[moduleDetails.module] === undefined) {
                        checkedModules[moduleDetails.module] = isInstalled(moduleDetails)
                        if (!checkedModules[moduleDetails.module]) {
                            if (installEnabled) {
                                promises.push(installModule(moduleDetails).catch(err => {
                                    errors.push({module: moduleDetails,error:err});
                                }))
                            } else if (!installEnabled) {
                                const e = new Error("Module install disabled - externalModules.modules.allowInstall=false");
                                e.code = "install_not_allowed";
                                errors.push({module: moduleDetails,error:e});
                            }
                        } else if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) {
                            const e = new Error("Module not allowed");
                            e.code = "module_not_allowed";
                            errors.push({module: moduleDetails,error:e});
                        }
                    }
                }
            })
        }
    }
    return Promise.all(promises).then(refreshExternalModules).then(() => {
        if (errors.length > 0) {
            throw errors;
        }
    })
}

async function ensureModuleDir() {
    const installDir = getInstallDir();

    if (!fs.existsSync(installDir)) {
        await fs.ensureDir(installDir);
    }
    const pkgFile = path.join(installDir,"package.json");
    if (!fs.existsSync(pkgFile)) {
        await fs.writeFile(path.join(installDir,"package.json"),`{
    "name": "Node-RED-External-Modules",
    "description": "These modules are automatically installed by Node-RED to use in Function nodes.",
    "version": "1.0.0",
    "private": true,
    "dependencies": {}
}`)
    }
}

async function installModule(moduleDetails) {
    let installSpec = moduleDetails.module;
    if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) {
        const e = new Error("Install not allowed");
        e.code = "install_not_allowed";
        throw e;
    }
    if (moduleDetails.version) {
        installSpec = installSpec+"@"+moduleDetails.version;
    }
    log.info(log._("server.install.installing",{name: moduleDetails.module,version: moduleDetails.version||"latest"}));
    const installDir = getInstallDir();

    await ensureModuleDir();

    let triggerPayload = {
        "module": moduleDetails.module,
        "version": moduleDetails.version,
        "dir": installDir,
        "args": ["--production","--engine-strict"]
    }
    return hooks.trigger("preInstall", triggerPayload).then((result) => {
        // preInstall passed
        // - run install
        if (result !== false) {
            let extraArgs = triggerPayload.args || [];
            let args = ['install', ...extraArgs, installSpec]
            log.trace(NPM_COMMAND + JSON.stringify(args));
            return exec.run(NPM_COMMAND, args, { cwd: installDir },true)
        } else {
            log.trace("skipping npm install");
        }
    }).then(() => {
        return hooks.trigger("postInstall", triggerPayload)
    }).then(() => {
        log.info(log._("server.install.installed", { name: installSpec }));
        const runtimeInstalledModules = settings.get("modules") || {};
        runtimeInstalledModules[moduleDetails.module] = moduleDetails;
        settings.set("modules",runtimeInstalledModules)
    }).catch(result => {
        var output = result.stderr || result.toString();
        var e;
        if (/E404/.test(output) || /ETARGET/.test(output)) {
            log.error(log._("server.install.install-failed-not-found",{name:installSpec}));
            e = new Error("Module not found");
            e.code = 404;
            throw e;
        } else {
            log.error(log._("server.install.install-failed-long",{name:installSpec}));
            log.error("------------------------------------------");
            log.error(output);
            log.error("------------------------------------------");
            e = new Error(log._("server.install.install-failed"));
            e.code = "unexpected_error";
            throw e;
        }
    })
}

module.exports = {
    init,
    register,
    registerSubflow,
    checkFlowDependencies,
    require: requireModule,
    import: importModule
}
<|endoftext|>


<commit_before>import { State } from '../../utils/fsm';

/**
 * Класс для состояния "Отрисовываем виджеты"
 */
export default class RenderingState extends State {
    /**
     * Что делать с необрабатываемыми в этом состояния хэндлеры
     */
    _anyEvents() {
        this.deferUntilTransition();
    }

    /**
     * Вызывается при входе в это состояние
     */
    _onEnter() {
        this.rendered = [];

        for ( let i = 0; i < this._fsm.changeWidgets.length; i++ ) {
            const ID = this._fsm.changeWidgets[ i ];
            const widget = this._fsm.byId[ ID ];
            if ( widget && widget.render ) {
                this.handle( "renderWidget", widget );
                this.emit( `RenderComplete[${ID}]` );
            }
        }

        // Все области отрисовались
        this.emit( "RenderComplete", this.rendered );
        this.transition( "ready" );
    }

    /**
     * Вызывается при выходе из этого состояни
     */
    _onExit() {
        this.rendered = [];
        this._fsm.changeWidgets = [];
    }

    /**
     * Отрисовать один виджет
     * @param {Object} widget   Виджет
     * @see Widget
     */
    renderWidget( widget ) {
        if ( widget.options.actionSequence[ 0 ] === "render" ) {
            widget.resolveContainer();
        }

        if ( widget.options.beforeRender ) {
            widget.options.beforeRender.call( widget );
        }

        try {
            const obj = widget.render();
            if ( !obj ) {
                this.rendered.push( widget.ID );
                return;
            }

            const newEl = obj.container.cloneNode( false );
            newEl.innerHTML = obj.html;
            obj.container.parentNode.replaceChild( newEl, obj.container );
            widget.container = newEl;

            this.rendered.push( widget.ID );

            if ( widget.options.afterRender ) {
                widget.options.afterRender.call( widget );
            }

            if ( widget.options.actionSequence[ 0 ] === "render" &&
                widget.bindEvents && !widget.isBinded ) {

                // Если после отрисовки нужно биндить обработчики событий виджета или нет
                if ( widget.options.beforeBindEvents ) {
                    widget.options.beforeBindEvents.call( widget );
                }
                widget.bindEvents();
                if ( widget.options.afterBindEvents ) {
                    widget.options.afterBindEvents.call( widget );
                }
                if ( widget.options.bindOnce ) {
                    widget.isBinded = true;
                }
            }
        } catch ( e ) {
            this.handleException( e );
        }
    }
}<commit_msg>Add todo<commit_after>import { State } from '../../utils/fsm';

/**
 * Класс для состояния "Отрисовываем виджеты"
 */
export default class RenderingState extends State {
    /**
     * Что делать с необрабатываемыми в этом состояния хэндлеры
     */
    _anyEvents() {
        this.deferUntilTransition();
    }

    /**
     * Вызывается при входе в это состояние
     */
    _onEnter() {
        this.rendered = [];

        for ( let i = 0; i < this._fsm.changeWidgets.length; i++ ) {
            const ID = this._fsm.changeWidgets[ i ];
            const widget = this._fsm.byId[ ID ];
            if ( widget && widget.render ) {
                this.handle( "renderWidget", widget );
                this.emit( `RenderComplete[${ID}]` );
            }
        }

        // Все области отрисовались
        this.emit( "RenderComplete", this.rendered );
        this.transition( "ready" );
    }

    /**
     * Вызывается при выходе из этого состояни
     */
    _onExit() {
        this.rendered = [];
        this._fsm.changeWidgets = [];
    }

    /**
     * @param {Widget} widget
     */
    register( widget ) {
        // TODO
    }

    /**
     * Отрисовать один виджет
     * @param {Object} widget   Виджет
     * @see Widget
     */
    renderWidget( widget ) {
        if ( widget.options.actionSequence[ 0 ] === "render" ) {
            widget.resolveContainer();
        }

        if ( widget.options.beforeRender ) {
            widget.options.beforeRender.call( widget );
        }

        try {
            const obj = widget.render();
            if ( !obj ) {
                this.rendered.push( widget.ID );

                // TODO: process widget.options.afterRender
                return;
            }

            const newEl = obj.container.cloneNode( false );
            newEl.innerHTML = obj.html;
            obj.container.parentNode.replaceChild( newEl, obj.container );
            widget.container = newEl;

            this.rendered.push( widget.ID );

            if ( widget.options.afterRender ) {
                widget.options.afterRender.call( widget );
            }

            // TODO: Extract
            if ( widget.options.actionSequence[ 0 ] === "render" &&
                widget.bindEvents && !widget.isBinded ) {

                // Если после отрисовки нужно биндить обработчики событий виджета или нет
                if ( widget.options.beforeBindEvents ) {
                    widget.options.beforeBindEvents.call( widget );
                }
                widget.bindEvents();
                if ( widget.options.afterBindEvents ) {
                    widget.options.afterBindEvents.call( widget );
                }
                if ( widget.options.bindOnce ) {
                    widget.isBinded = true;
                }
            }
        } catch ( e ) {
            this.handleException( e );
        }
    }
}<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var Step = require('step');
var paths = {
    user: path.join(process.env.HOME, '.tilemill/config.json'),
    vendor: path.resolve(__dirname + '/../lib/config.defaults.json')
};
var create_files = require('../lib/create_files');
models.Config.defaults = JSON.parse(fs.readFileSync(paths.vendor, 'utf8'));
models.Config.prototype.sync = function(method, model, success, error) {
    if (method === 'delete') return error(new Error('Method not supported.'));

    switch (method) {
    case 'defaults':
        var data = _(defaults).clone();
        if (data.files.indexOf(process.env.HOME) === 0)
            data.files = data.files.replace(process.env.HOME, '~');
        return success(data);
        break;
    case 'read':
        var data = _(Bones.plugin.config).clone();
        if (data.files.indexOf(process.env.HOME) === 0)
            data.files = data.files.replace(process.env.HOME, '~');
        return success(data);
        break;
    case 'create':
    case 'update':
        // Filter out keys that may not be written.
        var allowedKeys = [
            'files',
            'syncAccount',
            'syncAccessToken',
            'updates',
            'updatesTime',
            'updatesVersion',
            'profile',
            'guid',
            'httpProxy',
            'verbose'
        ];
        var data = _(model.toJSON()).reduce(function(memo, val, key) {
            if (key === 'files') val = val.replace(/^~/, process.env.HOME);
            if (_(allowedKeys).include(key)) memo[key] = val;
            return memo;
        }, {});

        Step(function() {
            // validate that the 'files' option is okay to write to
            // https://github.com/mapbox/tilemill/issues/2024
            try {
                create_files.init_dirs(['export','cache'],data);
                this();
            } catch (err) {
                throw new Error('The Documents path "' + data.files + '" cannot be written to, please choose a valid location (' + err.message + ')');
            }
        }, function(err) {
            if (err) throw err;
            fs.readFile(paths.user, 'utf8', this);
        // Write changes to user config file.
        }, function(err, current) {
            if (err && err.code !== 'ENOENT') throw err;

            // Catch & blow away invalid user JSON.
            try { current = JSON.parse(current); }
            catch (e) { current = {}; }
            data = _(current).extend(data);
            fs.writeFile(paths.user, JSON.stringify(data, null, 2), this);
        // May contain sensitive info. Set secure permissions.
        }, function(err) {
            if (err) throw err;
            fs.chmod(paths.user, 0600, this);
        // Update runtime config in memory.
        }, function(err) {
            if (err) return error(err);
            Bones.plugin.config = _(Bones.plugin.config).extend(data);
            return success({});
        });
        break;
    case 'delete':
        return error(new Error('Method not supported.'));
        break;
    }
};

models.Config.prototype.validate = function(attr) {
    if (attr.httpProxy && attr.httpProxy.indexOf('http://') !== 0) {
        return "HTTP Proxy must start with http://";
    }
    return this.validateAttributes(attr);
}
<commit_msg>only validate config files setting if present<commit_after>var fs = require('fs');
var path = require('path');
var Step = require('step');
var paths = {
    user: path.join(process.env.HOME, '.tilemill/config.json'),
    vendor: path.resolve(__dirname + '/../lib/config.defaults.json')
};
var create_files = require('../lib/create_files');
models.Config.defaults = JSON.parse(fs.readFileSync(paths.vendor, 'utf8'));
models.Config.prototype.sync = function(method, model, success, error) {
    if (method === 'delete') return error(new Error('Method not supported.'));

    switch (method) {
    case 'defaults':
        var data = _(defaults).clone();
        if (data.files.indexOf(process.env.HOME) === 0)
            data.files = data.files.replace(process.env.HOME, '~');
        return success(data);
        break;
    case 'read':
        var data = _(Bones.plugin.config).clone();
        if (data.files.indexOf(process.env.HOME) === 0)
            data.files = data.files.replace(process.env.HOME, '~');
        return success(data);
        break;
    case 'create':
    case 'update':
        // Filter out keys that may not be written.
        var allowedKeys = [
            'files',
            'syncAccount',
            'syncAccessToken',
            'updates',
            'updatesTime',
            'updatesVersion',
            'profile',
            'guid',
            'httpProxy',
            'verbose'
        ];
        var data = _(model.toJSON()).reduce(function(memo, val, key) {
            if (key === 'files') val = val.replace(/^~/, process.env.HOME);
            if (_(allowedKeys).include(key)) memo[key] = val;
            return memo;
        }, {});

        Step(function() {
            // validate that the 'files' option is okay to write to
            // https://github.com/mapbox/tilemill/issues/2024
            if (data.files) {
                try {
                    create_files.init_dirs(['export','cache'],data);
                    this();
                } catch (err) {
                    throw new Error('The Documents path "' + data.files + '" cannot be written to, please choose a valid location (' + err.message + ')');
                }
            } else {
                this();
            }
        }, function(err) {
            if (err) throw err;
            fs.readFile(paths.user, 'utf8', this);
        // Write changes to user config file.
        }, function(err, current) {
            if (err && err.code !== 'ENOENT') throw err;

            // Catch & blow away invalid user JSON.
            try { current = JSON.parse(current); }
            catch (e) { current = {}; }
            data = _(current).extend(data);
            fs.writeFile(paths.user, JSON.stringify(data, null, 2), this);
        // May contain sensitive info. Set secure permissions.
        }, function(err) {
            if (err) throw err;
            fs.chmod(paths.user, 0600, this);
        // Update runtime config in memory.
        }, function(err) {
            if (err) return error(err);
            Bones.plugin.config = _(Bones.plugin.config).extend(data);
            return success({});
        });
        break;
    case 'delete':
        return error(new Error('Method not supported.'));
        break;
    }
};

models.Config.prototype.validate = function(attr) {
    if (attr.httpProxy && attr.httpProxy.indexOf('http://') !== 0) {
        return "HTTP Proxy must start with http://";
    }
    return this.validateAttributes(attr);
}
<|endoftext|>


<commit_before>import { clone as objectClone } from '../../utils/object.js'
import { factory } from '../../utils/factory.js'

const name = 'clone'
const dependencies = ['typed']

export const createClone = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => {
  /**
   * Clone an object.
   *
   * Syntax:
   *
   *     math.clone(x)
   *
   * Examples:
   *
   *    math.clone(3.5)                   // returns number 3.5
   *    math.clone(math.complex('2-4i') // returns Complex 2 - 4i
   *    math.clone(math.unit(45, 'deg'))  // returns Unit 45 deg
   *    math.clone([[1, 2], [3, 4]])      // returns Array [[1, 2], [3, 4]]
   *    math.clone("hello world")         // returns string "hello world"
   *
   * @param {*} x   Object to be cloned
   * @return {*} A clone of object x
   */
  return typed(name, {
    any: objectClone
  })
})
<commit_msg>Update clone.js with doc about deep copying arrays (#2040)<commit_after>import { clone as objectClone } from '../../utils/object.js'
import { factory } from '../../utils/factory.js'

const name = 'clone'
const dependencies = ['typed']

export const createClone = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => {
  /**
   * Clone an object. Will make a deep copy of the data.
   *
   * Syntax:
   *
   *     math.clone(x)
   *
   * Examples:
   *
   *    math.clone(3.5)                   // returns number 3.5
   *    math.clone(math.complex('2-4i') // returns Complex 2 - 4i
   *    math.clone(math.unit(45, 'deg'))  // returns Unit 45 deg
   *    math.clone([[1, 2], [3, 4]])      // returns Array [[1, 2], [3, 4]]
   *    math.clone("hello world")         // returns string "hello world"
   *
   * @param {*} x   Object to be cloned
   * @return {*} A clone of object x
   */
  return typed(name, {
    any: objectClone
  })
})
<|endoftext|>