aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/collections/CollectionMapView.tsx128
-rw-r--r--webpack.config.js64
2 files changed, 126 insertions, 66 deletions
diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx
index a2d3c328d..8cdc145b8 100644
--- a/src/client/views/collections/CollectionMapView.tsx
+++ b/src/client/views/collections/CollectionMapView.tsx
@@ -4,37 +4,44 @@ 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 } from "../../../new_fields/Types";
+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 } from "../../util/UndoManager";
+import { IReactionDisposer, reaction } from "mobx";
+import requestPromise = require("request-promise");
type MapSchema = makeInterface<[typeof documentSchema]>;
const MapSchema = makeInterface(documentSchema);
-export type LocationData = google.maps.LatLngLiteral & { address?: string };
+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<MapSchema, Partial<MapProps> & { google: any }>(MapSchema) {
- getLocation = (doc: Opt<Doc>, fieldKey: string) => {
+ private mapRef = React.createRef<Map>();
+ private addressUpdaters: IReactionDisposer[] = [];
+ private latlngUpdaters: IReactionDisposer[] = [];
+
+ getLocation = (doc: Opt<Doc>, fieldKey: string): Opt<LocationData> => {
if (doc) {
- let lat: Opt<number> = Cast(doc[fieldKey + "-lat"], "number", null);
- let lng: Opt<number> = Cast(doc[fieldKey + "-lng"], "number", null);
+ const lat: Opt<number> = Cast(doc[fieldKey + "-lat"], "number", null);
+ const lng: Opt<number> = Cast(doc[fieldKey + "-lng"], "number", null);
const zoom: Opt<number> = Cast(doc[fieldKey + "-zoom"], "number", null);
- const address = Cast(doc[fieldKey + "-address"], "string", null);
- if (address) {
- // use geo service to convert to lat/lng
- lat = lat;
- lng = lng;
- }
return lat !== undefined && lng !== undefined ? ({ lat, lng, zoom }) : undefined;
}
return undefined;
}
+
renderMarker(layout: Doc, icon: Opt<google.maps.Icon>) {
const location = this.getLocation(layout, "mapLocation");
return !location ? (null) :
@@ -43,6 +50,7 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<MapProps> &
label={StrCast(layout.title)}
position={{ lat: location.lat, lng: location.lng }}
onClick={async () => {
+ this.map.panTo(location);
this.layoutDoc[this.props.fieldKey + "-mapCenter-lat"] = 0;
this.layoutDoc[this.props.fieldKey + "-mapCenter-lat"] = location.lat;
this.layoutDoc[this.props.fieldKey + "-mapCenter-lng"] = location.lng;
@@ -60,6 +68,73 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<MapProps> &
icon={icon}
/>;
}
+
+ private get contents() {
+ this.addressUpdaters.forEach(disposer => disposer());
+ this.addressUpdaters = [];
+ this.latlngUpdaters.forEach(disposer => disposer());
+ this.latlngUpdaters = [];
+ return this.childLayoutPairs.map(({ layout }) => {
+ let icon: Opt<google.maps.Icon>, iconUrl: Opt<string>;
+ if ((iconUrl = StrCast(this.props.Document.mapIconUrl, null))) {
+ const iconWidth = NumCast(layout["mapLocation-iconWidth"], 45);
+ const iconHeight = NumCast(layout["mapLocation-iconHeight"], 45);
+ const iconSize = new google.maps.Size(iconWidth, iconHeight);
+ icon = {
+ size: iconSize,
+ scaledSize: iconSize,
+ url: iconUrl
+ };
+ }
+ this.addressUpdaters.push(reaction(
+ () => ({
+ lat: NumCast(layout["mapLocation-lat"]),
+ lng: NumCast(layout["mapLocation-lng"])
+ }),
+ ({ lat, lng }) => {
+ if (!BoolCast(layout._ignoreNextUpdate)) {
+ if (lat !== undefined && lng !== undefined) {
+ const target = `${base}latlng=${lat},${lng}&key=${process.env.GOOGLE_MAPS_GEO!}`;
+ requestPromise.get(target).then(res => {
+ layout._ignoreNextUpdate = true;
+ layout["mapLocation-address"] = JSON.parse(res).results[0]?.formatted_address || "<invalid address>";
+ });
+ }
+ } else {
+ layout._ignoreNextUpdate = false;
+ }
+ }
+ ));
+ this.latlngUpdaters.push(reaction(
+ () => ({ address: Cast(layout["mapLocation-address"], "string", null) }),
+ ({ address }) => {
+ if (!BoolCast(layout._ignoreNextUpdate)) {
+ if (address && address.length) {
+ const target = `${base}address=${address.replace(/\s+/g, "+")}&key=${process.env.GOOGLE_MAPS_GEO!}`;
+ requestPromise.get(target).then(res => {
+ const result = JSON.parse(res).results[0];
+ const { lat, lng } = result.geometry.location;
+ layout._ignoreNextUpdate = true;
+ layout["mapLocation-lat"] = lat;
+ layout._ignoreNextUpdate = true;
+ layout["mapLocation-lng"] = lng;
+ layout._ignoreNextUpdate = true;
+ layout["mapLocation-address"] = result.formatted_address;
+ });
+ }
+ } else {
+ layout._ignoreNextUpdate = false;
+ }
+ }
+ ));
+ return this.renderMarker(layout, icon);
+ });
+ }
+
+ private get map() {
+ return (this.mapRef.current as any).map;
+ }
+
render() {
const { childLayoutPairs } = this;
const { Document } = this.props;
@@ -76,33 +151,18 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<MapProps> &
onWheel={e => e.stopPropagation()}
onPointerDown={e => (e.button === 0 && !e.ctrlKey) && e.stopPropagation()} >
<Map
+ ref={this.mapRef}
google={this.props.google}
zoom={center.zoom || 10}
initialCenter={center}
- onBoundsChanged={(props, map, e) => console.log("ON_BOUNDS_CHANGED", props, map, e)}
- onRecenter={(props, map, e) => console.log("ON_RECENTER", props, map, e)}
- onDragend={(centerMoved, center) => console.log("ON_DRAGEND", centerMoved, center)}
- onProjectionChanged={(props, map, e) => console.log("ON_PROJ_CHANGED", props, map, e)}
- onCenterChanged={((props, map, e) => {
- console.log("ON_CENTER_CHANGED", props, map, e);
- Document[this.props.fieldKey + "-mapCenter-lat"] = typeof e?.center?.lat === "number" ? e.center.lat : center!.lat;
- Document[this.props.fieldKey + "-mapCenter-lng"] = typeof e?.center?.lng === "number" ? e.center.lng : center!.lng;
- })}
+ center={center}
+ onDragend={() => {
+ const { center } = this.map;
+ Document[this.props.fieldKey + "-mapCenter-lat"] = center.lat();
+ Document[this.props.fieldKey + "-mapCenter-lng"] = center.lng();
+ }}
>
- {childLayoutPairs.map(({ layout }) => {
- let icon: Opt<google.maps.Icon>, iconUrl: Opt<string>;
- if ((iconUrl = StrCast(Document.mapIconUrl, null))) {
- const iconWidth = NumCast(layout["mapLocation-iconWidth"], 45);
- const iconHeight = NumCast(layout["mapLocation-iconHeight"], 45);
- const iconSize = new google.maps.Size(iconWidth, iconHeight);
- icon = {
- size: iconSize,
- scaledSize: iconSize,
- url: iconUrl
- };
- }
- return this.renderMarker(layout, icon);
- })}
+ {this.contents}
</Map>
</div>;
}
diff --git a/webpack.config.js b/webpack.config.js
index 1027f29a6..6265883fd 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -20,7 +20,7 @@ const dotenv = require('dotenv');
function transferEnvironmentVariables() {
const prefix = "_CLIENT_";
- const env = dotenv.config({ debug: true }).parsed;
+ const env = dotenv.config().parsed;
if (env) {
plugins.push(new webpack.DefinePlugin(Object.keys(env).reduce((mapping, envKey) => {
if (envKey.startsWith(prefix)) {
@@ -64,42 +64,42 @@ module.exports = {
},
module: {
rules: [{
- test: [/\.tsx?$/],
- use: [{
- loader: 'ts-loader',
- options: {
- transpileOnly: true
- }
- }]
- },
- {
- test: /\.scss|css$/,
- use: [{
- loader: "style-loader"
+ test: [/\.tsx?$/],
+ use: [{
+ loader: 'ts-loader',
+ options: {
+ transpileOnly: true
+ }
+ }]
},
{
- loader: "css-loader"
+ test: /\.scss|css$/,
+ use: [{
+ loader: "style-loader"
+ },
+ {
+ loader: "css-loader"
+ },
+ {
+ loader: "sass-loader"
+ }
+ ]
},
{
- loader: "sass-loader"
+ test: /\.(jpg|png|pdf)$/,
+ use: [{
+ loader: 'file-loader'
+ }]
+ },
+ {
+ test: /\.(png|jpg|gif)$/i,
+ use: [{
+ loader: 'url-loader',
+ options: {
+ limit: 8192
+ }
+ }]
}
- ]
- },
- {
- test: /\.(jpg|png|pdf)$/,
- use: [{
- loader: 'file-loader'
- }]
- },
- {
- test: /\.(png|jpg|gif)$/i,
- use: [{
- loader: 'url-loader',
- options: {
- limit: 8192
- }
- }]
- }
]
},
plugins,