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 dataBounds = win.L.latLngBounds([]) | |
| const isBairroLayer = (layer) => { | |
| try { | |
| const nome = String(layer?.options?.name || layer?.layerName || '').toLowerCase() | |
| if (nome.includes('bairro')) return true | |
| const featureName = String(layer?.feature?.properties?.NOME || layer?.feature?.properties?.BAIRRO || '').toLowerCase() | |
| return featureName.length > 0 | |
| } catch { | |
| return false | |
| } | |
| } | |
| map.eachLayer((layer) => { | |
| try { | |
| if (layer instanceof win.L.TileLayer) return | |
| if (isBairroLayer(layer)) return | |
| if (layer instanceof win.L.CircleMarker || layer instanceof win.L.Marker) { | |
| const latlng = layer.getLatLng() | |
| if (latlng && Number.isFinite(latlng.lat) && Number.isFinite(latlng.lng)) { | |
| dataBounds.extend(latlng) | |
| } | |
| return | |
| } | |
| if (layer instanceof win.L.Rectangle) { | |
| const rectBounds = layer.getBounds() | |
| if (rectBounds && typeof rectBounds.isValid === 'function' && rectBounds.isValid()) { | |
| dataBounds.extend(rectBounds) | |
| } | |
| return | |
| } | |
| } catch { | |
| // no-op | |
| } | |
| }) | |
| if (dataBounds.isValid()) { | |
| const size = map.getSize ? map.getSize() : null | |
| const basePadding = size | |
| ? Math.max(34, Math.min(84, Math.round(Math.min(size.x, size.y) * 0.085))) | |
| : 48 | |
| map.fitBounds(dataBounds, { padding: [basePadding, basePadding], maxZoom: 18, 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} | |
| /> | |
| ) | |
| } | |