Spaces:
Running
Running
| import React, { useCallback, useEffect, useMemo, useRef } from 'react' | |
| function hashHtml(value) { | |
| let hash = 0 | |
| const step = Math.max(1, Math.floor(value.length / 64)) | |
| for (let i = 0; i < value.length; i += step) { | |
| hash = ((hash << 5) - hash) + value.charCodeAt(i) | |
| hash |= 0 | |
| } | |
| return `${value.length}-${Math.abs(hash)}` | |
| } | |
| export default function MapFrame({ html }) { | |
| const iframeRef = useRef(null) | |
| const timersRef = useRef([]) | |
| const frameKey = useMemo(() => hashHtml(html || ''), [html]) | |
| const clearTimers = useCallback(() => { | |
| timersRef.current.forEach((id) => window.clearTimeout(id)) | |
| timersRef.current = [] | |
| }, []) | |
| const recenterFromLayers = useCallback(() => { | |
| const iframe = iframeRef.current | |
| if (!iframe) return | |
| try { | |
| const win = iframe.contentWindow | |
| const doc = iframe.contentDocument || win?.document | |
| if (!win || !doc || !win.L) return | |
| const maps = Object.values(win).filter( | |
| (item) => item | |
| && typeof item.fitBounds === 'function' | |
| && typeof item.eachLayer === 'function' | |
| && typeof item.invalidateSize === 'function', | |
| ) | |
| const map = maps[0] | |
| if (!map) return | |
| map.invalidateSize(true) | |
| const bounds = win.L.latLngBounds([]) | |
| map.eachLayer((layer) => { | |
| try { | |
| if (typeof layer.getLatLng === 'function') { | |
| const latlng = layer.getLatLng() | |
| if (latlng && Number.isFinite(latlng.lat) && Number.isFinite(latlng.lng)) { | |
| bounds.extend(latlng) | |
| } | |
| return | |
| } | |
| if (typeof layer.getBounds === 'function') { | |
| const layerBounds = layer.getBounds() | |
| if (layerBounds && typeof layerBounds.isValid === 'function' && layerBounds.isValid()) { | |
| bounds.extend(layerBounds) | |
| } | |
| } | |
| } catch { | |
| // no-op | |
| } | |
| }) | |
| if (bounds.isValid()) { | |
| map.fitBounds(bounds, { padding: [20, 20], maxZoom: 17, animate: false }) | |
| } | |
| } catch { | |
| // no-op | |
| } | |
| }, []) | |
| const scheduleRecenter = useCallback(() => { | |
| clearTimers() | |
| ;[40, 180, 520, 1100].forEach((delay) => { | |
| const timerId = window.setTimeout(() => { | |
| recenterFromLayers() | |
| }, delay) | |
| timersRef.current.push(timerId) | |
| }) | |
| }, [clearTimers, recenterFromLayers]) | |
| useEffect(() => { | |
| return () => { | |
| clearTimers() | |
| } | |
| }, [clearTimers]) | |
| if (!html) { | |
| return <div className="empty-box">Mapa indisponivel.</div> | |
| } | |
| return ( | |
| <iframe | |
| key={frameKey} | |
| ref={iframeRef} | |
| title="mapa" | |
| className="map-frame" | |
| srcDoc={html} | |
| sandbox="allow-scripts allow-same-origin allow-popups" | |
| onLoad={scheduleRecenter} | |
| /> | |
| ) | |
| } | |