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 += ``;
for (const pub of slicedArr) {
html += `-
${pub.name}
${pub.address}, ${pub.postcode}
`;
}
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();