diff --git a/composer.json b/composer.json
index 079838f..1e59fbd 100644
--- a/composer.json
+++ b/composer.json
@@ -26,6 +26,9 @@
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
- }
+ },
+ "expose": [
+ "dist"
+ ]
}
}
diff --git a/dist/css/map.css b/dist/css/map.css
new file mode 100644
index 0000000..5311516
--- /dev/null
+++ b/dist/css/map.css
@@ -0,0 +1,87 @@
+#map {
+ height: 700px;
+}
+
+#panel {
+ height: 700px;
+}
+
+/* Styling for Autocomplete search bar */
+#pac-card {
+ background-color: #fff;
+ border-radius: 2px 0 0 2px;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
+ box-sizing: border-box;
+ font-family: Roboto;
+ margin: 10px 10px 0 0;
+ -moz-box-sizing: border-box;
+ outline: none;
+ }
+
+ #pac-container {
+ padding-top: 12px;
+ padding-bottom: 12px;
+ margin-right: 12px;
+ }
+
+ #pac-input {
+ background-color: #fff;
+ font-family: Roboto;
+ font-size: 15px;
+ font-weight: 300;
+ margin-left: 12px;
+ padding: 0 11px 0 13px;
+ text-overflow: ellipsis;
+ width: 400px;
+ }
+
+ #pac-input:focus {
+ border-color: #4d90fe;
+ }
+
+ #title {
+ color: #fff;
+ background-color: #acbcc9;
+ font-size: 18px;
+ font-weight: 400;
+ padding: 6px 12px;
+ }
+
+ .hidden {
+ display: none;
+ }
+
+ /* Styling for an info pane that slides out from the left.
+ * Hidden by default. */
+ #panel {
+ height: 700px;
+ width: null;
+ background-color: white;
+ /* position: fixed; */
+ z-index: 1;
+ overflow-x: hidden;
+ transition: all .2s ease-out;
+ }
+
+ .open {
+ width: 250px;
+ }
+
+ .place {
+ font-family: 'open sans', arial, sans-serif;
+ font-size: 1.2em;
+ font-weight: 500;
+ margin-block-end: 0px;
+ padding-left: 18px;
+ padding-right: 18px;
+ }
+
+ .distanceText {
+ color: silver;
+ font-family: 'open sans', arial, sans-serif;
+ font-size: 1em;
+ font-weight: 400;
+ margin-block-start: 0.25em;
+ padding-left: 18px;
+ padding-right: 18px;
+ }
\ No newline at end of file
diff --git a/dist/js/map.js b/dist/js/map.js
new file mode 100644
index 0000000..df090b8
--- /dev/null
+++ b/dist/js/map.js
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
+ * file except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+ * ANY KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+// Style credit: https://snazzymaps.com/style/1/pale-dawn
+const mapStyle = [{
+ 'featureType': 'administrative',
+ 'elementType': 'all',
+ 'stylers': [{
+ 'visibility': 'on',
+ },
+ {
+ 'lightness': 33,
+ },
+ ],
+ },
+ {
+ 'featureType': 'landscape',
+ 'elementType': 'all',
+ 'stylers': [{
+ 'color': '#f2e5d4',
+ }],
+ },
+ {
+ 'featureType': 'poi.park',
+ 'elementType': 'geometry',
+ 'stylers': [{
+ 'color': '#c5dac6',
+ }],
+ },
+ {
+ 'featureType': 'poi.park',
+ 'elementType': 'labels',
+ 'stylers': [{
+ 'visibility': 'on',
+ },
+ {
+ 'lightness': 20,
+ },
+ ],
+ },
+ {
+ 'featureType': 'road',
+ 'elementType': 'all',
+ 'stylers': [{
+ 'lightness': 20,
+ }],
+ },
+ {
+ 'featureType': 'road.highway',
+ 'elementType': 'geometry',
+ 'stylers': [{
+ 'color': '#c5c6c6',
+ }],
+ },
+ {
+ 'featureType': 'road.arterial',
+ 'elementType': 'geometry',
+ 'stylers': [{
+ 'color': '#e4d7c6',
+ }],
+ },
+ {
+ 'featureType': 'road.local',
+ 'elementType': 'geometry',
+ 'stylers': [{
+ 'color': '#fbfaf7',
+ }],
+ },
+ {
+ 'featureType': 'water',
+ 'elementType': 'all',
+ 'stylers': [{
+ 'visibility': 'on',
+ },
+ {
+ 'color': '#acbcc9',
+ },
+ ],
+ },
+ ];
+
+ // Escapes HTML characters in a template literal string, to prevent XSS.
+ // See https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content
+ function sanitizeHTML(strings) {
+ const entities = {'&': '&', '<': '<', '>': '>', '"': '"', '\'': '''};
+ let result = strings[0];
+ for (let i = 1; i < arguments.length; i++) {
+ result += String(arguments[i]).replace(/[&<>'"]/g, (char) => {
+ return entities[char];
+ });
+ result += strings[i];
+ }
+ return result;
+ }
+
+ /**
+ * Initialize the Google Map.
+ */
+ function initMap() {
+ // Create the map.
+ const map = new google.maps.Map(document.getElementById('map'), {
+ zoom: 7,
+ center: {lat: 52.632469, lng: -1.689423},
+ styles: mapStyle,
+ });
+
+ // Load the stores GeoJSON onto the map.
+ map.data.loadGeoJson('$link', {idPropertyName: 'storeid'});
+
+ // Define the custom marker icons, using the store's "category".
+ // map.data.setStyle((feature) => {
+ // return {
+ // icon: {
+ // url: `img/icon_${feature.getProperty('category')}.png`,
+ // scaledSize: new google.maps.Size(64, 64),
+ // },
+ // };
+ // });
+
+ const apiKey = '$key';
+ const infoWindow = new google.maps.InfoWindow();
+
+ // Show the information for a store when its marker is clicked.
+ map.data.addListener('click', (event) => {
+ const category = event.feature.getProperty('category');
+ const name = event.feature.getProperty('name');
+ const description = event.feature.getProperty('description');
+ const hours = event.feature.getProperty('hours');
+ const phone = event.feature.getProperty('phone');
+ const position = event.feature.getGeometry().get();
+ const content = sanitizeHTML`
+
+
+
${name}
${description}
+
Open: ${hours} Phone: ${phone}
+
+
+ `;
+
+ infoWindow.setContent(content);
+ infoWindow.setPosition(position);
+ infoWindow.setOptions({pixelOffset: new google.maps.Size(0, -30)});
+ infoWindow.open(map);
+ });
+
+ // Build and add the search bar
+ const card = document.createElement('div');
+ const titleBar = document.createElement('div');
+ const title = document.createElement('div');
+ const container = document.createElement('div');
+ const input = document.createElement('input');
+ const options = {
+ types: ['address'],
+ componentRestrictions: {country: 'gb'},
+ };
+
+ card.setAttribute('id', 'pac-card');
+ title.setAttribute('id', 'title');
+ title.textContent = 'Find the nearest store';
+ titleBar.appendChild(title);
+ container.setAttribute('id', 'pac-container');
+ input.setAttribute('id', 'pac-input');
+ input.setAttribute('type', 'text');
+ input.setAttribute('placeholder', 'Enter an address');
+ container.appendChild(input);
+ card.appendChild(titleBar);
+ card.appendChild(container);
+ map.controls[google.maps.ControlPosition.TOP_RIGHT].push(card);
+
+ // Make the search bar into a Places Autocomplete search bar and select
+ // which detail fields should be returned about the place that
+ // the user selects from the suggestions.
+ const autocomplete = new google.maps.places.Autocomplete(input, options);
+
+ autocomplete.setFields(
+ ['address_components', 'geometry', 'name']);
+
+ // Set the origin point when the user selects an address
+ const originMarker = new google.maps.Marker({map: map});
+ originMarker.setVisible(false);
+ let originLocation = map.getCenter();
+
+ autocomplete.addListener('place_changed', async () => {
+ originMarker.setVisible(false);
+ originLocation = map.getCenter();
+ const place = autocomplete.getPlace();
+
+ if (!place.geometry) {
+ // User entered the name of a Place that was not suggested and
+ // pressed the Enter key, or the Place Details request failed.
+ window.alert('No address available for input: \'' + place.name + '\'');
+ return;
+ }
+
+ // Recenter the map to the selected address
+ originLocation = place.geometry.location;
+ map.setCenter(originLocation);
+ map.setZoom(9);
+ console.log(place);
+
+ originMarker.setPosition(originLocation);
+ originMarker.setVisible(true);
+
+ // Use the selected address as the origin to calculate distances
+ // to each of the store locations
+ const rankedStores = await calculateDistances(map.data, originLocation);
+ showStoresList(map.data, rankedStores);
+
+ return;
+ });
+ }
+
+ /**
+ * Use Distance Matrix API to calculate distance from origin to each store.
+ * @param {google.maps.Data} data The geospatial data object layer for the map
+ * @param {google.maps.LatLng} origin Geographical coordinates in latitude
+ * and longitude
+ * @return {Promise