PluginProbe
Block Animations, Motion & Scroll Effects – Ghost Kit / 3.4.1
Block Animations, Motion & Scroll Effects – Ghost Kit v3.4.1
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.4.1, at gutenberg/blocks/google-maps/edit.js

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