console.log('Loaded Locations Map'); // DYNAMIC MAP FOR PARTICIPATING LOCATIONS // map container (an element must exist on the page, e.g. add this div to the module) const mapId = 'map-galleria-percorso'; const mapLinkId = mapId + '-link'; const defaultLogo = 'https://cdn.sitetheory.io/nest001/site/1572/461423/Galleria%20Diffusa%20-%20Logo-xs.png'; // Change for Language const appEl = document.getElementById('app'); const appClasses = appEl ? [...appEl.classList] : []; const lang = appClasses.find(c => c.startsWith('lang-'))?.split('lang-')[1] || 'it'; const text = { confirm: lang === 'en' ? 'confirm' : 'conferma', artist: lang === 'en' ? 'artist' : 'artista', website: 'website', instagram: 'instagram' }; const defaultMapConfig = { locationsUrl: null, startingLocation: null, disableArtistName: false, zoomMin: 13, // city wide zoomMax: 16 // for single marker }; const customMapConfig = window.mapConfig || {}; const mapConfig = Object.assign(defaultMapConfig, customMapConfig); // Fetch locations JSON function fetchLocations(url) { if (!url) { console.error('fetchLocations: no URL provided'); return Promise.reject(new Error('No locations URL')); } return fetch(url) .then(response => { if (!response.ok) { throw new Error('Failed to load locations JSON: ' + response.status); } return response.json(); // assumes the .txt is valid JSON }); } // This executes on callback AFTER Google Maps loads let map, mapCenter, mapZoom; function boundsAreTiny(b) { const ne = b.getNorthEast(); const sw = b.getSouthWest(); const latSpan = Math.abs(ne.lat() - sw.lat()); const lngSpan = Math.abs(ne.lng() - sw.lng()); return latSpan < 0.002 && lngSpan < 0.002; // ~ a couple hundred meters } function buildMap(el, locations) { map = new google.maps.Map(el, { // MapId from console.cloud.google.com/google/maps-apis mapId: '96d47af0d151e2742dd5dfc5', mapTypeId: 'roadmap', disableDefaultUI: false, zoomControl: true, mapTypeControl: true, streetViewControl: false, fullscreenControl: true, clickableIcons: false }); const bounds = new google.maps.LatLngBounds(); const info = new google.maps.InfoWindow(); const stops = []; // requires &libraries=marker in the loader const pin = new google.maps.marker.PinElement({ background: '#f7931e', // fill borderColor: '#8d4e00', // stroke // glyph: '+', // text/icon in the pin glyphColor: '#8d4e00', scale: 1 // size multiplier }); /* const img = document.createElement('img'); img.src = 'https://cdn.sitetheory.io/.../marker.svg'; // SVG or 2x PNG img.width = 36; // visual size img.height = 36; // bottom-center anchor img.style.transform = 'translate(-50%, -100%)'; img.style.position = 'absolute'; // optional styling img.style.filter = 'drop-shadow(0 2px 6px rgba(0,0,0,.35))'; */ locations.forEach(loc => { const marker = new google.maps.marker.AdvancedMarkerElement({ map, position: loc.position, title: loc.title, gmpClickable: true, content: pin.element.cloneNode(true) }); const locationImage = loc.image || loc.logo || defaultLogo; let content = `
Vetrina - Logo

${loc.title}

${loc.addressShort}

${loc.hours} (${text.confirm})

${loc.website ? `

${text.website}: ${loc.websiteShort}

` : ''} ${loc.instagram ? `

${text.instagram}: @${loc.instagramShort}

` : ''} ${!mapConfig.disableArtistName && loc.artist ? `

${text.artist}: ${loc.artist.name}

` : ''}
`; // Either Maps event system … marker.addListener('click', () => { info.setContent(content); info.open({ map, anchor: marker }); // anchor AdvancedMarker }); // …or DOM event (works with addEventListener): // marker.element.addEventListener('gmp-click', () => { ... }); bounds.extend(loc.position); // stops.push(loc.address); stops.push(`${loc.position.lat},${loc.position.lng}`); }); // map.fitBounds(bounds); // mapCenter = bounds.getCenter(); // mapZoom = map.getZoom(); // Make zoom fit to one or many in a rational way // Prefer fitBounds for 2+ points, but clamp for 1 point / tiny area. const PADDING = { top: 60, right: 60, bottom: 60, left: 60 }; if (locations.length === 1 || boundsAreTiny(bounds)) { // Single marker: center it and use a safe default zoom. map.setCenter(bounds.getCenter()); map.setZoom(Math.max(mapConfig.zoomMin, Math.min(mapConfig.zoomMax))); } else { map.fitBounds(bounds, PADDING); // After fitBounds, clamp zoom so it never goes too far in. google.maps.event.addListenerOnce(map, 'idle', () => { const z = map.getZoom(); if (z > mapConfig.zoomMax) map.setZoom(mapConfig.zoomMax); if (z < mapConfig.zoomMin) map.setZoom(mapConfig.zoomMin); }); } // Store these AFTER the final zoom is set (idle ensures fitBounds finished). google.maps.event.addListenerOnce(map, 'idle', () => { mapCenter = map.getCenter(); mapZoom = map.getZoom(); }); el.style.opacity = '1'; // Build navigation URL const destination = stops[stops.length - 1]; // NOTE: adding `optimize` adds a random location to the list // 'optimize:true|' + const waypoints = stops.slice(0, -1).map(encodeURIComponent).join('|'); const mapNavigationLink = 'https://www.google.com/maps/dir/?api=1' + '&travelmode=walking' + '&dir_action=navigate' + '&destination=' + encodeURIComponent(destination) + '&waypoints=' + waypoints; // We don't need a starting point, it will just use the current location because it creates problems //+ '&origin=' + encodeURIComponent(mapConfig.startingLocation) ; const mapLinkEl = document.getElementById(mapLinkId); if (mapLinkEl) { mapLinkEl.href = mapNavigationLink; mapLinkEl.target = '_blank'; mapLinkEl.rel = 'noopener'; } } function setupMap() { // Try immediately if (tryInit()) return; // Or wait until the target element is inserted const obs = new MutationObserver(() => { if (tryInit()) obs.disconnect(); }); obs.observe(document.documentElement, { childList: true, subtree: true }); function tryInit() { const el = document.getElementById(mapId); if (!el) return false; // prevent null if (el.clientHeight === 0) { // ensure a real size el.style.height = `calc(100dvh - 100px)`; } // Fetch locations dynamically, then build the map let url = mapConfig.locationsUrl; fetchLocations(url) .then(locations => { buildMap(el, locations); }) .catch(err => { console.error('Error loading locations:', err); }); return true; } // Keep the map layout correct on window resize window.addEventListener('resize', () => { if (!map) return; google.maps.event.trigger(map, 'resize'); if (mapCenter) map.setCenter(mapCenter); if (mapZoom) map.setZoom(mapZoom); }); } // Make the callback global and let the API call it window.setupMap = setupMap;