PluginProbe
Block Animations, Motion & Scroll Effects – Ghost Kit / 3.3.3
Block Animations, Motion & Scroll Effects – Ghost Kit v3.3.3
3.7.2 3.7.1 3.7.0 3.6.1 trunk 1.6.3 2.25.0 3.3.0 3.3.1 3.3.2 3.3.3 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.5.0 3.5.1 3.6.0
ghostkit / gutenberg / blocks / google-maps / edit.js

edit.js in Block Animations, Motion & Scroll Effects – Ghost Kit 3.3.3, at gutenberg/blocks/google-maps/edit.js

828 lines 18.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import classnames from 'classnames/dedupe';
2 import { debounce } from 'throttle-debounce';
3
4 import apiFetch from '@wordpress/api-fetch';
5 import {
6 BlockControls,
7 InspectorControls,
8 MediaUpload,
9 RichText,
10 useBlockProps,
11 } from '@wordpress/block-editor';
12 import {
13 BaseControl,
14 Button,
15 Dropdown,
16 ExternalLink,
17 PanelBody,
18 ResizableBox,
19 TextareaControl,
20 TextControl,
21 ToggleControl,
22 ToolbarButton,
23 ToolbarGroup,
24 } from '@wordpress/components';
25 import { useEffect, useState } from '@wordpress/element';
26 import { applyFilters } from '@wordpress/hooks';
27 import { __ } from '@wordpress/i18n';
28
29 import DropdownPicker from '../../components/dropdown-picker';
30 import ImagePicker from '../../components/image-picker';
31 import RangeControl from '../../components/range-control';
32 import { maybeDecode, maybeEncode } from '../../utils/encode-decode';
33 import getIcon from '../../utils/get-icon';
34 import { ReactComponent as IconMarker } from './icons/marker.svg';
35 import MapBlock from './map-block';
36 import styles from './map-styles';
37 import SearchBox from './search-box';
38
39 const { GHOSTKIT } = window;
40
41 const mapsUrl = `${GHOSTKIT.googleMapsAPIUrl}&libraries=geometry,drawing,places`;
42 let geocoder = false;
43
44 const MIN_MARKER_WIDTH = 10;
45 const MAX_MARKER_WIDTH = 100;
46
47 function getStyles(string) {
48 let result = [];
49
50 try {
51 result = JSON.parse(maybeDecode(string));
52 } catch (e) {
53 return [];
54 }
55
56 return result;
57 }
58
59 function MarkerSettings(props) {
60 const {
61 googleMapURL,
62 title,
63 address,
64 addresses,
65 lat,
66 lng,
67 iconImageURL,
68 iconImageCustomWidth,
69 infoWindowText,
70 onChange,
71 } = props;
72
73 const previewIcon = iconImageURL ? (
74 <img src={iconImageURL} width={iconImageCustomWidth} alt="" />
75 ) : (
76 <img
77 src="https://maps.gstatic.com/mapfiles/api-3/images/spotlight-poi3_hdpi.png"
78 width="27"
79 alt=""
80 />
81 );
82
83 return (
84 <>
85 <TextControl
86 label={__('Title', 'ghostkit')}
87 value={title}
88 onChange={(value) => {
89 onChange({ title: value });
90 }}
91 />
92 <SearchBox
93 googleMapURL={googleMapURL}
94 label={__('Address', 'ghostkit')}
95 value={address || addresses[lat + lng] || ''}
96 onChange={(value) => {
97 if (value && value[0]) {
98 onChange({
99 address: value[0].formatted_address,
100 lat: value[0].geometry.location.lat(),
101 lng: value[0].geometry.location.lng(),
102 });
103 }
104 }}
105 className="ghostkit-google-maps-search-box"
106 />
107 <div className="ghostkit-google-maps-marker-options-content-icon">
108 {previewIcon}
109 <MediaUpload
110 onSelect={(media) => {
111 if (!media || !media.url) {
112 return;
113 }
114
115 onChange({
116 iconImageID: media.id,
117 iconImageURL: media.url,
118 iconImageCustomWidth: Math.min(
119 MAX_MARKER_WIDTH,
120 media.width
121 ),
122 iconImageWidth: media.width,
123 iconImageHeight: media.height,
124 });
125 }}
126 allowedTypes={['image']}
127 value={iconImageURL || false}
128 render={({ open }) => (
129 <Button variant="secondary" onClick={open}>
130 {__('Change Icon', 'ghostkit')}
131 </Button>
132 )}
133 />
134 </div>
135 {iconImageCustomWidth ? (
136 <div>
137 <Button
138 className="ghostkit-google-maps-icon-reset"
139 isSmall
140 onClick={() => {
141 onChange({
142 iconImageID: '',
143 iconImageURL: '',
144 iconImageCustomWidth: '',
145 iconImageWidth: '',
146 iconImageHeight: '',
147 });
148 }}
149 >
150 {__('Reset Icon to Default', 'ghostkit')}
151 </Button>
152 </div>
153 ) : null}
154 {iconImageCustomWidth ? (
155 <div>
156 <RangeControl
157 label={__('Marker Width', 'ghostkit')}
158 value={iconImageCustomWidth}
159 onChange={(val) =>
160 onChange({ iconImageCustomWidth: val })
161 }
162 min={MIN_MARKER_WIDTH}
163 max={MAX_MARKER_WIDTH}
164 />
165 </div>
166 ) : null}
167 <BaseControl
168 label={__('Info Window Text', 'ghostkit')}
169 className="ghostkit-google-maps-marker-options-content-info-window-text"
170 id="ghostkit-google-maps-marker-content-info-window-text"
171 >
172 <RichText
173 value={infoWindowText}
174 multiline
175 placeholder={__('Write text…', 'ghostkit')}
176 onChange={(val) => {
177 onChange({ infoWindowText: val });
178 }}
179 onRemove={() => {
180 onChange({ infoWindowText: '' });
181 }}
182 />
183 </BaseControl>
184 </>
185 );
186 }
187
188 /**
189 * Block Edit Class.
190 *
191 * @param props
192 */
193 export default function BlockEdit(props) {
194 const { attributes, setAttributes, isSelected, toggleSelection } = props;
195
196 let { className = '' } = props;
197
198 const {
199 height,
200 zoom,
201 lat,
202 lng,
203 showZoomButtons,
204 showMapTypeButtons,
205 showStreetViewButton,
206 showFullscreenButton,
207 optionScrollWheel,
208 optionDraggable,
209 gestureHandling,
210 markers,
211 fullHeight,
212 style,
213 styleCustom,
214 } = attributes;
215
216 const [mapID, setMapID] = useState(attributes.apiKey);
217 const [apiKey, setApiKey] = useState(GHOSTKIT.googleMapsAPIKey);
218 const [addresses, setAddresses] = useState({});
219
220 function updateMarkerAddress(latLng, address) {
221 if (markers && markers.length > 0) {
222 markers.forEach((marker, index) => {
223 if (
224 !marker.address &&
225 latLng.lat === marker.lat &&
226 latLng.lng === marker.lng
227 ) {
228 markers[index].address = address;
229 addresses[latLng.lat + latLng.lng] = address;
230 }
231 });
232
233 setAddresses(addresses);
234 }
235 }
236
237 // Updated.
238 useEffect(() => {
239 // find Address by lat and lng.
240 if (
241 geocoder ||
242 (window.google && window.google.maps && window.google.maps.Geocoder)
243 ) {
244 geocoder = new window.google.maps.Geocoder();
245 }
246
247 if (geocoder) {
248 if (markers && markers.length > 0) {
249 markers.forEach((marker) => {
250 if (!marker.address) {
251 if (addresses[marker.lat + marker.lng]) {
252 updateMarkerAddress(
253 {
254 lat: marker.lat,
255 lng: marker.lng,
256 },
257 addresses[marker.lat + marker.lng]
258 );
259 } else {
260 geocoder.geocode(
261 {
262 location: {
263 lat: marker.lat,
264 lng: marker.lng,
265 },
266 },
267 (results, status) => {
268 if (status === 'OK' && results.length) {
269 updateMarkerAddress(
270 {
271 lat: marker.lat,
272 lng: marker.lng,
273 },
274 results[0].formatted_address
275 );
276 }
277 }
278 );
279 }
280 }
281 });
282 }
283 }
284 });
285
286 const onChangeAPIKey = debounce(600, (newKey) => {
287 GHOSTKIT.googleMapsAPIKey = newKey;
288
289 setMapID(newKey);
290 });
291
292 const saveAPIKey = debounce(3000, (newKey) => {
293 apiFetch({
294 path: '/ghostkit/v1/update_google_maps_api_key',
295 method: 'POST',
296 data: {
297 key: newKey,
298 },
299 });
300 });
301
302 function getStylesPicker() {
303 return (
304 <>
305 <ImagePicker
306 value={maybeDecode(style)}
307 options={styles}
308 onChange={(value) => {
309 let customString = styleCustom;
310
311 if (value === 'default') {
312 customString = '';
313 } else if (value !== 'custom') {
314 styles.forEach((styleData) => {
315 if (value === styleData.value) {
316 customString = JSON.stringify(
317 styleData.json
318 );
319 }
320 });
321 }
322
323 setAttributes({
324 style: value,
325 styleCustom: maybeEncode(customString),
326 });
327 }}
328 />
329 {style === 'custom' ? (
330 <>
331 <TextareaControl
332 placeholder={__('Enter Style JSON', 'ghostkit')}
333 value={maybeDecode(styleCustom)}
334 onChange={(value) =>
335 setAttributes({
336 styleCustom: maybeEncode(value),
337 })
338 }
339 />
340 <p>
341 <em>
342 {__(
343 'You can use custom styles presets from the',
344 'ghostkit'
345 )}{' '}
346 <ExternalLink href="https://snazzymaps.com/">
347 {__('Snazzy Maps', 'ghostkit')}
348 </ExternalLink>
349 .
350 </em>
351 </p>
352 </>
353 ) : null}
354 </>
355 );
356 }
357
358 function getMapPreview() {
359 return (
360 <MapBlock
361 key={mapID + markers.length}
362 apiUrl={`${mapsUrl}&key=${maybeEncode(apiKey)}`}
363 markers={markers}
364 onChangeMarkers={(newMarkers) => {
365 setAttributes({ markers: newMarkers });
366 }}
367 options={{
368 styles: styleCustom ? getStyles(styleCustom) : [],
369 zoom,
370 center: { lat, lng },
371 zoomControl: showZoomButtons,
372 zoomControlOpt: {
373 style: 'DEFAULT',
374 position: 'RIGHT_BOTTOM',
375 },
376 mapTypeControl: showMapTypeButtons,
377 streetViewControl: showStreetViewButton,
378 fullscreenControl: showFullscreenButton,
379 gestureHandling: 'cooperative',
380 scrollwheel: false,
381 draggable: optionDraggable,
382 onZoomChange: debounce(500, (val) =>
383 setAttributes({ zoom: val })
384 ),
385 onCenterChange: debounce(500, (val) =>
386 setAttributes({ lat: val.lat(), lng: val.lng() })
387 ),
388 }}
389 />
390 );
391 }
392
393 className = classnames('ghostkit-google-maps', className);
394
395 // add full height classname.
396 if (fullHeight) {
397 className = classnames(className, 'ghostkit-google-maps-fullheight');
398 }
399
400 className = applyFilters('ghostkit.editor.className', className, props);
401
402 const blockProps = useBlockProps({ className });
403
404 return (
405 <>
406 {apiKey ? (
407 <BlockControls>
408 <ToolbarGroup>
409 <ToolbarButton
410 icon={getIcon('icon-fullheight')}
411 title={__('Full Height', 'ghostkit')}
412 onClick={() =>
413 setAttributes({ fullHeight: !fullHeight })
414 }
415 isActive={fullHeight}
416 />
417 </ToolbarGroup>
418 <ToolbarGroup>
419 <ToolbarButton
420 icon={getIcon('icon-marker')}
421 title={__('Add Marker', 'ghostkit')}
422 onClick={() => {
423 setAttributes({
424 markers: [
425 ...markers,
426 ...[
427 {
428 lat,
429 lng,
430 },
431 ],
432 ],
433 });
434 }}
435 />
436 <Dropdown
437 renderToggle={({ onToggle }) => (
438 <Button
439 label={__('Style', 'ghostkit')}
440 icon={getIcon('icon-map')}
441 className="components-toolbar__control"
442 onClick={onToggle}
443 />
444 )}
445 renderContent={() => (
446 <div
447 style={{
448 minWidth: 260,
449 }}
450 >
451 {getStylesPicker()}
452 </div>
453 )}
454 />
455 </ToolbarGroup>
456 </BlockControls>
457 ) : null}
458 <InspectorControls group="styles">
459 <PanelBody title={__('Styles', 'ghostkit')}>
460 {getStylesPicker()}
461 </PanelBody>
462 </InspectorControls>
463 <InspectorControls>
464 {apiKey ? (
465 <>
466 <PanelBody>
467 <RangeControl
468 label={
469 fullHeight
470 ? __('Minimal Height', 'ghostkit')
471 : __('Height', 'ghostkit')
472 }
473 value={height}
474 onChange={(value) =>
475 setAttributes({ height: value })
476 }
477 min={100}
478 max={800}
479 allowCustomMin
480 allowCustomMax
481 />
482 <RangeControl
483 label={__('Zoom', 'ghostkit')}
484 value={zoom}
485 onChange={(value) =>
486 setAttributes({ zoom: value })
487 }
488 min={1}
489 max={18}
490 allowCustomMax
491 />
492 </PanelBody>
493 <PanelBody title={__('Markers', 'ghostkit')}>
494 {markers && markers.length > 0 ? (
495 <ul className="ghostkit-google-maps-markers">
496 {markers.map((marker, index) => (
497 <DropdownPicker
498 key={index}
499 label={
500 marker.title ||
501 __('Marker', 'ghostkit')
502 }
503 contentClassName="ghostkit-component-google-maps-markers"
504 >
505 <MarkerSettings
506 index={index}
507 googleMapURL={`${mapsUrl}&key=${maybeEncode(apiKey)}`}
508 address={marker.address}
509 addresses={addresses}
510 lat={marker.lat}
511 lng={marker.lng}
512 title={marker.title}
513 iconImageURL={
514 marker.iconImageURL
515 }
516 iconImageCustomWidth={
517 marker.iconImageCustomWidth
518 }
519 infoWindowText={
520 marker.infoWindowText
521 }
522 onChange={(newAttrs) => {
523 const newMarkers =
524 Object.assign(
525 [],
526 markers
527 );
528
529 newMarkers[index] = {
530 ...newMarkers[index],
531 ...newAttrs,
532 };
533
534 setAttributes({
535 markers: newMarkers,
536 });
537 }}
538 />
539 <Button
540 onClick={() => {
541 const newMarkers =
542 Object.assign(
543 [],
544 markers
545 );
546
547 newMarkers.splice(index, 1);
548
549 setAttributes({
550 markers: newMarkers,
551 });
552 }}
553 className="ghostkit-google-maps-marker-remove"
554 >
555 {__(
556 'Remove Marker',
557 'ghostkit'
558 )}
559 </Button>
560 </DropdownPicker>
561 ))}
562 </ul>
563 ) : null}
564 <Button
565 isSecondary
566 onClick={() => {
567 setAttributes({
568 markers: [
569 ...markers,
570 ...[
571 {
572 lat,
573 lng,
574 },
575 ],
576 ],
577 });
578 }}
579 >
580 {__('+ Add Marker', 'ghostkit')}
581 </Button>
582 </PanelBody>
583 <PanelBody title={__('Options', 'ghostkit')}>
584 <ToggleControl
585 label={__('Zoom Buttons', 'ghostkit')}
586 checked={!!showZoomButtons}
587 onChange={(val) =>
588 setAttributes({ showZoomButtons: val })
589 }
590 />
591 <ToggleControl
592 label={__('Map Type Buttons', 'ghostkit')}
593 checked={!!showMapTypeButtons}
594 onChange={(val) =>
595 setAttributes({ showMapTypeButtons: val })
596 }
597 />
598 <ToggleControl
599 label={__('Street View Button', 'ghostkit')}
600 checked={!!showStreetViewButton}
601 onChange={(val) =>
602 setAttributes({ showStreetViewButton: val })
603 }
604 />
605 <ToggleControl
606 label={__('Fullscreen Button', 'ghostkit')}
607 checked={!!showFullscreenButton}
608 onChange={(val) =>
609 setAttributes({ showFullscreenButton: val })
610 }
611 />
612 <ToggleControl
613 label={__('Scroll Wheel', 'ghostkit')}
614 checked={!!optionScrollWheel}
615 onChange={(val) =>
616 setAttributes({ optionScrollWheel: val })
617 }
618 />
619 <ToggleControl
620 label={__('Draggable', 'ghostkit')}
621 checked={!!optionDraggable}
622 onChange={(val) =>
623 setAttributes({ optionDraggable: val })
624 }
625 />
626 {optionScrollWheel || optionDraggable ? (
627 <ToggleControl
628 label={(() => {
629 if (
630 optionScrollWheel &&
631 optionDraggable
632 ) {
633 return __(
634 'Better Scroll & Draggable',
635 'ghostkit'
636 );
637 }
638 if (optionScrollWheel) {
639 return __(
640 'Better Scroll',
641 'ghostkit'
642 );
643 }
644 if (optionDraggable) {
645 return __(
646 'Better Draggable',
647 'ghostkit'
648 );
649 }
650 return '';
651 })()}
652 help={(() => {
653 if (
654 optionScrollWheel &&
655 optionDraggable
656 ) {
657 return __(
658 'Scroll with pressed Ctrl or ⌘ key to zoom. Draggable with two fingers.',
659 'ghostkit'
660 );
661 }
662 if (optionScrollWheel) {
663 return __(
664 'Scroll with pressed Ctrl or ⌘ key to zoom.',
665 'ghostkit'
666 );
667 }
668 if (optionDraggable) {
669 return __(
670 'Draggable with two fingers.',
671 'ghostkit'
672 );
673 }
674 return '';
675 })()}
676 checked={gestureHandling === 'cooperative'}
677 onChange={() => {
678 setAttributes({
679 gestureHandling:
680 gestureHandling === 'greedy'
681 ? 'cooperative'
682 : 'greedy',
683 });
684 }}
685 />
686 ) : null}
687 </PanelBody>
688 </>
689 ) : null}
690 <PanelBody
691 title={__('API Key', 'ghostkit')}
692 initialOpen={!apiKey}
693 >
694 <TextControl
695 placeholder={__('Enter API Key', 'ghostkit')}
696 value={apiKey}
697 onChange={(value) => {
698 setApiKey(value);
699 onChangeAPIKey(value);
700 saveAPIKey(value);
701 }}
702 />
703 <p>
704 <em>
705 {__(
706 'A valid API key is required to use Google Maps. How to get API key',
707 'ghostkit'
708 )}{' '}
709 <ExternalLink href="https://developers.google.com/maps/documentation/javascript/get-api-key">
710 {__('read here', 'ghostkit')}
711 </ExternalLink>
712 .
713 </em>
714 </p>
715 <p>
716 <em>
717 {__(
718 'This key will be used in all Google Maps blocks on your site.',
719 'ghostkit'
720 )}
721 </em>
722 </p>
723 </PanelBody>
724 </InspectorControls>
725
726 <div {...blockProps}>
727 {apiKey ? (
728 <>
729 {fullHeight ? (
730 getMapPreview()
731 ) : (
732 <ResizableBox
733 className={classnames({
734 'is-selected': isSelected,
735 })}
736 size={{
737 width: '100%',
738 height,
739 }}
740 style={{ minHeight: height }}
741 minHeight="100"
742 enable={{ bottom: true }}
743 onResizeStart={() => {
744 toggleSelection(false);
745 }}
746 onResizeStop={(
747 event,
748 direction,
749 elt,
750 delta
751 ) => {
752 setAttributes({
753 height: parseInt(
754 height + delta.height,
755 10
756 ),
757 });
758 toggleSelection(true);
759 }}
760 >
761 {getMapPreview()}
762 </ResizableBox>
763 )}
764 {isSelected ? (
765 <div className="ghostkit-google-maps-search">
766 <SearchBox
767 googleMapURL={`${mapsUrl}&key=${maybeEncode(
768 apiKey
769 )}`}
770 label={__('Center Map', 'ghostkit')}
771 placeholder={__(
772 'Enter search query',
773 'ghostkit'
774 )}
775 onChange={(value) => {
776 if (value && value[0]) {
777 setAttributes({
778 lat: value[0].geometry.location.lat(),
779 lng: value[0].geometry.location.lng(),
780 });
781 }
782 }}
783 className="ghostkit-google-maps-search-box"
784 />
785 <div className="ghostkit-google-maps-search-note">
786 <p>
787 <small>
788 {__(
789 'You can also drag the map to change the center coordinates.',
790 'ghostkit'
791 )}
792 </small>
793 </p>
794 </div>
795 </div>
796 ) : null}
797 </>
798 ) : (
799 <div
800 className="ghostkit-google-maps-placeholder"
801 style={{ minHeight: height }}
802 >
803 <IconMarker />
804 <div className="ghostkit-google-maps-placeholder-key">
805 <div>
806 <strong>
807 {__(
808 'Google Maps API Key Required',
809 'ghostkit'
810 )}
811 </strong>
812 </div>
813 <div>
814 <small>
815 {__(
816 'Add an API key in block settings.',
817 'ghostkit'
818 )}
819 </small>
820 </div>
821 </div>
822 </div>
823 )}
824 </div>
825 </>
826 );
827 }
828