<commit_before>import { createSelector } from 'reselect';
import _ from 'lodash';
// ======================================================
// Helpers
// ======================================================
import { getPhysicalUsedSlotNo } from '../helpers/global';

const getProducts = state => state.products;
const getPromotionSets = state => state.promotionSets;
const getMobileTopup = state => state.mobileTopup;
const getDiscounts = state => state.discounts;
const getEvent = state => state.event;

const getSelectedEvent = createSelector(
  [getEvent],
  (event) => {
    return event.selectedEvent;
  }
);

const getEventId = createSelector(
  [getSelectedEvent],
  (event) => {
    return event.eventId;
  }
);

const getEventProduct = createSelector(
  [getSelectedEvent],
  (event) => {
    return event.product || {}
  }
);

const getEventRewards = createSelector(
  [getSelectedEvent],
  (event) => {
    return event.rewards || [];
  }
);

const getEventNextReward = createSelector(
  [getEventRewards],
  (eventRewards) => {
    if (_.size(eventRewards) <= 0) return '';
    const nextItem = _.find(eventRewards, item => item.completed === false);
    console.log('getEventNextReward', nextItem);
    return nextItem;
  }
);

const getEventRewardInstantly = createSelector(
  [getEventRewards],
  (eventRewards) => {
    return _.find(eventRewards, eventReward => _.includes(['VENDING_MACHINE_NOW'], eventReward.channel.toUpperCase()));
  }
);

const getEventNextRewardRoute = createSelector(
  [getEventNextReward],
  (eventNextReward) => {
    if (!eventNextReward) return 'REWARD_NOT_FOUND';
    const targetProduct = eventNextReward.name;
    switch (targetProduct) {
      case 'topup':
        return '/topup';
      case 'product':
        return '/product/single';
      default:
        return 'PAGE_NOT_FOUND';
    }
  }
);

const getEventInputs = createSelector(
  [getSelectedEvent],
  (selectedEvent) => {
    return selectedEvent.inputs || [];
  }
);

const getEventBarcodeOrQrcodeInput = createSelector(
  [getEventInputs],
  (eventInputs) => {
    if (_.size(eventInputs) <= 0) return '';
    const barcodeOrQrcodeInput = _.find(eventInputs, item => item.completed === true && _.includes(['BARCODE', 'QR_CODE'], item.name));
    return barcodeOrQrcodeInput ? barcodeOrQrcodeInput.value : '';
  }
);

const getEventNextInput = createSelector(
  [getEventInputs],
  (eventInputs) => {
    if (_.size(eventInputs) <= 0) return '';
    const nextItem = _.find(eventInputs, item => item.completed === false);
    return nextItem ? nextItem.name.toUpperCase() : '';
  }
);

const getEventWatches = createSelector(
  [getSelectedEvent],
  (selectedEvent) => {
    return selectedEvent.watches || [];
  }
);

const getEventNextWatch = createSelector(
  [getEventWatches],
  (eventWatches) => {
    if (_.size(eventWatches) <= 0) return '';
    const nextItem = _.find(eventWatches, item => item.completed === false);
    return nextItem;
  }
);

const verifyEventShouldSendReward = createSelector(
  [getEventRewards],
  (eventRewards) => {
    const foundChannel = _.some(eventRewards, reward => _.includes(['SMS', 'EMAIL'], reward.channel.toUpperCase()));
    return foundChannel;
  }
);

const verifyEventShouldUseRewardInstantly = createSelector(
  [getEventRewards],
  (eventRewards) => {
    const foundChannel = _.some(eventRewards, reward => _.includes(['VENDING_MACHINE'], reward.channel.toUpperCase()));
    return foundChannel;
  }
);

const getDiscount = createSelector(
  [getDiscounts],
  (discounts) => {
    return _.head(discounts);
  }
);

const getDiscountAmount = createSelector(
  [getDiscounts],
  (discounts) => {
    return Number(_.sumBy(discounts, discount => Number(discount.value)));
  }
);

const verifyOrderHasDiscount = createSelector(
  [getDiscounts],
  (discounts) => {
    return _.size(discounts) > 0;
  }
);

const getTopupMSISDN = createSelector([getMobileTopup], mobileTopup => mobileTopup.MSISDN || '');

const getSelectedMobileTopupProvider = createSelector(
  [getMobileTopup],
  mobileTopup => mobileTopup.selectedMobileTopupProvider || {},
);

const getSelectedMobileTopupValue = createSelector(
  [getMobileTopup],
  mobileTopup => mobileTopup.selectedMobileTopupValue || {},
);

const getSelectedMobileTopupPrice = createSelector(
  [getSelectedMobileTopupValue],
  selectedMobileTopupValue => Number(selectedMobileTopupValue.value),
);

const getSelectedMobileTopupFee = createSelector(
  [getSelectedMobileTopupValue],
  selectedMobileTopupValue => Number(selectedMobileTopupValue.fee),
);

const getSelectedMobileTopupTotalPrice = createSelector(
  [getSelectedMobileTopupValue, getSelectedMobileTopupPrice],
  (selectedMobileTopupValue, selectedMobileTopupPrice) =>
    selectedMobileTopupPrice + Number(selectedMobileTopupValue.fee),
);

const getMobileTopupName = createSelector(
  [getSelectedMobileTopupProvider],
  selectedMobileTopupProvider => selectedMobileTopupProvider.name || '',
);

const getMobileTopupBanner = createSelector(
  [getSelectedMobileTopupProvider],
  selectedMobileTopupProvider => selectedMobileTopupProvider.banner || '',
);

const getMobileTopupServiceCode = createSelector(
  [getSelectedMobileTopupProvider],
  selectedMobileTopupProvider => selectedMobileTopupProvider.serviceCode || '',
);

const getMobileTopupToService = createSelector(
  [getMobileTopupServiceCode, getTopupMSISDN, getSelectedMobileTopupPrice, getSelectedMobileTopupFee],
  (serviceCode, MSISDN, mobileTopupValue, mobileTopupFee) => ({
    serviceCode,
    MSISDN,
    mobileTopupValue,
    mobileTopupFee
  }),
);

const getSingleProduct = createSelector([getProducts], products => _.head(products));

const getPromotionSet = createSelector([getPromotionSets], promotionSets => _.head(promotionSets));

const getPromotionSetProducts = createSelector([getPromotionSet], promotionSet =>
  _.get(promotionSet, 'products', []),
);

const getSingleProductPrice = createSelector([getSingleProduct], singleProduct =>
  Number(_.get(singleProduct, 'price', '')),
);

const getSingleProductBgImage = createSelector([getSingleProduct], singleProduct => {
  const bgImage = _.get(singleProduct, 'imageBig', '');
  return bgImage !== '' ? bgImage : undefined;
});

const getPromotionSetPrice = createSelector([getPromotionSet], promotionSet =>
  Number(_.get(promotionSet, 'price', '')),
);

const getPromotionSetFirstProductPrice = createSelector([getPromotionSetProducts], products =>
  Number(_.head(products).price),
);

const getPromotionSetFirstProductBgImage = createSelector([getSingleProduct], (singleProduct) => {
  const bgImage = _.get(singleProduct, 'imageBig', '');
  return bgImage !== '' ? bgImage : undefined;
});

const getDroppedProducts = createSelector([getProducts], products =>
  _.filter(products, product => product.isDropped),
);

const getDroppedProductSummaryPrice = createSelector([getDroppedProducts], products =>
  _.sumBy(products, product => Number(product.price)),
);

const getProductToDrop = createSelector([getProducts], products =>
  _.find(products, product => !product.isDropped),
);

const getDropProductTargetPhysical = createSelector(
  [getProductToDrop],
  productToDrop => {
    const targetPhysical = _.find(productToDrop.physicals || [], physical => physical.isFree === false && physical.canDrop === true);
    console.log('getDropProductTargetRowColumn', targetPhysical);
    return targetPhysical;
  }
);

const getDropProductTargetRowColumn = createSelector(
  [getDropProductTargetPhysical],
  targetPhysical => {
    console.log('=== PHYSICAL SLOT ====', `${targetPhysical.row}${targetPhysical.col}`);
    return `${targetPhysical.row}${targetPhysical.col}`;
  }
);

const getFreeDropProductTargetPhysical = createSelector(
  [getProductToDrop],
  productToDrop => {
    const targetPhysical = _.find(productToDrop.physicals || [], physical => physical.isFree === true && physical.canDrop === true);
    console.log('getFreeDropProductTargetPhysical', targetPhysical);
    return targetPhysical;
  }
);

const getFreeDropProductTargetRowColumn = createSelector(
  [getFreeDropProductTargetPhysical],
  targetPhysical => {
    console.log('=== PHYSICAL SLOT ====', `${targetPhysical.row}${targetPhysical.col}`);
    return `${targetPhysical.row}${targetPhysical.col}`;
  }
);

const verifyProductToFreeDropHasAvailablePhysical = createSelector(
  [getFreeDropProductTargetPhysical],
  (targetPhysical) => {
    return targetPhysical !== undefined;
  }
);

const verifyProductToDropHasAvailablePhysical = createSelector(
  [getDropProductTargetPhysical],
  (targetPhysical) => {
    return targetPhysical !== undefined;
  }
);

const verifyOrderHasProduct = createSelector([getProducts], products => _.size(products) > 0);

const verifyHasDroppedProduct = createSelector([getProducts], products =>
  _.some(products, product => product.isDropped),
);

const verifyAllOrderDropped = createSelector([getProducts], products =>
  _.every(products, product => product.isDropped),
);

const verifyOrderHasPromotionSet = createSelector(
  [getPromotionSets],
  promotionSets => _.size(promotionSets) > 0,
);

const verifyMobileTopupOrder = createSelector(
  [getSelectedMobileTopupProvider],
  selectedMobileTopup => !_.isEmpty(selectedMobileTopup),
);

const verifyIsEventOrder = createSelector(
  [getSelectedEvent],
  selectedEvent => !_.isEmpty(selectedEvent),
);

const getOrderGrandTotalAmount = createSelector(
  [
    verifyOrderHasPromotionSet,
    verifyMobileTopupOrder,
    getSelectedMobileTopupTotalPrice,
    getSingleProductPrice,
    getPromotionSetPrice,
    getDiscountAmount,
  ],
  (
    hasPromotionSet,
    hasMobileTopup,
    selectedMobileTopupTotalPrice,
    singleProductPrice,
    promotionSetPrice,
    discountAmount,
  ) => {
    if (hasMobileTopup) return selectedMobileTopupTotalPrice - discountAmount;
    return hasPromotionSet ? promotionSetPrice - discountAmount : singleProductPrice - discountAmount;
  },
);

const getOrderType = createSelector(
  [
    verifyOrderHasPromotionSet,
    verifyMobileTopupOrder,
  ],
  (
    hasPromotionSet,
    hasMobileTopup,
  ) => {
    if (hasPromotionSet) return 'promotionSet';
    if (hasMobileTopup) return 'mobileTopup';
    return 'singleProduct';
  }
);

const getPaymentBgImage = createSelector(
  [
    getOrderType,
    getSingleProductBgImage,
    getPromotionSetFirstProductBgImage,
    getMobileTopupBanner
  ],
  (
    orderType,
    singleProductBgImage,
    promotionSetBgImage,
    mobileTopupBgImage
  ) => {
    if (orderType === 'promotionSet') return promotionSetBgImage;
    if (orderType === 'mobileTopup') return mobileTopupBgImage;
    return singleProductBgImage;
  }
);

const getOrderPoId = createSelector(
  [getOrderType, getPromotionSet, getSingleProduct, getMobileTopup],
  (orderType, promotionSet, singleProduct, mobileTopup) => {
    switch (orderType) {
      case 'promotionSet':
        return promotionSet.id || '';
      case 'singleProduct':
        return singleProduct.id || '';
      case 'mobileTopup':
        return mobileTopup.id || '';
      default:
    }
  }
);

const getOrderDiscountType = createSelector(
  [getOrderType],
  (orderType) => {
    return orderType === 'mobileTopup' ? 'topup' : 'product';
  }
);

const toSubmitOrder = createSelector(
  [
    getOrderType,
    verifyIsEventOrder,
    getSingleProduct,
    getPromotionSet,
    verifyOrderHasDiscount,
    getDiscount,
    getProducts,
    getSelectedEvent,
    getEventInputs
  ],
  (
    orderType,
    isEventOrder,
    singleProduct,
    promotionSet,
    hasDiscount,
    discount,
    products,
    selectedEvent,
    eventInputs
  ) => {
    let id;
    const poId = _.join(_.map(products, product => product.id), ',');
    let saleType;
    const qty = 1;
    const unitPrice = _.join(_.map(products, product => product.price), ',');
    const slotNo = _.join(_.map(products, product => getPhysicalUsedSlotNo(product)), ',');
    let barcode;
    let lineQrcode;
    switch (orderType) {
      case 'singleProduct':
        saleType = 'Normal';
        break;
      case 'promotionSet':
        id = promotionSet.id;
        saleType = 'Promotion';
        break;
      default:
        break;
    }
    if (isEventOrder) {
      id = selectedEvent.id;
      saleType = 'Activities';
      barcode = _.find(eventInputs, input => input.name === 'BARCODE' || input.name === 'QR_CODE').value || '';
      lineQrcode = _.find(eventInputs, input => input.name === 'LINE_QR_CODE').value || '';
    }
    let discountCode = '';
    if (hasDiscount) discountCode = discount.code;
    debugger;
    return {
      id,
      poId,
      saleType,
      discountCode,
      qty,
      unitPrice,
      slotNo,
      barcode,
      lineQrcode
    };
  }
);

const verifyOrderHasFreeProduct = createSelector(
  [
    getProducts
  ],
  (
    products
  ) => {
    const freeProduct = _.find(products, product => product.price === 0);
    if (freeProduct) return true;
    return false;
  }
);

export default {
  // ======================================================
  // Event
  // ======================================================
  getEvent,
  getSelectedEvent,
  getEventId,
  getEventProduct,
  getEventRewards,
  getEventRewardInstantly,
  getEventNextReward,
  getEventNextRewardRoute,
  getEventInputs,
  getEventNextInput,
  getEventBarcodeOrQrcodeInput,
  getEventWatches,
  getEventNextWatch,
  verifyEventShouldSendReward,
  verifyEventShouldUseRewardInstantly,
  // ======================================================
  // Mobile Topup
  // ======================================================
  getMobileTopup,
  getTopupMSISDN,
  getSelectedMobileTopupProvider,
  getSelectedMobileTopupValue,
  getSelectedMobileTopupTotalPrice,
  getMobileTopupName,
  getMobileTopupBanner,
  getMobileTopupServiceCode,
  getMobileTopupToService,
  // ======================================================
  // Single Product
  // ======================================================
  getProducts,
  getSingleProduct,
  getSingleProductPrice,
  getSingleProductBgImage,
  // ======================================================
  // PromotionSet
  // ======================================================
  getPromotionSet,
  getPromotionSetProducts,
  getPromotionSetPrice,
  getPromotionSetFirstProductPrice,
  getProductToDrop,
  getDropProductTargetPhysical,
  verifyProductToDropHasAvailablePhysical,
  getDropProductTargetRowColumn,
  getFreeDropProductTargetRowColumn,
  getFreeDropProductTargetPhysical,
  verifyProductToFreeDropHasAvailablePhysical,
  getDroppedProductSummaryPrice,
  getPromotionSetFirstProductBgImage,
  // ======================================================
  // Flag
  // ======================================================
  verifyOrderHasProduct,
  verifyHasDroppedProduct,
  verifyAllOrderDropped,
  verifyOrderHasPromotionSet,
  verifyMobileTopupOrder,
  verifyIsEventOrder,
  verifyOrderHasFreeProduct,
  // ======================================================
  // Payment
  // ======================================================
  getOrderGrandTotalAmount,
  getPaymentBgImage,
  // ======================================================
  // Discount
  // ======================================================
  getDiscounts,
  getDiscount,
  getDiscountAmount,
  verifyOrderHasDiscount,
  // ======================================================
  // Order
  // ======================================================
  getOrderType,
  getOrderDiscountType,
  getOrderPoId,
  toSubmitOrder
};
<commit_msg>Fixed eventid = undefined.<commit_after>import { createSelector } from 'reselect';
import _ from 'lodash';
// ======================================================
// Helpers
// ======================================================
import { getPhysicalUsedSlotNo } from '../helpers/global';

const getProducts = state => state.products;
const getPromotionSets = state => state.promotionSets;
const getMobileTopup = state => state.mobileTopup;
const getDiscounts = state => state.discounts;
const getEvent = state => state.event;

const getSelectedEvent = createSelector(
  [getEvent],
  (event) => {
    return event.selectedEvent;
  }
);

const getEventId = createSelector(
  [getSelectedEvent],
  (event) => {
    return event.eventId;
  }
);

const getEventProduct = createSelector(
  [getSelectedEvent],
  (event) => {
    return event.product || {}
  }
);

const getEventRewards = createSelector(
  [getSelectedEvent],
  (event) => {
    return event.rewards || [];
  }
);

const getEventNextReward = createSelector(
  [getEventRewards],
  (eventRewards) => {
    if (_.size(eventRewards) <= 0) return '';
    const nextItem = _.find(eventRewards, item => item.completed === false);
    console.log('getEventNextReward', nextItem);
    return nextItem;
  }
);

const getEventRewardInstantly = createSelector(
  [getEventRewards],
  (eventRewards) => {
    return _.find(eventRewards, eventReward => _.includes(['VENDING_MACHINE_NOW'], eventReward.channel.toUpperCase()));
  }
);

const getEventNextRewardRoute = createSelector(
  [getEventNextReward],
  (eventNextReward) => {
    if (!eventNextReward) return 'REWARD_NOT_FOUND';
    const targetProduct = eventNextReward.name;
    switch (targetProduct) {
      case 'topup':
        return '/topup';
      case 'product':
        return '/product/single';
      default:
        return 'PAGE_NOT_FOUND';
    }
  }
);

const getEventInputs = createSelector(
  [getSelectedEvent],
  (selectedEvent) => {
    return selectedEvent.inputs || [];
  }
);

const getEventBarcodeOrQrcodeInput = createSelector(
  [getEventInputs],
  (eventInputs) => {
    if (_.size(eventInputs) <= 0) return '';
    const barcodeOrQrcodeInput = _.find(eventInputs, item => item.completed === true && _.includes(['BARCODE', 'QR_CODE'], item.name));
    return barcodeOrQrcodeInput ? barcodeOrQrcodeInput.value : '';
  }
);

const getEventNextInput = createSelector(
  [getEventInputs],
  (eventInputs) => {
    if (_.size(eventInputs) <= 0) return '';
    const nextItem = _.find(eventInputs, item => item.completed === false);
    return nextItem ? nextItem.name.toUpperCase() : '';
  }
);

const getEventWatches = createSelector(
  [getSelectedEvent],
  (selectedEvent) => {
    return selectedEvent.watches || [];
  }
);

const getEventNextWatch = createSelector(
  [getEventWatches],
  (eventWatches) => {
    if (_.size(eventWatches) <= 0) return '';
    const nextItem = _.find(eventWatches, item => item.completed === false);
    return nextItem;
  }
);

const verifyEventShouldSendReward = createSelector(
  [getEventRewards],
  (eventRewards) => {
    const foundChannel = _.some(eventRewards, reward => _.includes(['SMS', 'EMAIL'], reward.channel.toUpperCase()));
    return foundChannel;
  }
);

const verifyEventShouldUseRewardInstantly = createSelector(
  [getEventRewards],
  (eventRewards) => {
    const foundChannel = _.some(eventRewards, reward => _.includes(['VENDING_MACHINE'], reward.channel.toUpperCase()));
    return foundChannel;
  }
);

const getDiscount = createSelector(
  [getDiscounts],
  (discounts) => {
    return _.head(discounts);
  }
);

const getDiscountAmount = createSelector(
  [getDiscounts],
  (discounts) => {
    return Number(_.sumBy(discounts, discount => Number(discount.value)));
  }
);

const verifyOrderHasDiscount = createSelector(
  [getDiscounts],
  (discounts) => {
    return _.size(discounts) > 0;
  }
);

const getTopupMSISDN = createSelector([getMobileTopup], mobileTopup => mobileTopup.MSISDN || '');

const getSelectedMobileTopupProvider = createSelector(
  [getMobileTopup],
  mobileTopup => mobileTopup.selectedMobileTopupProvider || {},
);

const getSelectedMobileTopupValue = createSelector(
  [getMobileTopup],
  mobileTopup => mobileTopup.selectedMobileTopupValue || {},
);

const getSelectedMobileTopupPrice = createSelector(
  [getSelectedMobileTopupValue],
  selectedMobileTopupValue => Number(selectedMobileTopupValue.value),
);

const getSelectedMobileTopupFee = createSelector(
  [getSelectedMobileTopupValue],
  selectedMobileTopupValue => Number(selectedMobileTopupValue.fee),
);

const getSelectedMobileTopupTotalPrice = createSelector(
  [getSelectedMobileTopupValue, getSelectedMobileTopupPrice],
  (selectedMobileTopupValue, selectedMobileTopupPrice) =>
    selectedMobileTopupPrice + Number(selectedMobileTopupValue.fee),
);

const getMobileTopupName = createSelector(
  [getSelectedMobileTopupProvider],
  selectedMobileTopupProvider => selectedMobileTopupProvider.name || '',
);

const getMobileTopupBanner = createSelector(
  [getSelectedMobileTopupProvider],
  selectedMobileTopupProvider => selectedMobileTopupProvider.banner || '',
);

const getMobileTopupServiceCode = createSelector(
  [getSelectedMobileTopupProvider],
  selectedMobileTopupProvider => selectedMobileTopupProvider.serviceCode || '',
);

const getMobileTopupToService = createSelector(
  [getMobileTopupServiceCode, getTopupMSISDN, getSelectedMobileTopupPrice, getSelectedMobileTopupFee],
  (serviceCode, MSISDN, mobileTopupValue, mobileTopupFee) => ({
    serviceCode,
    MSISDN,
    mobileTopupValue,
    mobileTopupFee
  }),
);

const getSingleProduct = createSelector([getProducts], products => _.head(products));

const getPromotionSet = createSelector([getPromotionSets], promotionSets => _.head(promotionSets));

const getPromotionSetProducts = createSelector([getPromotionSet], promotionSet =>
  _.get(promotionSet, 'products', []),
);

const getSingleProductPrice = createSelector([getSingleProduct], singleProduct =>
  Number(_.get(singleProduct, 'price', '')),
);

const getSingleProductBgImage = createSelector([getSingleProduct], singleProduct => {
  const bgImage = _.get(singleProduct, 'imageBig', '');
  return bgImage !== '' ? bgImage : undefined;
});

const getPromotionSetPrice = createSelector([getPromotionSet], promotionSet =>
  Number(_.get(promotionSet, 'price', '')),
);

const getPromotionSetFirstProductPrice = createSelector([getPromotionSetProducts], products =>
  Number(_.head(products).price),
);

const getPromotionSetFirstProductBgImage = createSelector([getSingleProduct], (singleProduct) => {
  const bgImage = _.get(singleProduct, 'imageBig', '');
  return bgImage !== '' ? bgImage : undefined;
});

const getDroppedProducts = createSelector([getProducts], products =>
  _.filter(products, product => product.isDropped),
);

const getDroppedProductSummaryPrice = createSelector([getDroppedProducts], products =>
  _.sumBy(products, product => Number(product.price)),
);

const getProductToDrop = createSelector([getProducts], products =>
  _.find(products, product => !product.isDropped),
);

const getDropProductTargetPhysical = createSelector(
  [getProductToDrop],
  productToDrop => {
    const targetPhysical = _.find(productToDrop.physicals || [], physical => physical.isFree === false && physical.canDrop === true);
    console.log('getDropProductTargetRowColumn', targetPhysical);
    return targetPhysical;
  }
);

const getDropProductTargetRowColumn = createSelector(
  [getDropProductTargetPhysical],
  targetPhysical => {
    console.log('=== PHYSICAL SLOT ====', `${targetPhysical.row}${targetPhysical.col}`);
    return `${targetPhysical.row}${targetPhysical.col}`;
  }
);

const getFreeDropProductTargetPhysical = createSelector(
  [getProductToDrop],
  productToDrop => {
    const targetPhysical = _.find(productToDrop.physicals || [], physical => physical.isFree === true && physical.canDrop === true);
    console.log('getFreeDropProductTargetPhysical', targetPhysical);
    return targetPhysical;
  }
);

const getFreeDropProductTargetRowColumn = createSelector(
  [getFreeDropProductTargetPhysical],
  targetPhysical => {
    console.log('=== PHYSICAL SLOT ====', `${targetPhysical.row}${targetPhysical.col}`);
    return `${targetPhysical.row}${targetPhysical.col}`;
  }
);

const verifyProductToFreeDropHasAvailablePhysical = createSelector(
  [getFreeDropProductTargetPhysical],
  (targetPhysical) => {
    return targetPhysical !== undefined;
  }
);

const verifyProductToDropHasAvailablePhysical = createSelector(
  [getDropProductTargetPhysical],
  (targetPhysical) => {
    return targetPhysical !== undefined;
  }
);

const verifyOrderHasProduct = createSelector([getProducts], products => _.size(products) > 0);

const verifyHasDroppedProduct = createSelector([getProducts], products =>
  _.some(products, product => product.isDropped),
);

const verifyAllOrderDropped = createSelector([getProducts], products =>
  _.every(products, product => product.isDropped),
);

const verifyOrderHasPromotionSet = createSelector(
  [getPromotionSets],
  promotionSets => _.size(promotionSets) > 0,
);

const verifyMobileTopupOrder = createSelector(
  [getSelectedMobileTopupProvider],
  selectedMobileTopup => !_.isEmpty(selectedMobileTopup),
);

const verifyIsEventOrder = createSelector(
  [getSelectedEvent],
  selectedEvent => !_.isEmpty(selectedEvent),
);

const getOrderGrandTotalAmount = createSelector(
  [
    verifyOrderHasPromotionSet,
    verifyMobileTopupOrder,
    getSelectedMobileTopupTotalPrice,
    getSingleProductPrice,
    getPromotionSetPrice,
    getDiscountAmount,
  ],
  (
    hasPromotionSet,
    hasMobileTopup,
    selectedMobileTopupTotalPrice,
    singleProductPrice,
    promotionSetPrice,
    discountAmount,
  ) => {
    if (hasMobileTopup) return selectedMobileTopupTotalPrice - discountAmount;
    return hasPromotionSet ? promotionSetPrice - discountAmount : singleProductPrice - discountAmount;
  },
);

const getOrderType = createSelector(
  [
    verifyOrderHasPromotionSet,
    verifyMobileTopupOrder,
  ],
  (
    hasPromotionSet,
    hasMobileTopup,
  ) => {
    if (hasPromotionSet) return 'promotionSet';
    if (hasMobileTopup) return 'mobileTopup';
    return 'singleProduct';
  }
);

const getPaymentBgImage = createSelector(
  [
    getOrderType,
    getSingleProductBgImage,
    getPromotionSetFirstProductBgImage,
    getMobileTopupBanner
  ],
  (
    orderType,
    singleProductBgImage,
    promotionSetBgImage,
    mobileTopupBgImage
  ) => {
    if (orderType === 'promotionSet') return promotionSetBgImage;
    if (orderType === 'mobileTopup') return mobileTopupBgImage;
    return singleProductBgImage;
  }
);

const getOrderPoId = createSelector(
  [getOrderType, getPromotionSet, getSingleProduct, getMobileTopup],
  (orderType, promotionSet, singleProduct, mobileTopup) => {
    switch (orderType) {
      case 'promotionSet':
        return promotionSet.id || '';
      case 'singleProduct':
        return singleProduct.id || '';
      case 'mobileTopup':
        return mobileTopup.id || '';
      default:
    }
  }
);

const getOrderDiscountType = createSelector(
  [getOrderType],
  (orderType) => {
    return orderType === 'mobileTopup' ? 'topup' : 'product';
  }
);

const toSubmitOrder = createSelector(
  [
    getOrderType,
    verifyIsEventOrder,
    getSingleProduct,
    getPromotionSet,
    verifyOrderHasDiscount,
    getDiscount,
    getProducts,
    getSelectedEvent,
    getEventInputs
  ],
  (
    orderType,
    isEventOrder,
    singleProduct,
    promotionSet,
    hasDiscount,
    discount,
    products,
    selectedEvent,
    eventInputs
  ) => {
    let id;
    const poId = _.join(_.map(products, product => product.id), ',');
    let saleType;
    const qty = 1;
    const unitPrice = _.join(_.map(products, product => product.price), ',');
    const slotNo = _.join(_.map(products, product => getPhysicalUsedSlotNo(product)), ',');
    let barcode;
    let lineQrcode;
    switch (orderType) {
      case 'singleProduct':
        saleType = 'Normal';
        break;
      case 'promotionSet':
        id = promotionSet.id;
        saleType = 'Promotion';
        break;
      default:
        break;
    }
    if (isEventOrder) {
      id = selectedEvent.eventId;
      saleType = 'Activities';
      barcode = _.find(eventInputs, input => input.name === 'BARCODE' || input.name === 'QR_CODE').value || '';
      lineQrcode = _.find(eventInputs, input => input.name === 'LINE_QR_CODE').value || '';
    }
    let discountCode = '';
    if (hasDiscount) discountCode = discount.code;
    debugger;
    return {
      id,
      poId,
      saleType,
      discountCode,
      qty,
      unitPrice,
      slotNo,
      barcode,
      lineQrcode
    };
  }
);

const verifyOrderHasFreeProduct = createSelector(
  [
    getProducts
  ],
  (
    products
  ) => {
    const freeProduct = _.find(products, product => product.price === 0);
    if (freeProduct) return true;
    return false;
  }
);

export default {
  // ======================================================
  // Event
  // ======================================================
  getEvent,
  getSelectedEvent,
  getEventId,
  getEventProduct,
  getEventRewards,
  getEventRewardInstantly,
  getEventNextReward,
  getEventNextRewardRoute,
  getEventInputs,
  getEventNextInput,
  getEventBarcodeOrQrcodeInput,
  getEventWatches,
  getEventNextWatch,
  verifyEventShouldSendReward,
  verifyEventShouldUseRewardInstantly,
  // ======================================================
  // Mobile Topup
  // ======================================================
  getMobileTopup,
  getTopupMSISDN,
  getSelectedMobileTopupProvider,
  getSelectedMobileTopupValue,
  getSelectedMobileTopupTotalPrice,
  getMobileTopupName,
  getMobileTopupBanner,
  getMobileTopupServiceCode,
  getMobileTopupToService,
  // ======================================================
  // Single Product
  // ======================================================
  getProducts,
  getSingleProduct,
  getSingleProductPrice,
  getSingleProductBgImage,
  // ======================================================
  // PromotionSet
  // ======================================================
  getPromotionSet,
  getPromotionSetProducts,
  getPromotionSetPrice,
  getPromotionSetFirstProductPrice,
  getProductToDrop,
  getDropProductTargetPhysical,
  verifyProductToDropHasAvailablePhysical,
  getDropProductTargetRowColumn,
  getFreeDropProductTargetRowColumn,
  getFreeDropProductTargetPhysical,
  verifyProductToFreeDropHasAvailablePhysical,
  getDroppedProductSummaryPrice,
  getPromotionSetFirstProductBgImage,
  // ======================================================
  // Flag
  // ======================================================
  verifyOrderHasProduct,
  verifyHasDroppedProduct,
  verifyAllOrderDropped,
  verifyOrderHasPromotionSet,
  verifyMobileTopupOrder,
  verifyIsEventOrder,
  verifyOrderHasFreeProduct,
  // ======================================================
  // Payment
  // ======================================================
  getOrderGrandTotalAmount,
  getPaymentBgImage,
  // ======================================================
  // Discount
  // ======================================================
  getDiscounts,
  getDiscount,
  getDiscountAmount,
  verifyOrderHasDiscount,
  // ======================================================
  // Order
  // ======================================================
  getOrderType,
  getOrderDiscountType,
  getOrderPoId,
  toSubmitOrder
};
<|endoftext|>


<commit_before>var extend = require('xtend')
var getFormData = require('form-data-set/element')

var ENTER = 13

module.exports = SubmitSinkHandler

function SubmitSinkHandler(sink, data) {
    if (!(this instanceof SubmitSinkHandler)) {
        return new SubmitSinkHandler(sink, data)
    }

    this.sink = sink
    this.data = data
    this.id = sink.id
    this.type = 'submit'
}

SubmitSinkHandler.prototype.handleEvent = handleEvent

function handleEvent(ev) {
    var target = ev.target

    var isValid =
        (ev.type === 'click' && target.tagName === 'BUTTON') ||
        (
            (target.type === 'text' || target.tagName === 'TEXTAREA') &&
            (ev.keyCode === ENTER && !ev.shiftKey && ev.type === 'input')
        )

    if (!isValid) {
        return
    }

    var value = getFormData(ev.currentTarget)
    var data = extend(value, this.data)

    if (typeof this.sink === 'function') {
        this.sink(data)
    } else {
        this.sink.write(data)
    }
}
<commit_msg>needs to be keydown<commit_after>var extend = require('xtend')
var getFormData = require('form-data-set/element')

var ENTER = 13

module.exports = SubmitSinkHandler

function SubmitSinkHandler(sink, data) {
    if (!(this instanceof SubmitSinkHandler)) {
        return new SubmitSinkHandler(sink, data)
    }

    this.sink = sink
    this.data = data
    this.id = sink.id
    this.type = 'submit'
}

SubmitSinkHandler.prototype.handleEvent = handleEvent

function handleEvent(ev) {
    var target = ev.target

    var isValid =
        (ev.type === 'click' && target.tagName === 'BUTTON') ||
        (
            (target.type === 'text' || target.tagName === 'TEXTAREA') &&
            (ev.keyCode === ENTER && !ev.shiftKey && ev.type === 'keydown')
        )

    if (!isValid) {
        return
    }

    var value = getFormData(ev.currentTarget)
    var data = extend(value, this.data)

    if (typeof this.sink === 'function') {
        this.sink(data)
    } else {
        this.sink.write(data)
    }
}
<|endoftext|>


<commit_before>"use strict";
var QUnit = require("steal-qunit");
var DefineMap = require("can-define/map/map");
var Observation = require("can-observation");
var canTypes = require("can-types");
var each = require("can-util/js/each/each");
var sealWorks = (function() {
	try {
		var o = {};
		Object.seal(o);
		o.prop = true;
		return false;
	} catch(e) {
		return true;
	}
})();

QUnit.module("can-define/map/map");

QUnit.test("Map is an event emitter", function (assert) {
	var Base = DefineMap.extend({});
	assert.ok(Base.on, 'Base has event methods.');
	var Map = Base.extend({});
	assert.ok(Map.on, 'Map has event methods.');
});

QUnit.test("creating an instance", function(){
    var map = new DefineMap({prop: "foo"});
    map.on("prop", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "BAR");
        QUnit.equal(oldVal, "foo");
    });

    map.prop ="BAR";
});

QUnit.test("creating an instance with nested prop", function(){

    var map = new DefineMap({name: {first: "Justin"}});

    map.name.on("first", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "David");
        QUnit.equal(oldVal, "Justin");
    });

    map.name.first ="David";
});

QUnit.test("extending", function(){
    var MyMap = DefineMap.extend({
        prop: {}
    });

    var map = new MyMap();
    map.on("prop", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "BAR");
        QUnit.equal(oldVal, undefined);
    });

    map.prop = "BAR";
});

QUnit.test("loop only through defined serializable props", function(){
    var MyMap = DefineMap.extend({
        propA: {},
        propB: {serialize: false},
        propC: {
            get: function(){
                return this.propA;
            }
        }
    });
    var inst = new MyMap({propA: 1, propB: 2});
    QUnit.deepEqual(Object.keys(inst.get()), ["propA"]);

});

QUnit.test("get and set can setup expandos", function(){
    var map = new DefineMap();
    var oi = new Observation(function(){
        return map.get("foo");
    },null,{
        updater: function(newVal){
            QUnit.equal(newVal, "bar", "updated to bar");
        }
    });
    oi.start();

    map.set("foo","bar");

});

QUnit.test("default settings", function(){
    var MyMap = DefineMap.extend({
        "*": "string",
        foo: {}
    });

    var m = new MyMap();
    m.set("foo",123);
    QUnit.ok(m.get("foo") === "123");

});

QUnit.test("default settings on unsealed", function(){
    var MyMap = DefineMap.extend({
        seal: false
    },{
        "*": "string"
    });

    var m = new MyMap();
    m.set("foo",123);
    QUnit.ok(m.get("foo") === "123");

});

if (!System.isEnv('production')) {
	QUnit.test("extends sealed objects (#48)", function(){
		var Map1 = DefineMap.extend({ seal: true }, {
			name: {
				get: function(curVal){
					return "computed " + curVal;
				}
			}
		});
		var Map2 = Map1.extend({ seal: false }, {});
		var Map3 = Map2.extend({ seal: true }, {});

		var map1 = new Map1({ name: "Justin" });
		try {
			map1.foo = "bar";
			if (map1.foo) {
				QUnit.ok(false, "map1 not sealed");
			} else {
				QUnit.ok(true, "map1 sealed - silent failure");
			}
		} catch(ex) {
			QUnit.ok(true, "map1 sealed");
		}
		QUnit.equal(map1.name, "computed Justin", "map1.name property is computed");

		var map2 = new Map2({ name: "Brian" });
		try {
			map2.foo = "bar";
			if (map2.foo) {
				QUnit.ok(true, "map2 not sealed");
			} else {
				QUnit.ok(false, "map2 sealed");
			}
		} catch (ex) {
			QUnit.ok(false, "map2 sealed");
		}
		QUnit.equal(map2.name, "computed Brian", "map2.name property is computed");

		var map3 = new Map3({ name: "Curtis" });
		try {
			map3.foo = "bar";
			if (map3.foo) {
				QUnit.ok(false, "map3 not sealed");
			} else {
				QUnit.ok(true, "map3 sealed");
			}
		} catch (ex) {
			QUnit.ok(true, "map3 sealed");
		}
		QUnit.equal(map3.name, "computed Curtis", "map3.name property is computed");
	});
}

QUnit.test("get with dynamically added properties", function(){
    var map = new DefineMap();
    map.set("a",1);
    map.set("b",2);
    QUnit.deepEqual(map.get(), {a: 1, b:2});
});

QUnit.test("set multiple props", function(){
    var map = new DefineMap();
    map.set({a: 0, b: 2});

    QUnit.deepEqual(map.get(), {a: 0, b:2});

    map.set({a: 2}, true);

    QUnit.deepEqual(map.get(), {a: 2});

    map.set({foo: {bar: "VALUE"}});

    QUnit.deepEqual(map.get(), {foo: {bar: "VALUE"}, a: 2});
});

QUnit.test("serialize responds to added props", function(){
    var map = new DefineMap();
    var oi = new Observation(function(){
        return map.serialize();
    },null,{
        updater: function(newVal){
            QUnit.deepEqual(newVal, {a: 1, b: 2}, "updated right");
        }
    });
    oi.start();

    map.set({a: 1, b: 2});
});

QUnit.test("initialize an undefined property", function(){
    var MyMap = DefineMap.extend({seal: false},{});
    var instance = new MyMap({foo:"bar"});

    equal(instance.foo, "bar");
});

QUnit.test("set an already initialized null property", function(){
  var map = new DefineMap({ foo: null });
  map.set({ foo: null });

  equal(map.foo, null);
});

QUnit.test("creating a new key doesn't cause two changes", 1, function(){
    var map = new DefineMap();
    var oi = new Observation(function(){
        return map.serialize();
    },null,{
        updater: function(newVal){
            QUnit.deepEqual(newVal, {a: 1}, "updated right");
        }
    });
    oi.start();

    map.set("a", 1);
});

QUnit.test("setting nested object", function(){
    var m = new DefineMap({});

    m.set({foo: {}});
    m.set({foo: {}});
    QUnit.deepEqual(m.get(), {foo: {}});
});

QUnit.test("passing a DefineMap to DefineMap (#33)", function(){
    var MyMap = DefineMap.extend({foo: "observable"});
    var m = new MyMap({foo: {}, bar: {}});

    var m2 = new MyMap(m);
    QUnit.deepEqual(m.get(), m2.get());
    QUnit.ok(m.foo === m2.foo, "defined props the same");
    QUnit.ok(m.bar === m2.bar, "expando props the same");

});

QUnit.test("serialize: function works (#38)", function(){
    var Something = DefineMap.extend({});

    var MyMap = DefineMap.extend({
        somethingRef: {
            type: function(val){
                return new Something({id: val});
            },
            serialize: function(val){
                return val.id;
            }
        },
        somethingElseRef: {
            type: function(val){
                return new Something({id: val});
            },
            serialize: false
        }
    });

    var myMap = new MyMap({somethingRef: 2, somethingElseRef: 3});

    QUnit.ok(myMap.somethingRef instanceof Something);
    QUnit.deepEqual( myMap.serialize(), {somethingRef: 2}, "serialize: function and serialize: false works");

    var MyMap2 = DefineMap.extend({
        "*": {
            serialize: function(value){
                return ""+value;
            }
        }
    });

    var myMap2 = new MyMap2({foo: 1, bar: 2});
    QUnit.deepEqual( myMap2.serialize(), {foo: "1", bar: "2"}, "serialize: function on default works");

});

QUnit.test("isMapLike", function(){
    var map = new DefineMap({});
    ok(canTypes.isMapLike(map), "is map like");
});

QUnit.test("get will not create properties", function(){
    var method = function(){};
    var MyMap = DefineMap.extend({
        method: method
    });
    var m = new MyMap();
    m.get("foo");

    QUnit.equal(m.get("method"), method);
});

QUnit.test("Properties are enumerable", function(){
  QUnit.expect(4);

  var VM = DefineMap.extend({
    foo: "string"
  });
  var vm = new VM({ foo: "bar", baz: "qux" });

  var i = 0;
  each(vm, function(value, key){
    if(i === 0) {
      QUnit.equal(key, "foo");
      QUnit.equal(value, "bar");
    } else {
      QUnit.equal(key, "baz");
      QUnit.equal(value, "qux");
    }
    i++;
  });
});

QUnit.test("Getters are not enumerable", function(){
  QUnit.expect(2);

  var MyMap = DefineMap.extend({
    foo: "string",
    baz: {
      get: function(){
        return this.foo;
      }
    }
  });

  var map = new MyMap({ foo: "bar" });

  each(map, function(value, key){
    QUnit.equal(key, "foo");
    QUnit.equal(value, "bar");
  });
});

QUnit.test("extending DefineMap constructor functions (#18)", function(){
    var AType = DefineMap.extend("AType", { aProp: {}, aMethod: function(){} });

    var BType = AType.extend("BType", { bProp: {}, bMethod: function(){} });

    var CType = BType.extend("CType", { cProp: {}, cMethod: function(){} });

    var map = new CType();

    map.on("aProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "PROP");
        QUnit.equal(oldVal, undefined);
    });
    map.on("bProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "FOO");
        QUnit.equal(oldVal, undefined);
    });
    map.on("cProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "BAR");
        QUnit.equal(oldVal, undefined);
    });

    map.aProp = "PROP";
    map.bProp = 'FOO';
    map.cProp = 'BAR';
    QUnit.ok(map.aMethod);
    QUnit.ok(map.bMethod);
    QUnit.ok(map.cMethod);
});

QUnit.test("extending DefineMap constructor functions more than once (#18)", function(){
    var AType = DefineMap.extend("AType", { aProp: {}, aMethod: function(){} });

    var BType = AType.extend("BType", { bProp: {}, bMethod: function(){} });

    var CType = AType.extend("CType", { cProp: {}, cMethod: function(){} });

    var map1 = new BType();
    var map2 = new CType();

    map1.on("aProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "PROP", "aProp newVal on map1");
        QUnit.equal(oldVal, undefined);
    });
    map1.on("bProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "FOO", "bProp newVal on map1");
        QUnit.equal(oldVal, undefined);
    });

    map2.on("aProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "PROP", "aProp newVal on map2");
        QUnit.equal(oldVal, undefined);
    });
    map2.on("cProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "BAR", "cProp newVal on map2");
        QUnit.equal(oldVal, undefined);
    });

    map1.aProp = "PROP";
    map1.bProp = 'FOO';
    map2.aProp = "PROP";
    map2.cProp = 'BAR';
    QUnit.ok(map1.aMethod, "map1 aMethod");
    QUnit.ok(map1.bMethod);
    QUnit.ok(map2.aMethod);
    QUnit.ok(map2.cMethod, "map2 cMethod");
});

QUnit.test("extending DefineMap constructor functions - value (#18)", function(){
    var AType = DefineMap.extend("AType", { aProp: {value: 1} });

    var BType = AType.extend("BType", { });

    var CType = BType.extend("CType",{ });

    var c = new CType();
    QUnit.equal( c.aProp , 1 ,"got initial value" );
});

QUnit.test("shorthand getter setter (#56)", function(){

    var Person = DefineMap.extend({
		first: "*",
		last: "*",
		get fullName() {
			return this.first + " " + this.last;
		},
		set fullName(newVal){
			var parts = newVal.split(" ");
			this.first = parts[0];
			this.last = parts[1];
		}
	});

	var p = new Person({first: "Mohamed", last: "Cherif"});

	p.on("fullName", function(ev, newVal, oldVal) {
		QUnit.equal(oldVal, "Mohamed Cherif");
		QUnit.equal(newVal, "Justin Meyer");
	});

	equal(p.fullName, "Mohamed Cherif", "fullName initialized right");

	p.fullName = "Justin Meyer";
});

QUnit.test('compute props can be set to null or undefined (#2372)', function() {
	var VM = DefineMap.extend({
		computeProp: {
			type: 'compute'
		}
	});

	var vmNull = new VM({computeProp: null});
	QUnit.equal(vmNull.get('computeProp'), null, 'computeProp is null, no error thrown');
	var vmUndef = new VM({computeProp: undefined});
	QUnit.equal(vmUndef.get('computeProp'), undefined, 'computeProp is undefined, no error thrown');
});

QUnit.test("Inheriting DefineMap .set doesn't work if prop is on base map (#74)", function(){
    var Base = DefineMap.extend({
        baseProp: "string"
    });

    var Inheriting = Base.extend();

    var inherting = new Inheriting();

    inherting.set("baseProp", "value");

    QUnit.equal(inherting.baseProp,"value", "set prop");
});

if(sealWorks && System.env.indexOf('production') < 0) {
	QUnit.test("setting not defined property", function(){
	    var MyMap = DefineMap.extend({
	        prop: {}
	    });
	    var mymap = new MyMap();

	    try {
	        mymap.notdefined = "value";
	        ok(false, "no error");
	    } catch(e) {
	        ok(true, "error thrown");
	    }
	});
}

QUnit.test(".extend errors when re-defining a property (#117)", function(){

	var A = DefineMap.extend("A", {
	    foo: {
	        type: "string",
	        value: "blah"
	    }
	});

	A.extend("B", {
	    foo: {
	        type: "string",
	        value: "flub"
	    }
	});

	var C = DefineMap.extend("C", {
	    foo: {
	        get: function() {
	            return "blah";
	        }
	    }
	});

	C.extend("D", {
	    foo: {
	        get: function() {
	            return "flub";
	        }
	    }
	});
	QUnit.ok(true, "extended without errors");
});
<commit_msg>Fix minor whitespace inconsistencies.<commit_after>"use strict";
var QUnit = require("steal-qunit");
var DefineMap = require("can-define/map/map");
var Observation = require("can-observation");
var canTypes = require("can-types");
var each = require("can-util/js/each/each");
var sealWorks = (function() {
	try {
		var o = {};
		Object.seal(o);
		o.prop = true;
		return false;
	} catch(e) {
		return true;
	}
})();

QUnit.module("can-define/map/map");

QUnit.test("Map is an event emitter", function (assert) {
	var Base = DefineMap.extend({});
	assert.ok(Base.on, 'Base has event methods.');
	var Map = Base.extend({});
	assert.ok(Map.on, 'Map has event methods.');
});

QUnit.test("creating an instance", function(){
    var map = new DefineMap({prop: "foo"});
    map.on("prop", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "BAR");
        QUnit.equal(oldVal, "foo");
    });

    map.prop = "BAR";
});

QUnit.test("creating an instance with nested prop", function(){

    var map = new DefineMap({name: {first: "Justin"}});

    map.name.on("first", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "David");
        QUnit.equal(oldVal, "Justin");
    });

    map.name.first = "David";
});

QUnit.test("extending", function(){
    var MyMap = DefineMap.extend({
        prop: {}
    });

    var map = new MyMap();
    map.on("prop", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "BAR");
        QUnit.equal(oldVal, undefined);
    });

    map.prop = "BAR";
});

QUnit.test("loop only through defined serializable props", function(){
    var MyMap = DefineMap.extend({
        propA: {},
        propB: {serialize: false},
        propC: {
            get: function(){
                return this.propA;
            }
        }
    });
    var inst = new MyMap({propA: 1, propB: 2});
    QUnit.deepEqual(Object.keys(inst.get()), ["propA"]);

});

QUnit.test("get and set can setup expandos", function(){
    var map = new DefineMap();
    var oi = new Observation(function(){
        return map.get("foo");
    },null,{
        updater: function(newVal){
            QUnit.equal(newVal, "bar", "updated to bar");
        }
    });
    oi.start();

    map.set("foo","bar");

});

QUnit.test("default settings", function(){
    var MyMap = DefineMap.extend({
        "*": "string",
        foo: {}
    });

    var m = new MyMap();
    m.set("foo",123);
    QUnit.ok(m.get("foo") === "123");

});

QUnit.test("default settings on unsealed", function(){
    var MyMap = DefineMap.extend({
        seal: false
    },{
        "*": "string"
    });

    var m = new MyMap();
    m.set("foo",123);
    QUnit.ok(m.get("foo") === "123");

});

if (!System.isEnv('production')) {
	QUnit.test("extends sealed objects (#48)", function(){
		var Map1 = DefineMap.extend({ seal: true }, {
			name: {
				get: function(curVal){
					return "computed " + curVal;
				}
			}
		});
		var Map2 = Map1.extend({ seal: false }, {});
		var Map3 = Map2.extend({ seal: true }, {});

		var map1 = new Map1({ name: "Justin" });
		try {
			map1.foo = "bar";
			if (map1.foo) {
				QUnit.ok(false, "map1 not sealed");
			} else {
				QUnit.ok(true, "map1 sealed - silent failure");
			}
		} catch(ex) {
			QUnit.ok(true, "map1 sealed");
		}
		QUnit.equal(map1.name, "computed Justin", "map1.name property is computed");

		var map2 = new Map2({ name: "Brian" });
		try {
			map2.foo = "bar";
			if (map2.foo) {
				QUnit.ok(true, "map2 not sealed");
			} else {
				QUnit.ok(false, "map2 sealed");
			}
		} catch (ex) {
			QUnit.ok(false, "map2 sealed");
		}
		QUnit.equal(map2.name, "computed Brian", "map2.name property is computed");

		var map3 = new Map3({ name: "Curtis" });
		try {
			map3.foo = "bar";
			if (map3.foo) {
				QUnit.ok(false, "map3 not sealed");
			} else {
				QUnit.ok(true, "map3 sealed");
			}
		} catch (ex) {
			QUnit.ok(true, "map3 sealed");
		}
		QUnit.equal(map3.name, "computed Curtis", "map3.name property is computed");
	});
}

QUnit.test("get with dynamically added properties", function(){
    var map = new DefineMap();
    map.set("a",1);
    map.set("b",2);
    QUnit.deepEqual(map.get(), {a: 1, b: 2});
});

QUnit.test("set multiple props", function(){
    var map = new DefineMap();
    map.set({a: 0, b: 2});

    QUnit.deepEqual(map.get(), {a: 0, b: 2});

    map.set({a: 2}, true);

    QUnit.deepEqual(map.get(), {a: 2});

    map.set({foo: {bar: "VALUE"}});

    QUnit.deepEqual(map.get(), {foo: {bar: "VALUE"}, a: 2});
});

QUnit.test("serialize responds to added props", function(){
    var map = new DefineMap();
    var oi = new Observation(function(){
        return map.serialize();
    },null,{
        updater: function(newVal){
            QUnit.deepEqual(newVal, {a: 1, b: 2}, "updated right");
        }
    });
    oi.start();

    map.set({a: 1, b: 2});
});

QUnit.test("initialize an undefined property", function(){
    var MyMap = DefineMap.extend({seal: false},{});
    var instance = new MyMap({foo: "bar"});

    equal(instance.foo, "bar");
});

QUnit.test("set an already initialized null property", function(){
  var map = new DefineMap({ foo: null });
  map.set({ foo: null });

  equal(map.foo, null);
});

QUnit.test("creating a new key doesn't cause two changes", 1, function(){
    var map = new DefineMap();
    var oi = new Observation(function(){
        return map.serialize();
    },null,{
        updater: function(newVal){
            QUnit.deepEqual(newVal, {a: 1}, "updated right");
        }
    });
    oi.start();

    map.set("a", 1);
});

QUnit.test("setting nested object", function(){
    var m = new DefineMap({});

    m.set({foo: {}});
    m.set({foo: {}});
    QUnit.deepEqual(m.get(), {foo: {}});
});

QUnit.test("passing a DefineMap to DefineMap (#33)", function(){
    var MyMap = DefineMap.extend({foo: "observable"});
    var m = new MyMap({foo: {}, bar: {}});

    var m2 = new MyMap(m);
    QUnit.deepEqual(m.get(), m2.get());
    QUnit.ok(m.foo === m2.foo, "defined props the same");
    QUnit.ok(m.bar === m2.bar, "expando props the same");

});

QUnit.test("serialize: function works (#38)", function(){
    var Something = DefineMap.extend({});

    var MyMap = DefineMap.extend({
        somethingRef: {
            type: function(val){
                return new Something({id: val});
            },
            serialize: function(val){
                return val.id;
            }
        },
        somethingElseRef: {
            type: function(val){
                return new Something({id: val});
            },
            serialize: false
        }
    });

    var myMap = new MyMap({somethingRef: 2, somethingElseRef: 3});

    QUnit.ok(myMap.somethingRef instanceof Something);
    QUnit.deepEqual( myMap.serialize(), {somethingRef: 2}, "serialize: function and serialize: false works");

    var MyMap2 = DefineMap.extend({
        "*": {
            serialize: function(value){
                return "" + value;
            }
        }
    });

    var myMap2 = new MyMap2({foo: 1, bar: 2});
    QUnit.deepEqual( myMap2.serialize(), {foo: "1", bar: "2"}, "serialize: function on default works");

});

QUnit.test("isMapLike", function(){
    var map = new DefineMap({});
    ok(canTypes.isMapLike(map), "is map like");
});

QUnit.test("get will not create properties", function(){
    var method = function(){};
    var MyMap = DefineMap.extend({
        method: method
    });
    var m = new MyMap();
    m.get("foo");

    QUnit.equal(m.get("method"), method);
});

QUnit.test("Properties are enumerable", function(){
  QUnit.expect(4);

  var VM = DefineMap.extend({
    foo: "string"
  });
  var vm = new VM({ foo: "bar", baz: "qux" });

  var i = 0;
  each(vm, function(value, key){
    if(i === 0) {
      QUnit.equal(key, "foo");
      QUnit.equal(value, "bar");
    } else {
      QUnit.equal(key, "baz");
      QUnit.equal(value, "qux");
    }
    i++;
  });
});

QUnit.test("Getters are not enumerable", function(){
  QUnit.expect(2);

  var MyMap = DefineMap.extend({
    foo: "string",
    baz: {
      get: function(){
        return this.foo;
      }
    }
  });

  var map = new MyMap({ foo: "bar" });

  each(map, function(value, key){
    QUnit.equal(key, "foo");
    QUnit.equal(value, "bar");
  });
});

QUnit.test("extending DefineMap constructor functions (#18)", function(){
    var AType = DefineMap.extend("AType", { aProp: {}, aMethod: function(){} });

    var BType = AType.extend("BType", { bProp: {}, bMethod: function(){} });

    var CType = BType.extend("CType", { cProp: {}, cMethod: function(){} });

    var map = new CType();

    map.on("aProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "PROP");
        QUnit.equal(oldVal, undefined);
    });
    map.on("bProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "FOO");
        QUnit.equal(oldVal, undefined);
    });
    map.on("cProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "BAR");
        QUnit.equal(oldVal, undefined);
    });

    map.aProp = "PROP";
    map.bProp = 'FOO';
    map.cProp = 'BAR';
    QUnit.ok(map.aMethod);
    QUnit.ok(map.bMethod);
    QUnit.ok(map.cMethod);
});

QUnit.test("extending DefineMap constructor functions more than once (#18)", function(){
    var AType = DefineMap.extend("AType", { aProp: {}, aMethod: function(){} });

    var BType = AType.extend("BType", { bProp: {}, bMethod: function(){} });

    var CType = AType.extend("CType", { cProp: {}, cMethod: function(){} });

    var map1 = new BType();
    var map2 = new CType();

    map1.on("aProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "PROP", "aProp newVal on map1");
        QUnit.equal(oldVal, undefined);
    });
    map1.on("bProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "FOO", "bProp newVal on map1");
        QUnit.equal(oldVal, undefined);
    });

    map2.on("aProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "PROP", "aProp newVal on map2");
        QUnit.equal(oldVal, undefined);
    });
    map2.on("cProp", function(ev, newVal, oldVal){
        QUnit.equal(newVal, "BAR", "cProp newVal on map2");
        QUnit.equal(oldVal, undefined);
    });

    map1.aProp = "PROP";
    map1.bProp = 'FOO';
    map2.aProp = "PROP";
    map2.cProp = 'BAR';
    QUnit.ok(map1.aMethod, "map1 aMethod");
    QUnit.ok(map1.bMethod);
    QUnit.ok(map2.aMethod);
    QUnit.ok(map2.cMethod, "map2 cMethod");
});

QUnit.test("extending DefineMap constructor functions - value (#18)", function(){
    var AType = DefineMap.extend("AType", { aProp: {value: 1} });

    var BType = AType.extend("BType", { });

    var CType = BType.extend("CType",{ });

    var c = new CType();
    QUnit.equal( c.aProp , 1 ,"got initial value" );
});

QUnit.test("shorthand getter setter (#56)", function(){

    var Person = DefineMap.extend({
		first: "*",
		last: "*",
		get fullName() {
			return this.first + " " + this.last;
		},
		set fullName(newVal){
			var parts = newVal.split(" ");
			this.first = parts[0];
			this.last = parts[1];
		}
	});

	var p = new Person({first: "Mohamed", last: "Cherif"});

	p.on("fullName", function(ev, newVal, oldVal) {
		QUnit.equal(oldVal, "Mohamed Cherif");
		QUnit.equal(newVal, "Justin Meyer");
	});

	equal(p.fullName, "Mohamed Cherif", "fullName initialized right");

	p.fullName = "Justin Meyer";
});

QUnit.test('compute props can be set to null or undefined (#2372)', function() {
	var VM = DefineMap.extend({
		computeProp: {
			type: 'compute'
		}
	});

	var vmNull = new VM({computeProp: null});
	QUnit.equal(vmNull.get('computeProp'), null, 'computeProp is null, no error thrown');
	var vmUndef = new VM({computeProp: undefined});
	QUnit.equal(vmUndef.get('computeProp'), undefined, 'computeProp is undefined, no error thrown');
});

QUnit.test("Inheriting DefineMap .set doesn't work if prop is on base map (#74)", function(){
    var Base = DefineMap.extend({
        baseProp: "string"
    });

    var Inheriting = Base.extend();

    var inherting = new Inheriting();

    inherting.set("baseProp", "value");

    QUnit.equal(inherting.baseProp,"value", "set prop");
});

if(sealWorks && System.env.indexOf('production') < 0) {
	QUnit.test("setting not defined property", function(){
	    var MyMap = DefineMap.extend({
	        prop: {}
	    });
	    var mymap = new MyMap();

	    try {
	        mymap.notdefined = "value";
	        ok(false, "no error");
	    } catch(e) {
	        ok(true, "error thrown");
	    }
	});
}

QUnit.test(".extend errors when re-defining a property (#117)", function(){

	var A = DefineMap.extend("A", {
	    foo: {
	        type: "string",
	        value: "blah"
	    }
	});

	A.extend("B", {
	    foo: {
	        type: "string",
	        value: "flub"
	    }
	});

	var C = DefineMap.extend("C", {
	    foo: {
	        get: function() {
	            return "blah";
	        }
	    }
	});

	C.extend("D", {
	    foo: {
	        get: function() {
	            return "flub";
	        }
	    }
	});
	QUnit.ok(true, "extended without errors");
});
<|endoftext|>


<commit_before>import { map } from 'supercolliderjs';
import _ from 'lodash';
import { createSelector } from 'reselect';
import { getNormalizedPoints } from './dataset';

export const getPointsUnderBrush = state => _.get(state, 'interaction.pointsUnderBrush', []);
const getPreviousPointsUnderBrush = state =>
  _.get(state, 'interaction.previousPointsUnderBrush', []);
const getInteraction = state => state.interaction || {};
const getSoundName = state => state.sound;
const getSounds = state => state.sounds;
const getMapping = state => state.mapping || {};

export const getLoop = state => _.get(state, 'interaction.loopMode', {});

export const getSound = createSelector([getSoundName, getSounds], (soundName, sounds) => {
  for (const sound of sounds) {
    if (sound.name === soundName) {
      return sound;
    }
  }

  return null;
});

/**
 * getXYMappingControls - Generates an object for each modulateable control of the current sound.
 *
 * used by XYParamTable
 *
 * Each object contains:
 *
 *   name
 *   xConnected
 *   yConnected
 *   connected
 *   unipolar
 *     value
 *     minval
 *     maxval
 *   natural
 *     value
 *     minval
 *     maxval
 *
 * @return {Function} A reselect selector
 */
export const getXYMappingControls = createSelector([getMapping, getSound], xyMappingControls);

export function xyMappingControls(mapping, sound) {
  if (!sound) {
    return [];
  }

  const modulateable = c => c.name !== 'out' && c.spec;

  const isConnected = (xy, param) => {
    if (!mapping) {
      return false;
    }

    return Boolean(_.get(mapping, `xy.${xy}.params.${param}`));
  };

  const findDefault = control => {
    const dv = _.find([control.defaultValue, control.spec.defaultValue], _.isNumber);
    if (_.isUndefined(dv)) {
      return 0.5;
    }

    return map.unmapWithSpec(dv, control.spec);
  };

  return sound.controls.filter(modulateable).map(control => {
    const xcon = isConnected('x', control.name);
    const ycon = isConnected('y', control.name);
    const connected = xcon || ycon;
    const spec = control.spec;
    // const minval = _.get(mapping, '')

    // value should be unmapped defaultValue
    const unipolar = _.assign(
      {
        value: _.clamp(findDefault(control), 0, 1),
        minval: 0.0,
        maxval: 1.0
      },
      _.get(mapping, `unipolarMappingRanges.${control.name}`)
    );

    // natural is mapping those three
    const natural = _.mapValues(unipolar, v => map.mapWithSpec(v, spec));
    natural.spec = {
      warp: spec.warp,
      step: spec.step,
      minval: natural.minval,
      maxval: natural.maxval
    };

    // defaultValue can be wonky due to floating point math
    // eg 440.0000000000001

    return {
      name: control.name,
      xConnected: xcon,
      yConnected: ycon,
      connected,
      unipolar,
      natural
    };
  });
}

/**
 * calcPointsEntering - given pointsUnderBrush and the previousPointsUnderBrush,
 * return a list of those points that are currently just entering the brush area.
 *
 * Normal function; not a selector.
 */
export const calcPointsEntering = (pointsUnderBrush, previousPointsUnderBrush) =>
  _.difference(pointsUnderBrush || [], previousPointsUnderBrush || []);

export function makeXYMapper(mappingControls, param) {
  if (param) {
    const control = _.find(mappingControls, { name: param });
    if (control) {
      return makeMapper(control.natural.spec);
    }
  }
}

/**
 * A normal function; not a selector
 * Needs the full state
 */
export function spawnEventsFromBrush(state) {
  const pointsUnderBrush = getPointsUnderBrush(state) || [];
  const previousPointsUnderBrush = getPreviousPointsUnderBrush(state) || [];
  const pointsEntering = calcPointsEntering(pointsUnderBrush, previousPointsUnderBrush);
  const interaction = getInteraction(state);
  const npoints = getNormalizedPoints(state);

  return xyPointsEnteringToSynthEvents(
    pointsEntering,
    interaction.m,
    interaction.n,
    getSound(state),
    getMapping(state),
    getXYMappingControls(state),
    npoints
  );
}

/**
 * Given the current mapping assignments and the mappingControls,
 * return a function that maps x and y to synth args.
 *
 * @param  {Object} mapping         [description]
 * @param  {Object} mappingControls [description]
 * @return {Object}                 [description]
 */
function makeXYMappingFn(mapping, mappingControls) {
  const paramsX = _.get(mapping, 'xy.x.params', {});
  const paramsY = _.get(mapping, 'xy.y.params', {});

  // {paramName: mappingFunction, ...}
  // If there is no available mapper (because mode is switching etc)
  // then map to null and then in the mapping function omit those nulls
  const alwaysNull = () => null;
  const mappersX = _.mapValues(
    paramsX,
    (v, paramX) => makeXYMapper(mappingControls, paramX) || alwaysNull
  );
  const mappersY = _.mapValues(
    paramsY,
    (v, paramY) => makeXYMapper(mappingControls, paramY) || alwaysNull
  );
  // mapping funciton
  return (x, y) => {
    return _.assign(
      {},
      // {paramName: mappingFunction, ...} -> {paramName: mappedValue, ...}
      _.omitBy(_.mapValues(mappersX, mapper => mapper(x)), _.isNull),
      _.omitBy(_.mapValues(mappersY, mapper => mapper(y)), _.isNull)
    );
  };
}

export function xyPointsEnteringToSynthEvents(
  pointsEntering,
  m,
  n,
  sound,
  mapping,
  mappingControls,
  npoints
) {
  if (pointsEntering.length === 0) {
    return [];
  }

  if (!sound) {
    return [];
  }

  const mapXY = makeXYMappingFn(mapping, mappingControls);

  const fixedArgs = {};
  mappingControls.forEach(mc => {
    if (!mc.connected) {
      fixedArgs[mc.name] = mc.natural.value;
    }
  });

  return pointsEntering.map(index => {
    const x = npoints[m].values[index];
    const y = 1.0 - npoints[n].values[index];
    const args = mapXY(x, 1.0 - y);

    return {
      defName: sound.name,
      args: _.assign(args, fixedArgs)
    };
  });
}

/**
 * makeMapper - return one of the mapping functions from supercollider.js
 *
 * @param  {Object} spec Similar to supercollider's ControlSpec
 * @return {Function}    Function that maps a unipolar value to the Spec's range and warp curve.
 */
export function makeMapper(spec) {
  switch (spec.warp) {
    case 'exp':
      return map.exp(spec);
    case 'db':
      return map.dB(spec);
    case 'amp':
      return map.fader(spec);
    default:
      return map.linear(spec);
  }
}

/**
 * getLoopModePayload - Builds the payload for 'setLoop' action that is sent to the main thread
 * to be sent by the SoundApp and then sent using the updateStream to the
 * SynthEventList dryad.
 *
 * This is a Reselect selector.
 *
 * @return {Object} events, loopTime, epoch
 */
export const getLoopModePayload = createSelector(
  [getSound, getLoop, getNormalizedPoints, getMapping, getXYMappingControls],
  (sound, loopMode, npoints, mapping, mappingControls) => {
    if (!sound || !loopMode.box) {
      return {
        events: []
      };
    }

    const events = loopModeEvents(
      loopMode.box.m,
      loopMode.box.n,
      npoints,
      mapping,
      mappingControls,
      sound,
      loopMode.loopTime
    );

    return {
      events,
      loopTime: loopMode.loopTime,
      epoch: loopMode.epoch
    };
  }
);

/**
 * Generate synth events for each point in the loop
 *
 * @param  {[type]} m               [description]
 * @param  {[type]} n               [description]
 * @param  {[type]} npoints         [description]
 * @param  {[type]} mapping         [description]
 * @param  {[type]} mappingControls [description]
 * @param  {[type]} sound           [description]
 * @param  {[type]} loopTime        [description]
 * @return {[type]}                 [description]
 */
export function loopModeEvents(m, n, npoints, mapping, mappingControls, sound, loopTime) {
  // Now you have both time and x mapped to the same parameters.
  // It will accentuate it I guess
  const mapXY = makeXYMappingFn(mapping, mappingControls);

  const timeSpec = {
    warp: 'lin',
    minval: 0.0,
    maxval: loopTime
  };
  const timeMapper = makeMapper(timeSpec);

  const fixedArgs = {};
  mappingControls.forEach(mc => {
    if (!mc.connected) {
      fixedArgs[mc.name] = mc.natural.value;
    }
  });

  // if size is wrong eg. after loading a new dataset and loopMode is set from previous one
  if (!(npoints[m] && npoints[n])) {
    // just return an empty list
    return [];
  }

  return npoints[m].values.map((x, i) => {
    const y = npoints[n].values[i];
    const args = mapXY(x, y);

    return {
      time: timeMapper(x),
      defName: sound.name,
      args: _.assign(args, fixedArgs)
    };
  });
}
<commit_msg>Enable loop to use index or any feature for the time event mapping.<commit_after>import { map } from 'supercolliderjs';
import _ from 'lodash';
import { createSelector } from 'reselect';
import { getNormalizedPoints } from './dataset';

export const getPointsUnderBrush = state => _.get(state, 'interaction.pointsUnderBrush', []);
const getPreviousPointsUnderBrush = state =>
  _.get(state, 'interaction.previousPointsUnderBrush', []);
const getInteraction = state => state.interaction || {};
const getSoundName = state => state.sound;
const getSounds = state => state.sounds;
const getMapping = state => state.mapping || {};

export const getLoop = state => _.get(state, 'interaction.loopMode', {});

export const getSound = createSelector([getSoundName, getSounds], (soundName, sounds) => {
  for (const sound of sounds) {
    if (sound.name === soundName) {
      return sound;
    }
  }

  return null;
});

/**
 * getXYMappingControls - Generates an object for each modulateable control of the current sound.
 *
 * used by XYParamTable
 *
 * Each object contains:
 *
 *   name
 *   xConnected
 *   yConnected
 *   connected
 *   unipolar
 *     value
 *     minval
 *     maxval
 *   natural
 *     value
 *     minval
 *     maxval
 *
 * @return {Function} A reselect selector
 */
export const getXYMappingControls = createSelector([getMapping, getSound], xyMappingControls);

export function xyMappingControls(mapping, sound) {
  if (!sound) {
    return [];
  }

  const modulateable = c => c.name !== 'out' && c.spec;

  const isConnected = (xy, param) => {
    if (!mapping) {
      return false;
    }

    return Boolean(_.get(mapping, `xy.${xy}.params.${param}`));
  };

  const findDefault = control => {
    const dv = _.find([control.defaultValue, control.spec.defaultValue], _.isNumber);
    if (_.isUndefined(dv)) {
      return 0.5;
    }

    return map.unmapWithSpec(dv, control.spec);
  };

  return sound.controls.filter(modulateable).map(control => {
    const xcon = isConnected('x', control.name);
    const ycon = isConnected('y', control.name);
    const connected = xcon || ycon;
    const spec = control.spec;
    // const minval = _.get(mapping, '')

    // value should be unmapped defaultValue
    const unipolar = _.assign(
      {
        value: _.clamp(findDefault(control), 0, 1),
        minval: 0.0,
        maxval: 1.0
      },
      _.get(mapping, `unipolarMappingRanges.${control.name}`)
    );

    // natural is mapping those three
    const natural = _.mapValues(unipolar, v => map.mapWithSpec(v, spec));
    natural.spec = {
      warp: spec.warp,
      step: spec.step,
      minval: natural.minval,
      maxval: natural.maxval
    };

    // defaultValue can be wonky due to floating point math
    // eg 440.0000000000001

    return {
      name: control.name,
      xConnected: xcon,
      yConnected: ycon,
      connected,
      unipolar,
      natural
    };
  });
}

/**
 * calcPointsEntering - given pointsUnderBrush and the previousPointsUnderBrush,
 * return a list of those points that are currently just entering the brush area.
 *
 * Normal function; not a selector.
 */
export const calcPointsEntering = (pointsUnderBrush, previousPointsUnderBrush) =>
  _.difference(pointsUnderBrush || [], previousPointsUnderBrush || []);

export function makeXYMapper(mappingControls, param) {
  if (param) {
    const control = _.find(mappingControls, { name: param });
    if (control) {
      return makeMapper(control.natural.spec);
    }
  }
}

/**
 * A normal function; not a selector
 * Needs the full state
 */
export function spawnEventsFromBrush(state) {
  const pointsUnderBrush = getPointsUnderBrush(state) || [];
  const previousPointsUnderBrush = getPreviousPointsUnderBrush(state) || [];
  const pointsEntering = calcPointsEntering(pointsUnderBrush, previousPointsUnderBrush);
  const interaction = getInteraction(state);
  const npoints = getNormalizedPoints(state);

  return xyPointsEnteringToSynthEvents(
    pointsEntering,
    interaction.m,
    interaction.n,
    getSound(state),
    getMapping(state),
    getXYMappingControls(state),
    npoints
  );
}

/**
 * Given the current mapping assignments and the mappingControls,
 * return a function that maps x and y to synth args.
 *
 * Mapping may specify to map x (or y) to multiple synth params,
 * it isn't just mapped to a single value.
 *
 * @param  {Object}     mapping
 * @param  {Object}     mappingControls
 * @return {Function}   (x, y) -> {param: value, ... paramN: valueN}
 */
function makeXYMappingFn(mapping, mappingControls) {
  const paramsX = _.get(mapping, 'xy.x.params', {});
  const paramsY = _.get(mapping, 'xy.y.params', {});

  // {paramName: mappingFunction, ...}
  // If there is no available mapper (because mode is switching etc)
  // then map to null and then in the mapping function omit those nulls
  const alwaysNull = _.constant(null);
  const mappersX = _.mapValues(
    paramsX,
    (v, paramX) => makeXYMapper(mappingControls, paramX) || alwaysNull
  );
  const mappersY = _.mapValues(
    paramsY,
    (v, paramY) => makeXYMapper(mappingControls, paramY) || alwaysNull
  );
  // mapping funciton
  return (x, y) => {
    return _.assign(
      {},
      // {paramName: mappingFunction, ...} -> {paramName: mappedValue, ...}
      _.omitBy(_.mapValues(mappersX, mapper => mapper(x)), _.isNull),
      _.omitBy(_.mapValues(mappersY, mapper => mapper(y)), _.isNull)
    );
  };
}

export function xyPointsEnteringToSynthEvents(
  pointsEntering,
  m,
  n,
  sound,
  mapping,
  mappingControls,
  npoints
) {
  if (pointsEntering.length === 0) {
    return [];
  }

  if (!sound) {
    return [];
  }

  const mapXY = makeXYMappingFn(mapping, mappingControls);

  const fixedArgs = {};
  mappingControls.forEach(mc => {
    if (!mc.connected) {
      fixedArgs[mc.name] = mc.natural.value;
    }
  });

  return pointsEntering.map(index => {
    const x = npoints[m].values[index];
    const y = 1.0 - npoints[n].values[index];
    const args = mapXY(x, 1.0 - y);

    return {
      defName: sound.name,
      args: _.assign(args, fixedArgs)
    };
  });
}

/**
 * makeMapper - return one of the mapping functions from supercollider.js
 *
 * @param  {Object} spec Similar to supercollider's ControlSpec
 * @return {Function}    Function that maps a unipolar value to the Spec's range and warp curve.
 */
export function makeMapper(spec) {
  switch (spec.warp) {
    case 'exp':
      return map.exp(spec);
    case 'db':
      return map.dB(spec);
    case 'amp':
      return map.fader(spec);
    default:
      return map.linear(spec);
  }
}

/**
 * getLoopModePayload - Builds the payload for 'setLoop' action that is sent to the main thread
 * to be sent by the SoundApp and then sent using the updateStream to the
 * SynthEventList dryad.
 *
 * This is a Reselect selector.
 *
 * @return {Object} events, loopTime, epoch
 */
export const getLoopModePayload = createSelector(
  [getSound, getLoop, getNormalizedPoints, getMapping, getXYMappingControls],
  (sound, loopMode, npoints, mapping, mappingControls) => {
    if (!sound || !loopMode.box) {
      return {
        events: []
      };
    }

    const events = loopModeEvents(
      loopMode.box.m,
      loopMode.box.n,
      loopMode.box.m,
      npoints,
      mapping,
      mappingControls,
      sound,
      loopMode.loopTime
    );

    return {
      events,
      loopTime: loopMode.loopTime,
      epoch: loopMode.epoch
    };
  }
);

/**
 * Generate synth events for each point in the loop
 *
 * @param  {int} m                  Feature to map to x
 * @param  {int} n                  Feature to map to y
 * @param  {int} t                  Feature to map to use for time axis.
 *                                    null means to use index, spacing events evenly
 *                                    over the loop time.
 * @param  {Array<Object>} npoints  Normalized points
 * @param  {Object} mapping         Mapping specification
 * @param  {Object} mappingControls Spec for mapping of two inputs (x y) to sound params
 * @param  {Object} sound           Sound with defName = .name
 * @param  {float} loopTime         Loop time in seconds.
 * @return {Array<Object>}          Array of synth events
 */
export function loopModeEvents(m, n, t, npoints, mapping, mappingControls, sound, loopTime) {
  // If size is wrong eg. after loading a new dataset and loopMode is set from
  // previous one
  if (!(npoints[m] && npoints[n])) {
    // just return an empty list
    return [];
  }

  const mapXY = makeXYMappingFn(mapping, mappingControls);

  const timeSpec = {
    warp: 'lin',
    minval: 0.0,
    maxval: loopTime
  };

  let mapTime;
  if (_.isNull(t)) {
    // Evenly step through the points in index order
    const timeStep = loopTime / npoints[m].values.length;
    mapTime = i => timeStep * i;
  } else {
    // Use dimension t as the time value
    const timeMapper = makeMapper(timeSpec);
    mapTime = i => timeMapper(npoints[t].values[i]);
  }

  const fixedArgs = {};
  mappingControls.forEach(mc => {
    if (!mc.connected) {
      fixedArgs[mc.name] = mc.natural.value;
    }
  });

  return npoints[m].values.map((x, i) => {
    const y = npoints[n].values[i];
    const args = mapXY(x, y);

    return {
      time: mapTime(i),
      defName: sound.name,
      args: _.assign(args, fixedArgs)
    };
  });
}
<|endoftext|>


<commit_before>'use strict';

var FilestubeAPI = require('../index');
var assert = require('assert');
var nock = require('nock');

var list;
var final;

var initMock = function(list, final){
  nock('http://www.filestube.to')
  .get('/query.html?q=stawka+wieksza+niz+zycie')
  .reply(200, list)
  .get('/details.html')
  .reply(200, final)
  .persist();
};

var teardown = function(){
  nock.restore();
};

var elementsOnPage = 5;

// getOne result with only one file
list = require('./mocks/list-one-page.js');
final = require('./mocks/final-single-link');
initMock(list, final);
FilestubeAPI.getOne('stawka wieksza niz zycie', {}, function(data){
  assert.equal(data.length, 1);
  assert.equal(
    data[0],
    'http://blablablabla.jczc/files/w2q9vfcl/Zbych.Andrzej-Stawka.wieksza.niz'
  );

  teardown();
});

// // getOne result with only one file
// list = require('./mocks/list-one-page.js');
// final = require('./mocks/final-multiple-links')
// initMock(require(list, final);
// FilestubeAPI.getOne('stawka wieksza niz zycie', {}, function(data){
//   assert.equal(data[0], 'http://blablablabla.jczc/files/w2q9vfcl/Zbych.Andrzej-Stawka.wieksza.niz');
// });
// teardown();
<commit_msg>[FILESTUBE-CLIENT] - :microphone: lint fixes<commit_after>'use strict';

var FilestubeAPI = require('../index');
var assert = require('assert');
var nock = require('nock');

var list;
var final;

var initMock = function(list, final){
  nock('http://www.filestube.to')
  .get('/query.html?q=stawka+wieksza+niz+zycie')
  .reply(200, list)
  .get('/details.html')
  .reply(200, final)
  .persist();
};

var teardown = function(){
  nock.restore();
};

//var elementsOnPage = 5;

// getOne result with only one file
list = require('./mocks/list-one-page.js');
final = require('./mocks/final-single-link');
initMock(list, final);
FilestubeAPI.getOne('stawka wieksza niz zycie', {}, function(data){
  assert.equal(data.length, 1);
  assert.equal(
    data[0],
    'http://blablablabla.jczc/files/w2q9vfcl/Zbych.Andrzej-Stawka.wieksza.niz'
  );

  teardown();
});
<|endoftext|>