diff options
Diffstat (limited to 'src/main.js')
-rw-r--r-- | src/main.js | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..1466b3f --- /dev/null +++ b/src/main.js @@ -0,0 +1,355 @@ +import { algoliasearch } from 'algoliasearch'; +const ALGOLIA_KEY = '9fb3db0222f7b5aef0e2b30791ee6201'; +const INDEX_NAME = 'pubfinder'; +const client = algoliasearch('YSWWVAX5RB', ALGOLIA_KEY); +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.508616, lng: -0.125319 }; + + return { + initialRectBounds, + centralPosition, + maxMapSpace, + initialPolygonBounds, + getSearchResults: async function (query = '', shape = '', bounds = [], hitsToGet) { + let geoParam; + if (shape === 'rectangle') { + geoParam = { + insideBoundingBox: [bounds], + }; + } else if (shape === 'polygon') { + geoParam = { + insidePolygon: [bounds], + }; + } else { + geoParam = { + aroundLatLng: bounds, + // aroundRadius: 1000, + }; + } + + 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', + ip: '.select--ip', + ipLoader: '.ip-loader', + hits: '#hits', + searchBar: '#searchbar', + pubCount: '.pub-count', + }; + + 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, + }); + + 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 += `<ul class="flex flex-col gap-2">`; + for (const pub of slicedArr) { + html += `<li class="relative bg-[#373737] rounded-lg p-4"> + <h3 class="font-medium mb-2">${pub.name}</h3> + <h4 class="text-sm">${pub.address}, ${pub.postcode}</h4> + <a href="https://www.google.co.uk/maps/place/Ye+Old+White+Horse/" class="absolute block top-4 right-4" target="_blank"> + <svg width="20px" height="20px" viewBox="0 0 24 24" fill="none" > + <path d="M5 12V6C5 5.44772 5.44772 5 6 5H18C18.5523 5 19 5.44772 19 6V18C19 18.5523 18.5523 19 18 19H12M8.11111 12H12M12 12V15.8889M12 12L5 19" stroke="#D97706" stroke-linecap="round" stroke-linejoin="round" /> + </svg> + </a> + </li>`; + } + html += `</ul>`; + } else { + html = `<div class="text-center mt-5">There are no results to show, try refinfing your search</div>`; + } + + $(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); + 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.ip).addEventListener('click', async () => { + // $(DOM.ipLoader).classList.replace('hidden', 'inline-flex'); + // navigator.geolocation.getCurrentPosition(async (pos) => { + // const position = { lat: pos.coords.latitude, lng: pos.coords.longitude }; + // await uiCTRL.initMap('', centralPoint, position, '', 'ip', 8); + // const searchResults = await dataCTRL.getSearchResults( + // dataCTRL.getCurrentQuery(), + // 'ip', + // `${pos.coords.latitude}, ${pos.coords.longitude}`, + // 20 + // ); + // await uiCTRL.embedSearchResults(searchResults.hits); + // uiCTRL.updateHitsList(searchResults.hits); + // $(DOM.ipLoader).classList.replace('inline-flex', 'hidden'); + // }); + // }), + $(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); + }); + }; + + return { + init: async function () { + await initialSetup(); + await setupMapListeners(); + await setupEventListeners(); + }, + }; +})(dataController, interfaceController); + +controller.init(); |