PageSourceSearch

https://www.paxlovid.com/lib/blocks/find-a-doctor-block/map/maps.js

js paxlovid.com collected 2026-09-25 20:09:19 UTC 8,473 bytes, 272 lines download raw bytes

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.