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 (
);
},
});