Skip to content

Commit

Permalink
Google Map: Add block
Browse files Browse the repository at this point in the history
  • Loading branch information
iandunn committed Aug 28, 2023
1 parent f2e8309 commit 584a32b
Show file tree
Hide file tree
Showing 17 changed files with 2,154 additions and 1,308 deletions.
35 changes: 35 additions & 0 deletions mu-plugins/blocks/google-map/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Google Map

Displays a Google Map with markers for each event. Markers will be clustered for performance and UX.

Currently only supports programmatic usage in block theme templates etc. There's no UI available for adding markers.

This doesn't currently utilize all the abilities of the `google-map-react` lib, but we can expand it over time.


## Usage

Place something like this in a pattern:

```php
<?php

$map_options = array(
'id' => 'all-upcoming-events',
'markers' => get_all_upcoming_events(),
);

?>

<!-- wp:wporg/google-map <?php echo wp_json_encode( $map_options ); ?> /-->
```

`markers` should be an array of objects with the fields in the example below. The `timestamp` field should be a true Unix timestamp, meaning it assumes UTC. The `wporg_events` database table is one potential source for the events, but you can pass anything.

```php
array(
0 => (object) array( ‘id’ => ‘72190236’, ‘type’ => ‘meetup’, ‘title’ => ‘WordPress For Beginners – WPSyd’, ‘url’ => ‘https://www.meetup.com/wordpress-sydney/events/294365830’, ‘meetup’ => ‘WordPress Sydney’, ‘location’ => ‘Sydney, Australia’, ‘latitude’ => ‘-33.865295’, ‘longitude’ => ‘151.2053’, ‘tz_offset’ => ‘36000’, ‘timestamp’ => 1693209600 ),
1 => (object) array( ‘id’ => ‘72190237’, ‘type’ => ‘meetup’, ‘title’ => ‘WordPress Help Desk’, ‘url’ => ‘https://www.meetup.com/wordpress-gwinnett/events/292032515’, ‘meetup’ => ‘WordPress Gwinnett’, ‘location’ => ‘online’, ‘latitude’ => ‘33.94’, ‘longitude’ => ‘-83.96’, ‘tz_offset’ => ‘-14400’, ‘timestamp’ => 1693260000 ),
2 => (object) array( ‘id’ => ‘72190235’, ‘type’ => ‘meetup’, ‘title’ => ‘WordPress Warwickshire Virtual Meetup ‘, ‘url’ => ‘https://www.meetup.com/wordpress-warwickshire-meetup/events/295325208’, ‘meetup’ => ‘WordPress Warwickshire Meetup’, ‘location’ => ‘online’, ‘latitude’ => ‘52.52’, ‘longitude’ => ‘-1.47’, ‘tz_offset’ => ‘3600’, ‘timestamp’ => 1693245600 ),
)
```
4 changes: 4 additions & 0 deletions mu-plugins/blocks/google-map/images/cluster-background.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions mu-plugins/blocks/google-map/images/map-marker.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 77 additions & 0 deletions mu-plugins/blocks/google-map/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

/**
* Block Name: WordPress.org Google Map
* Description: Renders a Google Map in a block template (no editor UI).
*/

namespace WordPressdotorg\MU_Plugins\Google_Map;

add_action( 'init', __NAMESPACE__ . '\init' );


/**
* Registers the block from `block.json`.
*/
function init() {
register_block_type(
__DIR__ . '/build',
array(
'render_callback' => __NAMESPACE__ . '\render',
)
);
}

/**
* Render the block content.
*
* @param array $attributes Block attributes.
* @param string $content Block default content.
* @param WP_Block $block Block instance.
*
* @return string Returns the block markup.
*/
function render( $attributes, $content, $block ) {
$attributes['id'] = 'wp-block-wporg-google-map-' . $attributes['id'];

if ( empty( $attributes['apiKey'] ) ) {
$default_key = 'production' === wp_get_environment_type() ? 'WORDCAMP_PROD_GOOGLE_MAPS_API_KEY' : 'WORDCAMP_DEV_GOOGLE_MAPS_API_KEY';

if ( defined( $default_key ) ) {
$attributes['apiKey'] = constant( $default_key );
}
}

$attributes['icon'] = array(
'markerUrl' => plugins_url( 'images/map-marker.svg', __FILE__ ),
'markerAnchorXOffset' => 34,
'markerHeight' => 68,
'markerWidth' => 68,
'clusterUrl' => plugins_url( 'images/cluster-background.svg', __FILE__ ),
'clusterWidth' => 38,
'clusterHeight' => 38,
);

wp_add_inline_script(
$block->block_type->view_script_handles[0],
sprintf(
'const wporgGoogleMap = %s;',
wp_json_encode( $attributes )
),
'before'
);

$wrapper_attributes = get_block_wrapper_attributes( array( 'id' => $attributes['id'] ) );

ob_start();

?>

<div <?php echo wp_kses_data( $wrapper_attributes ); ?>>
Loading...
</div>

<?php

return ob_get_clean();
}
23 changes: 23 additions & 0 deletions mu-plugins/blocks/google-map/postcss/style.pcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* These are foundational styles that provide a reasonable default for any theme.
*/

.wp-block-wporg-google-map {
position: relative;
width: 100%;
max-width: 100%;
background-color: #aadaff;

/*
* This is a bit too opinionated, but it's required to prevent the map from collapsing. The theme can override it when needed.
* @link https://developers.google.com/maps/documentation/javascript/overview#Map_DOM_Elements
*/
height: clamp(200px, 50vh, 90vh);

@media screen and ( max-width: 385px ) {
width: 100vw;

/* Make sure there's enough room to scroll past it using mobile gestures. */
max-height: 90vh;
}
}
30 changes: 30 additions & 0 deletions mu-plugins/blocks/google-map/src/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "wporg/google-map",
"title": "WordPress.org Google Map",
"icon": "admin-site-alt",
"category": "layout",
"description": "Displays a Google Map with markers and information windows.",
"textdomain": "wporg",
"attributes": {
"id": {
"type": "string",
"default": ""
},
"apiKey": {
"type": "string",
"default": ""
},
"markers": {
"type": "array",
"default": []
}
},
"supports": {
"inserter": false
},
"editorScript": "file:./index.js",
"viewScript": "file:./front.js",
"style": "file:./style.css"
}
63 changes: 63 additions & 0 deletions mu-plugins/blocks/google-map/src/components/map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* External dependencies
*/
import GoogleMapReact from 'google-map-react';

/**
* WordPress dependencies
*/
import { useCallback, useState } from '@wordpress/element';
import { Spinner } from '@wordpress/components';

/**
* Internal dependencies
*/
import { mapStyles } from '../utilities/map-styles';
import { createClusteredMarkers } from '../utilities/google-maps-api';

/**
* Render a Google Map with info windows for the given markers.
*
* @see https://github.com/google-map-react/google-map-react#use-google-maps-api
*
* @param {Object} props
* @param {string} props.apiKey
* @param {Array} props.markers
* @param {Object} props.icon
*
* @return {JSX.Element}
*/
export default function Map( { apiKey, markers, icon } ) {
const [ loaded, setLoaded ] = useState( false );

const options = {
zoomControl: true,
mapTypeControl: false,
streetViewControl: false,
styles: mapStyles,
};

const mapLoaded = useCallback( ( { map, maps } ) => {
createClusteredMarkers( map, maps, markers, icon );
setLoaded( true );
}, [] );

return (
// Container height must be set explicitly.
<>
{ ! loaded && <Spinner /> }

<GoogleMapReact
defaultZoom={ 1 }
defaultCenter={ {
lat: 30.0,
lng: 10.0,
} }
bootstrapURLKeys={ { key: apiKey } }
yesIWantToUseGoogleMapApiInternals
onGoogleApiLoaded={ mapLoaded }
options={ options }
/>
</>
);
}
98 changes: 98 additions & 0 deletions mu-plugins/blocks/google-map/src/components/marker-content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Internal dependencies
*/
import { getEventDateTime } from '../utilities/date-time';

/**
* Render the content for a map marker.
*
* @param {Object} props
*
* @return {JSX.Element}
*/
export default function MarkerContent( props ) {
const { type } = props;

switch ( type ) {
case 'wordcamp':
return <WordCampMarker { ...props } />;

case 'meetup':
return <MeetupMarker { ...props } />;

default:
throw 'Component not defined for marker type: ' + type;
}
}

/**
* Render the content for a WordCamp event.
*
* @param {Object} props
* @param {string} props.id
* @param {string} props.title
* @param {string} props.url
* @param {string} props.timestamp
* @param {string} props.location
*
* @return {JSX.Element}
*/
function WordCampMarker( { id, title, url, timestamp, location } ) {
return (
<div id={ 'wporg-map-marker__id-' + id } className="wporg-map-marker">
<h3 className="wporg-map-marker__title">{ title }</h3>

<p className="wporg-map-marker__url">
<a href={ url }>{ title }</a>
</p>

<p className="wporg-map-marker__location">{ formatLocation( location ) }</p>

<p className="wporg-map-marker__date-time">{ getEventDateTime( timestamp ) }</p>
</div>
);
}

/**
* Render the content for a meetup event.
*
* @param {Object} props
* @param {string} props.id
* @param {string} props.title
* @param {string} props.url
* @param {string} props.meetup
* @param {string} props.timestamp
* @param {string} props.location
*
* @return {JSX.Element}
*/
function MeetupMarker( { id, title, url, meetup, timestamp, location } ) {
return (
<div id={ 'wporg-map-marker__id-' + id } className="wporg-map-marker">
<h3 className="wporg-map-marker__title">{ meetup }</h3>

<p className="wporg-map-marker__url">
<a href={ url }>{ title }</a>
</p>

<p className="wporg-map-marker__location">{ formatLocation( location ) }</p>

<p className="wporg-map-marker__date-time">{ getEventDateTime( timestamp ) }</p>
</div>
);
}

/**
* Format the `online` location type for display.
*
* @param {string} location
*
* @return {string}
*/
function formatLocation( location ) {
if ( 'online' === location ) {
location = 'Online';
}

return location;
}
25 changes: 25 additions & 0 deletions mu-plugins/blocks/google-map/src/front.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* global wporgGoogleMap */

/**
* WordPress dependencies
*/
import { createRoot } from '@wordpress/element';

/**
* Internal dependencies
*/
import Map from './components/map';

const init = () => {
const wrapper = document.getElementById( wporgGoogleMap.id );

if ( ! wrapper ) {
throw "Map container element isn't present in the DOM.";
}

const root = createRoot( wrapper );

root.render( <Map { ...wporgGoogleMap } /> );
};

document.addEventListener( 'DOMContentLoaded', init );
27 changes: 27 additions & 0 deletions mu-plugins/blocks/google-map/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';
import { Placeholder } from '@wordpress/components';

/**
* Internal dependencies
*/
import metadata from './block.json';

function Edit() {
return (
<Placeholder
instructions={ __(
'This is a placeholder for the editor. Data is supplied to this block via the pattern that includes it.',
'wporg'
) }
label={ __( 'WordPress.org Google Map', 'wporg' ) }
/>
);
}

registerBlockType( metadata.name, {
edit: Edit,
} );
Loading

0 comments on commit 584a32b

Please sign in to comment.