diff --git a/src/lib/OverlayView.js b/src/lib/OverlayView.js deleted file mode 100644 index 691cd538..00000000 --- a/src/lib/OverlayView.js +++ /dev/null @@ -1,150 +0,0 @@ -/* global google */ -import _ from "lodash"; - -import PropTypes from "prop-types"; - -import createReactClass from "create-react-class"; - -import { - MAP, - OVERLAY_VIEW, -} from "./constants"; - -import { - addDefaultPrefixToPropTypes, - collectUncontrolledAndControlledProps, - default as enhanceElement, -} from "./enhanceElement"; - -import * as helpers from "./utils/OverlayViewHelper"; - -const controlledPropTypes = { - // NOTICE!!!!!! - // - // Only expose those with getters & setters in the table as controlled props. - // - // [].map.call($0.querySelectorAll("tr>td>code", function(it){ return it.textContent; }) - // .filter(function(it){ return it.match(/^set/) && !it.match(/^setMap/); }) - // - // https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView - mapPaneName: PropTypes.string, - position: PropTypes.object, - bounds: PropTypes.object, -}; - -const defaultUncontrolledPropTypes = addDefaultPrefixToPropTypes(controlledPropTypes); - -const eventMap = { - // https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView - // [].map.call($0.querySelectorAll("tr>td>code"), function(it){ return it.textContent; }) -}; - -const publicMethodMap = { - // Public APIs - // - // https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView - // - // [].map.call($0.querySelectorAll("tr>td>code"), function(it){ return it.textContent; }) - // .filter(function(it){ return it.match(/^get/) && !it.match(/Map$/); }) - getPanes(overlayView) { return overlayView.getPanes(); }, - - getProjection(overlayView) { return overlayView.getProjection(); }, - // END - Public APIs -}; - -const controlledPropUpdaterMap = { -}; - -function getInstanceFromComponent(component) { - return component.state[OVERLAY_VIEW]; -} - -export default _.flowRight( - createReactClass, - enhanceElement(getInstanceFromComponent, publicMethodMap, eventMap, controlledPropUpdaterMap), -)({ - displayName: `OverlayView`, - - statics: { - FLOAT_PANE: `floatPane`, - MAP_PANE: `mapPane`, - MARKER_LAYER: `markerLayer`, - OVERLAY_LAYER: `overlayLayer`, - OVERLAY_MOUSE_TARGET: `overlayMouseTarget`, - }, - - propTypes: { - ...controlledPropTypes, - ...defaultUncontrolledPropTypes, - children: PropTypes.node.isRequired, - getPixelPositionOffset: PropTypes.func, - }, - - contextTypes: { - [MAP]: PropTypes.object, - }, - - getInitialState() { - // https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView - const overlayView = new google.maps.OverlayView(); - // You must implement three methods: onAdd(), draw(), and onRemove(). - overlayView.onAdd = this.onAdd; - overlayView.draw = this.draw; - overlayView.onRemove = this.onRemove; - // You must call setMap() with a valid Map object to trigger the call to - // the onAdd() method and setMap(null) in order to trigger the onRemove() method. - overlayView.setMap(this.context[MAP]); - return { - [OVERLAY_VIEW]: overlayView, - }; - }, - - onAdd() { - this._containerElement = helpers.createContainerElement(); - }, - - draw() { - // https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView - const overlayView = getInstanceFromComponent(this); - // https://developers.google.com/maps/documentation/javascript/3.exp/reference#MapPanes - const mapPanes = overlayView.getPanes(); - // https://developers.google.com/maps/documentation/javascript/3.exp/reference#MapCanvasProjection - const mapCanvasProjection = overlayView.getProjection(); - // - const props = { - ...collectUncontrolledAndControlledProps( - defaultUncontrolledPropTypes, - controlledPropTypes, - this.props - ), - children: this.props.children, - getPixelPositionOffset: this.props.getPixelPositionOffset, - }; - helpers.mountContainerElementToPane(mapPanes, this._containerElement, props); - helpers.renderChildToContainerElement(mapCanvasProjection, this._containerElement, props); - }, - - onRemove() { - helpers.unmountAndDestroyContainerElement(this._containerElement); - this._containerElement = null; - }, - - componentDidUpdate() { - _.delay(this.draw) - }, - - componentWillUnmount() { - const overlayView = getInstanceFromComponent(this); - if (overlayView) { - overlayView.setMap(null); - // You must implement three methods: onAdd(), draw(), and onRemove(). - overlayView.onAdd = null; - overlayView.draw = null; - overlayView.onRemove = null; - } - }, - - render() { - return false; - }, -}); diff --git a/src/lib/utils/OverlayViewHelper.js b/src/lib/utils/OverlayViewHelper.js deleted file mode 100644 index 39e454c5..00000000 --- a/src/lib/utils/OverlayViewHelper.js +++ /dev/null @@ -1,128 +0,0 @@ -/* global google */ -import _ from "lodash"; - -import invariant from "invariant"; - -import { - Children, -} from "react"; - -import { - render, - unmountComponentAtNode, -} from "react-dom"; - -export function createContainerElement() { - const containerElement = document.createElement(`div`); - containerElement.style.position = `absolute`; - return containerElement; -} - -export function mountContainerElementToPane(mapPanes, containerElement, props) { - const { - mapPaneName, - } = props; - invariant(!!mapPaneName, -`OverlayView requires either props.mapPaneName or props.defaultMapPaneName but got %s`, - mapPaneName - ); - // https://developers.google.com/maps/documentation/javascript/3.exp/reference#MapPanes - mapPanes[mapPaneName].appendChild(containerElement); -} - -function getOffsetOverride(containerElement, props) { - const { - getPixelPositionOffset, - } = props; - // - // Allows the component to control the visual position of the OverlayView - // relative to the LatLng pixel position. - // - if (_.isFunction(getPixelPositionOffset)) { - return getPixelPositionOffset( - containerElement.offsetWidth, - containerElement.offsetHeight, - ); - } else { - return {}; - } -} - -function createLatLng(inst, Type) { - return new Type(inst.lat, inst.lng); -} - -function createLatLngBounds(inst, Type) { - return new Type( - new google.maps.LatLng(inst.ne.lat, inst.ne.lng), - new google.maps.LatLng(inst.sw.lat, inst.sw.lng) - ); -} - -function ensureOfType(inst, type, factory) { - if (inst instanceof type) { - return inst; - } else { - return factory(inst, type); - } -} - -function getLayoutStylesByBounds(mapCanvasProjection, offset, bounds) { - const ne = mapCanvasProjection.fromLatLngToDivPixel(bounds.getNorthEast()); - const sw = mapCanvasProjection.fromLatLngToDivPixel(bounds.getSouthWest()); - if (ne && sw) { - return { - left: `${sw.x + offset.x}px`, - top: `${ne.y + offset.y}px`, - width: `${ne.x - sw.x - offset.x}px`, - height: `${sw.y - ne.y - offset.y}px`, - }; - } - return { - left: `-9999px`, - top: `-9999px`, - }; -} - -function getLayoutStylesByPosition(mapCanvasProjection, offset, position) { - const point = mapCanvasProjection.fromLatLngToDivPixel(position); - if (point) { - const { x, y } = point; - return { - left: `${x + offset.x}px`, - top: `${y + offset.y}px`, - }; - } - return { - left: `-9999px`, - top: `-9999px`, - }; -} - -function getLayoutStyles(mapCanvasProjection, offset, props) { - if (props.bounds) { - const bounds = ensureOfType(props.bounds, google.maps.LatLngBounds, createLatLngBounds); - return getLayoutStylesByBounds(mapCanvasProjection, offset, bounds); - } else { - const position = ensureOfType(props.position, google.maps.LatLng, createLatLng); - return getLayoutStylesByPosition(mapCanvasProjection, offset, position); - } -} - -export function renderChildToContainerElement(mapCanvasProjection, containerElement, props) { - const child = Children.only(props.children); - render(child, containerElement, () => { - const offset = { - x: 0, - y: 0, - ...getOffsetOverride(containerElement, props), - }; - const layoutStyles = getLayoutStyles(mapCanvasProjection, offset, props); - _.assign(containerElement.style, layoutStyles); - }); -} - -export function unmountAndDestroyContainerElement(containerElement) { - containerElement.parentNode.removeChild(containerElement); - unmountComponentAtNode(containerElement); -} diff --git a/src/macros/OverlayView.jsx b/src/macros/OverlayView.jsx new file mode 100644 index 00000000..a5b6fd7c --- /dev/null +++ b/src/macros/OverlayView.jsx @@ -0,0 +1,165 @@ +/* global google */ +import _ from "lodash" +import invariant from "invariant" +import React from "react" +import ReactDOM from "react-dom" +import PropTypes from "prop-types" + +import { + componentDidMount, + componentDidUpdate, + componentWillUnmount, +} from "../utils/MapChildHelper" + +import { getOffsetOverride, getLayoutStyles } from "../utils/OverlayViewHelper" + +import { MAP, ANCHOR, OVERLAY_VIEW } from "../constants" + +export const __jscodeshiftPlaceholder__ = `{ + "eventMapOverrides": { + }, + "getInstanceFromComponent": "this.state[OVERLAY_VIEW]" +}` + +/** + * @url https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView + */ +export class OverlayView extends React.PureComponent { + static LOAT_PANE = `floatPane` + static MAP_PANE = `mapPane` + static MARKER_LAYER = `markerLayer` + static OVERLAY_LAYER = `overlayLayer` + static OVERLAY_MOUSE_TARGET = `overlayMouseTarget` + + static propTypes = { + __jscodeshiftPlaceholder__: null, + /** + * @see https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView + */ + mapPaneName: PropTypes.string, + /** + * @see https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView + */ + position: PropTypes.object, + /** + * @see https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView + */ + bounds: PropTypes.object, + /** + * @see https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView + */ + children: PropTypes.node.isRequired, + /** + * @see https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView + */ + getPixelPositionOffset: PropTypes.func, + } + + static contextTypes = { + [MAP]: PropTypes.object, + [ANCHOR]: PropTypes.object, + } + + /* + * @url https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView + */ + constructor(props, context) { + super(props, context) + const overlayView = new google.maps.OverlayView() + // You must implement three methods: onAdd(), draw(), and onRemove(). + overlayView.onAdd = _.bind(this.onAdd, this) + overlayView.draw = _.bind(this.draw, this) + overlayView.onRemove = _.bind(this.onRemove, this) + this.onPositionElement = _.bind(this.onPositionElement, this) + // You must call setMap() with a valid Map object to trigger the call to + // the onAdd() method and setMap(null) in order to trigger the onRemove() method. + overlayView.setMap(this.context[MAP]) + this.state = { + [OVERLAY_VIEW]: overlayView, + } + } + + onAdd() { + this.containerElement = document.createElement(`div`) + this.containerElement.style.position = `absolute` + } + + draw() { + const { mapPaneName } = this.props + invariant( + !!mapPaneName, + `OverlayView requires either props.mapPaneName or props.defaultMapPaneName but got %s`, + mapPaneName + ) + // https://developers.google.com/maps/documentation/javascript/3.exp/reference#MapPanes + const mapPanes = this.state[OVERLAY_VIEW].getPanes() + mapPanes[mapPaneName].appendChild(this.containerElement) + + ReactDOM.unstable_renderSubtreeIntoContainer( + this, + React.Children.only(this.props.children), + this.containerElement, + this.onPositionElement + ) + } + + onPositionElement() { + // https://developers.google.com/maps/documentation/javascript/3.exp/reference#MapCanvasProjection + const mapCanvasProjection = this.state[OVERLAY_VIEW].getProjection() + + const offset = { + x: 0, + y: 0, + ...getOffsetOverride(this.containerElement, this.props), + } + const layoutStyles = getLayoutStyles( + mapCanvasProjection, + offset, + this.props + ) + _.assign(this.containerElement.style, layoutStyles) + } + + onRemove() { + this.containerElement.parentNode.removeChild(this.containerElement) + ReactDOM.unmountComponentAtNode(this.containerElement) + this.containerElement = null + } + + componentDidMount() { + componentDidMount(this, this.state[OVERLAY_VIEW], eventMap) + } + + componentDidUpdate(prevProps) { + componentDidUpdate( + this, + this.state[OVERLAY_VIEW], + eventMap, + updaterMap, + prevProps + ) + _.delay(this.state[OVERLAY_VIEW].draw) + } + + componentWillUnmount() { + componentWillUnmount(this) + const overlayView = this.state[OVERLAY_VIEW] + if (overlayView) { + overlayView.setMap(null) + // You must implement three methods: onAdd(), draw(), and onRemove(). + overlayView.onAdd = null + overlayView.draw = null + overlayView.onRemove = null + } + } + + render() { + return false + } +} + +export default OverlayView + +const eventMap = {} + +const updaterMap = {}