UNPKG

@appbaseio/reactivesearch-vue

Version:

A Vue UI components library for building search experiences

1,436 lines (1,417 loc) 74.9 kB
import { Actions, helper } from '@appbaseio/reactivecore'; import { componentTypes } from '@appbaseio/reactivecore/lib/utils/constants'; import _transformOn from '@vue/babel-helper-vue-transform-on'; import { a as _extends, b as _objectWithoutPropertiesLoose, c as _toPropertyKey, d as _inheritsLoose, _ as _taggedTemplateLiteralLoose } from './_rollupPluginBabelHelpers-5e8399d7.js'; import { createVNode, mergeProps } from 'vue'; import VueTypes from 'vue-types'; import '@appbaseio/reactivecore/lib/utils/helper'; import { styled } from '@appbaseio/vue-emotion'; import { css } from '@emotion/css'; import 'polished'; import './Button-8178e39a.js'; import { t as types } from './vueTypes-5d575822.js'; import { P as Pagination } from './Pagination-80244481.js'; import '@appbaseio/reactivecore/lib/utils/transform'; import 'redux'; import { h as hasCustomRenderer, i as isQueryIdentical, g as getComponent, a as isFunction, c as connect } from './index-3af85a74.js'; import { C as ComponentWrapper } from './ComponentWrapper-90d42a29.js'; import { P as PreferencesConsumer } from './PreferencesConsumer-a2bd59db.js'; import { C as Checkbox } from './FormControlList-968ff972.js'; import VueGoogleMaps, { InfoWindow, MapElementMixin, Marker, GMapCluster, Map } from '@appbaseio/vue-google-maps-community-fork'; import geohash from 'ngeohash'; var setStreaming = Actions.setStreaming, setQueryOptions = Actions.setQueryOptions, updateQuery = Actions.updateQuery, loadMore = Actions.loadMore, setValue = Actions.setValue, updateComponentProps = Actions.updateComponentProps, setDefaultQuery = Actions.setDefaultQuery, recordResultClick = Actions.recordResultClick, setMapData = Actions.setMapData; var isEqual = helper.isEqual, getQueryOptions = helper.getQueryOptions, getClassName = helper.getClassName, parseHits = helper.parseHits, getOptionsFromQuery = helper.getOptionsFromQuery, getResultStats = helper.getResultStats; // default map center var MAP_CENTER = { lat: 37.7749, lng: 122.4194 }; var style = { width: '100%', height: '100vh', position: 'relative' }; function getPrecision(a) { if (isNaN(a)) return 0; // eslint-disable-line var e = 1; var p = 0; while (Math.round(a * e) / e !== a) { e *= 10; p += 1; } return p; } function withDistinctLat(loc, count) { var length = getPrecision(loc.lat); var noiseFactor = length >= 6 ? 4 : length - 2; // eslint-disable-next-line var suffix = 1 / Math.pow(10, noiseFactor) * count; var location = _extends({}, loc, { lat: parseFloat((loc.lat + suffix).toFixed(length)) }); return location; } function getLocationObject(location) { var resultType = Array.isArray(location) ? 'array' : typeof location; switch (resultType) { case 'string': { if (location.indexOf(',') > -1) { var locationSplit = location.split(','); return { lat: parseFloat(locationSplit[0]), lng: parseFloat(locationSplit[1]) }; } var locationDecode = geohash.decode(location); return { lat: locationDecode.latitude, lng: locationDecode.longitude }; } case 'array': { return { lat: location[1], lng: location[0] }; } default: { return location; } } } var ReactiveMap = { name: 'ReactiveMap', props: { className: types.string, componentId: types.stringRequired, compoundClause: types.compoundClause, dataField: types.stringRequired, react: types.react, size: types.number, sortBy: types.sortBy, URLParams: VueTypes.bool, autoCenter: VueTypes.bool, getMapRef: VueTypes.func.isRequired, center: types.location, defaultCenter: types.location, defaultPin: types.string, defaultZoom: VueTypes.number.def(13), defaultQuery: types.func, innerClass: types.style, loader: types.title, render: types.func, renderItem: types.func, renderError: types.title, pages: VueTypes.number.def(5), currentPage: VueTypes.number.def(0), pagination: VueTypes.bool, showMarkers: VueTypes.bool, defaultSearchAsMove: VueTypes.bool, showSearchAsMove: VueTypes.bool, defaultRadius: types.number, unit: types.string, autoClosePopover: VueTypes.bool, renderMap: VueTypes.func.isRequired, renderPopover: VueTypes.func, calculateMarkers: VueTypes.func, searchAsMoveLabel: VueTypes.string.def('Search as I move the map') }, preserveCenter: false, data: function data() { var props = this.$props; var currentPageState = 0; if (props.currentPage) { currentPageState = Math.max(props.currentPage - 1, 0); } this.__state = { from: currentPageState * props.size, zoom: props.defaultZoom, searchAsMove: props.defaultSearchAsMove, currentPageState: currentPageState, mapBoxBounds: null, markersData: null, filteredResults: null }; return this.__state; }, computed: { totalPages: function totalPages() { return Math.ceil(this.total / this.$props.size) || 0; }, stats: function stats() { var _this$getAllData = this.getAllData(), resultsToRender = _this$getAllData.resultsToRender; return _extends({}, getResultStats(this), { currentPage: this.currentPageState, displayedResults: resultsToRender.length }); }, hasCustomRender: function hasCustomRender() { return hasCustomRenderer(this); } }, watch: { defaultZoom: function defaultZoom(newVal) { this.zoom = newVal; }, currentPage: function currentPage(newVal, oldVal) { if (oldVal !== newVal && newVal > 0 && newVal <= this.totalPages) { this.setPage(newVal - 1); } }, defaultQuery: function defaultQuery(newVal, oldVal) { if (!isQueryIdentical(newVal, oldVal, null, this.$props)) { var options = getQueryOptions(this.$props); options.from = 0; this.$defaultQuery = newVal(null, this.$props); var _ref = this.$defaultQuery || {}, sort = _ref.sort, query = _ref.query; if (sort) { options.sort = this.$defaultQuery.sort; } var queryOptions = getOptionsFromQuery(this.$defaultQuery); if (queryOptions) { options = _extends({}, options, getOptionsFromQuery(this.$defaultQuery)); } // Update calculated default query in store this.setQueryOptions(this.$props.componentId, options, false); var persistMapQuery = true; var forceExecute = true; // Update default query to include the geo bounding box query this.setDefaultQueryForRSAPI(); var meta = { mapBoxBounds: this.mapBoxBounds }; this.setMapData(this.componentId, query, persistMapQuery, forceExecute, meta); this.currentPageState = 0; this.from = 0; } }, promotedResults: function promotedResults(newVal, oldVal) { if (!isEqual(newVal, oldVal)) { this.$emit('data', this.getData()); } }, hidden: function hidden(newVal, oldVal) { if (!isEqual(newVal, oldVal)) { this.$emit('data', this.getData()); } }, time: function time(newVal, oldVal) { if (!isEqual(newVal, oldVal)) { this.$emit('data', this.getData()); } }, hits: function hits(newVal, oldVal) { this.$emit('data', this.getData()); if (this.pagination) { // called when page is changed if (this.isLoading && (oldVal || newVal)) { this.$emit('page-change', this.currentPageState + 1, this.totalPages); } } }, rawData: function rawData(newVal, oldVal) { var _this = this; if (!isEqual(newVal, oldVal)) { var promotedResults = this.promotedResults, hits = this.hits; var results = parseHits(hits) || []; var parsedPromotedResults = parseHits(promotedResults) || []; var filteredResults = results; if (parsedPromotedResults.length) { var ids = parsedPromotedResults.map(function (item) { return item._id; }).filter(Boolean); if (ids) { filteredResults = filteredResults.filter(function (item) { return !ids.includes(item._id); }); } filteredResults = [].concat(parsedPromotedResults, filteredResults); } filteredResults = filteredResults.filter(function (item) { return !!item[_this.dataField]; }).map(function (item) { var _extends2; return _extends({}, item, (_extends2 = {}, _extends2[_this.dataField] = getLocationObject(item[_this.dataField]), _extends2)); }); this.filteredResults = this.addNoise(filteredResults); if (this.calculateMarkers) { this.markersData = this.calculateMarkers({ data: this.filteredResults, rawData: this.rawData }) || []; } this.$emit('data', this.getData()); } }, center: function center(newVal, oldVal) { if (!isEqual(newVal, oldVal)) { var persistMapQuery = !!this.center; // we need to forceExecute the query because the center has changed var forceExecute = true; var geoQuery = this.getGeoQuery(this.$props); // Update default query for RS API this.setDefaultQueryForRSAPI(); var meta = { mapBoxBounds: this.mapBoxBounds }; this.setMapData(this.componentId, geoQuery, persistMapQuery, forceExecute, meta); } } }, methods: { parseLocation: function parseLocation(location) { if (Array.isArray(location)) { return { lat: Number(location[0]), lng: Number(location[1]) }; } return { lat: location ? Number(location.lat) : this.defaultCenter.lat, lng: location ? Number(location.lon === undefined ? location.lng : location.lon) : this.defaultCenter.lng }; }, getDefaultCenter: function getDefaultCenter() { if (this.defaultCenter) return this.parseLocation(this.defaultCenter); return this.parseLocation(MAP_CENTER); }, addNoise: function addNoise(hits) { var _this2 = this; var hitMap = {}; var updatedHits = []; hits.forEach(function (item) { var updatedItem = _extends({}, item); var location = _this2.parseLocation(item[_this2.dataField]); var key = JSON.stringify(location); var count = hitMap[key] || 0; updatedItem[_this2.dataField] = count ? withDistinctLat(location, count) : location; updatedHits = [].concat(updatedHits, [updatedItem]); hitMap[key] = count + 1; }); return updatedHits; }, getPosition: function getPosition(result) { if (result) { return this.parseLocation(result[this.dataField]); } return null; }, getHitsCenter: function getHitsCenter(hits) { var _this3 = this; var data = hits.map(function (hit) { return hit[_this3.dataField]; }); if (data.length) { var numCoords = data.length; var X = 0.0; var Y = 0.0; var Z = 0.0; data.forEach(function (location) { if (location) { var _lat = 0.0; var _lng = 0.0; var locationObj = getLocationObject(location); _lat = locationObj.lat * Math.PI / 180; _lng = (locationObj.lng !== undefined ? locationObj.lng : locationObj.lon) * Math.PI / 180; var a = Math.cos(_lat) * Math.cos(_lng); var b = Math.cos(_lat) * Math.sin(_lng); var c = Math.sin(_lat); X += a; Y += b; Z += c; } }); X /= numCoords; Y /= numCoords; Z /= numCoords; var lng = Math.atan2(Y, X); // eslint-disable-next-line var hyp = Math.sqrt(X * X + Y * Y); var lat = Math.atan2(Z, hyp); var newX = lat * 180 / Math.PI; var newY = lng * 180 / Math.PI; return { lat: newX, lng: newY }; } return false; }, getCenter: function getCenter(hits) { var _this4 = this; if (this.center) { return this.parseLocation(this.center); } var mapRef = this.getMapRef(); if (mapRef && typeof mapRef.getCenter === 'function' && this.$options.preserveCenter) { var currentCenter = mapRef.getCenter(); setTimeout(function () { _this4.$options.preserveCenter = false; }, 100); return this.parseLocation({ lat: currentCenter.lat(), lng: currentCenter.lng() }); } if (hits && hits.length) { if (this.autoCenter) { return this.getHitsCenter(hits) || this.getDefaultCenter(); } return hits[0] && hits[0][this.dataField] ? this.getPosition(hits[0]) : this.getDefaultCenter(); } return this.getDefaultCenter(); }, handleZoomChange: function handleZoomChange(zoom) { if (zoom) { var prevZoom = this.zoom; if (this.searchAsMove) { this.zoom = zoom; this.$options.preserveCenter = true; this.setGeoQuery(true); } else { this.zoom = zoom; } if (prevZoom !== zoom) { this.$emit('zoom-changed', zoom); } } }, handleOnDragEnd: function handleOnDragEnd() { if (this.searchAsMove) { this.$options.preserveCenter = true; this.setGeoQuery(true); } this.$emit('drag-end'); }, handlePreserveCenter: function handlePreserveCenter(preserveCenter) { this.$options.preserveCenter = preserveCenter; }, handleOnIdle: function handleOnIdle() { // only make the geo_bounding query if we have hits data if (this.hits && this.hits.length && this.searchAsMove) { // always execute geo-bounds query when center is set // to improve the specificity of search results var executeUpdate = !!this.center; this.setGeoQuery(executeUpdate); } this.$emit('idle'); }, setGeoQuery: function setGeoQuery(executeUpdate) { if (executeUpdate === void 0) { executeUpdate = false; } // execute a new query on the initial mount // or whenever searchAsMove is true and the map is dragged if (executeUpdate || !this.skipBoundingBox && !this.mapBoxBounds) { this.$defaultQuery = this.getGeoQuery(); var persistMapQuery = !!this.center; var forceExecute = this.searchAsMove; var meta = { mapBoxBounds: this.mapBoxBounds }; this.setMapData(this.componentId, this.$defaultQuery, persistMapQuery, forceExecute, meta); } this.skipBoundingBox = false; }, getMapParams: function getMapParams() { var _this$getData = this.getData(), data = _this$getData.data; var showMarkers = this.showMarkers, defaultPin = this.defaultPin, renderPopover = this.renderPopover, autoClosePopover = this.autoClosePopover, renderItem = this.renderItem; return { resultsToRender: data, center: this.getCenter(data), getPosition: this.getPosition, zoom: this.zoom, renderItem: renderItem, showMarkers: showMarkers, defaultPin: defaultPin, renderPopover: renderPopover, autoClosePopover: autoClosePopover, renderSearchAsMove: this.renderSearchAsMove, handlePreserveCenter: this.handlePreserveCenter, preserveCenter: this.$options.preserveCenter, handleOnDragEnd: this.handleOnDragEnd, handleOnIdle: this.handleOnIdle, handleZoomChange: this.handleZoomChange }; }, getAllData: function getAllData() { var size = this.size, promotedResults = this.promotedResults, customData = this.customData, currentPage = this.currentPage, hits = this.hits; var results = parseHits(hits) || []; var parsedPromotedResults = parseHits(promotedResults) || []; var base = currentPage * size; var resultsToRender = this.filteredResults || []; if (this.markersData) { resultsToRender = this.markersData; } return { results: results, resultsToRender: resultsToRender, customData: customData || {}, promotedResults: parsedPromotedResults, loadMore: this.loadMore, base: base, triggerClickAnalytics: this.triggerClickAnalytics }; }, getData: function getData() { var _this$getAllData2 = this.getAllData(), promotedResults = _this$getAllData2.promotedResults, aggregationData = _this$getAllData2.aggregationData, customData = _this$getAllData2.customData, resultsToRender = _this$getAllData2.resultsToRender; return { data: this.withClickIds(resultsToRender), aggregationData: this.withClickIds(aggregationData || []), promotedData: this.withClickIds(promotedResults), rawData: this.rawData, resultStats: this.stats, customData: customData }; }, getComponent: function getComponent$1() { var error = this.error, isLoading = this.isLoading; var data = _extends({ error: error, loading: isLoading, loadMore: this.loadMore, triggerClickAnalytics: this.triggerClickAnalytics, setPage: this.setPage }, this.getData()); return getComponent(data, this); }, setPage: function setPage(page) { // pageClick will be called every time a pagination button is clicked if (page !== this.currentPageState) { this.$emit('pageClick', page + 1); this.$emit('page-click', page + 1); var value = this.size * page; var options = getQueryOptions(this.$props); options.from = this.$data.from; this.from = value; this.currentPageState = page; this.loadMoreAction(this.componentId, _extends({}, options, { from: value }), false); if (this.URLParams) { this.setPageURL(this.componentId, page + 1, this.componentId, false, true); } } }, setDefaultQueryForRSAPI: function setDefaultQueryForRSAPI() { if (this.defaultQuery && typeof this.defaultQuery === 'function') { var defaultQuery = this.defaultQuery(); this.setDefaultQuery(this.componentId, defaultQuery); } }, getArrPosition: function getArrPosition(location) { return { lat: location.lat, lon: location.lon || location.lng }; }, getGeoQuery: function getGeoQuery(props) { if (props === void 0) { props = this.$props; } this.$defaultQuery = props.defaultQuery ? props.defaultQuery() : null; var mapRef = this.getMapRef(); var mapBounds = mapRef && typeof mapRef.getBounds === 'function' ? mapRef.getBounds() : false; var north; var south; var east; var west; if (mapBounds) { var _geo_bounding_box; north = mapBounds.getNorthEast().lat(); south = mapBounds.getSouthWest().lat(); east = mapBounds.getNorthEast().lng(); west = mapBounds.getSouthWest().lng(); var boundingBoxCoordinates = { top_left: [west, north], bottom_right: [east, south] }; this.mapBoxBounds = boundingBoxCoordinates; var geoQuery = { geo_bounding_box: (_geo_bounding_box = {}, _geo_bounding_box[this.dataField] = boundingBoxCoordinates, _geo_bounding_box) }; if (this.$defaultQuery) { var _ref2 = this.$defaultQuery || {}, query = _ref2.query; if (query) { // adds defaultQuery's query to geo-query // to generate a map query return { must: [geoQuery, query] }; } } return geoQuery; } // return the defaultQuery (if set) or null when map query not available return this.$defaultQuery ? this.$defaultQuery.query : null; }, getGeoDistanceQuery: function getGeoDistanceQuery() { var center = this.center || this.defaultCenter; if (center && this.defaultRadius) { var _geo_distance; // skips geo bounding box query on initial load this.skipBoundingBox = true; return { geo_distance: (_geo_distance = { distance: "" + this.defaultRadius + this.unit }, _geo_distance[this.dataField] = this.getArrPosition(center), _geo_distance) }; } return null; }, loadMore: function loadMore() { if (this.hits && !this.pagination && this.total !== this.hits.length) { var value = this.from + this.size; var options = getQueryOptions(this.$props); this.from = value; this.loadMoreAction(this.componentId, _extends({}, options, { from: value }), true); } }, triggerClickAnalytics: function triggerClickAnalytics(searchPosition, documentId) { var docId = documentId; if (!docId) { var _this$getData2 = this.getData(), data = _this$getData2.data; var hitData = data.find(function (hit) { return hit._click_id === searchPosition; }); if (hitData && hitData._id) { docId = hitData._id; } } this.recordResultClick(searchPosition, docId); }, withClickIds: function withClickIds(results) { var _this$getAllData3 = this.getAllData(), base = _this$getAllData3.base; return results.map(function (result, index) { return _extends({}, result, { _click_id: base + index }); }); }, toggleSearchAsMove: function toggleSearchAsMove() { this.searchAsMove = !this.searchAsMove; this.$emit('search-as-move', this.searchAsMove); }, renderErrorComponent: function renderErrorComponent() { var renderError = this.$slots.renderError || this.$props.renderError; if (renderError && this.error && !this.isLoading) { return isFunction(renderError) ? renderError(this.error) : renderError; } return null; }, renderSearchAsMove: function renderSearchAsMove() { if (this.showSearchAsMove) { return createVNode("div", { "style": { position: 'absolute', bottom: '30px', left: '10px', width: '240px', backgroundColor: '#fff', padding: '8px 10px', boxShadow: 'rgba(0,0,0,0.3) 0px 1px 4px -1px', borderRadius: 2, zIndex: 10000 }, "className": getClassName(this.innerClass, 'checkboxContainer') || null }, [createVNode(Checkbox, { "type": "checkbox", "class": getClassName(this.$props.innerClass, 'checkbox'), "id": this.$props.componentId + "-searchasmove", "onClick": this.toggleSearchAsMove, "checked": this.searchAsMove, "show": true }, null), createVNode("label", { "className": getClassName(this.innerClass, 'label') || null, "for": this.$props.componentId + "-searchasmove" }, [this.searchAsMoveLabel])]); } return null; }, renderPagination: function renderPagination() { return createVNode(Pagination, { "pages": this.pages, "totalPages": this.totalPages, "currentPage": this.currentPageState, "setPage": this.setPage, "innerClass": this.innerClass }, null); } }, created: function created() { if (this.defaultPage >= 0) { this.currentPageState = this.defaultPage; this.from = this.currentPageState * this.$props.size; } this.internalComponent = this.$props.componentId + "__internal"; this.updateComponentProps(this.componentId, { from: this.from }, componentTypes.reactiveMap); this.updateComponentProps(this.internalComponent, { from: this.from }, componentTypes.reactiveMap); }, mounted: function mounted() { if (this.defaultPage < 0 && this.currentPage > 0) { if (this.$props.URLParams) { this.setPageURL(this.$props.componentId, this.currentPage, this.$props.componentId, false, true); } } var options = getQueryOptions(this.$props); options.from = this.$data.from; if (this.$props.sortBy) { var _ref3; options.sort = [(_ref3 = {}, _ref3[this.$props.dataField] = { order: this.$props.sortBy }, _ref3)]; } this.$defaultQuery = null; if (this.$props.defaultQuery) { this.$defaultQuery = this.$props.defaultQuery() || {}; options = _extends({}, options, getOptionsFromQuery(this.$defaultQuery)); // Override sort query with defaultQuery's sort if defined if (this.$defaultQuery.sort) { options.sort = this.$defaultQuery.sort; } // since we want defaultQuery to be executed anytime // map component's query is being executed var persistMapQuery = true; // no need to forceExecute because setReact() will capture the main query // and execute the defaultQuery along with it var forceExecute = false; // Update default query for RS API this.setDefaultQueryForRSAPI(); var meta = { mapBoxBounds: this.mapBoxBounds }; this.setMapData(this.componentId, this.$defaultQuery.query, persistMapQuery, forceExecute, meta); } else { // only apply geo-distance when defaultQuery prop is not set var query = this.getGeoDistanceQuery(); if (query) { // - only persist the map query if center prop is set // - ideally, persist the map query if you want to keep executing it // whenever there is a change (due to subscription) in the component query var _persistMapQuery = !!this.center; // - forceExecute will make sure that the component query + Map query gets executed // irrespective of the changes in the component query // - forceExecute will only come into play when searchAsMove is true // - kindly note that forceExecute may result in one additional network request // since it bypasses the gatekeeping var _forceExecute = this.searchAsMove; // Set meta for `distance` and `coordinates` in selected value var center = this.center || this.defaultCenter; var coordinatesObject = this.getArrPosition(center); var _meta = { distance: this.defaultRadius, coordinates: coordinatesObject.lat + ", " + coordinatesObject.lon }; this.setMapData(this.componentId, query, _persistMapQuery, _forceExecute, _meta); } } this.setQueryOptions(this.componentId, options, !(this.$defaultQuery && this.$defaultQuery.query)); }, render: function render() { var loader = this.$slots.loader || this.$props.loader; return createVNode("div", { "style": _extends({}, style, this.$props.style), "class": this.$props.className }, [this.renderErrorComponent(), this.isLoading && loader, this.hasCustomRender ? this.getComponent() : null, this.renderMap(this.getMapParams()), this.pagination ? this.renderPagination() : null]); } }; var mapStateToProps = function mapStateToProps(state, props) { return { defaultPage: state.selectedValues[props.componentId] && state.selectedValues[props.componentId].value - 1 || -1, error: state.error[props.componentId], isLoading: state.isLoading[props.componentId], hits: state.hits[props.componentId] && state.hits[props.componentId].hits, promotedResults: state.promotedResults[props.componentId], customData: state.customData[props.componentId], total: state.hits[props.componentId] && state.hits[props.componentId].total, time: state.hits[props.componentId] && state.hits[props.componentId].time, rawData: state.rawData[props.componentId], hidden: state.hits[props.componentId] && state.hits[props.componentId].hidden }; }; var mapDispatchToProps = { loadMoreAction: loadMore, setPageURL: setValue, setQueryOptions: setQueryOptions, setStreaming: setStreaming, updateQuery: updateQuery, updateComponentProps: updateComponentProps, setDefaultQuery: setDefaultQuery, recordResultClick: recordResultClick, setMapData: setMapData }; var RMConnected = PreferencesConsumer(ComponentWrapper(connect(mapStateToProps, mapDispatchToProps)(ReactiveMap), { componentType: componentTypes.reactiveMap, internalComponent: true })); ReactiveMap.install = function (Vue) { Vue.component(ReactiveMap.name, RMConnected); }; // Add componentType for SSR ReactiveMap.componentType = componentTypes.reactiveMap; var infoWindowMappedProps = { content: { type: Object, twoWay: true }, options: { type: Object, required: false, "default": function _default() { return {}; } }, position: { type: Object, twoWay: true }, zIndex: { type: Number, twoWay: true } }; /** * This function helps you to bind events from Google Maps API to Vue events * * @param {Object} vueInst the Vue instance * @param {Object} googleMapsInst the Google Maps instance * @param {string[]} events an array of string with all events that you want to bind * @returns {void} */ function bindEvents(vueInst, googleMapsInst, events) { events.forEach(function (eventName) { if (vueInst.$gmapOptions.autoBindAllEvents || vueInst.$attrs[eventName]) { googleMapsInst.addListener(eventName, function (ev) { vueInst.$emit(eventName, ev); }); } }); } /** * Function that helps you to capitalize the first letter on a word * * @param {string} text the text that you want to capitalize * @returns {string} */ function capitalizeFirstLetter(text) { return text.charAt(0).toUpperCase() + text.slice(1); } /** * Function that helps you to get all non nullable props from a component * * @param {Object} vueInst the Vue component instance * @param {Object} props the props object * @returns {Object} */ function getPropsValues(vueInst, props) { return Object.keys(props).reduce(function (acc, prop) { if (vueInst[prop] !== undefined) { acc[prop] = vueInst[prop]; } return acc; }, {}); } /** * Watch the individual properties of a PoD object, instead of the object * per se. This is different from a deep watch where both the reference * and the individual values are watched. * * In effect, it throttles the multiple $watch to execute at most once per tick. * * @param {Object} vueInst the component instance * @param {string[]} propertiesToTrack string array with all properties that you want to track * @param {Function} handler function to be fired when the prop change * @param {boolean} immediate=false * @returns {void} */ function watchPrimitiveProperties(vueInst, propertiesToTrack, handler, immediate) { if (immediate === void 0) { immediate = false; } var isHandled = false; /** * Function in charge to execute the handler function if it was not fired * * @returns void */ function requestHandle() { if (!isHandled) { isHandled = true; vueInst.$nextTick(function () { isHandled = false; handler(); }); } } propertiesToTrack.forEach(function (prop) { vueInst.$watch(prop, requestHandle, { immediate: immediate }); }); } /** * Binds the properties defined in props to the google maps instance. * If the prop is an Object type, and we wish to track the properties * of the object (e.g. the lat and lng of a LatLng), then we do a deep * watch. For deep watch, we also prevent the _changed event from being * emitted if the data source was external. * * @param {Object} vueInst the component instance * @param {Object} googleMapsInst the Google Maps instance * @param {Object} props object with the component props tha should be synched with the Google Maps instances props * @returns {void} */ function bindProps(vueInst, googleMapsInst, props) { Object.keys(props).forEach(function (attribute) { var _props$attribute = props[attribute], twoWay = _props$attribute.twoWay, type = _props$attribute.type, trackProperties = _props$attribute.trackProperties, noBind = _props$attribute.noBind; if (!noBind) { var setMethodName = "set" + capitalizeFirstLetter(attribute); var getMethodName = "get" + capitalizeFirstLetter(attribute); var eventName = attribute.toLowerCase() + "_changed"; var initialValue = vueInst[attribute]; if (typeof googleMapsInst[setMethodName] === 'undefined') { throw new Error( // TODO: Analyze all disabled rules in the file // eslint-disable-next-line no-underscore-dangle -- old code should be analyzed setMethodName + " is not a method of (the Maps object corresponding to) " + vueInst.$options._componentTag); } // We need to avoid an endless // propChanged -> event emitted -> propChanged -> event emitted loop // although this may really be the user's responsibility if (type !== Object || !trackProperties) { // Track the object deeply vueInst.$watch(attribute, function () { var attributeValue = vueInst[attribute]; googleMapsInst[setMethodName](attributeValue); }, { immediate: typeof initialValue !== 'undefined', deep: type === Object }); } else { watchPrimitiveProperties(vueInst, trackProperties.map(function (prop) { return attribute + "." + prop; }), function () { googleMapsInst[setMethodName](vueInst[attribute]); }, vueInst[attribute] !== undefined); } if (twoWay && (vueInst.$gmapOptions.autoBindAllEvents || vueInst.$attrs[eventName])) { googleMapsInst.addListener(eventName, function () { vueInst.$emit(eventName, googleMapsInst[getMethodName]()); }); } } }); } var _excluded = ["options", "position"]; var InfoWindowClusterManager = { name: 'InfoWindowClusterManager', inject: { $markerPromise: { "default": null }, $mapPromise: {} }, provide: function provide() { var _this = this; var events = ['domready', 'closeclick', 'content_changed']; // Infowindow needs this to be immediately available var promise = this.$mapPromise.then(function (map) { _this.$map = map; // Initialize the maps with the given options var initialOptions = _extends({}, _this.options, { map: map }, getPropsValues(_this, infoWindowMappedProps)); var extraOptions = initialOptions.options, position = initialOptions.position, finalOptions = _objectWithoutPropertiesLoose(initialOptions, _excluded); finalOptions.content = _this.$refs.flyaway; if (_this.$markerPromise) { _this.$markerPromise.then(function (markerObject) { _this.$markerObject = markerObject; // eslint-disable-next-line _this.$infoWindowObject = new google.maps.InfoWindow(finalOptions); bindProps(_this, _this.$infoWindowObject, infoWindowMappedProps); bindEvents(_this, _this.$infoWindowObject, events); // TODO: This function names should be analyzed /* eslint-disable no-underscore-dangle -- old style */ _this._openInfoWindow(); _this.$watch('opened', function () { _this._openInfoWindow(); }); /* eslint-enable no-underscore-dangle */ return _this.$infoWindowObject; }); } })["catch"](function (error) { throw error; }); // TODO: analyze the efects of only returns the instance and remove completely the promise this.$infoWindowPromise = promise; return { $infoWindowPromise: promise }; }, props: { /** * NOTE: This prop overrides the content of the default slot, use only one of them, not both at the same time * Content to display in the InfoWindow. This can be an HTML element, a plain-text string, or a string containing HTML. The InfoWindow will be sized according to the content. To set an explicit size for the content, set content to be a HTML element with that size. * @value undefined * @see [InfoWindow content](https://developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindowOptions.content) */ content: { type: [String, Object], "default": undefined }, /** * Determines if the info-window is open or not */ opened: { type: Boolean, "default": true }, /** * Contains the LatLng at which this info window is anchored. * Note: An InfoWindow may be attached either to a Marker object * (in which case its position is based on the marker's location) * or on the map itself at a specified LatLng. * * The LatLng at which to display this InfoWindow. If the InfoWindow is opened with an anchor, the anchor's position will be used instead. * @value undefined * @type LatLng|LatLngLiteral * @see [InfoWindow position](https://developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindowOptions.position) */ position: { type: Object, "default": undefined }, /** * All InfoWindows are displayed on the map in order of their zIndex, with higher values displaying in front of InfoWindows with lower values. By default, InfoWindows are displayed according to their latitude, with InfoWindows of lower latitudes appearing in front of InfoWindows at higher latitudes. InfoWindows are always displayed in front of markers. * @value 0 * @see [InfoWindow position](https://developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindowOptions.zIndex) */ zIndex: { type: Number, "default": 0 }, /** * Extra options that you want to pass to the component */ options: { type: Object, required: false, "default": undefined } }, mounted: function mounted() { var el = this.$refs.flyaway; el.parentNode.removeChild(el); }, destroyed: function destroyed() { // Note: not all Google Maps components support maps if (this.$infoWindowObject && this.$infoWindowObject.setMap) { this.$infoWindowObject.setMap(null); } }, methods: { // TODO: we need to analyze the following method name // eslint-disable-next-line no-underscore-dangle -- old code _openInfoWindow: function _openInfoWindow() { if (this.opened) { if (this.$markerObject !== null) { this.$infoWindowObject.open(this.$map, this.$markerObject); } else { this.$infoWindowObject.open(this.$map); } } else { this.$infoWindowObject.close(); } } }, render: function render() { return createVNode("div", null, [createVNode("div", { "ref": "flyaway" }, [this.$slots["default"]()])]); } }; var InfoWindowWrapper = { name: 'InfoWindowWrapperClusterManager', props: { id: VueTypes.string, renderPopover: VueTypes.func, infoWindowProps: VueTypes.object, events: VueTypes.object, marker: VueTypes.Object }, data: function data() { return { infoWindowRef: null }; }, provide: function provide() { return { $markerPromise: Promise.resolve(this.marker) }; }, mounted: function mounted() { this.infoWindowRef = this.$refs[this.id + "-Info-Window"]; }, methods: { handleClose: function handleClose() { this.infoWindowRef.$infoWindowObject.close(); } }, render: function render() { var _this = this; var renderPopover = this.renderPopover, events = this.events; return createVNode(InfoWindowClusterManager, mergeProps({ "ref": this.id + "-Info-Window" }, { props: this.infoWindowProps, on: events }), { "default": function _default() { return [createVNode("div", null, [renderPopover(_this.handleClose)])]; } }); } }; var _excluded$1 = ["options"]; var isEqual$1 = helper.isEqual; var recordResultClick$1 = Actions.recordResultClick; var ClusterMarkers = { name: 'ClusterMarkers', props: { markers: VueTypes.array, getPosition: VueTypes.func, defaultPin: VueTypes.string, renderItem: VueTypes.func, markerProps: VueTypes.object, handlePreserveCenter: VueTypes.func.isRequired, autoClosePopover: VueTypes.bool, renderPopover: VueTypes.func, highlightMarkerOnHover: VueTypes.bool }, inject: { $clusterPromise: { "default": null }, $mapPromise: {} }, data: function data() { return { openMarkers: {} }; }, mounted: function mounted() { this.buildMarkers(this.markers); }, destroy: function destroy() { // Remove active markers this.buildMarkers([]); }, watch: { markers: function markers(newVal, oldVal) { if (!isEqual$1(oldVal, newVal)) { this.buildMarkers(newVal); } } }, methods: { triggerAnalytics: function triggerAnalytics(clickPosition, markerId) { this.recordResultClick(clickPosition, markerId); }, setOpenMarkers: function setOpenMarkers(openMarkers) { this.openMarkers = openMarkers; }, closeMarker: function closeMarker(marker) { var _this$$props = this.$props, autoClosePopover = _this$$props.autoClosePopover, handlePreserveCenter = _this$$props.handlePreserveCenter; var id = marker.metaData && marker.metaData._id; var _this$openMarkers = this.openMarkers, del = _this$openMarkers[id], activeMarkers = _objectWithoutPropertiesLoose(_this$openMarkers, [id].map(_toPropertyKey)); var newOpenMarkers = autoClosePopover ? {} : activeMarkers; this.setOpenMarkers(newOpenMarkers); handlePreserveCenter(true); this.$emit('close-marker-popover', marker); }, openMarker: function openMarker(marker, index) { var _ref, _extends2; var autoClosePopover = this.autoClosePopover, handlePreserveCenter = this.handlePreserveCenter; var id = marker.metaData && marker.metaData._id; var newOpenMarkers = autoClosePopover ? (_ref = {}, _ref[id] = marker, _ref) : _extends({}, this.openMarkers, (_extends2 = {}, _extends2[id] = marker, _extends2)); this.setOpenMarkers(newOpenMarkers); handlePreserveCenter(true); this.triggerAnalytics(id, index); this.$emit('open-marker-popover', marker); }, removeMarkers: function removeMarkers() { if (this.$clusterObject) { // Remove old markers this.$clusterObject.clearMarkers(true); this.$markers = []; } }, buildMarkers: function buildMarkers(markersToRender) { var _this = this; this.$mapPromise.then(function (map) { if (_this.$clusterPromise) { _this.$clusterPromise.then(function (clusterObject) { // Detect changed markers var markersToAdd = []; var markersToRemove = []; // A map of marker id to active status, `true` means marker is active // `false` means marker is stale and should be removed var oldMarkersIdMap = {}; (_this.$markers || []).forEach(function (marker) { if (marker.metaData && marker.metaData._id) { // mark all old markers as stale oldMarkersIdMap[marker.metaData._id] = false; } }); // build map markers markersToRender.forEach(function (marker, index) { // Avoid if a marker is already rendered if (marker._id && oldMarkersIdMap[marker._id] !== undefined) { // Set old marker as active oldMarkersIdMap[marker._id] = true; } else { // Initialize the maps with the given options var initialOptions = _extends({}, _this.markerProps, { metaData: marker, map: map, position: _this.getPosition(marker) }); if (_this.renderItem) { var data = _this.renderItem(marker); if ('label' in data) { initialOptions.label = data.label; } if ('icon' in data) { initialOptions.icon = data.icon; } } else if (_this.defaultPin) { initialOptions.icon = { url: _this.defaultPin }; } var extraOptions = initialOptions.options, finalOptions = _objectWithoutPropertiesLoose(initialOptions, _excluded$1); if (_this.$clusterPromise) { finalOptions.map = null; } // eslint-disable-next-line var markerObject = new google.maps.Marker(finalOptions); markerObject.addListener('click', function () { _this.openMarker(markerObject, index); }); markersToAdd.push(markerObject); } }); // Current active markers var currentMarkers = []; markersToAdd.forEach(function (marker) { currentMarkers.push(marker); }); (_this.$markers || []).forEach(function (marker) { if (marker.metaData && marker.metaData._id) { // if maker is not active then add to remove list if (!oldMarkersIdMap[marker.metaData._id]) { markersToRemove.push(marker); } else { currentMarkers.push(marker); } } }); // Remove old markers clusterObject.removeMarkers(markersToRemove); // Add inital markers at once clusterObject.addMarkers(markersToAdd); _this.$clusterObject = clusterObject; _this.$map = map; _this.$markers = currentMarkers; }); } })["catch"](function (error) { throw error; }); } }, render: function render() { var _this2 = this; if (!this.renderPopover) { return null; } return createVNode("div", null, [Object.keys(this.openMarkers).map(function (markerId) { var marker = _this2.openMarkers[markerId]; var item = marker.metaData; return createVNode(InfoWindowWrapper, { "key": markerId + "-InfoWindow", "id": markerId, "renderPopover": function renderPopover(_handleClose) { return _this2.renderPopover({ item: item, handleClose: function handleClose() { _handleClose(); _this2.closeMarker(item); } }); }, "marker": marker, "infoWindowProps": { zIndex: 500 }, "events": { closeclick: function closeclick() { return _this2.closeMarker(marker); } } }, null); })]); } }; var mapDispatchToProps$1 = { recordResultClick: recordResultClick$1 }; var ClusterManager = connect(function () { return null; }, mapDispatchToProps$1)(ClusterMarkers); var InfoWindowWrapper$1 = { name: 'InfoWindowWrapper', props: { id: VueTypes.string, renderPopover: VueTypes.func, infoWindowProps: VueTypes.object, events: VueTypes.object }, data: function data() { return { infoWindowRef: null }; }, mounted: function mounted() { this.infoWindowRef = this.$refs[this.id + "-Info-Window"]; }, methods: { handleClose: function handleClose() { this.infoWindowRef.$infoWindowObject.close(); } }, render: function render() { var _this = this; var renderPopover = this.renderPopover, events = this.events; return createVNode(InfoWindow, mergeProps({ "closeclick": "true", "ref": this.id + "-Info-Window" }, this.infoWindowProps, _transformOn(events)), { "default": function _default() { return [createVNode("div", null, [renderPopover(_this.handleClose)])]; } }); } }; // Note: This file has been taken from https://github.com/eregnier/vue2-gmap-custom-marker/blob/master/gmap-custom-marker.vue var MarkerWithLabel = { name: 'MarkerWithLabel', props: { metaData: { type: Object, "default": undefined }, marker: { type: Object, "default": undefined }, offsetX: { type: Number, "default": 0 }, offsetY: { type: Number, "default": 0 }, alignment: { type: String, "default": 'top' }, zIndex: { type: Number, "default": 50 }, cssPosition: { type: Boolean, "default": false }, renderMarker: VueTypes.func.isRequired, handleMouseOver: VueTypes.func, handleFocus: VueTypes.func, handleMouseOut: VueTypes.func, handleBlur: VueTypes.func, handleClick: VueTypes.func }, data: function data() { return { opacity: 0.01 }; }, mixins: [MapElementMixin], inject: { $clusterPromise: { "default": null } }, computed: { lat: function lat() { return parseFloat(Number.isNaN(this.marker.lat) ? this.marker.latitude : this.marker.lat); }, lng: fun