diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/collections/MapView/CollectionMapView.tsx | 309 | ||||
-rw-r--r-- | src/client/views/nodes/MapMarker/MapMarker.tsx | 23 |
2 files changed, 207 insertions, 125 deletions
diff --git a/src/client/views/collections/MapView/CollectionMapView.tsx b/src/client/views/collections/MapView/CollectionMapView.tsx index 4243b89a5..ebf57c0c1 100644 --- a/src/client/views/collections/MapView/CollectionMapView.tsx +++ b/src/client/views/collections/MapView/CollectionMapView.tsx @@ -1,5 +1,5 @@ -import { GoogleMap, Marker, InfoWindow, InfoBox, useJsApiLoader, LoadScript, GoogleMapProps, StandaloneSearchBox, DrawingManager } from '@react-google-maps/api'; -import { observable, action, computed, Lambda, runInAction } from "mobx"; +import { GoogleMap, Marker, InfoWindow, InfoBox, useJsApiLoader, LoadScript, GoogleMapProps, StandaloneSearchBox, Autocomplete } from '@react-google-maps/api'; +import { observable, action, computed, Lambda, runInAction, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, Field, FieldResult, Opt } from "../../../../fields/Doc"; import { documentSchema } from "../../../../fields/documentSchemas"; @@ -13,8 +13,21 @@ import { CollectionSubView } from "../CollectionSubView"; import React = require("react"); import requestPromise = require("request-promise"); import ReactDOM from 'react-dom'; +import { DragManager } from '../../../util/DragManager'; +import { MapMarker } from '../../nodes/MapMarker/MapMarker'; +/** + * Idea behind storing a marker: + * 1. on the map api, have a button "add marker" that adds the marker on the map & store the marker as a node in the collection + * (but don't render the marker in the collection itself) + * 2. each marker, as a node, has the same feature as all other nodes for linking (the same way one could form a link between a node inside a child collection + * and a node outside the child collection) + * + * /util/LinkManager.ts -- link relations + * + */ + type MapSchema = makeInterface<[typeof documentSchema]>; const MapSchema = makeInterface(documentSchema); @@ -33,11 +46,13 @@ const defaultCenter = { }; @observer -export default class CollectionMapView extends CollectionSubView<MapSchema, GoogleMapProps>(MapSchema) { +export default class CollectionMapView extends CollectionSubView<MapSchema, Partial<GoogleMapProps>>(MapSchema) { + private _dropDisposer?: DragManager.DragDropDisposer; + private _disposers: { [name: string]: IReactionDisposer } = {}; + - @observable private _map = null as any - @observable private mapRef = null as any; - @observable private selectedPlace: LocationData = { id: undefined, pos: undefined }; + @observable private _map = null as unknown as google.maps.Map; + @observable private selectedPlace: Doc = ; @observable private markerMap = {}; @observable private center = defaultCenter; @observable private zoom = 2.5; @@ -45,14 +60,21 @@ export default class CollectionMapView extends CollectionSubView<MapSchema, Goog @observable private infoWindowOpen = false; @observable private bounds = new window.google.maps.LatLngBounds(); @observable private inputRef = React.createRef<HTMLInputElement>(); - @observable private searchBox = new window.google.maps.places.SearchBox(this.inputRef.current!); + @observable private buttonRef = React.createRef<HTMLDivElement>(); @observable private searchMarkers: google.maps.Marker[] = []; + private options = { + fields: ["formatted_address", "geometry", "name"], // note: level of details is charged by item per retrieval, not recommended to return all fields + strictBounds: false, + types: ["establishment"], // type pf places, subject of change according to user need + } as google.maps.places.AutocompleteOptions; + + @observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, this.options); + @observable private myPlaces = [ { id: 1, pos: { lat: 39.09366509575983, lng: -94.58751660204751 } }, { id: 2, pos: { lat: 41.82399, lng: -71.41283 } }, { id: 3, pos: { lat: 47.606214, lng: -122.33207 } }, - { id: 4, pos: { lat: 36.7783, lng: 119.4179 } } ]; @action @@ -60,8 +82,14 @@ export default class CollectionMapView extends CollectionSubView<MapSchema, Goog this.searchBox = searchBox; } + @action + private setButton = (button: any) => { + this.buttonRef = button; + this._map.controls[google.maps.ControlPosition.TOP_RIGHT].push(this.buttonRef.current!) + } + // iterate myPlaces to size, center, and zoom map to contain all markers - private fitBounds = (map: any) => { + private fitBounds = (map: google.maps.Map) => { console.log('map bound is:' + this.bounds); this.myPlaces.map(place => { this.bounds.extend(place.pos); @@ -72,8 +100,8 @@ export default class CollectionMapView extends CollectionSubView<MapSchema, Goog // store a reference to google map instance; fit map bounds to contain all markers @action - private loadHandler = (map: any) => { - this.mapRef = map; + private loadHandler = (map: google.maps.Map) => { + this._map = map; this.fitBounds(map); // //add a custom control for button add marker @@ -86,7 +114,7 @@ export default class CollectionMapView extends CollectionSubView<MapSchema, Goog } @action - private markerClickHandler = (e: MouseEvent, place: LocationData) => { + private markerClickHandler = (e: MouseEvent, place: Doc) => { // set which place was clicked this.selectedPlace = place @@ -96,20 +124,23 @@ export default class CollectionMapView extends CollectionSubView<MapSchema, Goog // used so clicking a second marker works if (this.infoWindowOpen) { this.infoWindowOpen = false; + console.log("closeinfowindow") } - - this.infoWindowOpen = true; - } - - @action - private handleInfoWindowClose = () => { - if (this.infoWindowOpen) { - this.infoWindowOpen = false; + else { + this.infoWindowOpen = true; + console.log("open infowindow") } - this.infoWindowOpen = false; - this.selectedPlace = { id: undefined, pos: undefined }; } + // @action + // private handleInfoWindowClose = () => { + // if (this.infoWindowOpen) { + // this.infoWindowOpen = false; + // } + // this.infoWindowOpen = false; + // this.selectedPlace = { id: undefined, pos: undefined }; + // } + // @action // private markerLoadHandler = (marker: any, place: LocationData) => { // if (marker != null) { @@ -119,118 +150,140 @@ export default class CollectionMapView extends CollectionSubView<MapSchema, Goog @action @undoBatch - private handleDragMarker = (marker: any, place: LocationData) => { - if (marker != null) { - place = { - id: place.id, - pos: { - lat: marker.latLng.lat().toFixed(3), - lng: marker.latLng.lng().toFixed(3) - } - } - - console.log(place); - console.log(this.myPlaces); - } + private handleDragMarker = (marker: any, place: Doc) => { + // if (marker != null) { + // place = { + // id: place.id, + // position: { + // lat: marker.latLng.lat().toFixed(3), + // lng: marker.latLng.lng().toFixed(3) + // } + // } + + // console.log(place); + // console.log(this.myPlaces); + // } } - // private mapRef = document.getElementById("map") - // private markers: google.maps.Marker[] = []; - - - // @action - // private setInputRef = (element: HTMLInputElement) => { - // if (this.inputRef.current) { - // this.inputRef! = element; - // console.log("htmlinputelement is:" + this.inputRef) - // } - // } - - // @observable private searchBox = new window.google.maps.places.SearchBox(this.inputRef.current!); - @action - private handlePlacesChanged = () => { + private handlePlaceChanged = () => { console.log(this.searchBox); - const places = this.searchBox.getPlaces(); + const place = this.searchBox.getPlace(); + + if (!place.geometry || !place.geometry.location) { + // user entered the name of a place that wasn't suggested & pressed the enter key, or place details request failed + window.alert("No details available for input: '" + place.name + "'"); + return; + } - console.log(places); + // zoom in on the location of the search result + if (place.geometry.viewport) { + console.log(this._map); + this._map.fitBounds(place.geometry.viewport); + } else { + console.log(this._map); + this._map.setCenter(place.geometry.location); + this._map.setZoom(17); + } - if (places.length == 0) { return; } + /** + * customize icon => customized icon for the nature of the location selected (hard to tell from other pre-existing markers, probably won't implement + */ + // const icon = { + // url: place.icon as string, + // size: new google.maps.Size(71, 71), + // origin: new google.maps.Point(0, 0), + // anchor: new google.maps.Point(17, 34), + // scaledSize: new google.maps.Size(25, 25), + // }; - // clear out old markers this.searchMarkers.forEach((marker) => { marker.setMap(null); }); - this.searchMarkers = [] - - // for each place, get icon, name, location - places.forEach((place) => { - if (!place.geometry || !place.geometry.location) { - console.log("Returned place contains no geometry"); - return; - } - - const icon = { - url: place.icon as string, - size: new google.maps.Size(71, 71), - origin: new google.maps.Point(0, 0), - anchor: new google.maps.Point(17, 34), - scaledSize: new google.maps.Size(25, 25), - } + this.searchMarkers = []; + this.searchMarkers.push( + new window.google.maps.Marker({ + map: this._map, + title: place.name, + position: place.geometry.location, + }) + ) + } - //create a marker for each place - this.searchMarkers.push( - new google.maps.Marker({ - map: this._map, - icon: icon, - title: place.name, - position: place.geometry.location, - }) - ); - - if (place.geometry.viewport) { - // only geocodes have viewport - this.bounds.union(place.geometry.viewport); - } - else { - this.bounds.extend(place.geometry.location); + @computed get markerContent() { + const allMarkers = this.childLayoutPairs + const markerId = NumCast(this.layoutDoc._itemIndex); + const selectedMarker = this.childLayoutPairs?.[markerId]; + const position = { + lat: NumCast(this.layoutDoc.lat), + lng: NumCast(this.layoutDoc.lng) + } + return <> + { + allMarkers?.map(place => ( + <Marker + key={markerId} + position={position} + onClick={e => this.markerClickHandler(e, place.layout)} //?? + draggable={true} + onDragEnd={marker => this.handleDragMarker(marker, place.layout)} + /> + )) } - }); - - console.log(this.searchMarkers); + {this.infoWindowOpen && selectedMarker && ( + <InfoBox + //anchor={selectedMarker} + // onCloseClick={this.handleInfoWindowClose} + position={position} + // options={{ enableEventPropagation: true }} + > + <div style={{ backgroundColor: 'white', opacity: 0.75, padding: 12 }}> + <div style={{ fontSize: 16 }}> + {/* the linkmenu as the ones in other nodes */} + <div> + <a>a link to another node</a> + <hr /> + </div> + <div> + <a>a link to another node</a> + <hr /> + </div> + <div> + <a>a link to another node</a> + <hr /> + </div> + <div> + <button>New +</button> + </div> + </div> + </div> + </InfoBox> + )} + + </> } - private onBoundsChanged = () => { - this.searchBox.setBounds(this.bounds); + @action + private addMarker = (location: google.maps.LatLng | undefined, map: google.maps.Map) => { + new window.google.maps.Marker({ + position: location, + map: map + }); } - private onCenterChanged = () => { - + @action + private renderMarkerToMap = (marker: Doc) => { + const id = StrCast(marker.id); + const lat = NumCast(marker.lat); + const lng = NumCast(marker.lng); + + return <Marker + key={id} + position={{ lat: lat, lng: lng }} + onClick={e => this.markerClickHandler(e, marker)} + /> } - // private setBounds = () => { - // const places = this.searchBox.getPlaces(); - // if (places.length == 0) { return; } - - // const bounds = new google.maps.LatLngBounds(); - // places.forEach((place) => { - // if (!place.geometry || !place.geometry.location) { - // console.log("Returned place contains no geometry"); - // return; - // } - - // if (place.geometry.viewport) { - // // only geocodes have viewport - // bounds.union(place.geometry.viewport); - // } - // else { - // bounds.extend(place.geometry.location); - // } - // }); - - // return bounds - // } - render() { const { Document, fieldKey, isContentActive: active } = this.props; @@ -246,19 +299,22 @@ export default class CollectionMapView extends CollectionSubView<MapSchema, Goog > */} <div className="map-wrapper"> <GoogleMap - ref={(map) => this._map = map} mapContainerStyle={mapContainerStyle} zoom={this.zoom} center={this.center} onLoad={map => this.loadHandler(map)} - onBoundsChanged={this.onBoundsChanged} - onCenterChanged={this.onCenterChanged} > - <StandaloneSearchBox + <Autocomplete onLoad={this.setSearchBox} - onPlacesChanged={this.handlePlacesChanged}> + onPlaceChanged={this.handlePlaceChanged}> <input ref={this.inputRef} className="searchbox" type="text" placeholder="Search anywhere:" /> - </StandaloneSearchBox> + </Autocomplete> + + <div onLoad={this.setButton}> + <div ref={this.buttonRef} className="add-button-UI" > + <div className="add-button-Text">Add Marker</div> + </div> + </div> {this.myPlaces.map(place => ( <Marker @@ -271,9 +327,9 @@ export default class CollectionMapView extends CollectionSubView<MapSchema, Goog /> ))} {this.infoWindowOpen && this.selectedPlace && ( - <InfoWindow + <InfoBox // anchor={this.markerMap[this.selectedPlace.id]} - onCloseClick={this.handleInfoWindowClose} + // onCloseClick={this.handleInfoWindowClose} position={this.selectedPlace.pos} // options={{ enableEventPropagation: true }} > @@ -281,19 +337,22 @@ export default class CollectionMapView extends CollectionSubView<MapSchema, Goog <div style={{ fontSize: 16 }}> <div> <a>a link to another node</a> + <hr /> </div> <div> <a>a link to another node</a> + <hr /> </div> <div> <a>a link to another node</a> + <hr /> </div> <div> <button>New +</button> </div> </div> </div> - </InfoWindow> + </InfoBox> )} </GoogleMap> </div> diff --git a/src/client/views/nodes/MapMarker/MapMarker.tsx b/src/client/views/nodes/MapMarker/MapMarker.tsx new file mode 100644 index 000000000..9705986a8 --- /dev/null +++ b/src/client/views/nodes/MapMarker/MapMarker.tsx @@ -0,0 +1,23 @@ +//TODO: mock imagebox, create marker as a doc +import { IReactionDisposer } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; +import { documentSchema } from "../../../../fields/documentSchemas"; +import { createSchema, makeInterface } from "../../../../fields/Schema"; +import { ViewBoxBaseComponent } from "../../DocComponent"; +import { FieldView, FieldViewProps } from "../FieldView"; + +export const markerSchema = createSchema({ + lat: "number", + lng: "number" +}); + +type MarkerDocument = makeInterface<[typeof markerSchema, typeof documentSchema]>; +const MarkerDocument = makeInterface(markerSchema, documentSchema); + +@observer +export class MapMarker extends ViewBoxBaseComponent<FieldViewProps, MarkerDocument>(MarkerDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MapMarker, fieldKey); } + private _markerRef: React.RefObject<google.maps.Marker> = React.createRef(); + private _disposers: { [name: string]: IReactionDisposer } = {}; +}
\ No newline at end of file |