const { registerBlockType } = wp.blocks; const { __ } = wp.i18n; const { useEffect, useState } = wp.element; const { PanelBody, SelectControl, TextControl, Button, RangeControl, ToggleControl, } = wp.components; const { InspectorControls, useBlockProps } = wp.blockEditor || wp.editor; import "./style.css"; // Marker row component for editor UI function MarkerRow({ marker, index, onChange, onRemove }) { const [lat, setLat] = useState(marker.lat); const [lng, setLng] = useState(marker.lng); const [title, setTitle] = useState(marker.title || ""); useEffect(() => { onChange(index, { lat: parseFloat(lat) || 0, lng: parseFloat(lng) || 0, title }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [lat, lng, title]); return (
); } registerBlockType("ai-builder/aibui-map", { title: __("Map (AI Builder)", "ai-builder"), icon: "location", category: "widgets", supports: { align: ["wide", "full"], html: false }, attributes: { provider: { type: "string", default: "osm" }, // 'osm' | 'google' apiKey: { type: "string", default: "" }, centerLat: { type: "number", default: 48.8566 }, centerLng: { type: "number", default: 2.3522 }, zoom: { type: "number", default: 12 }, height: { type: "string", default: "360px" }, showZoomControl: { type: "boolean", default: true }, draggable: { type: "boolean", default: true }, markers: { type: "array", default: [], // [{lat, lng, title}] }, }, edit: (props) => { const { attributes, setAttributes } = props; const { provider, apiKey, centerLat, centerLng, zoom, height, markers, showZoomControl, draggable } = attributes; const blockProps = useBlockProps({ className: "aibui-map" }); const setCenter = (patch) => setAttributes({ ...patch }); const addMarker = () => setAttributes({ markers: [...markers, { lat: centerLat, lng: centerLng, title: "" }] }); const updateMarker = (idx, patch) => setAttributes({ markers: markers.map((m, i) => (i === idx ? { ...m, ...patch } : m)) }); const removeMarker = (idx) => setAttributes({ markers: markers.filter((_, i) => i !== idx) }); useEffect(() => { const root = document.querySelector(`#block-${props.clientId}`) || null; if (!root) return; const canvas = root.querySelector('.aibui-map-canvas'); if (!canvas) return; // Clean up any previous map instance bound to this canvas if (canvas.__aibuiMap && typeof canvas.__aibuiMap.remove === 'function') { try { canvas.__aibuiMap.remove(); } catch (e) { } } canvas.__aibuiMap = null; canvas.innerHTML = ''; const initOSM = () => { if (window.L && window.L.map) { const map = window.L.map(canvas, { zoomControl: !!showZoomControl, dragging: !!draggable }).setView([centerLat, centerLng], zoom); window.L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map); (markers || []).forEach(m => { const mk = window.L.marker([parseFloat(m.lat) || 0, parseFloat(m.lng) || 0]).addTo(map); if (m.title) mk.bindPopup(m.title); }); canvas.__aibuiMap = map; return; } const css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'; document.head.appendChild(css); const s = document.createElement('script'); s.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'; s.onload = initOSM; document.head.appendChild(s); }; const initGoogle = () => { if (window.google && window.google.maps) { const map = new window.google.maps.Map(canvas, { center: { lat: centerLat, lng: centerLng }, zoom: zoom, zoomControl: !!showZoomControl, draggable: !!draggable, }); (markers || []).forEach(m => { const mk = new window.google.maps.Marker({ position: { lat: parseFloat(m.lat) || 0, lng: parseFloat(m.lng) || 0 }, map, title: m.title || '' }); if (m.title) { const iw = new window.google.maps.InfoWindow({ content: m.title }); mk.addListener('click', () => iw.open({ anchor: mk, map })); } }); canvas.__aibuiMap = map; return; } const cbName = '__aibui_gmaps_cb_' + Math.random().toString(36).slice(2); window[cbName] = () => { initGoogle(); delete window[cbName]; }; const s = document.createElement('script'); const base = 'https://maps.googleapis.com/maps/api/js'; const qs = (apiKey ? ('key=' + encodeURIComponent(apiKey) + '&') : '') + 'callback=' + cbName; s.src = base + '?' + qs; s.async = true; s.defer = true; document.head.appendChild(s); }; if (provider === 'google') initGoogle(); else initOSM(); }, [provider, apiKey, centerLat, centerLng, zoom, height, JSON.stringify(markers), showZoomControl, draggable, props.clientId]); return (
setAttributes({ provider: v })} /> {provider === "google" && ( setAttributes({ apiKey: v })} help={__("Required to load Google Maps.", "ai-builder")} /> )} setCenter({ centerLat: parseFloat(v) || 0 })} /> setCenter({ centerLng: parseFloat(v) || 0 })} /> setAttributes({ zoom: v })} /> setAttributes({ height: v })} help={__("Any CSS size (e.g. 360px, 50vh)", "ai-builder")} /> setAttributes({ showZoomControl: v })} /> setAttributes({ draggable: v })} /> {markers.map((m, i) => ( ))}
); }, save: (props) => { const { attributes } = props; const { provider, apiKey, centerLat, centerLng, zoom, height, markers, showZoomControl, draggable } = attributes; const blockProps = wp.blockEditor ? wp.blockEditor.useBlockProps.save({ className: "aibui-map" }) : {}; return (