Repository: jsdelivr/www.jsdelivr.com
Pull request #747: Convert map instances to components
Author: login: mpourismaiel
Created: 2025-03-27T08:33:23Z | Merged: 2025-03-30T14:04:03Z

Map instances:

- [x] users
- [x] network

Closes #735 

The index page has map but its much more different than users and network page and was ignored in the component definition.

Linked issue #735: Cleanup map pages code
              This was copied from the network page. We can extract the map from network, home page and users page into a component. Needs a bit of cleanup and I suggest doing it in another PR if you agree. It being a component would mean we calculate user probes and filtered probes in case of network page and just pass it on. I assume extracting it to an asset file would mean exporting a bunch of functions to call separately. I think the component approach is better.

_Originally posted by @mpourismaiel in https://github.com/jsdelivr/www.jsdelivr.com/pull/734#discussion_r2000592716_
            
Linked issue comments: user: login: MartinKolarik
created_at: 2025-03-23T18:29:07Z
body: @mpourismaiel please self-assign after you accept the repo invite.
author_association: MEMBER

Diff:
diff --git a/src/views/components/google-maps.html b/src/views/components/google-maps.html
new file mode 100644
index 000000000..9a7e01ce0
--- /dev/null
+++ b/src/views/components/google-maps.html
@@ -0,0 +1,353 @@
+{{#if !@shared.isServer && !@shared.googleMapsLoaded}}
+	<script>
+		function onDocumentReady (fn) {
+			if (document.readyState !== 'loading') {
+				fn();
+			} else {
+				document.addEventListener('DOMContentLoaded', fn);
+			}
+		}
+		/* eslint-disable */
+		function initMap () {
+			onDocumentReady(function () {
+				app.loaded = true;
+
+				if (app.initMap && app.router.route.view) {
+					app.initMap();
+				}
+			});
+		}
+	</script>
+	<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBTICLY5LgWNJ0Uly61Q1sNXz_1KZIECwg&amp;language=en&amp;callback=initMap&libraries=marker&loading=async" async></script>
+	{{#if probesByCoords}}
+		<script src="https://cdn.jsdelivr.net/npm/@googlemaps/markerclusterer@2.5.3/dist/index.umd.min.js"></script>
+	{{/if}}
+{{/if}}
+<div id="gp-map" class="{{class}}"></div>
+
+<script>
+	const MAP_MIN_ZOOM = 2;
+	const MAP_MAX_ZOOM = 22;
+	const MAP_ZOOM_REG = 3.74;
+	const MAP_ZOOM_ALT = 2.14;
+	const MAP_CENTER_REG = { lat: 48, lng: 16 };
+	const MAP_CENTER_ALT = { lat: 30, lng: 18 };
+
+	const getMarkerSize = (probeCount, zoom) => {
+		let size = {
+			svgWidth: 16,
+			svgHeight: 16,
+			circleRadius: 4.5,
+			strokeWidth: 1.5,
+		};
+
+		if (probeCount > 1) {
+			let scalingFactor = probeCount.toString().length;
+			size.svgWidth += 4 * scalingFactor + 4;
+			size.svgHeight += 4 * scalingFactor + 4;
+			size.circleRadius += 2 * scalingFactor + 1.5;
+			size.strokeWidth += .5;
+		}
+
+		if (zoom >= 5) {
+			size.svgWidth *= 1.1;
+			size.svgHeight *= 1.1;
+		}
+
+		if (zoom >= 10) {
+			size.svgWidth *= 1.1;
+			size.svgHeight *= 1.1;
+		}
+
+		return size;
+	};
+
+	let map;
+	let infoWindows = [];
+	let mapMarkers = [];
+	let markerCluster;
+
+	component.exports = {
+		data () {
+			return {
+				class: '',
+				iwClass: '',
+				probesByCoords: null,
+				onProbesByCoordsChange: null,
+			};
+		},
+		onrender () {
+			if (!Ractive.isServer) {
+				app.initMap = this.initMap.bind(this);
+
+				if (app.loaded) {
+					this.initMap();
+				}
+			}
+		},
+		onteardown () {
+			if (!Ractive.isServer) {
+				map = null;
+				markerCluster = null;
+				infoWindows = [];
+				mapMarkers = [];
+			}
+		},
+		initMap () {
+			let mq = window.matchMedia('(min-width: 768px)');
+
+			map = new google.maps.Map(this.find('#gp-map'), {
+				zoom: MAP_ZOOM_REG,
+				center: MAP_CENTER_REG,
+				mapTypeId: 'roadmap',
+				draggableCursor: 'default',
+				mapTypeControl: false,
+				streetViewControl: false,
+				fullscreenControl: false,
+				minZoom: MAP_MIN_ZOOM,
+				maxZoom: MAP_MAX_ZOOM,
+				mapId: 'ce04bbf9d49b6f34',
+			});
+
+			if (mq.matches) {
+				map.setCenter(MAP_CENTER_ALT);
+				map.setZoom(MAP_ZOOM_ALT);
+			}
+
+			map.addListener('zoom_changed', () => {
+				markerCluster?.clusters.forEach((c) => {
+					let m = c.marker;
+					let { svgWidth, svgHeight } = getMarkerSize(Number(m.content.getAttribute('data-probe-count')), map.zoom);
+					m.content.style.width = `${svgWidth}px`;
+					m.content.style.height = `${svgHeight}px`;
+				});
+
+				mapMarkers.forEach((m) => {
+					let { svgWidth, svgHeight } = getMarkerSize(Number(m.content.getAttribute('data-probe-count')), map.zoom);
+					m.content.style.width = `${svgWidth}px`;
+					m.content.style.height = `${svgHeight}px`;
+					m.content.setAttribute('data-zoom', map.zoom);
+				});
+
+				infoWindows.forEach(iw => iw.close());
+			});
+
+			this.observe('probesByCoords', (markersData, prevMarkersData) => {
+				if (markersData && JSON.stringify(markersData) !== JSON.stringify(prevMarkersData)) {
+					// hide all existing markers from the map
+					mapMarkers.forEach(mm => mm.map = null);
+
+					// show the marker or create one if it is missing
+					let filteredMarkers = Object.keys(markersData).map((key) => {
+						let [ lat, lng ] = key.split(', ');
+						let marker = mapMarkers.find(mm => mm.position.lat === lat && mm.position.lng === lng);
+
+						if (!marker) {
+							marker = this.createMapMarkerWithIW(key, markersData[key]);
+							mapMarkers.push(marker);
+						}
+
+						marker.map = map;
+
+						return marker;
+					});
+
+					let createClusterMarkerSVG = this.createClusterMarkerSVG;
+
+					if (markerCluster) {
+						markerCluster.clearMarkers();
+						markerCluster.addMarkers(filteredMarkers);
+					} else {
+						// eslint-disable-next-line no-undef
+						markerCluster = new markerClusterer.MarkerClusterer({
+							map,
+							markers: filteredMarkers,
+							renderer: {
+								render ({ markers, position }) {
+									let totalCustomNumber = markers.reduce((sum, marker) => sum + (marker.markersCount || 0), 0);
+									let svg = createClusterMarkerSVG(totalCustomNumber);
+
+									let clusterDiv = document.createElement('div');
+									clusterDiv.setAttribute('data-probe-count', totalCustomNumber);
+									clusterDiv.setAttribute('data-zoom', map.zoom);
+
+									clusterDiv.style.position = 'relative';
+									clusterDiv.style.backgroundImage = `url('data:image/svg+xml;base64,${svg}')`;
+									clusterDiv.style.backgroundRepeat = 'no-repeat';
+									clusterDiv.style.backgroundPosition = 'center';
+									clusterDiv.style.backgroundSize = 'contain';
+
+									let { svgWidth, svgHeight } = getMarkerSize(totalCustomNumber, map.zoom);
+									clusterDiv.style.width = `${svgWidth}px`;
+									clusterDiv.style.height = `${svgHeight}px`;
+
+									return new google.maps.marker.AdvancedMarkerElement({
+										position,
+										content: clusterDiv,
+										zIndex: totalCustomNumber,
+									});
+								},
+							},
+							// eslint-disable-next-line no-undef
+							algorithm: new markerClusterer.SuperClusterAlgorithm({
+								radius: 40,
+							}),
+						});
+					}
+
+					if (this.get('onProbesByCoordsChange') && this.get('onProbesByCoordsChange')()) {
+						this.focusMapOnMarkers(markersData);
+					} else {
+						if (!mq.matches) {
+							map.setCenter(MAP_CENTER_REG);
+							map.setZoom(MAP_ZOOM_REG);
+						} else {
+							map.setCenter(MAP_CENTER_ALT);
+							map.setZoom(MAP_ZOOM_ALT);
+						}
+					}
+				}
+			});
+
+
+			// close IWs on click on Map
+			google.maps.event.addListener(map, 'click', () => {
+				infoWindows.forEach(iw => iw.close());
+			});
+		},
+		createMapMarkerWithIW (key, markerData) {
+			let infoWindow = new google.maps.InfoWindow();
+			let svg = this.createMapMarkerSvg(markerData);
+			let [ lat, lng ] = key.split(', ');
+
+			// Collect InfoWindows
+			infoWindows.push(infoWindow);
+			infoWindow.setContent(this.createInfoWindowContent(markerData));
+
+			let markerContent = document.createElement('div');
+			markerContent.style.backgroundImage = `url('data:image/svg+xml;base64,${svg}')`;
+			markerContent.style.backgroundRepeat = 'no-repeat';
+			markerContent.style.backgroundPosition = 'center';
+			markerContent.style.backgroundSize = 'contain';
+			markerContent.setAttribute('data-probe-count', markerData.length);
+
+			let { svgWidth, svgHeight } = getMarkerSize(markerData.length, map.zoom);
+			markerContent.style.width = `${svgWidth}px`;
+			markerContent.style.height = `${svgHeight}px`;
+
+			// Create a new AdvancedMarkerView
+			let marker = new google.maps.marker.AdvancedMarkerElement({
+				map,
+				content: markerContent,
+				position: { lat: Number(lat), lng: Number(lng) },
+				zIndex: 1,
+			});
+
+			marker.markersCount = markerData.length || 0;
+
+			// Handle closing of all InfoWindows on a click on a marker
+			marker.addListener('click', () => {
+				infoWindows.forEach(iw => iw.close());
+				infoWindow.open({ map, anchor: marker });
+			});
+
+			return marker;
+		},
+		createMapMarkerSvg (markerData) {
+			return this.createClusterMarkerSVG(markerData.length);
+		},
+		createInfoWindowContent (markersByCoords) {
+			let markersCnt = markersByCoords.length;
+			let groupedNetworks = markersByCoords.reduce((res, m) => {
+				let marker = res.find(i => i.location.network === m.location.network);
+
+				if (marker) {
+					marker.cnt += 1;
+				} else {
+					res.push({ ...m, cnt: 1, sortKey: m.location.network.toLowerCase() });
+				}
+
+				return res;
+			}, []).sort((a, b) => a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey);
+
+			let infoWindowContent = `<div class="${this.get('iwClass')}">`;
+
+			if (markersCnt === 1) {
+				let { network, city, country } = markersByCoords[0].location;
+
+				infoWindowContent += `<span>${network} (${city}, ${country})</span>`;
+			} else {
+				// in this case we should render a list of probes
+				groupedNetworks.forEach((m, mIdx) => {
+					if (mIdx === 0) {
+						// add header content
+						infoWindowContent += `<span class="${this.get('iwClass')}_header">`;
+						infoWindowContent += `<img width="16" height="16" src="${this.get('@shared.assetsHost')}/img/globalping/location-icon.svg">`;
+						infoWindowContent += `<span class="${this.get('iwClass')}_header_location">${m.location.city}, ${m.location.country}</span>`;
+						infoWindowContent += `<span class="${this.get('iwClass')}_header_cnt">${markersByCoords.length}</span>`;
+						infoWindowContent += '</span>';
+					}
+
+					// render the list of the probes, grouped by the same network
+					infoWindowContent += `<span class="${this.get('iwClass')}_row">${m.location.network} ${m.cnt > 1 ? `<span class="${this.get('iwClass')}_row_network-cnt">${m.cnt}</span>` : ''}</span>`;
+				});
+			}
+
+			infoWindowContent += '</div>';
+
+			return infoWindowContent;
+		},
+		createClusterMarkerSVG (elementsCnt) {
+			let { svgWidth, svgHeight, circleRadius, strokeWidth } = getMarkerSize(elementsCnt, 1); // here zoom must always be 1
+
+			return window.btoa(`
+				<svg width="${svgWidth}" height="${svgHeight}" viewBox="0 0 ${svgWidth} ${svgHeight}" fill="none" xmlns="http://www.w3.org/2000/svg">
+					<g filter="url(#filter0_d_6106_3045)">
+						<circle cx="${svgWidth / 2}" cy="${svgHeight / 2}" r="${circleRadius}" fill="#17D4A7"/>
+						<circle cx="${svgWidth / 2}" cy="${svgHeight / 2}" r="${circleRadius + 1}" stroke="white" stroke-width="${strokeWidth}"/>
+					</g>
+					${elementsCnt !== 1 ? `<text x="${svgWidth / 2}" y="${svgHeight / 2}" text-anchor="middle" dy=".35em" font-family="Arial" font-size="10" font-weight="bold" fill="#fff">${elementsCnt}</text>` : ''}
+					<defs>
+						<filter id="filter0_d_6106_3045" x="0" y="0" width="${svgWidth}" height="${svgHeight}" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+							<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+							<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+							<feOffset dy="1"/>
+							<feGaussianBlur stdDeviation="1"/>
+							<feComposite in2="hardAlpha" operator="out"/>
+							<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
+							<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6106_3045"/>
+							<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6106_3045" result="shape"/>
+						</filter>
+					</defs>
+				</svg>
+			`);
+		},
+		focusMapOnMarkers (markersData) {
+			let locationStrings = Object.keys(markersData);
+
+			if (locationStrings.length === 0) {
+				return;
+			}
+
+			let positions = locationStrings.map((str) => {
+				let [ lat, lng ] = str.split(', ');
+
+				return { lat: Number(lat), lng: Number(lng) };
+			});
+
+			let bounds = new google.maps.LatLngBounds();
+
+			positions.forEach(position => bounds.extend(position));
+
+			// temp limit for zooming
+			map.setOptions({ maxZoom: 12 });
+
+			map.fitBounds(bounds);
+
+			google.maps.event.addListenerOnce(map, 'idle', () => {
+				// resore zooming to inital
+				map.setOptions({ maxZoom: MAP_MAX_ZOOM });
+			});
+		},
+	};
+</script>
diff --git a/src/views/pages/globalping/_users.html b/src/views/pages/globalping/_users.html
index ee0108b2d..c953aa8e9 100644
--- a/src/views/pages/globalping/_users.html
+++ b/src/views/pages/globalping/_users.html
@@ -1,5 +1,6 @@
 <link rel="ractive" href="../../r-page-globalping.html" name="r-page">
 <link rel="ractive" href="../../components/gp-header.html" name="c-gp-header">
+<link rel="ractive" href="../../components/google-maps.html" name="c-google-maps">
 <link rel="ractive" href="../../components/gp-footer.html" name="c-gp-footer">
 <link rel="ractive" href="../../components/notification.html" name="c-notification">
 
@@ -11,30 +12,6 @@
 	<c-notification></c-notification>
 	<c-gp-header additionalClasses="header-with-gp-translucent-bg"></c-gp-header>
 
-	{{#if !@shared.isServer && !@shared.googleMapsLoaded}}
-		<script>
-			function onDocumentReady (fn) {
-				if (document.readyState !== 'loading') {
-					fn();
-				} else {
-					document.addEventListener('DOMContentLoaded', fn);
-				}
-			}
-			/* eslint-disable */
-			function initMap () {
-				onDocumentReady(function () {
-					app.loaded = true;
-
-					if (app.router.route && app.router.route.view && app.router.route.view.initMap) {
-						app.router.route.view.initMap();
-					}
-				});
-			}
-		</script>
-		<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBTICLY5LgWNJ0Uly61Q1sNXz_1KZIECwg&amp;language=en&amp;callback=initMap&libraries=marker&loading=async" async></script>
-		<script src="https://cdn.jsdelivr.net/npm/@googlemaps/markerclusterer@2.5.3/dist/index.umd.min.js"></script>
-	{{/if}}
-
 	<div class="p-users">
 		<div class="page-content">
 			<div class="page-content_side-menu">
@@ -81,7 +58,10 @@
 			<div class="page-content_main">
 				<div class="page-content_main_section">
 					<div class="p-users_map-wrapper">
-						<div id="gp-map" class="p-users_map-wrapper_map"></div>
+						<c-google-maps
+							class="p-users_map-wrapper_map"
+							iwClass="p-users_iw"
+							probesByCoords="{{probesByCoords}}" />
 					</div>
 				</div>
 
@@ -153,46 +133,6 @@ <h2 class="page-content_main_section_title">List of probes</h2>
 	const http = require('../../../assets/js/utils/http');
 	const dataCache = require('../../../assets/js/utils/data-cache');
 	const tooltip = require('../../../assets/js/decorators/tooltip');
-	const MAP_MIN_ZOOM = 2;
-	const MAP_MAX_ZOOM = 22;
-	const MAP_ZOOM_REG = 3.74;
-	const MAP_ZOOM_ALT = 2.14;
-	const MAP_CENTER_REG = { lat: 48, lng: 16 };
-	const MAP_CENTER_ALT = { lat: 30, lng: 18 };
-
-	const getMarkerSize = (probeCount, zoom) => {
-		let size = {
-			svgWidth: 16,
-			svgHeight: 16,
-			circleRadius: 4.5,
-			strokeWidth: 1.5,
-		};
-
-		if (probeCount > 1) {
-			let scalingFactor = probeCount.toString().length;
-			size.svgWidth += 4 * scalingFactor + 4;
-			size.svgHeight += 4 * scalingFactor + 4;
-			size.circleRadius += 2 * scalingFactor + 1.5;
-			size.strokeWidth += .5;
-		}
-
-		if (zoom >= 5) {
-			size.svgWidth *= 1.1;
-			size.svgHeight *= 1.1;
-		}
-
-		if (zoom >= 10) {
-			size.svgWidth *= 1.1;
-			size.svgHeight *= 1.1;
-		}
-
-		return size;
-	};
-
-	let map;
-	let infoWindows = [];
-	let mapMarkers = [];
-	let markerCluster;
 
 	component.exports = {
 		decorators: {
@@ -247,10 +187,6 @@ <h2 class="page-content_main_section_title">List of probes</h2>
 		},
 		onrender () {
 			if (!Ractive.isServer) {
-				if (app.loaded) {
-					this.initMap();
-				}
-
 				window.addEventListener('resize', debounce(throttle(() => this.calculateMaxTagCount(), 200)));
 				this.observe('userProbes', () => this.calculateMaxTagCount(), { defer: true });
 			}
@@ -307,230 +243,5 @@ <h2 class="page-content_main_section_title">List of probes</h2>
 
 			return groups;
 		},
-		initMap () {
-			let mq = window.matchMedia('(min-width: 768px)');
-
-			map = new google.maps.Map(this.find('#gp-map'), {
-				zoom: MAP_ZOOM_REG,
-				center: MAP_CENTER_REG,
-				mapTypeId: 'roadmap',
-				draggableCursor: 'default',
-				mapTypeControl: false,
-				streetViewControl: false,
-				fullscreenControl: false,
-				minZoom: MAP_MIN_ZOOM,
-				maxZoom: MAP_MAX_ZOOM,
-				mapId: 'ce04bbf9d49b6f34',
-			});
-
-			if (mq.matches) {
-				map.setCenter(MAP_CENTER_ALT);
-				map.setZoom(MAP_ZOOM_ALT);
-			}
-
-			map.addListener('zoom_changed', () => {
-				markerCluster?.clusters.forEach((c) => {
-					let m = c.marker;
-					let { svgWidth, svgHeight } = getMarkerSize(Number(m.content.getAttribute('data-probe-count')), map.zoom);
-					m.content.style.width = `${svgWidth}px`;
-					m.content.style.height = `${svgHeight}px`;
-				});
-
-				mapMarkers.forEach((m) => {
-					let { svgWidth, svgHeight } = getMarkerSize(Number(m.content.getAttribute('data-probe-count')), map.zoom);
-					m.content.style.width = `${svgWidth}px`;
-					m.content.style.height = `${svgHeight}px`;
-					m.content.setAttribute('data-zoom', map.zoom);
-				});
-
-				infoWindows.forEach(iw => iw.close());
-			});
-
-			this.observe('probesByCoords', (markersData, prevMarkersData) => {
-				if (markersData && JSON.stringify(markersData) !== JSON.stringify(prevMarkersData)) {
-					// hide all existing markers from the map
-					mapMarkers.forEach(mm => mm.map = null);
-
-					// show the marker or create one if it is missing
-					let filteredMarkers = Object.keys(markersData).map((key) => {
-						let [ lat, lng ] = key.split(', ');
-						let marker = mapMarkers.find(mm => mm.position.lat === lat && mm.position.lng === lng);
-
-						if (!marker) {
-							marker = this.createMapMarkerWithIW(key, markersData[key]);
-							mapMarkers.push(marker);
-						}
-
-						marker.map = map;
-
-						return marker;
-					});
-
-					let createClusterMarkerSVG = this.createClusterMarkerSVG;
-
-					if (markerCluster) {
-						markerCluster.clearMarkers();
-						markerCluster.addMarkers(filteredMarkers);
-					} else {
-						// eslint-disable-next-line no-undef
-						markerCluster = new markerClusterer.MarkerClusterer({
-							map,
-							markers: filteredMarkers,
-							renderer: {
-								render ({ markers, position }) {
-									let totalCustomNumber = markers.reduce((sum, marker) => sum + (marker.markersCount || 0), 0);
-									let svg = createClusterMarkerSVG(totalCustomNumber);
-
-									let clusterDiv = document.createElement('div');
-									clusterDiv.setAttribute('data-probe-count', totalCustomNumber);
-									clusterDiv.setAttribute('data-zoom', map.zoom);
-
-									clusterDiv.style.position = 'relative';
-									clusterDiv.style.backgroundImage = `url('data:image/svg+xml;base64,${svg}')`;
-									clusterDiv.style.backgroundRepeat = 'no-repeat';
-									clusterDiv.style.backgroundPosition = 'center';
-									clusterDiv.style.backgroundSize = 'contain';
-
-									let { svgWidth, svgHeight } = getMarkerSize(totalCustomNumber, map.zoom);
-									clusterDiv.style.width = `${svgWidth}px`;
-									clusterDiv.style.height = `${svgHeight}px`;
-
-									return new google.maps.marker.AdvancedMarkerElement({
-										position,
-										content: clusterDiv,
-										zIndex: totalCustomNumber,
-									});
-								},
-							},
-							// eslint-disable-next-line no-undef
-							algorithm: new markerClusterer.SuperClusterAlgorithm({
-								radius: 40,
-							}),
-						});
-					}
-
-					if (!mq.matches) {
-						map.setCenter(MAP_CENTER_REG);
-						map.setZoom(MAP_ZOOM_REG);
-					} else {
-						map.setCenter(MAP_CENTER_ALT);
-						map.setZoom(MAP_ZOOM_ALT);
-					}
-				}
-			});
-
-
-			// close IWs on click on Map
-			google.maps.event.addListener(map, 'click', () => {
-				infoWindows.forEach(iw => iw.close());
-			});
-		},
-		createMapMarkerWithIW (key, markerData) {
-			let infoWindow = new google.maps.InfoWindow();
-			let svg = this.createMapMarkerSvg(markerData);
-			let [ lat, lng ] = key.split(', ');
-
-			// Collect InfoWindows
-			infoWindows.push(infoWindow);
-			infoWindow.setContent(this.createInfoWindowContent(markerData));
-
-			let markerContent = document.createElement('div');
-			markerContent.style.backgroundImage = `url('data:image/svg+xml;base64,${svg}')`;
-			markerContent.style.backgroundRepeat = 'no-repeat';
-			markerContent.style.backgroundPosition = 'center';
-			markerContent.style.backgroundSize = 'contain';
-			markerContent.setAttribute('data-probe-count', markerData.length);
-
-			let { svgWidth, svgHeight } = getMarkerSize(markerData.length, map.zoom);
-			markerContent.style.width = `${svgWidth}px`;
-			markerContent.style.height = `${svgHeight}px`;
-
-			// Create a new AdvancedMarkerView
-			let marker = new google.maps.marker.AdvancedMarkerElement({
-				map,
-				content: markerContent,
-				position: { lat: Number(lat), lng: Number(lng) },
-				zIndex: 1,
-			});
-
-			marker.markersCount = markerData.length || 0;
-
-			// Handle closing of all InfoWindows on a click on a marker
-			marker.addListener('click', () => {
-				infoWindows.forEach(iw => iw.close());
-				infoWindow.open({ map, anchor: marker });
-			});
-
-			return marker;
-		},
-		createMapMarkerSvg (markerData) {
-			return this.createClusterMarkerSVG(markerData.length);
-		},
-		createInfoWindowContent (markersByCoords) {
-			let markersCnt = markersByCoords.length;
-			let groupedNetworks = markersByCoords.reduce((res, m) => {
-				let marker = res.find(i => i.location.network === m.location.network);
-
-				if (marker) {
-					marker.cnt += 1;
-				} else {
-					res.push({ ...m, cnt: 1, sortKey: m.location.network.toLowerCase() });
-				}
-
-				return res;
-			}, []).sort((a, b) => a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey);
-
-			let infoWindowContent = `<div class="p-users_iw">`;
-
-			if (markersCnt === 1) {
-				let { network, city, country } = markersByCoords[0].location;
-
-				infoWindowContent += `<span>${network} (${city}, ${country})</span>`;
-			} else {
-				// in this case we should render a list of probes
-				groupedNetworks.forEach((m, mIdx) => {
-					if (mIdx === 0) {
-						// add header content
-						infoWindowContent += '<span class="p-users_iw_header">';
-						infoWindowContent += `<img width="16" height="16" src="${this.get('@shared.assetsHost')}/img/globalping/location-icon.svg">`;
-						infoWindowContent += `<span class="p-users_iw_header_location">${m.location.city}, ${m.location.country}</span>`;
-						infoWindowContent += `<span class="p-users_iw_header_cnt">${markersByCoords.length}</span>`;
-						infoWindowContent += '</span>';
-					}
-
-					// render the list of the probes, grouped by the same network
-					infoWindowContent += `<span class="p-users_iw_row">${m.location.network} ${m.cnt > 1 ? `<span class="p-users_iw_row_network-cnt">${m.cnt}</span>` : ''}</span>`;
-				});
-			}
-
-			infoWindowContent += '</div>';
-
-			return infoWindowContent;
-		},
-		createClusterMarkerSVG (elementsCnt) {
-			let { svgWidth, svgHeight, circleRadius, strokeWidth } = getMarkerSize(elementsCnt, 1); // here zoom must always be 1
-
-			return window.btoa(`
-				<svg width="${svgWidth}" height="${svgHeight}" viewBox="0 0 ${svgWidth} ${svgHeight}" fill="none" xmlns="http://www.w3.org/2000/svg">
-					<g filter="url(#filter0_d_6106_3045)">
-						<circle cx="${svgWidth / 2}" cy="${svgHeight / 2}" r="${circleRadius}" fill="#17D4A7"/>
-						<circle cx="${svgWidth / 2}" cy="${svgHeight / 2}" r="${circleRadius + 1}" stroke="white" stroke-width="${strokeWidth}"/>
-					</g>
-					${elementsCnt !== 1 ? `<text x="${svgWidth / 2}" y="${svgHeight / 2}" text-anchor="middle" dy=".35em" font-family="Arial" font-size="10" font-weight="bold" fill="#fff">${elementsCnt}</text>` : ''}
-					<defs>
-						<filter id="filter0_d_6106_3045" x="0" y="0" width="${svgWidth}" height="${svgHeight}" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
-							<feFlood flood-opacity="0" result="BackgroundImageFix"/>
-							<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-							<feOffset dy="1"/>
-							<feGaussianBlur stdDeviation="1"/>
-							<feComposite in2="hardAlpha" operator="out"/>
-							<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
-							<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6106_3045"/>
-							<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6106_3045" result="shape"/>
-						</filter>
-					</defs>
-				</svg>
-			`);
-		},
 	};
 </script>
diff --git a/src/views/pages/globalping/network.html b/src/views/pages/globalping/network.html
index a6df805c4..ff196e22d 100644
--- a/src/views/pages/globalping/network.html
+++ b/src/views/pages/globalping/network.html
@@ -1,33 +1,10 @@
 <link rel="ractive" href="../../r-page-globalping.html" name="r-page">
 <link rel="ractive" href="../../components/gp-header.html" name="c-gp-header">
+<link rel="ractive" href="../../components/google-maps.html" name="c-google-maps">
 <link rel="ractive" href="../../components/gp-footer.html" name="c-gp-footer">
 <link rel="ractive" href="../../components/notification.html" name="c-notification">
 
 <r-page noYield="{{noYield}}" title="{{title}}" description="{{description}}">
-	{{#if !@shared.isServer && !@shared.googleMapsLoaded}}
-		<script>
-			function onDocumentReady (fn) {
-				if (document.readyState !== 'loading') {
-					fn();
-				} else {
-					document.addEventListener('DOMContentLoaded', fn);
-				}
-			}
-			/* eslint-disable */
-			function initMap () {
-				onDocumentReady(function () {
-					app.loaded = true;
-
-					if (app.router.route && app.router.route.view && app.router.route.view.initMap) {
-						app.router.route.view.initMap();
-					}
-				});
-			}
-		</script>
-		<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBTICLY5LgWNJ0Uly61Q1sNXz_1KZIECwg&amp;language=en&amp;callback=initMap&libraries=marker&loading=async" async></script>
-		<script src="https://cdn.jsdelivr.net/npm/@googlemaps/markerclusterer@2.5.3/dist/index.umd.min.js"></script>
-	{{/if}}
-
 	<c-notification></c-notification>
 
 	<c-gp-header additionalClasses="header-with-globalping-bg"></c-gp-header>
@@ -35,7 +12,11 @@
 	<div class="p-globalping-network">
 		<div class="gp-network">
 			<div class="gp-network_map-wrapper">
-				<div id="gp-map" class="gp-network_map-wrapper_map"></div>
+				<c-google-maps
+					class="gp-network_map-wrapper_map"
+					iwClass="gp-network_iw"
+					probesByCoords="{{filteredMarkersData}}"
+					onProbesByCoordsChange="{{@this.handleProbesByCoordsChange}}" />
 			</div>
 
 			<div class="gp-network_filters">
@@ -113,50 +94,10 @@
 	const has = require('../../../assets/js/utils/has');
 	const http = require('../../../assets/js/utils/http');
 	const CONTINENTS = require('../../../assets/json/continents.json');
-	const MAP_MIN_ZOOM = 2;
-	const MAP_MAX_ZOOM = 22;
-	const MAP_ZOOM_REG = 3.74;
-	const MAP_ZOOM_ALT = 2.14;
-	const MAP_CENTER_REG = { lat: 48, lng: 16 };
-	const MAP_CENTER_ALT = { lat: 30, lng: 18 };
 	const FILTER_TYPE_BY_CONTINENT = 'byContinent';
 	const FILTER_TYPE_BY_NETWORK = 'byNetwork';
 	const FILTER_TYPE_BY_CLOUD = 'byCloud';
 
-	const getMarkerSize = (probeCount, zoom) => {
-		let size = {
-			svgWidth: 16,
-			svgHeight: 16,
-			circleRadius: 4.5,
-			strokeWidth: 1.5,
-		};
-
-		if (probeCount > 1) {
-			let scalingFactor = probeCount.toString().length;
-			size.svgWidth += 4 * scalingFactor + 4;
-			size.svgHeight += 4 * scalingFactor + 4;
-			size.circleRadius += 2 * scalingFactor + 1.5;
-			size.strokeWidth += .5;
-		}
-
-		if (zoom >= 5) {
-			size.svgWidth *= 1.1;
-			size.svgHeight *= 1.1;
-		}
-
-		if (zoom >= 10) {
-			size.svgWidth *= 1.1;
-			size.svgHeight *= 1.1;
-		}
-
-		return size;
-	};
-
-	let map;
-	let infoWindows = [];
-	let mapMarkers = [];
-	let markerCluster;
-
 	component.exports = {
 		data () {
 			return {
@@ -300,146 +241,15 @@
 			if (!Ractive.isServer) {
 				this.set('@shared.googleMapsLoaded', true);
 
-				if (app.loaded) {
-					this.initMap();
-				}
-
 				// detect window resize
 				window.addEventListener('resize', debounce(throttle(() => this.set('screenWidth', innerWidth), 200)));
 			}
 		},
-		onteardown () {
-			if (!Ractive.isServer) {
-				map = null;
-				markerCluster = null;
-				infoWindows = [];
-				mapMarkers = [];
-			}
-		},
-		initMap () {
-			let mq = window.matchMedia('(min-width: 768px)');
-
-			map = new google.maps.Map(this.find('#gp-map'), {
-				zoom: MAP_ZOOM_REG,
-				center: MAP_CENTER_REG,
-				mapTypeId: 'roadmap',
-				draggableCursor: 'default',
-				mapTypeControl: false,
-				streetViewControl: false,
-				fullscreenControl: false,
-				minZoom: MAP_MIN_ZOOM,
-				maxZoom: MAP_MAX_ZOOM,
-				mapId: 'ce04bbf9d49b6f34',
-			});
-
-			if (mq.matches) {
-				map.setCenter(MAP_CENTER_ALT);
-				map.setZoom(MAP_ZOOM_ALT);
-			}
-
-			map.addListener('zoom_changed', () => {
-				markerCluster?.clusters.forEach((c) => {
-					let m = c.marker;
-					let { svgWidth, svgHeight } = getMarkerSize(Number(m.content.getAttribute('data-probe-count')), map.zoom);
-					m.content.style.width = `${svgWidth}px`;
-					m.content.style.height = `${svgHeight}px`;
-				});
-
-				mapMarkers.forEach((m) => {
-					let { svgWidth, svgHeight } = getMarkerSize(Number(m.content.getAttribute('data-probe-count')), map.zoom);
-					m.content.style.width = `${svgWidth}px`;
-					m.content.style.height = `${svgHeight}px`;
-					m.content.setAttribute('data-zoom', map.zoom);
-				});
-
-				infoWindows.forEach(iw => iw.close());
-			});
-
-			this.observe('filteredMarkersData', (markersData, prevMarkersData) => {
-				if (markersData && JSON.stringify(markersData) !== JSON.stringify(prevMarkersData)) {
-					// hide all existing markers from the map
-					mapMarkers.forEach(mm => mm.map = null);
-
-					// show the marker or create one if it is missing
-					let filteredMarkers = Object.keys(markersData).map((key) => {
-						let [ lat, lng ] = key.split(', ');
-						let marker = mapMarkers.find(mm => mm.position.lat === lat && mm.position.lng === lng);
-
-						if (!marker) {
-							marker = this.createMapMarkerWithIW(key, markersData[key]);
-							mapMarkers.push(marker);
-						}
-
-						marker.map = map;
-
-						return marker;
-					});
-
-					let createClusterMarkerSVG = this.createClusterMarkerSVG;
-
-					if (markerCluster) {
-						markerCluster.clearMarkers();
-						markerCluster.addMarkers(filteredMarkers);
-					} else {
-						// eslint-disable-next-line no-undef
-						markerCluster = new markerClusterer.MarkerClusterer({
-							map,
-							markers: filteredMarkers,
-							renderer: {
-								render ({ markers, position }) {
-									let totalCustomNumber = markers.reduce((sum, marker) => sum + (marker.markersCount || 0), 0);
-									let svg = createClusterMarkerSVG(totalCustomNumber);
-
-									let clusterDiv = document.createElement('div');
-									clusterDiv.setAttribute('data-probe-count', totalCustomNumber);
-									clusterDiv.setAttribute('data-zoom', map.zoom);
-
-									clusterDiv.style.position = 'relative';
-									clusterDiv.style.backgroundImage = `url('data:image/svg+xml;base64,${svg}')`;
-									clusterDiv.style.backgroundRepeat = 'no-repeat';
-									clusterDiv.style.backgroundPosition = 'center';
-									clusterDiv.style.backgroundSize = 'contain';
-
-									let { svgWidth, svgHeight } = getMarkerSize(totalCustomNumber, map.zoom);
-									clusterDiv.style.width = `${svgWidth}px`;
-									clusterDiv.style.height = `${svgHeight}px`;
-
-									return new google.maps.marker.AdvancedMarkerElement({
-										position,
-										content: clusterDiv,
-										zIndex: totalCustomNumber,
-									});
-								},
-							},
-							// eslint-disable-next-line no-undef
-							algorithm: new markerClusterer.SuperClusterAlgorithm({
-								radius: 40,
-							}),
-						});
-					}
-
-					let filtersSet = this.get('filtersSet');
-					let byContinentFilerRule = filtersSet ? filtersSet.find(fR => fR.type === FILTER_TYPE_BY_CONTINENT) : null;
-
-					if (byContinentFilerRule) {
-						this.focusMapOnMarkers(markersData);
-					} else {
-						if (!mq.matches) {
-							map.setCenter(MAP_CENTER_REG);
-							map.setZoom(MAP_ZOOM_REG);
-						} else {
-							map.setCenter(MAP_CENTER_ALT);
-							map.setZoom(MAP_ZOOM_ALT);
-						}
-					}
-				}
-			});
-
+		handleProbesByCoordsChange () {
+			let filtersSet = this.get('filtersSet');
+			let byContinentFilerRule = filtersSet ? filtersSet.find(fR => fR.type === FILTER_TYPE_BY_CONTINENT) : null;
 
-			// close IWs on click on Map
-			google.maps.event.addListener(map, 'click', () => {
-				infoWindows.forEach(iw => iw.close());
-			});
+			return byContinentFilerRule;
 		},
 		getProbesData () {
 			if (has.sessionStorage()) {
@@ -593,88 +403,6 @@
 			this.set('filtersSet', filtersSet.length ? filtersSet : []);
 			this.set('availableFilters', availableFilters);
 		},
-		createMapMarkerWithIW (key, markerData) {
-			let infoWindow = new google.maps.InfoWindow();
-			let svg = this.createMapMarkerSvg(markerData);
-			let [ lat, lng ] = key.split(', ');
-
-			// Collect InfoWindows
-			infoWindows.push(infoWindow);
-			infoWindow.setContent(this.createInfoWindowContent(markerData));
-
-			let markerContent = document.createElement('div');
-			markerContent.style.backgroundImage = `url('data:image/svg+xml;base64,${svg}')`;
-			markerContent.style.backgroundRepeat = 'no-repeat';
-			markerContent.style.backgroundPosition = 'center';
-			markerContent.style.backgroundSize = 'contain';
-			markerContent.setAttribute('data-probe-count', markerData.length);
-
-			let { svgWidth, svgHeight } = getMarkerSize(markerData.length, map.zoom);
-			markerContent.style.width = `${svgWidth}px`;
-			markerContent.style.height = `${svgHeight}px`;
-
-			// Create a new AdvancedMarkerView
-			let marker = new google.maps.marker.AdvancedMarkerElement({
-				map,
-				content: markerContent,
-				position: { lat: Number(lat), lng: Number(lng) },
-				zIndex: 1,
-			});
-
-			marker.markersCount = markerData.length || 0;
-
-			// Handle closing of all InfoWindows on a click on a marker
-			marker.addListener('click', () => {
-				infoWindows.forEach(iw => iw.close());
-				infoWindow.open({ map, anchor: marker });
-			});
-
-			return marker;
-		},
-		createMapMarkerSvg (markerData) {
-			return this.createClusterMarkerSVG(markerData.length);
-		},
-		createInfoWindowContent (markersByCoords) {
-			let markersCnt = markersByCoords.length;
-			let groupedNetworks = markersByCoords.reduce((res, m) => {
-				let marker = res.find(i => i.location.network === m.location.network);
-
-				if (marker) {
-					marker.cnt += 1;
-				} else {
-					res.push({ ...m, cnt: 1, sortKey: m.location.network.toLowerCase() });
-				}
-
-				return res;
-			}, []).sort((a, b) => a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey);
-
-			let infoWindowContent = `<div class="gp-network_iw">`;
-
-			if (markersCnt === 1) {
-				let { network, city, country } = markersByCoords[0].location;
-
-				infoWindowContent += `<span>${network} (${city}, ${country})</span>`;
-			} else {
-				// in this case we should render a list of probes
-				groupedNetworks.forEach((m, mIdx) => {
-					if (mIdx === 0) {
-						// add header content
-						infoWindowContent += '<span class="gp-network_iw_header">';
-						infoWindowContent += `<img width="16" height="16" src="${this.get('@shared.assetsHost')}/img/globalping/location-icon.svg">`;
-						infoWindowContent += `<span class="gp-network_iw_header_location">${m.location.city}, ${m.location.country}</span>`;
-						infoWindowContent += `<span class="gp-network_iw_header_cnt">${markersByCoords.length}</span>`;
-						infoWindowContent += '</span>';
-					}
-
-					// render the list of the probes, grouped by the same network
-					infoWindowContent += `<span class="gp-network_iw_row">${m.location.network} ${m.cnt > 1 ? `<span class="gp-network_iw_row_network-cnt">${m.cnt}</span>` : ''}</span>`;
-				});
-			}
-
-			infoWindowContent += '</div>';
-
-			return infoWindowContent;
-		},
 		getFilteredProbesList (filtersSet) {
 			let probesByContinents = this.get('probesByContinents');
 
@@ -818,58 +546,6 @@
 
 			return sortedArray;
 		},
-		focusMapOnMarkers (markersData) {
-			let locationStrings = Object.keys(markersData);
-
-			if (locationStrings.length === 0) {
-				return;
-			}
-
-			let positions = locationStrings.map((str) => {
-				let [ lat, lng ] = str.split(', ');
-
-				return { lat: Number(lat), lng: Number(lng) };
-			});
-
-			let bounds = new google.maps.LatLngBounds();
-
-			positions.forEach(position => bounds.extend(position));
-
-			// temp limit for zooming
-			map.setOptions({ maxZoom: 12 });
-
-			map.fitBounds(bounds);
-
-			google.maps.event.addListenerOnce(map, 'idle', () => {
-				// resore zooming to inital
-				map.setOptions({ maxZoom: MAP_MAX_ZOOM });
-			});
-		},
-		createClusterMarkerSVG (elementsCnt) {
-			let { svgWidth, svgHeight, circleRadius, strokeWidth } = getMarkerSize(elementsCnt, 1); // here zoom must always be 1
-
-			return window.btoa(`
-				<svg width="${svgWidth}" height="${svgHeight}" viewBox="0 0 ${svgWidth} ${svgHeight}" fill="none" xmlns="http://www.w3.org/2000/svg">
-					<g filter="url(#filter0_d_6106_3045)">
-						<circle cx="${svgWidth / 2}" cy="${svgHeight / 2}" r="${circleRadius}" fill="#17D4A7"/>
-						<circle cx="${svgWidth / 2}" cy="${svgHeight / 2}" r="${circleRadius + 1}" stroke="white" stroke-width="${strokeWidth}"/>
-					</g>
-					${elementsCnt !== 1 ? `<text x="${svgWidth / 2}" y="${svgHeight / 2}" text-anchor="middle" dy=".35em" font-family="Arial" font-size="10" font-weight="bold" fill="#fff">${elementsCnt}</text>` : ''}
-					<defs>
-						<filter id="filter0_d_6106_3045" x="0" y="0" width="${svgWidth}" height="${svgHeight}" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
-							<feFlood flood-opacity="0" result="BackgroundImageFix"/>
-							<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-							<feOffset dy="1"/>
-							<feGaussianBlur stdDeviation="1"/>
-							<feComposite in2="hardAlpha" operator="out"/>
-							<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
-							<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6106_3045"/>
-							<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6106_3045" result="shape"/>
-						</filter>
-					</defs>
-				</svg>
-			`);
-		},
 		splitDataIntoColumns (data, columnsCnt = 4, minItemsPerColumn = 30) {
 			let totalItems = data.length;
 			let itemsPerColumn = Math.max(minItemsPerColumn, Math.ceil(totalItems / columnsCnt));


Reviews:
user: login: MartinKolarik
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-30T13:55:10Z
user: login: mpourismaiel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T13:57:13Z
user: login: mpourismaiel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T14:01:16Z

Review comments:
diff_hunk: @@ -22,29 +22,6 @@
 		{{/each}}
 	</style>
 
-	{{#if !@shared.isServer && !@shared.googleMapsLoaded}}
-		<script>
-			function onDocumentReady (fn) {
-				if (document.readyState !== 'loading') {
-					fn();
-				} else {
-					document.addEventListener('DOMContentLoaded', fn);
-				}
-			}
-			/* eslint-disable */
-			function initMap () {
-				onDocumentReady(function () {
-					app.loaded = true;
-
-					if (app.router.route && app.router.route.view && app.router.route.view.initMap) {
-						app.router.route.view.initMap();
-					}
-				});
-			}
-		</script>
-		<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBTICLY5LgWNJ0Uly61Q1sNXz_1KZIECwg&amp;language=en&amp;callback=initMap&amp;libraries=marker&amp;loading=async" async></script>
-	{{/if}}
-
path: src/views/pages/network.html
user: login: MartinKolarik
body: This is a different page: https://www.jsdelivr.com/network
created_at: 2025-03-30T13:51:02Z
author_association: MEMBER
diff_hunk: @@ -22,29 +22,6 @@
 		{{/each}}
 	</style>
 
-	{{#if !@shared.isServer && !@shared.googleMapsLoaded}}
-		<script>
-			function onDocumentReady (fn) {
-				if (document.readyState !== 'loading') {
-					fn();
-				} else {
-					document.addEventListener('DOMContentLoaded', fn);
-				}
-			}
-			/* eslint-disable */
-			function initMap () {
-				onDocumentReady(function () {
-					app.loaded = true;
-
-					if (app.router.route && app.router.route.view && app.router.route.view.initMap) {
-						app.router.route.view.initMap();
-					}
-				});
-			}
-		</script>
-		<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBTICLY5LgWNJ0Uly61Q1sNXz_1KZIECwg&amp;language=en&amp;callback=initMap&amp;libraries=marker&amp;loading=async" async></script>
-	{{/if}}
-
path: src/views/pages/network.html
user: login: mpourismaiel
body: Oh wow, sorry
created_at: 2025-03-30T13:57:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,29 +22,6 @@
 		{{/each}}
 	</style>
 
-	{{#if !@shared.isServer && !@shared.googleMapsLoaded}}
-		<script>
-			function onDocumentReady (fn) {
-				if (document.readyState !== 'loading') {
-					fn();
-				} else {
-					document.addEventListener('DOMContentLoaded', fn);
-				}
-			}
-			/* eslint-disable */
-			function initMap () {
-				onDocumentReady(function () {
-					app.loaded = true;
-
-					if (app.router.route && app.router.route.view && app.router.route.view.initMap) {
-						app.router.route.view.initMap();
-					}
-				});
-			}
-		</script>
-		<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBTICLY5LgWNJ0Uly61Q1sNXz_1KZIECwg&amp;language=en&amp;callback=initMap&amp;libraries=marker&amp;loading=async" async></script>
-	{{/if}}
-
path: src/views/pages/network.html
user: login: mpourismaiel
body: I think I removed it from jsdelivr and got an error because of multiple map scripts in the page and realized the mistake but forgot to revert it. Fixed and pushed.
created_at: 2025-03-30T14:01:15Z
author_association: CONTRIBUTOR

PR comments:
user: login: mpourismaiel
created_at: 2025-03-29T18:07:30Z
body: @MartinKolarik Regarding the index page I'm not sure what to do. I think map in index is different enough to warrant not using the component in there. What do you think?
author_association: CONTRIBUTOR
user: login: MartinKolarik
created_at: 2025-03-29T20:52:11Z
body: Yes, sorry I didn't get to this earlier, but you're probably right.
author_association: MEMBER
user: login: mpourismaiel
created_at: 2025-03-30T10:58:45Z
body: @MartinKolarik In that case this pr can be tested and merged
author_association: CONTRIBUTOR