import { GoogleApiWrapper, Map as GeoMap, MapProps, Marker } from "google-maps-react"; import { observer } from "mobx-react"; import { Doc, Opt, DocListCast } from "../../../new_fields/Doc"; import { documentSchema } from "../../../new_fields/documentSchemas"; import { Id } from "../../../new_fields/FieldSymbols"; import { makeInterface } from "../../../new_fields/Schema"; import { Cast, NumCast, ScriptCast, StrCast, BoolCast } from "../../../new_fields/Types"; import { TraceMobx } from "../../../new_fields/util"; import "./CollectionMapView.scss"; import { CollectionSubView } from "./CollectionSubView"; import React = require("react"); import { DocumentManager } from "../../util/DocumentManager"; import { UndoManager, undoBatch } from "../../util/UndoManager"; import { IReactionDisposer, reaction, action, computed } from "mobx"; import requestPromise = require("request-promise"); type MapSchema = makeInterface<[typeof documentSchema]>; const MapSchema = makeInterface(documentSchema); export type LocationData = google.maps.LatLngLiteral & { address?: string resolvedAddress?: string; zoom?: number; }; const base = "https://maps.googleapis.com/maps/api/geocode/json?"; @observer class CollectionMapView extends CollectionSubView & { google: any }>(MapSchema) { // private mapRef = React.createRef(); // private get map() { // return (this.mapRef.current as any).map; // } private _cancelAddrReq = new Map(); private _cancelLocReq = new Map(); private addressUpdaters: IReactionDisposer[] = []; private latlngUpdaters: IReactionDisposer[] = []; getLocation = (doc: Opt, fieldKey: string): Opt => { if (doc) { const lat: Opt = Cast(doc[fieldKey + "-lat"], "number", null); const lng: Opt = Cast(doc[fieldKey + "-lng"], "number", null); const zoom: Opt = Cast(doc[fieldKey + "-zoom"], "number", null); return lat !== undefined && lng !== undefined ? ({ lat, lng, zoom }) : undefined; } return undefined; } markerClick = action(async (layout: Doc, location: LocationData) => { //this.map.panTo(location); const batch = UndoManager.StartBatch("marker click"); this.layoutDoc[this.props.fieldKey + "-mapCenter-lat"] = location.lat; this.layoutDoc[this.props.fieldKey + "-mapCenter-lng"] = location.lng; location.zoom && (this.layoutDoc[this.props.fieldKey + "-mapCenter-zoom"] = location.zoom); if (layout.isLinkButton && DocListCast(layout.links).length) { await DocumentManager.Instance.FollowLink(undefined, layout, (doc: Doc, where: string, finished?: () => void) => { this.props.addDocTab(doc, where); finished?.(); }, false, this.props.ContainingCollectionDoc, batch.end, undefined); } else { ScriptCast(layout.onClick)?.script.run({ this: layout, self: Cast(layout.rootDocument, Doc, null) || layout }); batch.end(); } }); renderMarkerIcon(layout: Doc) { const iconUrl = StrCast(this.props.Document.mapIconUrl, null); if (iconUrl) { const iconWidth = NumCast(layout["mapLocation-iconWidth"], 45); const iconHeight = NumCast(layout["mapLocation-iconHeight"], 45); const iconSize = new google.maps.Size(iconWidth, iconHeight); return { size: iconSize, scaledSize: iconSize, url: iconUrl }; } } renderMarker(layout: Doc) { const location = this.getLocation(layout, "mapLocation"); return !location ? (null) : this.markerClick(layout, location)} icon={this.renderMarkerIcon(layout)} />; } @computed get contents() { this.addressUpdaters.forEach(disposer => disposer()); this.addressUpdaters = []; this.latlngUpdaters.forEach(disposer => disposer()); this.latlngUpdaters = []; return this.childLayoutPairs.map(({ layout }) => { this.addressUpdaters.push(reaction( () => ({ lat: layout["mapLocation-lat"], lng: layout["mapLocation-lng"] }), ({ lat, lng }) => { if (this._cancelLocReq.get(layout[Id])) { this._cancelLocReq.set(layout[Id], false); } else if (lat !== undefined && lng !== undefined) { const target = `${base}latlng=${NumCast(lat)},${NumCast(lng)}&key=${process.env.GOOGLE_MAPS_GEO!}`; requestPromise.get(target).then(res => { const formatted_address = JSON.parse(res).results[0].formatted_address || ""; if (formatted_address !== layout["mapLocation-address"]) { this._cancelAddrReq.set(layout[Id], true); Doc.SetInPlace(layout, "mapLocation-address", formatted_address, true); } }); } } )); this.latlngUpdaters.push(reaction( () => ({ address: Cast(layout["mapLocation-address"], "string", null) }), ({ address }) => { if (this._cancelAddrReq.get(layout[Id])) { this._cancelAddrReq.set(layout[Id], false); } else if (address?.length) { const target = `${base}address=${address.replace(/\s+/g, "+")}&key=${process.env.GOOGLE_MAPS_GEO!}`; requestPromise.get(target).then(action((res: any) => { const { geometry, formatted_address } = JSON.parse(res).results[0]; const { lat, lng } = geometry.location; if (layout["mapLocation-lat"] !== lat || layout["mapLocation-lng"] !== lng) { this._cancelLocReq.set(layout[Id], true); Doc.SetInPlace(layout, "mapLocation-lat", lat, true); Doc.SetInPlace(layout, "mapLocation-lng", lng, true); } if (formatted_address !== address) { this._cancelAddrReq.set(layout[Id], true); Doc.SetInPlace(layout, "mapLocation-address", formatted_address, true); } })); } } )); return this.renderMarker(layout); }); } render() { const { childLayoutPairs } = this; const { Document } = this.props; let center = this.getLocation(Document, this.props.fieldKey + "-mapCenter"); if (center === undefined) { center = childLayoutPairs.map(pair => this.getLocation(pair.layout, "mapLocation")).find(layout => layout); if (center === undefined) { center = { lat: 35.1592238, lng: -98.444512, zoom: 15 }; // nowhere, OK } } TraceMobx(); return
e.stopPropagation()} onPointerDown={e => (e.button === 0 && !e.ctrlKey) && e.stopPropagation()} > { Document[this.props.fieldKey + "-mapCenter-lat"] = map.center.lat(); Document[this.props.fieldKey + "-mapCenter-lng"] = map.center.lng(); }))} > {this.contents}
; } } const LoadingContainer = () =>
; export default GoogleApiWrapper({ apiKey: process.env.GOOGLE_MAPS!, LoadingContainer })(CollectionMapView) as any;