1/* global google */ 2/* eslint-disable no-console */ 3 4import { decorateIcons } from '../../../scripts/lib-franklin.js'; 5 6/** 7 * Map class defined in gmaps module 8 */ 9let GoogleMap; 10/** 11 * the map 12 */ 13let resultsMap; 14let markers = []; 15 16export function initMap(gmapsModule, coords, container, mapId) { 17 GoogleMap = gmapsModule.default; 18 resultsMap = GoogleMap.createMap(container, coords, mapId); 19 return resultsMap; 20} 21 22export async function addMap(mapContainer, data, mapId) { 23 const primaryPractice = data.practices?.find((p) => p.primaryLocation) || data.practices?.[0]; 24 const center = primaryPractice?.location || (await GoogleMap.getCoordinates(data.address)); 25 const map = GoogleMap.createMap(mapContainer, center, mapId, 16); 26 if (center) { 27 GoogleMap.addMarkerAtCoords(map, center.lat, center.lng, `${data.name.fullName}\n${data.address}`); 28 } 29 return map; 30} 31 32function coordKey(lat, lng) { 33 return `${lat.toFixed(5)},${lng.toFixed(5)}`; 34} 35 36function groupByLocation(doctors) { 37 const groups = new Map(); 38 doctors.forEach((doc) => { 39 const practice = doc.practices?.find((p) => p.primaryLocation) || doc.practices?.[0]; 40 const coords = practice?.location; 41 if (coords) { 42 const key = coordKey(coords.lat, coords.lng); 43 if (!groups.has(key)) groups.set(key, { lat: coords.lat, lng: coords.lng, doctors: [] }); 44 groups.get(key).doctors.push(doc); 45 } 46 }); 47 return groups; 48} 49 50let activeInfoWindow = null; 51 52function buildInfoWindowContent(doctors, onDoctorClick) { 53 const container = document.createElement('div'); 54 container.className = 'fad-info-window'; 55 56 const headerRow = document.createElement('div'); 57 headerRow.className = 'fad-info-window-header'; 58 59 const headerText = document.createElement('span'); 60 headerText.className = 'fad-info-window-header-text'; 61 headerText.textContent = `${doctors.length} Providers`; 62 63 const closeBtn = document.createElement('button'); 64 closeBtn.className = 'fad-info-window-close'; 65 closeBtn.setAttribute('aria-label', 'Close'); 66 closeBtn.innerHTML = '<span class="icon icon-lib-close-small"></span>'; 67 closeBtn.addEventListener('click', () => { 68 if (activeInfoWindow) activeInfoWindow.close(); 69 }); 70 71 headerRow.append(headerText, closeBtn); 72 container.append(headerRow); 73 74 const list = document.createElement('div'); 75 list.className = 'fad-info-window-list'; 76 77 doctors.forEach((doc) => { 78 const row = document.createElement('div'); 79 row.className = 'fad-info-window-row'; 80 81 const info = document.createElement('div'); 82 info.className = 'fad-info-window-info'; 83 84 const name = document.createElement('a'); 85 name.className = 'fad-info-window-name'; 86 name.textContent = doc.name?.fullName || ''; 87 name.href = '#'; 88 name.addEventListener('click', (e) => { 89 e.preventDefault(); 90 if (activeInfoWindow) activeInfoWindow.close(); 91 onDoctorClick(doc); 92 }); 93 94 const specialty = document.createElement('div'); 95 specialty.className = 'fad-info-window-specialty'; 96 specialty.textContent = doc.name?.title || ''; 97 98 info.append(name, specialty); 99 100 if (doc.phone) { 101 const phone = document.createElement('a'); 102 phone.className = 'fad-info-window-phone'; 103 phone.textContent = doc.phone; 104 phone.href = `tel:${doc.phone.replace(/[^+\d]/g, '')}`; 105 info.append(phone); 106 } 107 108 if (doc.address) { 109 const addr = document.createElement('a'); 110 addr.className = 'fad-info-window-address'; 111 addr.textContent = doc.address; 112 addr.href = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(doc.address)}`; 113 addr.target = '_blank'; 114 addr.rel = 'noopener'; 115 info.append(addr); 116 } 117 118 const aside = document.createElement('div'); 119 aside.className = 'fad-info-window-aside'; 120 121 if (doc.picture) { 122 const photo = document.createElement('img'); 123 photo.className = 'fad-info-window-photo'; 124 photo.src = doc.picture; 125 photo.alt = doc.name?.fullName || 'Provider photo'; 126 aside.append(photo); 127 } else { 128 const placeholder = document.createElement('div'); 129 placeholder.className = 'fad-info-window-photo fad-info-window-photo-placeholder'; 130 placeholder.setAttribute('role', 'img'); 131 placeholder.setAttribute('aria-label', 'Provider photo'); 132 placeholder.innerHTML = '<span class="icon icon-lib-user"></span>'; 133 aside.append(placeholder); 134 } 135 136 const chevron = document.createElement('button'); 137 chevron.className = 'fad-info-window-chevron'; 138 chevron.setAttribute('aria-label', `View ${doc.name?.fullName || 'provider'} details`); 139 chevron.innerHTML = '<span class="icon icon-lib-chevron-right"></span>'; 140 chevron.addEventListener('click', () => { 141 if (activeInfoWindow) activeInfoWindow.close(); 142 onDoctorClick(doc); 143 }); 144 145 aside.append(chevron); 146 row.append(info, aside); 147 list.append(row); 148 }); 149 150 container.append(list); 151 return container; 152} 153 154async function openInfoWindow(map, position, doctors, onDoctorClick) { 155 if (activeInfoWindow) activeInfoWindow.close(); 156 157 const content = buildInfoWindowContent(doctors, onDoctorClick); 158 await decorateIcons(content); 159 160 activeInfoWindow = new google.maps.InfoWindow({ 161 content, 162 position, 163 maxWidth: 600, 164 }); 165 activeInfoWindow.open(map); 166} 167 168export async function addMarkers(markerData, onClickCallback, fit = true) { 169 GoogleMap.deleteMarkers(markers); 170 markers = []; 171 if (activeInfoWindow) { 172 activeInfoWindow.close(); 173 activeInfoWindow = null; 174 } 175 176 const groups = groupByLocation(markerData); 177 const ungrouped = markerData.filter((doc) => { 178 const practice = doc.practices?.find((p) => p.primaryLocation) || doc.practices?.[0]; 179 return !practice?.location; 180 }); 181 182 groups.forEach(({ lat, lng, doctors }) => { 183 if (doctors.length === 1) { 184 const doc = doctors[0]; 185 markers.push(GoogleMap.addMarkerAtCoords(resultsMap, lat, lng, `${doc.name.fullName}\n${doc.address}`, () => onClickCallback(doc))); 186 } else { 187 markers.push( 188 GoogleMap.addMarkerAtCoords( 189 resultsMap, 190 lat, 191 lng, 192 `${doctors.length} providers`, 193 () => openInfoWindow(resultsMap, { lat, lng }, doctors, onClickCallback), 194 GoogleMap.createClusterMarkerContent(doctors.length) 195 ) 196 ); 197 } 198 }); 199 200 await Promise.all( 201 ungrouped.map(async (data) => { 202 markers.push(await GoogleMap.addSimpleMarker(resultsMap, data.address, `${data.name.fullName}\n${data.address}`, () => onClickCallback(data))); 203 }) 204 ); 205 206 if (fit) GoogleMap.fitMarkers(resultsMap, markers); 207} 208 209export async function setCenter(address) { 210 const center = await GoogleMap.getCoordinates(address); 211 GoogleMap.setCenterIfNeeded(resultsMap, center.lat, center.lng); 212} 213 214export function setCenterByCoords(lat, lng) { 215 GoogleMap.setCenterIfNeeded(resultsMap, lat, lng); 216} 217 218export async function getDirectionsLink(address) { 219 // const center = await GoogleMap.getCurrentCoordinates(); 220 // return `https://www.google.com/maps/dir/?api=1&origin=${center.lat},${center.lng}&destination=${address}`; 221 const center = await GoogleMap.getCoordinates(address); 222 return `https://www.google.com/maps/dir/?api=1&destination=${center.lat},${center.lng}`; 223} 224 225export async function getCurrentCoordinates() { 226 return new Promise((res) => { 227 if (navigator.geolocation) { 228 navigator.geolocation.getCurrentPosition( 229 (position) => 230 res({ 231 lat: position.coords.latitude, 232 lng: position.coords.longitude, 233 }), 234 (error) => { 235 console.error(error); 236 res(null); 237 } 238 ); 239 } 240 console.error('Geolocation is not supported'); 241 res(null); 242 }); 243} 244 245/** 246 * Adds the markers of the locations returned by the vaccine-finder request 247 */ 248export async function addLocationMarkers(results) { 249 if (!GoogleMap) { 250 console.error('Failed to load gmaps module'); 251 return; 252 } 253 254 GoogleMap.deleteMarkers(resultsMap);
255 results.forEach(({ lat, lng, name, address1 }, i) => { 256 GoogleMap.addMarker( 257 resultsMap, 258 lat, 259 lng, 260 name, 261 ` 262 <div> 263 <div class="marker-name">${i + 1}. ${name}</div> 264 <div class="marker-address">${address1}</div> 265 </div> 266 ` 267 ); 268 }); 269 if (results.length) { 270 GoogleMap.setCenter(resultsMap, results[0].lat, results[0].lng, document.body.clientWidth >= 1200 ? 300 : 0); 271 } 272}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.