<commit_before>this.ckan = this.ckan || {};
this.ckan.orgportals = this.ckan.orgportals || {};
this.ckan.orgportals.dashboardmap = this.ckan.dashboardmap || {};

(function (self, $) {

  self.init = function init(elementId, countryName, mapURL, color, mainProperty, map_selector_name, organization_entity_name) {
    renderMap(elementId, countryName, mapURL, color, mainProperty, map_selector_name, organization_entity_name);
  };

  var disclaimerText = $('.hero-info .media-body');
  var disclaimerContainer = $('.media.hero-info');

  // Click handler for the disclaimer icon.
  $('.hero-info > .media-left').click(function onMapDisclaimerClick(event) {
    var bodyWidth = $('body').outerWidth();
    var topPosition;

    if (disclaimerText.hasClass('hidden')) {
      topPosition = bodyWidth <= 976 ? '790px' : '295px';

      disclaimerText.removeClass('hidden');
      disclaimerContainer.css({
          'width': '300px',
          'padding': '10px',
      });
    } else {
      topPosition = bodyWidth <= 976 ? '905px' : '410px';

      disclaimerText.addClass('hidden');
      disclaimerContainer.css({
          'width': '54px',
          'padding': '2px',
      });
    }
  });

  function renderMap(elementId, countryName, mapURL, color, mainProperty, map_selector_name, organization_entity_name) {
    var mainProperties = [];
    var fitBounds = false;

    if (mapURL.length > 0 && typeof mainProperty === 'string') {
      var mapURLS = mapURL.split(',');

      mainProperties = mainProperty.split(',');
    }

    if (organization_entity_name === 'country' && countryName !== 'none') {
      $.getJSON('https://maps.googleapis.com/maps/api/geocode/json?address=' + encodeURI(countryName)).done(function (data) {
         if (data['status'] == 'ZERO_RESULTS') {
           initLeaflet(elementId, 39, 40, 2);
         } else {
           var lat = data['results'][0]['geometry']['location']['lat'],
             lng = data['results'][0]['geometry']['location']['lng'];
           initLeaflet(elementId, lat, lng, 5);
         }
       }).fail(function (data) {
         console.log(data);
         initLeaflet(elementId, 39, 40, 2);
       });
    } else {
      fitBounds = true;
      initLeaflet(elementId, 39, 40, 2);
    }

    // geo layer
    var geoL;

    function initLeaflet(elementId, lat, lng, zoom) {
      var map;

      if (fitBounds) {
        if (!mapURLS && mainProperties.length === 0) {
          map = new L.Map(elementId, {scrollWheelZoom: true, inertiaMaxSpeed: 200}).setView([lat, lng], zoom);
        } else {
          map = new L.Map(elementId, {scrollWheelZoom: true, inertiaMaxSpeed: 200});
        }
      } else {
        map = new L.Map(elementId, {scrollWheelZoom: false, inertiaMaxSpeed: 200}).setView([lat, lng], zoom);
      }

      var osmUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
      var osmAttrib = 'Map data © OpenStreetMap contributors';
      var osm = new L.TileLayer(osmUrl, {
        minZoom: 2,
        maxZoom: 18,
        attribution: osmAttrib
      });

      map.addLayer(osm);

      highlightCountry();

      if (mapURLS && mainProperties.length > 0) {

        // Initialize markers
        initDatasetMarkers(mapURLS[0], mainProperties[0]);
      }

      function highlightCountry() {
        var siteURL = $('body').attr('data-site-root');
        var countriesUrl = siteURL + 'countries.json';
        var country;
        $.getJSON(countriesUrl).done(function(data) {
          data.features.some(function(feature) {
            if (feature.properties.name === countryName && countryName !== 'none') {
              country = L.geoJson(feature);
              country.setStyle({fill: false, color: '#154289'})
              map.addLayer(country);
              map.fitBounds(country.getBounds());
              fitBounds = false;
              return true;
            }
          });
        });
      }

      function initDatasetMarkers(mapURL, mainField) {

        var layers = [];

        $.getJSON(mapURL).done(function (data) {
          geoL = L.geoJson(data, {
            style: function (feature) {
              var styles = {};
              var color = feature.properties.stroke;
              var fillColor = feature.properties.fill;

              if (color) {
                styles.color = color;
              }

              if (fillColor) {
                styles.fillColor = fillColor;
              }

              return styles;
            },
            pointToLayer: function (feature, latlng) {
              var markerColor = feature.properties['marker-color'] || '#c71111';
              markerColor = markerColor.substr(1);
              var icon = L.divIcon({
                html: ''
              });

              return L.marker(latlng, {
                icon: icon
              });
            },
            onEachFeature: function (feature, layer) {
              var popup = document.createElement("div"),
                header = document.createElement("h5"),
                headerText = document.createTextNode(feature.properties[mainField]),
                list = document.createElement("ul"),
                listElement,
                listElementText,
                boldElement,
                boldElementText;
              header.appendChild(headerText);
              for (var info in feature.properties) {
                if (info != mainField) {
                  boldElementText = document.createTextNode(info + ': ');
                  boldElement = document.createElement("b");
                  boldElement.appendChild(boldElementText);

                  listElementText = document.createTextNode(feature.properties[info]);
                  listElement = document.createElement("li");
                  listElement.appendChild(boldElement);
                  listElement.appendChild(listElementText);

                  list.appendChild(listElement);
                }
              }
              popup.appendChild(header);
              popup.appendChild(list);
              layer.bindPopup(popup);
              layer.name = feature.properties[mainField];
              layers.push(layer);
            }
          }).addTo(map);

          map.on('popupopen', function (e) {
            if (map._zoom == 10) {
              var px = map.project(e.popup._latlng, 10);
              px.y -= e.popup._container.clientHeight / 2;
              map.flyTo(map.unproject(px), 10, {animate: true, duration: 1});
            } else {
              map.flyTo(e.popup._latlng, 10, {animate: true, duration: 1})
            }
            $('.leaflet-popup-content-wrapper').css({'border-top': '5px solid ' + color});
          });

          var select_dataset = $('#dataset');
          var select_resource = $('#orgportals_resource');
          select_dataset.append('<option>' + map_selector_name + '</option>');

          for (var elem in layers) {
            select_dataset.append('<option>' + layers[elem].name + '</option>');
          }

          select_dataset.change(
            function datasetsClick(a) {
              var selected = $('#dataset option:selected').text();
              for (var elem in layers) {
                if (layers[elem].name == selected) {
                  layers[elem].openPopup();
                }
              }
            }
          );

          $('#map-info').removeClass('hidden');

          // Properly zoom the map to fit all markers/polygons
          if (fitBounds) {
            map.fitBounds(geoL.getBounds().pad(0.5));
          }
        }).fail(function (data) {
          console.log("GeoJSON could not be loaded " + mapURL);
        });

      }

      $(document).ready(function () {
        $('.leaflet-control-zoom-in').css({'color': color});
        $('.leaflet-control-zoom-out').css({'color': color});

        var select_resource = $('#orgportals_resource');
        var select_dataset = $('#dataset');

        select_resource.change(function click() {
          var selectedIndex = $('#orgportals_resource').prop('selectedIndex');
          fitBounds = true;
          select_dataset.children('option').remove();
          layers = [];
          map.removeLayer(geoL);
          initDatasetMarkers(mapURLS[selectedIndex], mainProperties[selectedIndex]);
        });

      });
    }

  }
})(this.ckan.orgportals.dashboardmap, this.jQuery);
<commit_msg>Disable auto zooming on polygons<commit_after>this.ckan = this.ckan || {};
this.ckan.orgportals = this.ckan.orgportals || {};
this.ckan.orgportals.dashboardmap = this.ckan.dashboardmap || {};

(function (self, $) {

  self.init = function init(elementId, countryName, mapURL, color, mainProperty, map_selector_name, organization_entity_name) {
    renderMap(elementId, countryName, mapURL, color, mainProperty, map_selector_name, organization_entity_name);
  };

  var disclaimerText = $('.hero-info .media-body');
  var disclaimerContainer = $('.media.hero-info');

  // Click handler for the disclaimer icon.
  $('.hero-info > .media-left').click(function onMapDisclaimerClick(event) {
    var bodyWidth = $('body').outerWidth();
    var topPosition;

    if (disclaimerText.hasClass('hidden')) {
      topPosition = bodyWidth <= 976 ? '790px' : '295px';

      disclaimerText.removeClass('hidden');
      disclaimerContainer.css({
          'width': '300px',
          'padding': '10px',
      });
    } else {
      topPosition = bodyWidth <= 976 ? '905px' : '410px';

      disclaimerText.addClass('hidden');
      disclaimerContainer.css({
          'width': '54px',
          'padding': '2px',
      });
    }
  });

  function renderMap(elementId, countryName, mapURL, color, mainProperty, map_selector_name, organization_entity_name) {
    var mainProperties = [];
    var fitBounds = false;

    if (mapURL.length > 0 && typeof mainProperty === 'string') {
      var mapURLS = mapURL.split(',');

      mainProperties = mainProperty.split(',');
    }

    if (organization_entity_name === 'country' && countryName !== 'none') {
      $.getJSON('https://maps.googleapis.com/maps/api/geocode/json?address=' + encodeURI(countryName)).done(function (data) {
         if (data['status'] == 'ZERO_RESULTS') {
           initLeaflet(elementId, 39, 40, 2);
         } else {
           var lat = data['results'][0]['geometry']['location']['lat'],
             lng = data['results'][0]['geometry']['location']['lng'];
           initLeaflet(elementId, lat, lng, 5);
         }
       }).fail(function (data) {
         console.log(data);
         initLeaflet(elementId, 39, 40, 2);
       });
    } else {
      fitBounds = true;
      initLeaflet(elementId, 39, 40, 2);
    }

    // geo layer
    var geoL;

    function initLeaflet(elementId, lat, lng, zoom) {
      var map;

      if (fitBounds) {
        if (!mapURLS && mainProperties.length === 0) {
          map = new L.Map(elementId, {scrollWheelZoom: true, inertiaMaxSpeed: 200}).setView([lat, lng], zoom);
        } else {
          map = new L.Map(elementId, {scrollWheelZoom: true, inertiaMaxSpeed: 200});
        }
      } else {
        map = new L.Map(elementId, {scrollWheelZoom: false, inertiaMaxSpeed: 200}).setView([lat, lng], zoom);
      }

      var osmUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
      var osmAttrib = 'Map data © OpenStreetMap contributors';
      var osm = new L.TileLayer(osmUrl, {
        minZoom: 2,
        maxZoom: 18,
        attribution: osmAttrib
      });

      map.addLayer(osm);

      highlightCountry();

      if (mapURLS && mainProperties.length > 0) {

        // Initialize markers
        initDatasetMarkers(mapURLS[0], mainProperties[0]);
      }

      function highlightCountry() {
        var siteURL = $('body').attr('data-site-root');
        var countriesUrl = siteURL + 'countries.json';
        var country;
        $.getJSON(countriesUrl).done(function(data) {
          data.features.some(function(feature) {
            if (feature.properties.name === countryName && countryName !== 'none') {
              country = L.geoJson(feature);
              country.setStyle({fill: false, color: '#154289'})
              map.addLayer(country);
              map.fitBounds(country.getBounds());
              fitBounds = false;
              return true;
            }
          });
        });
      }

      function initDatasetMarkers(mapURL, mainField) {

        var layers = [];

        $.getJSON(mapURL).done(function (data) {
          geoL = L.geoJson(data, {
            style: function (feature) {
              var styles = {};
              var color = feature.properties.stroke;
              var fillColor = feature.properties.fill;

              if (color) {
                styles.color = color;
              }

              if (fillColor) {
                styles.fillColor = fillColor;
              }

              return styles;
            },
            pointToLayer: function (feature, latlng) {
              var markerColor = feature.properties['marker-color'] || '#c71111';
              markerColor = markerColor.substr(1);
              var icon = L.divIcon({
                html: ''
              });

              return L.marker(latlng, {
                icon: icon
              });
            },
            onEachFeature: function (feature, layer) {
              var popup = document.createElement("div"),
                header = document.createElement("h5"),
                headerText = document.createTextNode(feature.properties[mainField]),
                list = document.createElement("ul"),
                listElement,
                listElementText,
                boldElement,
                boldElementText;
              header.appendChild(headerText);
              for (var info in feature.properties) {
                if (info != mainField) {
                  boldElementText = document.createTextNode(info + ': ');
                  boldElement = document.createElement("b");
                  boldElement.appendChild(boldElementText);

                  listElementText = document.createTextNode(feature.properties[info]);
                  listElement = document.createElement("li");
                  listElement.appendChild(boldElement);
                  listElement.appendChild(listElementText);

                  list.appendChild(listElement);
                }
              }
              popup.appendChild(header);
              popup.appendChild(list);
              layer.bindPopup(popup);
              layer.name = feature.properties[mainField];
              layers.push(layer);
            }
          }).addTo(map);

          map.on('popupopen', function (e) {
            if (map._zoom == 10) {
              var px = map.project(e.popup._latlng, 10);
              px.y -= e.popup._container.clientHeight / 2;
              map.flyTo(map.unproject(px), 10, {animate: true, duration: 1});
            } else {
              map.flyTo(e.popup._latlng, 10, {animate: true, duration: 1})
            }
            $('.leaflet-popup-content-wrapper').css({'border-top': '5px solid ' + color});
          });

          var select_dataset = $('#dataset');
          var select_resource = $('#orgportals_resource');
          select_dataset.append('<option>' + map_selector_name + '</option>');

          for (var elem in layers) {
            select_dataset.append('<option>' + layers[elem].name + '</option>');
          }

          select_dataset.change(
            function datasetsClick(a) {
              var selected = $('#dataset option:selected').text();
              for (var elem in layers) {
                  if (layers[elem].feature.geometry.type != 'Polygon' &&  layers[elem].name == selected) {
                      layers[elem].openPopup();
                  }
              }
            }
          );

          $('#map-info').removeClass('hidden');

          // Properly zoom the map to fit all markers/polygons
          if (fitBounds) {
            map.fitBounds(geoL.getBounds().pad(0.5));
          }
        }).fail(function (data) {
          console.log("GeoJSON could not be loaded " + mapURL);
        });

      }

      $(document).ready(function () {
        $('.leaflet-control-zoom-in').css({'color': color});
        $('.leaflet-control-zoom-out').css({'color': color});

        var select_resource = $('#orgportals_resource');
        var select_dataset = $('#dataset');

        select_resource.change(function click() {
          var selectedIndex = $('#orgportals_resource').prop('selectedIndex');
          fitBounds = true;
          select_dataset.children('option').remove();
          layers = [];
          map.removeLayer(geoL);
          initDatasetMarkers(mapURLS[selectedIndex], mainProperties[selectedIndex]);
        });

      });
    }

  }
})(this.ckan.orgportals.dashboardmap, this.jQuery);
<|endoftext|>


<commit_before>(function() {
  'use strict';

  angular.module('app.config')
    .run(init);

  /** @ngInject */
  function init(routerHelper) {
    routerHelper.configureStates(getLayouts());
  }

  function getLayouts() {
    return {
      'blank': {
        abstract: true,
        templateUrl: 'app/layouts/blank.html',
      },
      'application': {
        abstract: true,
        templateUrl: 'app/layouts/application.html',
        onEnter: enterApplication,
        onExit: exitApplication,
      },
    };
  }

  /** @ngInject */
  function enterApplication(Polling, lodash, $state, NavCounts, Navigation) {
    // Application layout displays the navigation which might have items that require polling to update the counts
    angular.forEach(NavCounts.counts, updateCount);
    angular.forEach(Navigation.items, function(value, key) {
      lodash.merge(value, $state.navFeatures[key]);
      angular.forEach(value.secondary, function(secondaryValue, secondaryKey) {
        lodash.merge(secondaryValue, $state.actionFeatures[secondaryKey]);
      });
    });

    function updateCount(count, key) {
      count.func();
      if (count.interval) {
        Polling.start(key, count.func, count.interval);
      }
    }
  }

  /** @ngInject */
  function exitApplication(lodash, Polling, NavCounts) {
    // Remove all of the navigation polls
    angular.forEach(lodash.keys(NavCounts.counts), Polling.stop);
  }
})();
<commit_msg>turned off polling based on menu access<commit_after>(function() {
  'use strict';

  angular.module('app.config')
    .run(init);

  /** @ngInject */
  function init(routerHelper) {
    routerHelper.configureStates(getLayouts());
  }

  function getLayouts() {
    return {
      'blank': {
        abstract: true,
        templateUrl: 'app/layouts/blank.html',
      },
      'application': {
        abstract: true,
        templateUrl: 'app/layouts/application.html',
        onEnter: enterApplication,
        onExit: exitApplication,
      },
    };
  }

  /** @ngInject */
  function enterApplication(Polling, lodash, $state, NavCounts, Navigation) {
    // Application layout displays the navigation which might have items that require polling to update the counts
    angular.forEach(NavCounts.counts, updateCount);
    angular.forEach(Navigation.items, function(value, key) {
      lodash.merge(value, $state.navFeatures[key]);
      angular.forEach(value.secondary, function(secondaryValue, secondaryKey) {
        lodash.merge(secondaryValue, $state.actionFeatures[secondaryKey]);
      });
    });

    function updateCount(count, key) {
      switch (key) {
        case 'rules':
        case 'blueprints':
        case 'dialogs':
          if ($state.navFeatures.designer.show === false) {
            return false;
          }
          break;
        case 'profiles':
          if ($state.navFeatures.administration.show === false) {
            return false;
          }
          break;
      }
      count.func();
      if (count.interval) {
        Polling.start(key, count.func, count.interval);
      }
    }
  }

  /** @ngInject */
  function exitApplication(lodash, Polling, NavCounts) {
    // Remove all of the navigation polls
    angular.forEach(lodash.keys(NavCounts.counts), Polling.stop);
  }
})();
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { renderDateTimePicker, InputText, renderDropdownList } from '../components/InputText';
import validate from '../helpers/validations/borrowBook';

const borrowReason = [{ reason: 'Assignment', value: 'Assignment' },
  { reason: 'Research', value: 'Research' }, { reason: 'Leisure reading', value: 'Leisure reading' }];

class BorrowBookForm extends Component {
  constructor(props) {
    super(props);
    this.submitForm = this.submitForm.bind(this);
  }

  submitForm(values) {
    console.log(values);
    const request = { ...values, reason: values.reason.value };
    this.props.borrowBook(this.props.user.id, this.props.book.id, request);
  }

  render() {
    const {
      handleSubmit, submitting, book,
    } = this.props;
    console.log(book);
    return (
            <form onSubmit = { handleSubmit(this.submitForm)}>

Borrow Book

                {
                    this.props.isBorrowing &&

 Sending your borrow request...

                }

                        <Field name="bookTitle" label="Book Title" defaultValue = { book.title } component={InputText} disabled />

                        Return Date
                        <Field name="returnDate" showTime = { false }component={renderDateTimePicker} />

                        <Field name="comment" type = "text" label="Comment(optional...)" component={InputText} />

                        Reason for borrow
                        <Field name="reason" component={renderDropdownList} data = { borrowReason } textField = "reason" valueField = "value"/>

                        <button className="btn waves-effect waves-light blue" type="submit" disabled={submitting} >Submit send</button>

            </form>

    );
  }
}

const borrowBookForm = reduxForm({
  form: 'borrowBook',
  validate,
})(BorrowBookForm);

export default borrowBookForm;
<commit_msg>Add appropriate class to borrow request message<commit_after>import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { renderDateTimePicker, InputText, renderDropdownList } from '../components/InputText';
import validate from '../helpers/validations/borrowBook';

const borrowReason = [{ reason: 'Assignment', value: 'Assignment' },
  { reason: 'Research', value: 'Research' }, { reason: 'Leisure reading', value: 'Leisure reading' }];

class BorrowBookForm extends Component {
  constructor(props) {
    super(props);
    this.submitForm = this.submitForm.bind(this);
  }

  submitForm(values) {
    console.log(values);
    const request = { ...values, reason: values.reason.value };
    this.props.borrowBook(this.props.user.id, this.props.book.id, request);
  }

  render() {
    const {
      handleSubmit, submitting, book,
    } = this.props;
    console.log(book);
    return (
            <form onSubmit = { handleSubmit(this.submitForm)}>

Borrow Book

                {
                    this.props.isBorrowing &&

 Sending your borrow request...

                }

                        <Field name="bookTitle" label="Book Title" defaultValue = { book.title } component={InputText} disabled />

                        Return Date
                        <Field name="returnDate" showTime = { false }component={renderDateTimePicker} />

                        <Field name="comment" type = "text" label="Comment(optional...)" component={InputText} />

                        Reason for borrow
                        <Field name="reason" component={renderDropdownList} data = { borrowReason } textField = "reason" valueField = "value"/>

                        <button className="btn waves-effect waves-light blue" type="submit" disabled={submitting} >Submit send</button>

            </form>

    );
  }
}

const borrowBookForm = reduxForm({
  form: 'borrowBook',
  validate,
})(BorrowBookForm);

export default borrowBookForm;
<|endoftext|>


<commit_before>'use strict';
import { _ } from 'meteor/underscore';
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { ReactiveVar } from 'meteor/reactive-var';
import { dbFoundations } from '../../db/dbFoundations';
import { formatDateText, isUserId } from '../utils/helpers';
import { config } from '../../config';
import { inheritedShowLoadingOnSubscribing } from '../layout/loading';
import { alertDialog } from '../layout/alertDialog';
import { shouldStopSubscribe } from '../utils/idle';
import { rCompanyListViewMode } from '../utils/styles';

inheritedShowLoadingOnSubscribing(Template.foundationList);
const rKeyword = new ReactiveVar('');
export const rFoundationOffset = new ReactiveVar(0);
Template.foundationList.onCreated(function() {
  rFoundationOffset.set(0);
  this.autorun(() => {
    if (shouldStopSubscribe()) {
      return false;
    }
    this.subscribe('foundationList', rKeyword.get(), rFoundationOffset.get());
  });
});
Template.foundationList.helpers({
  viewModeIsCard() {
    return rCompanyListViewMode.get() === 'card';
  },
  getFoundCompanyHref() {
    return FlowRouter.path('createFoundationPlan');
  },
  foundationList() {
    return dbFoundations.find({}, {
      sort: {
        createdAt: 1
      },
      limit: 12
    });
  },
  paginationData() {
    return {
      useVariableForTotalCount: 'totalCountOfFoundationPlan',
      dataNumberPerPage: 12,
      offset: rFoundationOffset,
      useHrefRoute: true
    };
  }
});

Template.foundationListFilterForm.onRendered(function() {
  this.$keyword = this.$('[name="keyword"]');
});
Template.foundationListFilterForm.helpers({
  viewModeBtnClass() {
    if (rCompanyListViewMode.get() === 'card') {
      return 'fa-th';
    }

    return 'fa-th-list';
  },
  keyword() {
    return rKeyword.get();
  }
});
Template.foundationListFilterForm.events({
  'click [data-action="toggleViewMode"]'(event) {
    event.preventDefault();
    let mode = 'card';
    if (rCompanyListViewMode.get() === mode) {
      mode = 'form';
    }
    rCompanyListViewMode.set(mode);
  },
  submit(event, templateInstance) {
    event.preventDefault();
    rKeyword.set(templateInstance.$keyword.val());
    rFoundationOffset.set(0);
  }
});

const foundationListHelpers = {
  displayTagList(tagList) {
    return tagList.join('、');
  },
  investPplsNumberClass(investNumber) {
    return (investNumber >= config.foundationNeedUsers) ? 'text-success' : 'text-danger';
  },
  foundationNeedUsers() {
    return config.foundationNeedUsers;
  },
  getTotalInvest(investList) {
    return _.reduce(investList, (totalInvest, investData) => {
      return totalInvest + investData.amount;
    }, 0);
  },
  getExpireDateText(createdAt) {
    const expireDate = new Date(createdAt.getTime() + config.foundExpireTime);

    return formatDateText(expireDate);
  },
  getEditHref(foundationId) {
    return FlowRouter.path('editFoundationPlan', {foundationId});
  },
  alreadyInvest() {
    const user = Meteor.user();
    if (user) {
      const userId = user._id;
      const invest = this.invest;
      const investData = _.findWhere(invest, {userId});
      if (investData) {
        return investData.amount;
      }
    }

    return 0;
  },
  cardDisplayClass() {
    if (! Meteor.user()) {
      return 'company-card-default';
    }
    if (isUserId(this.manager)) {
      return 'company-card-manager';
    }
    const invest = this.invest;
    const userId = Meteor.user()._id;
    const investData = _.findWhere(invest, {userId});
    if (investData) {
      return 'company-card-holder';
    }

    return 'company-card-default';
  }
};
const foundationListEvents = {
  'click [data-expand-order]'(event, templateInstance) {
    event.preventDefault();
    const panel = templateInstance.$('.order-panel');
    const maxHeight = panel.css('max-height');
    if (maxHeight === '0px') {
      panel.css('max-height', panel.prop('scrollHeight'));
    }
    else {
      panel.css('max-height', 0);
    }
  },
  'click [data-action="invest"]'(event, templaceInstance) {
    event.preventDefault();
    const user = Meteor.user();
    if (! user) {
      return false;
    }
    const userId = user._id;
    const minimumInvest = Math.ceil(config.minReleaseStock / config.foundationNeedUsers);
    const foundationData = templaceInstance.data;
    const alreadyInvest = _.findWhere(foundationData.invest, {userId});
    const alreadyInvestAmount = alreadyInvest ? alreadyInvest.amount : 0;
    const maximumInvest = Math.min(Meteor.user().profile.money, config.maximumInvest - alreadyInvestAmount);
    if (minimumInvest > maximumInvest) {
      alertDialog.alert('您的投資已達上限或剩餘金錢不足以進行投資！');

      return false;
    }

    alertDialog.dialog({
      type: 'prompt',
      title: '投資',
      message: `
        要投資多少金額？(${minimumInvest}~${maximumInvest})

          投資理財有賺有賠，請先確認您要投資的公司是否符合
          金管會的規定。

      `,
      defaultValue: null,
      callback: function(result) {
        const amount = parseInt(result, 10);
        if (! amount) {
          return false;
        }
        if (amount >= minimumInvest && amount <= maximumInvest) {
          Meteor.customCall('investFoundCompany', templaceInstance.data._id, amount);
        }
        else {
          alertDialog.alert('不正確的金額數字！');
        }
      }
    });
  }
};
Template.foundationListCard.helpers(foundationListHelpers);
Template.foundationListCard.events(foundationListEvents);
Template.foundationListTable.helpers(foundationListHelpers);
Template.foundationListTable.events(foundationListEvents);
<commit_msg>修正在新創計劃搜尋時，網址列的頁碼沒有跳回第一頁的bug。<commit_after>'use strict';
import { _ } from 'meteor/underscore';
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { ReactiveVar } from 'meteor/reactive-var';
import { dbFoundations } from '../../db/dbFoundations';
import { formatDateText, isUserId } from '../utils/helpers';
import { config } from '../../config';
import { inheritedShowLoadingOnSubscribing } from '../layout/loading';
import { alertDialog } from '../layout/alertDialog';
import { shouldStopSubscribe } from '../utils/idle';
import { rCompanyListViewMode } from '../utils/styles';

inheritedShowLoadingOnSubscribing(Template.foundationList);
const rKeyword = new ReactiveVar('');
export const rFoundationOffset = new ReactiveVar(0);
Template.foundationList.onCreated(function() {
  rFoundationOffset.set(0);
  this.autorun(() => {
    if (shouldStopSubscribe()) {
      return false;
    }
    this.subscribe('foundationList', rKeyword.get(), rFoundationOffset.get());
  });
});
Template.foundationList.helpers({
  viewModeIsCard() {
    return rCompanyListViewMode.get() === 'card';
  },
  getFoundCompanyHref() {
    return FlowRouter.path('createFoundationPlan');
  },
  foundationList() {
    return dbFoundations.find({}, {
      sort: {
        createdAt: 1
      },
      limit: 12
    });
  },
  paginationData() {
    return {
      useVariableForTotalCount: 'totalCountOfFoundationPlan',
      dataNumberPerPage: 12,
      offset: rFoundationOffset,
      useHrefRoute: true
    };
  }
});

Template.foundationListFilterForm.onRendered(function() {
  this.$keyword = this.$('[name="keyword"]');
});
Template.foundationListFilterForm.helpers({
  viewModeBtnClass() {
    if (rCompanyListViewMode.get() === 'card') {
      return 'fa-th';
    }

    return 'fa-th-list';
  },
  keyword() {
    return rKeyword.get();
  }
});
Template.foundationListFilterForm.events({
  'click [data-action="toggleViewMode"]'(event) {
    event.preventDefault();
    let mode = 'card';
    if (rCompanyListViewMode.get() === mode) {
      mode = 'form';
    }
    rCompanyListViewMode.set(mode);
    FlowRouter.go('foundationList', {
      page: 1
    });
  },
  submit(event, templateInstance) {
    event.preventDefault();
    rKeyword.set(templateInstance.$keyword.val());
    FlowRouter.go('foundationList', {
      page: 1
    });
  }
});

const foundationListHelpers = {
  displayTagList(tagList) {
    return tagList.join('、');
  },
  investPplsNumberClass(investNumber) {
    return (investNumber >= config.foundationNeedUsers) ? 'text-success' : 'text-danger';
  },
  foundationNeedUsers() {
    return config.foundationNeedUsers;
  },
  getTotalInvest(investList) {
    return _.reduce(investList, (totalInvest, investData) => {
      return totalInvest + investData.amount;
    }, 0);
  },
  getExpireDateText(createdAt) {
    const expireDate = new Date(createdAt.getTime() + config.foundExpireTime);

    return formatDateText(expireDate);
  },
  getEditHref(foundationId) {
    return FlowRouter.path('editFoundationPlan', {foundationId});
  },
  alreadyInvest() {
    const user = Meteor.user();
    if (user) {
      const userId = user._id;
      const invest = this.invest;
      const investData = _.findWhere(invest, {userId});
      if (investData) {
        return investData.amount;
      }
    }

    return 0;
  },
  cardDisplayClass() {
    if (! Meteor.user()) {
      return 'company-card-default';
    }
    if (isUserId(this.manager)) {
      return 'company-card-manager';
    }
    const invest = this.invest;
    const userId = Meteor.user()._id;
    const investData = _.findWhere(invest, {userId});
    if (investData) {
      return 'company-card-holder';
    }

    return 'company-card-default';
  }
};
const foundationListEvents = {
  'click [data-expand-order]'(event, templateInstance) {
    event.preventDefault();
    const panel = templateInstance.$('.order-panel');
    const maxHeight = panel.css('max-height');
    if (maxHeight === '0px') {
      panel.css('max-height', panel.prop('scrollHeight'));
    }
    else {
      panel.css('max-height', 0);
    }
  },
  'click [data-action="invest"]'(event, templaceInstance) {
    event.preventDefault();
    const user = Meteor.user();
    if (! user) {
      return false;
    }
    const userId = user._id;
    const minimumInvest = Math.ceil(config.minReleaseStock / config.foundationNeedUsers);
    const foundationData = templaceInstance.data;
    const alreadyInvest = _.findWhere(foundationData.invest, {userId});
    const alreadyInvestAmount = alreadyInvest ? alreadyInvest.amount : 0;
    const maximumInvest = Math.min(Meteor.user().profile.money, config.maximumInvest - alreadyInvestAmount);
    if (minimumInvest > maximumInvest) {
      alertDialog.alert('您的投資已達上限或剩餘金錢不足以進行投資！');

      return false;
    }

    alertDialog.dialog({
      type: 'prompt',
      title: '投資',
      message: `
        要投資多少金額？(${minimumInvest}~${maximumInvest})

          投資理財有賺有賠，請先確認您要投資的公司是否符合
          金管會的規定。

      `,
      defaultValue: null,
      callback: function(result) {
        const amount = parseInt(result, 10);
        if (! amount) {
          return false;
        }
        if (amount >= minimumInvest && amount <= maximumInvest) {
          Meteor.customCall('investFoundCompany', templaceInstance.data._id, amount);
        }
        else {
          alertDialog.alert('不正確的金額數字！');
        }
      }
    });
  }
};
Template.foundationListCard.helpers(foundationListHelpers);
Template.foundationListCard.events(foundationListEvents);
Template.foundationListTable.helpers(foundationListHelpers);
Template.foundationListTable.events(foundationListEvents);
<|endoftext|>


<commit_before>
import featureTemplate from './featureTemplate.stache!';
import template from './template.stache!';
import './styles.less!';

import 'spectre-canjs/property-table/property-table';
import 'spectre-canjs/paginate-widget/paginate-widget';
import {parseFieldArray} from 'spectre-canjs/util/field';
import ajax from 'can-util/dom/ajax/ajax';
import assign from 'can-util/js/assign/assign';
import DefineMap from 'can-define/map/map';
import Component from 'can-component';
import DefineList from 'can-define/list/list';
import ol from 'openlayers';

import canViewModel from 'can-view-model';

/**
 * @constructor identify-widget.ViewModel ViewModel
 * @parent identify-widget
 * @group identify-widget.ViewModel.props Properties
 *
 * @description A `<identify-widget />` component's ViewModel
 */
export const ViewModel = DefineMap.extend('IdentifyWidget', {
  /**
   * @prototype
   */
    /**
     * The max number of features to return for each layer. The default is 10.
     * @signature `{Number}` `max-feature-count="10"`
     * @property {Number} identify-widget.ViewModel.props.maxFeatureCount
     * @parent identify-widget.ViewModel.props
     */
    maxFeatureCount: {
        type: 'number',
        value: 10
    },
    /**
     * Buffer distance in pixels around the map click. The default is 10.
     * @signature `{Number}` `feature-buffer="10"`
     * @property {Number} identify-widget.ViewModel.props.featureBuffer
     * @parent identify-widget.ViewModel.props
     */
    featureBuffer: {
        type: 'number',
        value: 10
    },
    /**
     * Layer configuration properties
     * @property {geo.types.LayerPropertiesObject} identify-widget.ViewModel.props.layerProperties
     * @parent identify-widget.ViewModel.props
     */
    layerProperties: {
        Value: DefineMap
    },
    /**
     * The list of features that have been identified
     * @property {Array<ol.Feature>} identify-widget.ViewModel.props.features
     * @parent identify-widget.ViewModel.props
     */
    features: {
        Value: DefineList
    },
    /**
     * Whether or not all identifies have completed. This is used internally by the template.
     * @property {can.Deferred} identify-widget.ViewModel.props.loading
     * @parent identify-widget.ViewModel.props
     */
    loading: {
        get () {
            return Promise.all(this.promises);
        }
    },
    /**
     * A list of pending identify deferreds
     * @property {Array<can.Deferred>} identify-widget.ViewModel.props.promises
     * @parent identify-widget.ViewModel.props
     */
    promises: {
        Value: DefineList
    },
    /**
     * The currently selected feature index
     * @property {Number} identify-widget.ViewModel.props.activeFeatureIndex
     * @parent identify-widget.ViewModel.props
     */
    activeFeatureIndex: {
        value: 0,
        type: 'number'
    },
    hasError: {
        type: 'boolean',
        value: false
    },
    /**
     * A virtual property that returns an object consisting of the formatted fields, values, and layer properties.
     * @property {can.Map} identify-widget.ViewModel.props.activeFeature
     * @parent identify-widget.ViewModel.props
     */
    activeFeature: {
        get () {
            //if no features, return null
            if (!this.features.length) {
                //update Map layer
                this.updateSelectedFeature(null);
                return null;
            }

            //get this active feature by array index
            const feature = this.features[this.activeFeatureIndex];

            //update Map layer
            this.updateSelectedFeature(feature);

            //get the layer key name. Layer id is returned from wms by LayerName.fetureID
            let layer = feature.getId().split('.');
            const index = layer[1];
            layer = layer[0];

            //get the configured layer properties object key this.layerProperties.layerName
            const layerProperties = this.layerProperties[layer];
            let title, template, fields;

            //if its provided parse the alias and formatters
            if (layerProperties) {

                //set the layer alias
                title = layerProperties.alias;

                //set the correct template for this feature this.layerproperties.layername.template
                template = layerProperties.template;

                // set up the fields
                fields = layerProperties.fields;
            } else {
                fields = Object.keys(feature.getProperties());
            }

            return {
                //raw ol.feature
                feature: feature,
                //feature property values
                properties: feature.getProperties(),
                //get the field properties like alias and formatters this.layerproperties.layername.properties
                fields: parseFieldArray(fields),
                //feature template
                featureTemplate: template || featureTemplate,
                //the default template in case the other template wants to use it
                defaultTemplate: featureTemplate,
                layer: layer,
                title: title || layer,
                index: index
            };
        }
    },
    /**
     * The map object to bind a click event handler. Note: this should be removed
     * and put into a separate module. Idealy we could instead use this identify-widget
     * simply to hold the results from any map click identify.
     * @property {ol.Map} identify-widget.ViewModel.props.map
     * @parent identify-widget.ViewModel.props
     */
    map: {
        type: '*',
        set (map) {
            if (map) {
                map.on('click', (event) => {
                    if (!this.active) {
                        return false;
                    }
                    this.identify(event.coordinate);
                });
                map.addLayer(this.layer);
            }
            return map;
        }
    },
    /**
     * the openlayers vector layer in which features are identified on
     * @property {ol.layer.Vector} identify-widget.ViewModel.props.layer
     * @parent identify-widget.ViewModel.props
     */
    layer: {
        type: '*',
        value: function () {
            const source = new ol.source.Vector({
                features: []
            });
            return new ol.layer.Vector({
                title: 'Identify Results',
                id: this.id,
                source: source,
                excludeControl: true
            });
        }
    },
    /**
     * The popup viewModel. If provided, the identify widget will move the popup
     * to each feature as it is identified
     * @property {ol.layer.Vector} identify-widget.ViewModel.props.popup
     * @parent identify-widget.ViewModel.props
     */
    popup: {
        value: null
    },
    /**
     * A selector for a ol-popup node, for example `#identify-popup` or `ol-popup`
     * If provided, the identify widget will automatically set this `popup`
     * property using `can-view-model`
     * @property {String} identify-widget.ViewModel.props.popupSelector
     * @parent identify-widget.ViewModel.props
     */
    popupSelector: 'string',
    /**
     * Whether or not this identify widget should actively perform an identify
     * task. This must be set in order for the identify to be used.
     * @property {HTMLBoolean} identify-widget.ViewModel.props.active
     * @parent identify-widget.ViewModel.props
     */
    active: 'htmlbool',
  /**
   * @function identify
   * Queries the available map wms layers and updates the loading status
   * @signature
   * @param  {ol.events.Event} event The click event dispatched by the map
   * @param  {Array<Number>} coordinate Optional coordinate array to identify at
   * @return {Promise} A promise that is resolved when all of the identifies have finished loading
   */
    identify (event, coordinate) {
        if (!coordinate) {
            coordinate = event;
        }
        this.clearFeatures();
        this.hasError = false;
        const layers = this.map.getLayers();
        const promises = [];
        this.getQueryURLsRecursive(layers, coordinate).forEach((url) => {
            promises.push(this.getFeatureInfo(url));
        });
        promises.forEach((promise) => {
            promise.then((json) => {
                this.addFeatures(json, coordinate);
            }).catch((e) => {
                this.error(e);
            });
        });
        this.promises = promises;
        return this.loading;
    },
  /**
   * @function getQueryURLsRecursive
   * Recursively queries wms layers for identify results. Recursion is used to drill into group layers
   * @signature
   * @param  {ol.Collection<ol.Layer>} layers     A collection (array) of layers
   * @param  {Array<Number>} coordinate The current identify coordinate
   * @return {Array<String>}            Array of GetFeatureInfo urls
   */
    getQueryURLsRecursive (layers, coordinate) {
        let urls = [];
        layers.forEach((layer) => {
            if (layer.getVisible() && !layer.get('excludeIdentify')) {
                if (layer instanceof ol.layer.Group) {
                    urls = urls.concat(this.getQueryURLsRecursive(layer.getLayers(), coordinate));
                } else {
                    const url = this.getQueryURL(layer, coordinate);
                    if (url) {
                        urls.push(url);
                    }
                }
            }
        });
        return urls;
    },
  /**
   * @function getQueryURL
   * Creates a wms getGetFeatureInfo url
   * @signature
   * @param  {ol.layer} layer      The wms layer
   * @param  {Array<Number>} coordinate The coordinate pair to identify at
   * @return {String}            The url to query for identify results
   */
    getQueryURL (layer, coordinate) {
        if (layer.getSource && layer.getVisible()) {
            const source = layer.getSource();
            if (source && typeof source.getGetFeatureInfoUrl !== 'undefined') {
                const map = this.map;
                const view = map.getView();
                return source.getGetFeatureInfoUrl(
                  coordinate,
                  view.getResolution(),
                  view.getProjection(), {
                      'INFO_FORMAT': 'application/json',
                      'FEATURE_COUNT': this.maxFeatureCount,
                      'BUFFER': this.featureBuffer
                    //'QUERY_LAYERS': 'only_query_these_layers'
                  });
            }
        }
    },
  /**
   * @function getFeatureInfo
   * Queries the wms endpoint for identify results
   * @signature
   * @param  {String} url The url to query
   * @return {Deferred}     The deferred that is resolved to the raw wms feature data
   */
    getFeatureInfo (url) {
        return ajax({
            url: url,
            dataType: 'json',
            method: 'GET'
        });
    },
  /**
   * @function addFeatures
   * Adds features to this widgets collection after a layer has been identified
   * @signature
   * @param  {Object} collection A GeoJSON feature collection
   * @param  {Array<Number>} coordinate The coordinate pair where the mouse click occurred
   */
    addFeatures (collection, coordinate) {
        if (collection.features.length) {
            let features = [];
            collection.features.forEach((feature) => {

                //if this layer should be excluded, skip it
                //path to exclude is this.layerproperties.layerName.excludeIdentify
                const layer = feature.id.split('.')[0];
                const exclude = this.layerProperties[layer] && this.layerProperties[layer].excludeIdentify;
                if (!exclude) {

                    //otherwise, add the crs to each feature
                    feature.crs = collection.crs;
                    features.push(feature);
                }
            });
            const newCollection = assign(collection, {
                features: features
            });
            features = this.getFeaturesFromJson(newCollection);
            if (!this.features.length) {
                const index = this.getClosestFeatureIndex(features, coordinate);
                if (index) {
                    //swap the feature for the first so it shows up first
                    const temp = features[index];
                    features[index] = features[0];
                    features[0] = temp;
                }
            }
            this.features = this.features.concat(features);
        }
    },
  /**
   * @function getFeaturesFromJson
   * Converts raw geojson into openlayers features
   * @signature
   * @param  {Object } collection Raw GeoJSON object
   * @return {ol.Collection<ol.Feature>}            The collection of openlayers features
   */
    getFeaturesFromJson (collection) {
        const proj = this.map.getView().getProjection();
        const gjson = new ol.format.GeoJSON();
        const features = gjson.readFeatures(collection, {
            dataProjection: gjson.readProjection(collection),
            featureProjection: proj
        });
        return features;
    },
  /**
   * @function clearFeatures
   * clears the features in this widget
   * @signature
   */
    clearFeatures () {
        this.promises.forEach((p) => {
            p.abort();
        });
        this.features.replace([]);
        this.updateSelectedFeature(null);
        this.activeFeatureIndex = 0;
    },
  /**
   * @function updateSelectedFeature
   * Updates the currently selected feature and replaces the table attributes and the vector layer with the new feature
   * @signature
   * @param  {ol.Feature} feature The feature
   */
    updateSelectedFeature (feature) {
        if (!feature) {
            this.layer.getSource().clear();
            return;
        }

        const source = this.layer.getSource();
        source.clear();
        source.addFeature(feature);
        if (this.popup) {
            const extent = feature.getGeometry().getExtent();
            const center = ol.extent.getCenter(extent);
            this.popup.showPopup(center);
        }
        return;
    },
  /**
   * @function zoomToFeature
   * Zooms the map to a feature
   * @signature
   * @param  {Object} object An object containing a feature property
   */
    zoomToFeature (object) {
        const extent = object.feature
      .getGeometry()
      .getExtent();

        if (this.popup) {
            const key = this.map.on('postrender', () => {
                this.popup.showPopup(ol.extent.getCenter(extent));
                this.map.unByKey(key);
            });
        }

        this.animateZoomToExtent(extent);
    },
  /**
   * @function animateZoomToExtent
   * Zooms the map to an extent and creates the zoom animation
   * @signature
   * @param  {Array<Number>} extent The extent to zoom the map to
   */
    animateZoomToExtent (extent) {
        const map = this.map;
        const duration = 750;
        const pan = ol.animation.pan({
            duration: duration,
            source: map.getView().getCenter()
        });
        const zoom = ol.animation.zoom({
            duration: duration,
            resolution: map.getView().getResolution()
        });
        map.beforeRender(pan, zoom);
        map.getView().fit(
      extent, map.getSize(), [50, 50, 50, 50]);
    },
  /**
   * @function error
   * An error logging function for failed ajax requests
   * @signature
   * @param  {Error} e The error
   */
    error (e) {
        this.hasErrors = true;
        console.warn('Could not perform ajax request: ', e);
    },
  /**
   * @function getClosestFeatureIndex
   * finds the closest feature to the coordinate and returns that feature index
   * @signature
   * @param  {ol.feature[]} features The array of features to search through
   * @return {Number}          The index value of the closest feature
   */
    getClosestFeatureIndex (features, coord) {
        if (features.length === 0) {
            return 0;
        }
        let current = 0;
        let currentDistance = 99999;
        const getDistance = this.getDistance;
        features.forEach(function (feature, index) {
            const center = ol.extent.getCenter(feature.getGeometry().getExtent());
            const distance = getDistance(center, coord);
            if (distance < currentDistance) {
                currentDistance = distance;
                current = index;
            }
        });
        return current;
    },
  /**
   * @function getDistance
   * Gets the approximate distance value for two coordinates.
   * Not to be used for measuring as it uses a simple distance calculation
   * and does not take the earth's curvature into consideration
   * @signature
   * @param  {Number[]} c1 The first xy coordinate
   * @param  {Number[]} c2 The second xy coordinate
   * @return {Number}    The distance between the two points
   */
    getDistance (c1, c2) {
        return Math.sqrt(
      Math.pow((c1[0] - c2[0]), 2) +
      Math.pow((c1[1] - c2[1]), 2)
    );
    }
});

export default Component.extend({
    tag: 'identify-widget',
    view: template,
    viewModel: ViewModel,
    events: {
        inserted () {
            if (this.viewModel.popupSelector) {
                this.viewModel.popup = canViewModel(document.querySelector(this.viewModel.popupSelector));
            }
        },
        removed () {
            if (this.map) {
                this.map.removeLayer(this.layer);
                this.layer = null;
                this.map = null;
            }
        }
    }
});
<commit_msg>move 'updateSelectedFeature' to the feature getter<commit_after>
import featureTemplate from './featureTemplate.stache!';
import template from './template.stache!';
import './styles.less!';

import 'spectre-canjs/property-table/property-table';
import 'spectre-canjs/paginate-widget/paginate-widget';
import {parseFieldArray} from 'spectre-canjs/util/field';
import ajax from 'can-util/dom/ajax/ajax';
import assign from 'can-util/js/assign/assign';
import DefineMap from 'can-define/map/map';
import Component from 'can-component';
import DefineList from 'can-define/list/list';
import ol from 'openlayers';

import canViewModel from 'can-view-model';

/**
 * @constructor identify-widget.ViewModel ViewModel
 * @parent identify-widget
 * @group identify-widget.ViewModel.props Properties
 *
 * @description A `<identify-widget />` component's ViewModel
 */
export const ViewModel = DefineMap.extend('IdentifyWidget', {
  /**
   * @prototype
   */
    /**
     * The max number of features to return for each layer. The default is 10.
     * @signature `{Number}` `max-feature-count="10"`
     * @property {Number} identify-widget.ViewModel.props.maxFeatureCount
     * @parent identify-widget.ViewModel.props
     */
    maxFeatureCount: {
        type: 'number',
        value: 10
    },
    /**
     * Buffer distance in pixels around the map click. The default is 10.
     * @signature `{Number}` `feature-buffer="10"`
     * @property {Number} identify-widget.ViewModel.props.featureBuffer
     * @parent identify-widget.ViewModel.props
     */
    featureBuffer: {
        type: 'number',
        value: 10
    },
    /**
     * Layer configuration properties
     * @property {geo.types.LayerPropertiesObject} identify-widget.ViewModel.props.layerProperties
     * @parent identify-widget.ViewModel.props
     */
    layerProperties: {
        Value: DefineMap
    },
    /**
     * The list of features that have been identified
     * @property {Array<ol.Feature>} identify-widget.ViewModel.props.features
     * @parent identify-widget.ViewModel.props
     */
    features: {
        Value: DefineList
    },
    /**
     * Whether or not all identifies have completed. This is used internally by the template.
     * @property {can.Deferred} identify-widget.ViewModel.props.loading
     * @parent identify-widget.ViewModel.props
     */
    loading: {
        get () {
            return Promise.all(this.promises);
        }
    },
    /**
     * A list of pending identify promises
     * @property {Array<Promise>} identify-widget.ViewModel.props.promises
     * @parent identify-widget.ViewModel.props
     */
    promises: {
        Value: DefineList
    },
    /**
     * The currently selected feature index
     * @property {Number} identify-widget.ViewModel.props.activeFeatureIndex
     * @parent identify-widget.ViewModel.props
     */
    activeFeatureIndex: {
        value: 0,
        type: 'number'
    },
    hasError: {
        type: 'boolean',
        value: false
    },
    /**
     * A virtual property that returns an object consisting of the formatted fields, values, and layer properties.
     * @property {can.Map} identify-widget.ViewModel.props.activeFeature
     * @parent identify-widget.ViewModel.props
     */
    activeFeature: {
        get () {

            //if no features, return null
            if (!this.features.length) {
                this.layer.getSource().clear();
                return null;
            }

            //get this active feature by array index
            const feature = this.features[this.activeFeatureIndex];

            //clear any active features and add the new one
            const source = this.layer.getSource();
            source.clear();
            source.addFeature(feature);

            //get the layer key name. Layer id is returned from wms by LayerName.fetureID
            let layer = feature.getId().split('.');
            const index = layer[1];
            layer = layer[0];

            //get the configured layer properties object key this.layerProperties.layerName
            const layerProperties = this.layerProperties[layer];
            let title, template, fields;

            //if its provided parse the alias and formatters
            if (layerProperties) {

                //set the layer alias
                title = layerProperties.alias;

                //set the correct template for this feature this.layerproperties.layername.template
                template = layerProperties.template;

                // set up the fields
                fields = layerProperties.fields;
            } else {
                fields = Object.keys(feature.getProperties());
            }
            return {
                //raw ol.feature
                feature: feature,
                //feature property values
                properties: feature.getProperties(),
                //get the field properties like alias and formatters this.layerproperties.layername.properties
                fields: parseFieldArray(fields),
                //feature template
                featureTemplate: template || featureTemplate,
                //the default template in case the other template wants to use it
                defaultTemplate: featureTemplate,
                layer: layer,
                title: title || layer,
                index: index
            };
        }
    },
    /**
     * The map object to bind a click event handler. Note: this should be removed
     * and put into a separate module. Idealy we could instead use this identify-widget
     * simply to hold the results from any map click identify.
     * @property {ol.Map} identify-widget.ViewModel.props.map
     * @parent identify-widget.ViewModel.props
     */
    map: {
        type: '*',
        set (map) {
            if (map) {
                map.on('click', (event) => {
                    if (!this.active) {
                        return false;
                    }
                    this.identify(event.coordinate);
                });
                map.addLayer(this.layer);
            }
            return map;
        }
    },
    /**
     * the openlayers vector layer in which features are identified on
     * @property {ol.layer.Vector} identify-widget.ViewModel.props.layer
     * @parent identify-widget.ViewModel.props
     */
    layer: {
        type: '*',
        value: function () {
            const source = new ol.source.Vector({
                features: []
            });
            return new ol.layer.Vector({
                title: 'Identify Results',
                id: this.id,
                source: source,
                excludeControl: true
            });
        }
    },
    /**
     * Whether or not this identify widget should actively perform an identify
     * task. This must be set in order for the identify to be used.
     * @property {HTMLBoolean} identify-widget.ViewModel.props.active
     * @parent identify-widget.ViewModel.props
     */
    active: 'htmlbool',
  /**
   * @function identify
   * Queries the available map wms layers and updates the loading status
   * @signature
   * @param  {ol.events.Event} event The click event dispatched by the map
   * @param  {Array<Number>} coordinate Optional coordinate array to identify at
   * @return {Promise} A promise that is resolved when all of the identifies have finished loading
   */
    identify (event, coordinate) {
        if (!coordinate) {
            coordinate = event;
        }
        this.clearFeatures();
        this.hasError = false;
        const layers = this.map.getLayers();
        const promises = [];
        this.getQueryURLsRecursive(layers, coordinate).forEach((url) => {
            promises.push(this.getFeatureInfo(url));
        });
        promises.forEach((promise) => {
            promise.then((json) => {
                this.addFeatures(json, coordinate);
            }).catch((e) => {
                this.error(e);
            });
        });
        this.promises = promises;
        return this.loading;
    },
  /**
   * @function getQueryURLsRecursive
   * Recursively queries wms layers for identify results. Recursion is used to drill into group layers
   * @signature
   * @param  {ol.Collection<ol.Layer>} layers     A collection (array) of layers
   * @param  {Array<Number>} coordinate The current identify coordinate
   * @return {Array<String>}            Array of GetFeatureInfo urls
   */
    getQueryURLsRecursive (layers, coordinate) {
        let urls = [];
        layers.forEach((layer) => {
            if (layer.getVisible() && !layer.get('excludeIdentify')) {
                if (layer instanceof ol.layer.Group) {
                    urls = urls.concat(this.getQueryURLsRecursive(layer.getLayers(), coordinate));
                } else {
                    const url = this.getQueryURL(layer, coordinate);
                    if (url) {
                        urls.push(url);
                    }
                }
            }
        });
        return urls;
    },
  /**
   * @function getQueryURL
   * Creates a wms getGetFeatureInfo url
   * @signature
   * @param  {ol.layer} layer      The wms layer
   * @param  {Array<Number>} coordinate The coordinate pair to identify at
   * @return {String}            The url to query for identify results
   */
    getQueryURL (layer, coordinate) {
        if (layer.getSource && layer.getVisible()) {
            const source = layer.getSource();
            if (source && typeof source.getGetFeatureInfoUrl !== 'undefined') {
                const map = this.map;
                const view = map.getView();
                return source.getGetFeatureInfoUrl(
                  coordinate,
                  view.getResolution(),
                  view.getProjection(), {
                      'INFO_FORMAT': 'application/json',
                      'FEATURE_COUNT': this.maxFeatureCount,
                      'BUFFER': this.featureBuffer
                    //'QUERY_LAYERS': 'only_query_these_layers'
                  });
            }
        }
    },
  /**
   * @function getFeatureInfo
   * Queries the wms endpoint for identify results
   * @signature
   * @param  {String} url The url to query
   * @return {Deferred}     The deferred that is resolved to the raw wms feature data
   */
    getFeatureInfo (url) {
        return ajax({
            url: url,
            dataType: 'json',
            method: 'GET'
        });
    },
  /**
   * @function addFeatures
   * Adds features to this widgets collection after a layer has been identified
   * @signature
   * @param  {Object} collection A GeoJSON feature collection
   * @param  {Array<Number>} coordinate The coordinate pair where the mouse click occurred
   */
    addFeatures (collection, coordinate) {
        if (collection.features.length) {
            let features = [];
            collection.features.forEach((feature) => {

                //if this layer should be excluded, skip it
                //path to exclude is this.layerproperties.layerName.excludeIdentify
                const layer = feature.id.split('.')[0];
                const exclude = this.layerProperties[layer] && this.layerProperties[layer].excludeIdentify;
                if (!exclude) {

                    //otherwise, add the crs to each feature
                    feature.crs = collection.crs;
                    features.push(feature);
                }
            });
            const newCollection = assign(collection, {
                features: features
            });
            features = this.getFeaturesFromJson(newCollection);
            if (!this.features.length) {
                const index = this.getClosestFeatureIndex(features, coordinate);
                if (index) {
                    //swap the feature for the first so it shows up first
                    const temp = features[index];
                    features[index] = features[0];
                    features[0] = temp;
                }
            }
            this.features = this.features.concat(features);
        }
    },
  /**
   * @function getFeaturesFromJson
   * Converts raw geojson into openlayers features
   * @signature
   * @param  {Object } collection Raw GeoJSON object
   * @return {ol.Collection<ol.Feature>}            The collection of openlayers features
   */
    getFeaturesFromJson (collection) {
        const proj = this.map.getView().getProjection();
        const gjson = new ol.format.GeoJSON();
        const features = gjson.readFeatures(collection, {
            dataProjection: gjson.readProjection(collection),
            featureProjection: proj
        });
        return features;
    },
  /**
   * @function clearFeatures
   * clears the features in this widget
   * @signature
   */
    clearFeatures () {
        this.promises.forEach((p) => {
            p.abort();
        });
        this.features.replace([]);
        this.activeFeatureIndex = 0;
    },
  /**
   * @function zoomToFeature
   * Zooms the map to a feature
   * @signature
   * @param  {Object} object An object containing a feature property
   */
    zoomToFeature (object) {
        const extent = object.feature
                        .getGeometry()
                        .getExtent();

        this.animateZoomToExtent(extent);
    },
  /**
   * @function animateZoomToExtent
   * Zooms the map to an extent and creates the zoom animation
   * @signature
   * @param  {Array<Number>} extent The extent to zoom the map to
   */
    animateZoomToExtent (extent) {
        const map = this.map;
        const duration = 750;
        const pan = ol.animation.pan({
            duration: duration,
            source: map.getView().getCenter()
        });
        const zoom = ol.animation.zoom({
            duration: duration,
            resolution: map.getView().getResolution()
        });
        map.beforeRender(pan, zoom);
        map.getView().fit(
      extent, map.getSize(), [50, 50, 50, 50]);
    },
  /**
   * @function error
   * An error logging function for failed ajax requests
   * @signature
   * @param  {Error} e The error
   */
    error (e) {
        this.hasErrors = true;
        console.warn('Could not perform ajax request: ', e);
    },
  /**
   * @function getClosestFeatureIndex
   * finds the closest feature to the coordinate and returns that feature index
   * @signature
   * @param  {ol.feature[]} features The array of features to search through
   * @return {Number}          The index value of the closest feature
   */
    getClosestFeatureIndex (features, coord) {
        if (features.length === 0) {
            return 0;
        }
        let current = 0;
        let currentDistance = 99999;
        const getDistance = this.getDistance;
        features.forEach(function (feature, index) {
            const center = ol.extent.getCenter(feature.getGeometry().getExtent());
            const distance = getDistance(center, coord);
            if (distance < currentDistance) {
                currentDistance = distance;
                current = index;
            }
        });
        return current;
    },
  /**
   * @function getDistance
   * Gets the approximate distance value for two coordinates.
   * Not to be used for measuring as it uses a simple distance calculation
   * and does not take the earth's curvature into consideration
   * @signature
   * @param  {Number[]} c1 The first xy coordinate
   * @param  {Number[]} c2 The second xy coordinate
   * @return {Number}    The distance between the two points
   */
    getDistance (c1, c2) {
        return Math.sqrt(
      Math.pow((c1[0] - c2[0]), 2) +
      Math.pow((c1[1] - c2[1]), 2)
    );
    }
});

export const IdentifyWidget = Component.extend({
    tag: 'identify-widget',
    view: template,
    viewModel: ViewModel,
    events: {
        inserted () {
        },
        removed () {
            if (this.map) {
                this.map.removeLayer(this.layer);
                this.layer = null;
                this.map = null;
            }
        }
    }
});
<|endoftext|>


<commit_before>function EntityManager(scene, conn, world, clock) {
	var self = this;

	var controllers = {};

	var _playerId = null;
	var _playerEntity;
	//This is only for the player which represents the player!
	self.setPlayer = function(id, entity, controller) {
		if (_playerId || _playerEntity) {
			throw "Attempt to set player when player has already been set.";
		}
		_playerId = id;
		controllers[id] = controller;
		_playerEntity = entity;
		if (localStorage.showOwnEntity || localStorage.thirdPerson) {
			entity.addTo(scene);
		}
	};

	conn.on('entity-create', function (payload) {
		var id = payload.ID;
		if (controllers[id]) {
			console.warn("Got entity-create message for entity which already exists!", id);
			return;
		}
		var entity = new PlayerEntity()
		entity.addTo(scene);

		var initialState = protocolToLocal(payload);
		var controller = new EntityNetworkController(entity, clock, initialState);

		controllers[id] = controller;

		if (localStorage.posHistoryBar) {
			entity.add(new EntityBar(controller.drawState, _playerEntity));
		}
	});

	function protocolToLocal(payload) {
		function vec(obj) {
			return new THREE.Vector3(obj.X, obj.Y, obj.Z)
		}
		return {
			time: payload.Timestamp,
			data: new EntityState(
				vec(payload.Pos),
				vec(payload.Look),
				payload.Health,
				payload.Vy),
		};
	}

	conn.on('entity-state', function (payload) {
		var id = payload.ID;
		var controller = controllers[id];
		if (!controller) {
			console.warn("Got entity-pos message for entity which does not exist!", id);
			return;
		}

		controller.message(protocolToLocal(payload));
	});

	conn.on('entity-remove', function (payload) {
		var id = payload.ID;
		var controller = controllers[id];
		if (!controller) {
			console.warn("Got entity-remove message for entity which does not exist: ", id);
		}
		controller.entity().removeFrom(scene);
		delete controllers[id];
	});

	self.entityAt = function (wcX, wcY, wcZ) {
		for (var id in controllers) {
			var entity = controllers[id].entity();
			if (entity.contains(wcX, wcY, wcZ)) {
				return entity;
			}
		}
	};

	self.update = function() {
		for (var id in controllers) {
			var controller = controllers[id];
			controller.update();
		}
	};
}
<commit_msg>Remove old comment and redundant variable.<commit_after>function EntityManager(scene, conn, world, clock) {
	var self = this;

	var controllers = {};

	var _playerId = null;
	self.setPlayer = function(id, entity, controller) {
		if (_playerId) {
			throw "Attempt to set player when player has already been set.";
		}
		_playerId = id;
		controllers[id] = controller;
		if (localStorage.showOwnEntity || localStorage.thirdPerson) {
			entity.addTo(scene);
		}
	};

	conn.on('entity-create', function (payload) {
		var id = payload.ID;
		if (controllers[id]) {
			console.warn("Got entity-create message for entity which already exists!", id);
			return;
		}
		var entity = new PlayerEntity()
		entity.addTo(scene);

		var initialState = protocolToLocal(payload);
		var controller = new EntityNetworkController(entity, clock, initialState);

		controllers[id] = controller;

		if (localStorage.posHistoryBar) {
			var playerEntity = controllers[_playerId].entity();
			entity.add(new EntityBar(controller.drawState, playerEntity));
		}
	});

	function protocolToLocal(payload) {
		function vec(obj) {
			return new THREE.Vector3(obj.X, obj.Y, obj.Z)
		}
		return {
			time: payload.Timestamp,
			data: new EntityState(
				vec(payload.Pos),
				vec(payload.Look),
				payload.Health,
				payload.Vy),
		};
	}

	conn.on('entity-state', function (payload) {
		var id = payload.ID;
		var controller = controllers[id];
		if (!controller) {
			console.warn("Got entity-pos message for entity which does not exist!", id);
			return;
		}

		controller.message(protocolToLocal(payload));
	});

	conn.on('entity-remove', function (payload) {
		var id = payload.ID;
		var controller = controllers[id];
		if (!controller) {
			console.warn("Got entity-remove message for entity which does not exist: ", id);
		}
		controller.entity().removeFrom(scene);
		delete controllers[id];
	});

	self.entityAt = function (wcX, wcY, wcZ) {
		for (var id in controllers) {
			var entity = controllers[id].entity();
			if (entity.contains(wcX, wcY, wcZ)) {
				return entity;
			}
		}
	};

	self.update = function() {
		for (var id in controllers) {
			var controller = controllers[id];
			controller.update();
		}
	};
}
<|endoftext|>


<commit_before>const _ = require('lodash');
const uuid = require('uuid');
const ObjectId = require('bson-objectid');
const moment = require('moment-timezone');
const errors = require('@tryghost/errors');
const membersService = require('../index');
const {i18n} = require('../../../lib/common');
const db = require('../../../data/db');
const logging = require('../../../../shared/logging');

const cleanupUndefined = (obj) => {
    for (let key in obj) {
        if (obj[key] === 'undefined') {
            delete obj[key];
        }
    }
};

function serializeMemberLabels(labels) {
    if (_.isString(labels)) {
        if (labels === '') {
            return [];
        }

        return [{
            name: labels.trim()
        }];
    } else if (labels) {
        return labels.filter((label) => {
            return !!label;
        }).map((label) => {
            if (_.isString(label)) {
                return {
                    name: label.trim()
                };
            }
            return label;
        });
    }
    return [];
}

const doImport = async ({membersBatch: members, allLabelModels, importSetLabels, imported, invalid, createdBy}) => {
    const mappedMemberBatchData = [];
    const mappedMembersLabelsBatchAssociations = [];
    const membersWithStripeCustomers = [];
    const membersWithComplimentaryPlans = [];

    members.forEach((member) => {
        cleanupUndefined(member);

        let subscribed;
        if (_.isUndefined(member.subscribed_to_emails)) {
            // model default
            subscribed = 'true';
        } else {
            subscribed = (String(member.subscribed_to_emails).toLowerCase() !== 'false');
        }

        member.labels = (member.labels && member.labels.split(',')) || [];
        const entryLabels = serializeMemberLabels(member.labels);
        const mergedLabels = _.unionBy(entryLabels, importSetLabels, 'name');

        let createdAt = member.created_at === '' ? undefined : member.created_at;

        if (createdAt) {
            const date = new Date(createdAt);

            // CASE: client sends `0000-00-00 00:00:00`
            if (isNaN(date)) {
                // TODO: throw in validation stage for single record, not whole batch!
                throw new errors.ValidationError({
                    message: i18n.t('errors.models.base.invalidDate', {key: 'created_at'}),
                    code: 'DATE_INVALID'
                });
            }

            createdAt = moment(createdAt).toDate();
        } else {
            createdAt = new Date();
        }

        const memberId = ObjectId.generate();
        mappedMemberBatchData.push({
            id: memberId,
            uuid: uuid.v4(), // member model default
            email: member.email,
            name: member.name,
            note: member.note,
            subscribed: subscribed,
            created_at: createdAt,
            created_by: createdBy
        });

        if (mergedLabels) {
            mergedLabels.forEach((label, index) => {
                const matchedLabel = allLabelModels.find(labelModel => labelModel.get('name') === label.name);

                mappedMembersLabelsBatchAssociations.push({
                    id: ObjectId.generate(),
                    member_id: memberId,
                    label_id: matchedLabel.id,
                    sort_order: index
                });
            });
        }

        if (member.stripe_customer_id) {
            membersWithStripeCustomers.push({
                stripe_customer_id: member.stripe_customer_id,
                id: memberId,
                email: member.email
            });
        }

        if ((String(member.complimentary_plan).toLocaleLowerCase() === 'true')) {
            membersWithComplimentaryPlans.push({
                id: memberId,
                email: member.email
            });
        }
    });

    try {
        // TODO: below inserts most likely need to be wrapped into transaction
        //       to avoid creating orphaned member_labels connections
        await db.knex('members')
            .insert(mappedMemberBatchData);

        await db.knex('members_labels')
            .insert(mappedMembersLabelsBatchAssociations);

        imported.count += mappedMemberBatchData.length;
    } catch (error) {
        logging.error(error);

        if (error.code && error.message.toLowerCase().indexOf('unique') !== -1) {
            invalid.errors.push(new errors.ValidationError({
                message: i18n.t('errors.api.members.memberAlreadyExists.message'),
                context: i18n.t('errors.api.members.memberAlreadyExists.context')
            }));
        } else {
            // NOTE: probably need to wrap this error into something more specific e.g. ImportError
            invalid.errors.push(error);
        }

        invalid.count += mappedMemberBatchData.length;
    }

    if (membersWithStripeCustomers.length || membersWithComplimentaryPlans.length) {
        const deleteMemberKnex = async (id) => {
            // TODO: cascading wont work on SQLite needs 2 separate deletes
            //       for members_labels and members wrapped into a transaction
            const deletedMembersCount = await db.knex('members')
                .where('id', id)
                .del();

            if (deletedMembersCount) {
                imported.count -= deletedMembersCount;
                invalid.count += deletedMembersCount;
            }
        };

        if (!membersService.config.isStripeConnected()) {
            const memberIdsToDestroy = _.uniq([
                ...membersWithStripeCustomers.map(m => m.id),
                ...membersWithComplimentaryPlans.map(m => m.id)
            ]);

            // TODO: cascading wont work on SQLite needs 2 separate deletes
            //       for members_labels and members wrapped into a transaction
            const deleteMembersCount = await db.knex('members')
                .whereIn('id', memberIdsToDestroy)
                .del();

            imported.count -= deleteMembersCount;
            invalid.count += deleteMembersCount;
            invalid.errors.push(new errors.ValidationError({
                message: i18n.t('errors.api.members.stripeNotConnected.message'),
                context: i18n.t('errors.api.members.stripeNotConnected.context'),
                help: i18n.t('errors.api.members.stripeNotConnected.help')
            }));
        } else {
            if (membersWithStripeCustomers.length) {
                await Promise.map(membersWithStripeCustomers, async (stripeMember) => {
                    try {
                        await membersService.api.members.linkStripeCustomerById(stripeMember.stripe_customer_id, stripeMember.id);
                    } catch (error) {
                        if (error.message.indexOf('customer') && error.code === 'resource_missing') {
                            error.message = `Member not imported. ${error.message}`;
                            error.context = i18n.t('errors.api.members.stripeCustomerNotFound.context');
                            error.help = i18n.t('errors.api.members.stripeCustomerNotFound.help');
                        }
                        logging.error(error);
                        invalid.errors.push(error);

                        await deleteMemberKnex(stripeMember.id);
                    }
                }, {
                    concurrency: 10
                });
            }

            if (membersWithComplimentaryPlans.length) {
                await Promise.map(membersWithComplimentaryPlans, async (complimentaryMember) => {
                    try {
                        await membersService.api.members.setComplimentarySubscriptionById(complimentaryMember.id);
                    } catch (error) {
                        logging.error(error);
                        invalid.errors.push(error);
                        await deleteMemberKnex(complimentaryMember.id);
                    }
                }, {
                    concurrency: 10 // TODO: check if this concurrency level doesn't fail rate limits
                });
            }
        }
    }
};

module.exports = doImport;
<commit_msg>Moved batching logic inside the members importer module<commit_after>const _ = require('lodash');
const uuid = require('uuid');
const ObjectId = require('bson-objectid');
const moment = require('moment-timezone');
const errors = require('@tryghost/errors');
const membersService = require('../index');
const {i18n} = require('../../../lib/common');
const db = require('../../../data/db');
const logging = require('../../../../shared/logging');

const cleanupUndefined = (obj) => {
    for (let key in obj) {
        if (obj[key] === 'undefined') {
            delete obj[key];
        }
    }
};

function serializeMemberLabels(labels) {
    if (_.isString(labels)) {
        if (labels === '') {
            return [];
        }

        return [{
            name: labels.trim()
        }];
    } else if (labels) {
        return labels.filter((label) => {
            return !!label;
        }).map((label) => {
            if (_.isString(label)) {
                return {
                    name: label.trim()
                };
            }
            return label;
        });
    }
    return [];
}

const doImport = async ({membersBatch: members, allLabelModels, importSetLabels, imported, invalid, createdBy}) => {
    const mappedMemberBatchData = [];
    const mappedMembersLabelsBatchAssociations = [];
    const membersWithStripeCustomers = [];
    const membersWithComplimentaryPlans = [];

    members.forEach((member) => {
        cleanupUndefined(member);

        let subscribed;
        if (_.isUndefined(member.subscribed_to_emails)) {
            // model default
            subscribed = 'true';
        } else {
            subscribed = (String(member.subscribed_to_emails).toLowerCase() !== 'false');
        }

        member.labels = (member.labels && member.labels.split(',')) || [];
        const entryLabels = serializeMemberLabels(member.labels);
        const mergedLabels = _.unionBy(entryLabels, importSetLabels, 'name');

        let createdAt = member.created_at === '' ? undefined : member.created_at;

        if (createdAt) {
            const date = new Date(createdAt);

            // CASE: client sends `0000-00-00 00:00:00`
            if (isNaN(date)) {
                // TODO: throw in validation stage for single record, not whole batch!
                throw new errors.ValidationError({
                    message: i18n.t('errors.models.base.invalidDate', {key: 'created_at'}),
                    code: 'DATE_INVALID'
                });
            }

            createdAt = moment(createdAt).toDate();
        } else {
            createdAt = new Date();
        }

        const memberId = ObjectId.generate();
        mappedMemberBatchData.push({
            id: memberId,
            uuid: uuid.v4(), // member model default
            email: member.email,
            name: member.name,
            note: member.note,
            subscribed: subscribed,
            created_at: createdAt,
            created_by: createdBy
        });

        if (mergedLabels) {
            mergedLabels.forEach((label, index) => {
                const matchedLabel = allLabelModels.find(labelModel => labelModel.get('name') === label.name);

                mappedMembersLabelsBatchAssociations.push({
                    id: ObjectId.generate(),
                    member_id: memberId,
                    label_id: matchedLabel.id,
                    sort_order: index
                });
            });
        }

        if (member.stripe_customer_id) {
            membersWithStripeCustomers.push({
                stripe_customer_id: member.stripe_customer_id,
                id: memberId,
                email: member.email
            });
        }

        if ((String(member.complimentary_plan).toLocaleLowerCase() === 'true')) {
            membersWithComplimentaryPlans.push({
                id: memberId,
                email: member.email
            });
        }
    });

    try {
        // TODO: below inserts most likely need to be wrapped into transaction
        //       to avoid creating orphaned member_labels connections
        const CHUNK_SIZE = 5000;
        const chunkedMembers = _.chunk(mappedMemberBatchData, CHUNK_SIZE);
        for (const data of chunkedMembers) {
            await db.knex('members')
                .insert(data);
        }

        const chunkedLebelAssociations = _.chunk(mappedMembersLabelsBatchAssociations, CHUNK_SIZE);

        for (const data of chunkedLebelAssociations) {
            await db.knex('members_labels')
                .insert(data);
        }

        imported.count += mappedMemberBatchData.length;
    } catch (error) {
        logging.error(error);

        if (error.code && error.message.toLowerCase().indexOf('unique') !== -1) {
            invalid.errors.push(new errors.ValidationError({
                message: i18n.t('errors.api.members.memberAlreadyExists.message'),
                context: i18n.t('errors.api.members.memberAlreadyExists.context')
            }));
        } else {
            // NOTE: probably need to wrap this error into something more specific e.g. ImportError
            invalid.errors.push(error);
        }

        invalid.count += mappedMemberBatchData.length;
    }

    if (membersWithStripeCustomers.length || membersWithComplimentaryPlans.length) {
        const deleteMemberKnex = async (id) => {
            // TODO: cascading wont work on SQLite needs 2 separate deletes
            //       for members_labels and members wrapped into a transaction
            const deletedMembersCount = await db.knex('members')
                .where('id', id)
                .del();

            if (deletedMembersCount) {
                imported.count -= deletedMembersCount;
                invalid.count += deletedMembersCount;
            }
        };

        if (!membersService.config.isStripeConnected()) {
            const memberIdsToDestroy = _.uniq([
                ...membersWithStripeCustomers.map(m => m.id),
                ...membersWithComplimentaryPlans.map(m => m.id)
            ]);

            // TODO: cascading wont work on SQLite needs 2 separate deletes
            //       for members_labels and members wrapped into a transaction
            const deleteMembersCount = await db.knex('members')
                .whereIn('id', memberIdsToDestroy)
                .del();

            imported.count -= deleteMembersCount;
            invalid.count += deleteMembersCount;
            invalid.errors.push(new errors.ValidationError({
                message: i18n.t('errors.api.members.stripeNotConnected.message'),
                context: i18n.t('errors.api.members.stripeNotConnected.context'),
                help: i18n.t('errors.api.members.stripeNotConnected.help')
            }));
        } else {
            if (membersWithStripeCustomers.length) {
                await Promise.map(membersWithStripeCustomers, async (stripeMember) => {
                    try {
                        await membersService.api.members.linkStripeCustomerById(stripeMember.stripe_customer_id, stripeMember.id);
                    } catch (error) {
                        if (error.message.indexOf('customer') && error.code === 'resource_missing') {
                            error.message = `Member not imported. ${error.message}`;
                            error.context = i18n.t('errors.api.members.stripeCustomerNotFound.context');
                            error.help = i18n.t('errors.api.members.stripeCustomerNotFound.help');
                        }
                        logging.error(error);
                        invalid.errors.push(error);

                        await deleteMemberKnex(stripeMember.id);
                    }
                }, {
                    concurrency: 10
                });
            }

            if (membersWithComplimentaryPlans.length) {
                await Promise.map(membersWithComplimentaryPlans, async (complimentaryMember) => {
                    try {
                        await membersService.api.members.setComplimentarySubscriptionById(complimentaryMember.id);
                    } catch (error) {
                        logging.error(error);
                        invalid.errors.push(error);
                        await deleteMemberKnex(complimentaryMember.id);
                    }
                }, {
                    concurrency: 10 // TODO: check if this concurrency level doesn't fail rate limits
                });
            }
        }
    }
};

module.exports = doImport;
<|endoftext|>