PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.2.3
AI Builder – Generate pages, blocks, images & translate with AI v2.2.3
2.7.10 2.7.9 2.7.8 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.3.10 All 122 releases
ai-builder / assets / js / src / editor-blocks / map / index.js

index.js in AI Builder – Generate pages, blocks, images & translate with AI 2.2.3, at assets/js/src/editor-blocks/map/index.js

216 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 const { registerBlockType } = wp.blocks;
2 const { __ } = wp.i18n;
3 const { useEffect, useState } = wp.element;
4 const {
5 PanelBody,
6 SelectControl,
7 TextControl,
8 Button,
9 RangeControl,
10 ToggleControl,
11 } = wp.components;
12 const { InspectorControls, useBlockProps } = wp.blockEditor || wp.editor;
13 import "./style.css";
14
15 // Marker row component for editor UI
16 function MarkerRow({ marker, index, onChange, onRemove }) {
17 const [lat, setLat] = useState(marker.lat);
18 const [lng, setLng] = useState(marker.lng);
19 const [title, setTitle] = useState(marker.title || "");
20 useEffect(() => {
21 onChange(index, { lat: parseFloat(lat) || 0, lng: parseFloat(lng) || 0, title });
22 // eslint-disable-next-line react-hooks/exhaustive-deps
23 }, [lat, lng, title]);
24 return (
25 <div className="aibui-map-marker-row">
26 <div className="aibui-map-marker-fields">
27 <TextControl label={__("Lat", "ai-builder")} value={String(lat)} onChange={setLat} />
28 <TextControl label={__("Lng", "ai-builder")} value={String(lng)} onChange={setLng} />
29 <TextControl label={__("Title", "ai-builder")} value={title} onChange={setTitle} />
30 </div>
31 <Button isDestructive onClick={() => onRemove(index)}>
32 {__("Remove", "ai-builder")}
33 </Button>
34 </div>
35 );
36 }
37
38 registerBlockType("ai-builder/aibui-map", {
39 title: __("Map (AI Builder)", "ai-builder"),
40 icon: "location",
41 category: "widgets",
42 supports: { align: ["wide", "full"], html: false },
43 attributes: {
44 provider: { type: "string", default: "osm" }, // 'osm' | 'google'
45 apiKey: { type: "string", default: "" },
46 centerLat: { type: "number", default: 48.8566 },
47 centerLng: { type: "number", default: 2.3522 },
48 zoom: { type: "number", default: 12 },
49 height: { type: "string", default: "360px" },
50 showZoomControl: { type: "boolean", default: true },
51 draggable: { type: "boolean", default: true },
52 markers: {
53 type: "array",
54 default: [], // [{lat, lng, title}]
55 },
56 },
57 edit: (props) => {
58 const { attributes, setAttributes } = props;
59 const { provider, apiKey, centerLat, centerLng, zoom, height, markers, showZoomControl, draggable } = attributes;
60 const blockProps = useBlockProps({ className: "aibui-map" });
61
62 const setCenter = (patch) => setAttributes({ ...patch });
63 const addMarker = () => setAttributes({ markers: [...markers, { lat: centerLat, lng: centerLng, title: "" }] });
64 const updateMarker = (idx, patch) => setAttributes({ markers: markers.map((m, i) => (i === idx ? { ...m, ...patch } : m)) });
65 const removeMarker = (idx) => setAttributes({ markers: markers.filter((_, i) => i !== idx) });
66
67 useEffect(() => {
68 const root = document.querySelector(`#block-${props.clientId}`) || null;
69 if (!root) return;
70 const canvas = root.querySelector('.aibui-map-canvas');
71 if (!canvas) return;
72 // Clean up any previous map instance bound to this canvas
73 if (canvas.__aibuiMap && typeof canvas.__aibuiMap.remove === 'function') {
74 try { canvas.__aibuiMap.remove(); } catch (e) { }
75 }
76 canvas.__aibuiMap = null;
77 canvas.innerHTML = '';
78 const initOSM = () => {
79 if (window.L && window.L.map) {
80 const map = window.L.map(canvas, { zoomControl: !!showZoomControl, dragging: !!draggable }).setView([centerLat, centerLng], zoom);
81 window.L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '&copy; OpenStreetMap' }).addTo(map);
82 (markers || []).forEach(m => {
83 const mk = window.L.marker([parseFloat(m.lat) || 0, parseFloat(m.lng) || 0]).addTo(map);
84 if (m.title) mk.bindPopup(m.title);
85 });
86 canvas.__aibuiMap = map;
87 return;
88 }
89 const css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'; document.head.appendChild(css);
90 const s = document.createElement('script'); s.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'; s.onload = initOSM; document.head.appendChild(s);
91 };
92 const initGoogle = () => {
93 if (window.google && window.google.maps) {
94 const map = new window.google.maps.Map(canvas, {
95 center: { lat: centerLat, lng: centerLng },
96 zoom: zoom,
97 zoomControl: !!showZoomControl,
98 draggable: !!draggable,
99 });
100 (markers || []).forEach(m => {
101 const mk = new window.google.maps.Marker({ position: { lat: parseFloat(m.lat) || 0, lng: parseFloat(m.lng) || 0 }, map, title: m.title || '' });
102 if (m.title) {
103 const iw = new window.google.maps.InfoWindow({ content: m.title });
104 mk.addListener('click', () => iw.open({ anchor: mk, map }));
105 }
106 });
107 canvas.__aibuiMap = map;
108 return;
109 }
110 const cbName = '__aibui_gmaps_cb_' + Math.random().toString(36).slice(2);
111 window[cbName] = () => { initGoogle(); delete window[cbName]; };
112 const s = document.createElement('script');
113 const base = 'https://maps.googleapis.com/maps/api/js';
114 const qs = (apiKey ? ('key=' + encodeURIComponent(apiKey) + '&') : '') + 'callback=' + cbName;
115 s.src = base + '?' + qs; s.async = true; s.defer = true; document.head.appendChild(s);
116 };
117 if (provider === 'google') initGoogle(); else initOSM();
118 }, [provider, apiKey, centerLat, centerLng, zoom, height, JSON.stringify(markers), showZoomControl, draggable, props.clientId]);
119
120 return (
121 <div {...blockProps}>
122 <InspectorControls>
123 <PanelBody title={__("Map Provider", "ai-builder")} initialOpen>
124 <SelectControl
125 label={__("Provider", "ai-builder")}
126 value={provider}
127 options={[
128 { label: "OpenStreetMap", value: "osm" },
129 { label: "Google Maps", value: "google" },
130 ]}
131 onChange={(v) => setAttributes({ provider: v })}
132 />
133 {provider === "google" && (
134 <TextControl
135 label={__("Google Maps API key", "ai-builder")}
136 value={apiKey}
137 onChange={(v) => setAttributes({ apiKey: v })}
138 help={__("Required to load Google Maps.", "ai-builder")}
139 />
140 )}
141 </PanelBody>
142 <PanelBody title={__("Map Settings", "ai-builder")} initialOpen>
143 <TextControl
144 label={__("Center latitude", "ai-builder")}
145 value={String(centerLat)}
146 onChange={(v) => setCenter({ centerLat: parseFloat(v) || 0 })}
147 />
148 <TextControl
149 label={__("Center longitude", "ai-builder")}
150 value={String(centerLng)}
151 onChange={(v) => setCenter({ centerLng: parseFloat(v) || 0 })}
152 />
153 <RangeControl
154 label={__("Zoom", "ai-builder")}
155 min={1}
156 max={20}
157 value={zoom}
158 onChange={(v) => setAttributes({ zoom: v })}
159 />
160 <TextControl
161 label={__("Height", "ai-builder")}
162 value={height}
163 onChange={(v) => setAttributes({ height: v })}
164 help={__("Any CSS size (e.g. 360px, 50vh)", "ai-builder")}
165 />
166 <ToggleControl
167 label={__("Show zoom control", "ai-builder")}
168 checked={showZoomControl}
169 onChange={(v) => setAttributes({ showZoomControl: v })}
170 />
171 <ToggleControl
172 label={__("Draggable", "ai-builder")}
173 checked={draggable}
174 onChange={(v) => setAttributes({ draggable: v })}
175 />
176 </PanelBody>
177 <PanelBody title={__("Markers", "ai-builder")} initialOpen>
178 {markers.map((m, i) => (
179 <MarkerRow key={i} index={i} marker={m} onChange={updateMarker} onRemove={removeMarker} />
180 ))}
181 <Button isPrimary onClick={addMarker} style={{ marginTop: 8 }}>
182 {__("Add marker", "ai-builder")}
183 </Button>
184 </PanelBody>
185 </InspectorControls>
186
187 <div className="aibui-map-preview" style={{ height }}>
188 <div className="aibui-map-canvas" style={{ height: '100%', width: '100%' }} />
189 </div>
190 </div>
191 );
192 },
193 save: (props) => {
194 const { attributes } = props;
195 const { provider, apiKey, centerLat, centerLng, zoom, height, markers, showZoomControl, draggable } = attributes;
196 const blockProps = wp.blockEditor ? wp.blockEditor.useBlockProps.save({ className: "aibui-map" }) : {};
197 return (
198 <div
199 {...blockProps}
200 data-provider={provider}
201 data-api-key={provider === "google" ? (apiKey || "") : ""}
202 data-center-lat={centerLat}
203 data-center-lng={centerLng}
204 data-zoom={zoom}
205 data-zoom-control={showZoomControl ? "1" : "0"}
206 data-draggable={draggable ? "1" : "0"}
207 >
208 <div className="aibui-map-canvas" style={{ height }} />
209 <script type="application/json" className="aibui-map-markers" dangerouslySetInnerHTML={{ __html: JSON.stringify(markers || []) }} />
210 </div>
211 );
212 },
213 });
214
215
216