import { algoliasearch } from 'algoliasearch'; import slugify from '@sindresorhus/slugify'; const INDEX_NAME = 'pubfinder'; const client = algoliasearch('YSWWVAX5RB', '4203de3b08b981c149883b0af830db30'); const $ = document.querySelector.bind(document); const dataController = (function () { const initialRectBounds = { north: 51.522899, south: 51.484821, east: -0.07766, west: -0.138968, }; const maxMapSpace = { north: 51.74, south: 51.27, west: -0.51, east: 0.23, }; const initialPolygonBounds = [ { lat: 51.522786, lng: -0.077603 }, { lat: 51.525376, lng: -0.108023 }, { lat: 51.520791, lng: -0.138718 }, { lat: 51.506604, lng: -0.151214 }, { lat: 51.485288, lng: -0.106803 }, { lat: 51.493989, lng: -0.085187 }, ]; const centralPosition = { lat: 51.476, lng: -0.11 }; return { initialRectBounds, centralPosition, maxMapSpace, initialPolygonBounds, getSearchResults: async function (query = '', shape = '', bounds = [], hitsToGet) { let geoParam; if (shape === 'rectangle') { geoParam = { insideBoundingBox: [bounds], }; } else { geoParam = { insidePolygon: [bounds], }; } const res = await client.searchSingleIndex({ indexName: INDEX_NAME, searchParams: { query, hitsPerPage: hitsToGet, ...geoParam, attributesToRetrieve: ['_geoloc', 'name', 'address', 'postcode'], }, }); return res; }, getCurrentQuery: function () { return document.querySelector('#searchbar').value; }, }; })(); const interfaceController = (function () { let markers = []; let shape; let map; const DOMStrings = { selectRect: '.select--rectangle', selectPoly: '.select--polygon', hits: '#hits', searchBar: '#searchbar', pubCount: '.pub-count', toggle: '#toggle', bottom: '#bottom', }; const generateMap = async function (bounds, position, maxSpace, initialPolygonBounds, currentShape, zoom = 12) { const { Map, Polygon, Rectangle } = await google.maps.importLibrary('maps'); map = new Map(document.getElementById('map'), { zoom, center: position, mapId: 'pub_map', colorScheme: 'DARK', restriction: { latLngBounds: maxSpace, strictBounds: true, }, }); if (currentShape === 'rectangle') { shape = new Rectangle({ bounds: bounds, editable: true, draggable: true, strokeWeight: 2, strokeOpacity: 0.8, strokeColor: '#D97706', fillColor: '#D97706', fillOpacity: 0.35, }); shape.setMap(map); } else if (currentShape === 'polygon') { shape = new Polygon({ paths: initialPolygonBounds, strokeColor: '#D97706', strokeOpacity: 0.8, strokeWeight: 2, fillColor: '#D97706', fillOpacity: 0.35, editable: true, draggable: true, geodesic: true, }); shape.setMap(map); } }; return { handleShapeSelect: function (e) {}, initMap: generateMap, embedSearchResults: async function (hits) { const { AdvancedMarkerElement } = await google.maps.importLibrary('marker'); const { InfoWindow } = await google.maps.importLibrary('maps'); hits.forEach((hit) => { const marker = new AdvancedMarkerElement({ map: map, position: { lat: hit._geoloc.lat, lng: hit._geoloc.lng, }, }); const window = new InfoWindow({ content: `

${hit.name}

${hit.address}

📍 `, }); marker.addListener('click', function () { window.open({ anchor: marker, map, }); }); markers.push(marker); }); }, updateHitsList: function (hits) { $(DOMStrings.hits).innerHTML = ''; let html = ``; if (hits.length > 0) { const slicedArr = hits.slice(0, 20); html += ``; } else { html = `
There are no results to show, try refinfing your search
`; } $(DOMStrings.hits).insertAdjacentHTML('beforeend', html); $(DOMStrings.pubCount).innerHTML = `${hits.length > 0 ? 'Showing ' + hits.length + ' pubs' : 'No pubs'}`; }, handleBoundChange: function () { const { south, west, north, east } = shape.getBounds()?.toJSON(); return [north, east, south, west]; }, getActiveShape: function () { return shape; }, getShapeBounds: function () { if (shape instanceof google.maps.Polygon) { const paths = shape.getPaths(); let points = []; paths.forEach((path) => { path.forEach((latLng) => { const lat = latLng.lat(); const lng = latLng.lng(); points.push(lat, lng); }); }); return points; } else { const { south, west, north, east } = shape.getBounds()?.toJSON(); return [north, east, south, west]; } }, removeMarkers: function () { for (let i = 0; i < markers.length; i++) { markers[i].setMap(null); } }, getDOMStrings: function () { return DOMStrings; }, }; })(); const controller = (function (dataCTRL, uiCTRL) { const DOM = uiCTRL.getDOMStrings(); const rectBounds = dataCTRL.initialRectBounds; const centralPoint = dataCTRL.centralPosition; const mapBounds = dataCTRL.maxMapSpace; // Rectangle bounds const initialPolygonBounds = dataCTRL.initialPolygonBounds; // Polygon bounds const initialSetup = async function () { await uiCTRL.initMap(rectBounds, centralPoint, mapBounds, initialPolygonBounds, 'rectangle', 12); const searchResults = await dataCTRL.getSearchResults( '', 'rectangle', [rectBounds.north, rectBounds.east, rectBounds.south, rectBounds.west], 1000 ); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); }; const setupMapListeners = async function () { const shape = uiCTRL.getActiveShape(); if (shape instanceof google.maps.Rectangle) { let debounceTimer; shape.addListener('bounds_changed', () => { uiCTRL.removeMarkers(); clearTimeout(debounceTimer); debounceTimer = setTimeout(async () => { const newAlgoliaBounds = uiCTRL.handleBoundChange(); const searchResults = await dataCTRL.getSearchResults( dataCTRL.getCurrentQuery(), 'rectangle', newAlgoliaBounds, 1000 ); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); }, 200); }); } if (shape instanceof google.maps.Polygon) { let debounceTimer; const paths = shape.getPaths(); const handlePathDrag = async function () { uiCTRL.removeMarkers(); clearTimeout(debounceTimer); debounceTimer = setTimeout(async () => { const points = uiCTRL.getShapeBounds(); const searchResults = await dataCTRL.getSearchResults(dataCTRL.getCurrentQuery(), 'polygon', points, 1000); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); }, 300); }; paths.forEach((path) => { path.addListener('set_at', handlePathDrag); path.addListener('insert_at', handlePathDrag); }); } }; const setupEventListeners = async function () { $(DOM.selectRect).addEventListener('click', async () => { await uiCTRL.initMap(rectBounds, centralPoint, mapBounds, initialPolygonBounds, 'rectangle', 12.5); const searchResults = await dataCTRL.getSearchResults( dataCTRL.getCurrentQuery(), 'rectangle', [rectBounds.north, rectBounds.east, rectBounds.south, rectBounds.west], 1000 ); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); await setupMapListeners(); }); $(DOM.selectPoly).addEventListener('click', async () => { await uiCTRL.initMap(rectBounds, centralPoint, mapBounds, initialPolygonBounds, 'polygon', 12); const translatedPolyBounds = initialPolygonBounds.flatMap(({ lat, lng }) => [lat, lng]); const searchResults = await dataCTRL.getSearchResults( dataCTRL.getCurrentQuery(), 'polygon', translatedPolyBounds, 1000 ); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); await setupMapListeners(); }); $(DOM.searchBar).addEventListener('input', async (e) => { const q = dataCTRL.getCurrentQuery(); if (q > 0 && q < 3) return; const bounds = uiCTRL.getShapeBounds(); const activeShape = uiCTRL.getActiveShape(); const searchResults = await dataCTRL.getSearchResults( q, activeShape instanceof google.maps.Polygon ? 'polygon' : 'rectangle', bounds, 1000 ); uiCTRL.removeMarkers(); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); }); $(DOM.toggle).addEventListener('click', () => { $(DOM.bottom).classList.toggle('bottom--hide'); }); }; return { init: async function () { await initialSetup(); await setupMapListeners(); await setupEventListeners(); }, }; })(dataController, interfaceController); controller.init();