diff options
Diffstat (limited to 'src/client/views/nodes/MapBox/MapBox.tsx')
-rw-r--r-- | src/client/views/nodes/MapBox/MapBox.tsx | 875 |
1 files changed, 630 insertions, 245 deletions
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index cf017d746..93020354d 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -1,31 +1,36 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api'; +import { GoogleMapProps, Marker } from '@react-google-maps/api'; import BingMapsReact from 'bingmaps-react'; -import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx'; +import { Button, EditableText, IconButton, Type } from 'browndash-components'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { TbHeartMinus } from 'react-icons/tb'; import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; -import { Width } from '../../../../fields/DocSymbols'; +import { Highlight, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; +import { ScriptField } from '../../../../fields/ScriptField'; import { NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; +import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; -import { UndoManager } from '../../../util/UndoManager'; +import { Transform } from '../../../util/Transform'; +import { undoable, UndoManager } from '../../../util/UndoManager'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { MarqueeAnnotator } from '../../MarqueeAnnotator'; -import { AnchorMenu } from '../../pdf/AnchorMenu'; import { Annotation } from '../../pdf/Annotation'; import { SidebarAnnos } from '../../SidebarAnnos'; +import { DocumentView, OpenWhere } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; -import { PinProps } from '../trails'; +import { PinProps, PresBox } from '../trails'; +import { MapAnchorMenu } from './MapAnchorMenu'; import './MapBox.scss'; -import { MapBoxInfoWindow } from './MapBoxInfoWindow'; - +// amongus /** * MapBox architecture: * Main component: MapBox.tsx @@ -55,13 +60,12 @@ const mapOptions = { }; const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/ and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey> -const apiKey = process.env.GOOGLE_MAPS; -const script = document.createElement('script'); -script.defer = true; -script.async = true; -script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places,drawing`; -document.head.appendChild(script); +// const script = document.createElement('script'); +// script.defer = true; +// script.async = true; +// script.src = `https://maps.googleapis.com/maps/api/js?key=${bingApiKey}&libraries=places,drawing`; +// document.head.appendChild(script); /** * Consider integrating later: allows for drawing, circling, making shapes on map @@ -79,16 +83,8 @@ document.head.appendChild(script); // }, // }); -// options for searchbox in Google Maps Places Autocomplete API -const 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; - @observer export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>>() { - static UseBing = true; private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); @@ -110,10 +106,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable private markerMap: { [id: string]: google.maps.Marker } = {}; @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter; @observable private _marqueeing: number[] | undefined; - @observable private _isAnnotating = false; @observable private inputRef = React.createRef<HTMLInputElement>(); @observable private searchMarkers: google.maps.Marker[] = []; - @observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, options); + @observable private searchBox = undefined as any; // new window.google.maps.places.Autocomplete(this.inputRef.current!, options); @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); @@ -124,25 +119,23 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable private toggleAddMarker = false; private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); - @observable _showSidebar = false; @computed get SidebarShown() { - return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false; + return this.layoutDoc._layout_showSidebar ? true : false; } static _canAnnotate = true; static _hadSelection: boolean = false; private _sidebarRef = React.createRef<SidebarAnnos>(); private _ref: React.RefObject<HTMLDivElement> = React.createRef(); - + private _disposer: { [key: string]: IReactionDisposer } = {}; componentDidMount() { this.props.setContentView?.(this); } - @action - private setSearchBox = (searchBox: any) => { - this.searchBox = searchBox; - }; - + componentWillUnmount(): void { + this.deselectPin(); + Object.keys(this._disposer).forEach(key => this._disposer[key]?.()); + } // iterate allMarkers to size, center, and zoom map to contain all markers private fitBounds = (map: google.maps.Map) => { const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds(); @@ -151,72 +144,25 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; /** - * Custom control for add marker button - * @param controlDiv - * @param map + * Load and render all map markers + * @param marker + * @param place */ - private CenterControl = () => { - const controlDiv = document.createElement('div'); - controlDiv.className = 'mapBox-addMarker'; - // Set CSS for the control border. - const controlUI = document.createElement('div'); - controlUI.style.backgroundColor = '#fff'; - controlUI.style.borderRadius = '3px'; - controlUI.style.cursor = 'pointer'; - controlUI.style.marginTop = '10px'; - controlUI.style.borderRadius = '4px'; - controlUI.style.marginBottom = '22px'; - controlUI.style.textAlign = 'center'; - controlUI.style.position = 'absolute'; - controlUI.style.width = '32px'; - controlUI.style.height = '32px'; - controlUI.title = 'Click to toggle marker mode. In marker mode, click on map to place a marker.'; - - const plIcon = document.createElement('img'); - plIcon.src = 'https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png'; - plIcon.style.color = 'rgb(25,25,25)'; - plIcon.style.fontFamily = 'Roboto,Arial,sans-serif'; - plIcon.style.fontSize = '16px'; - plIcon.style.lineHeight = '32px'; - plIcon.style.left = '18'; - plIcon.style.top = '15'; - plIcon.style.position = 'absolute'; - plIcon.width = 14; - plIcon.height = 14; - plIcon.innerHTML = 'Add'; - controlUI.appendChild(plIcon); - - // Set CSS for the control interior. - const markerIcon = document.createElement('img'); - markerIcon.src = 'https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png'; - markerIcon.style.color = 'rgb(25,25,25)'; - markerIcon.style.fontFamily = 'Roboto,Arial,sans-serif'; - markerIcon.style.fontSize = '16px'; - markerIcon.style.lineHeight = '32px'; - markerIcon.style.left = '-2'; - markerIcon.style.top = '1'; - markerIcon.width = 30; - markerIcon.height = 30; - markerIcon.style.position = 'absolute'; - markerIcon.innerHTML = 'Add'; - controlUI.appendChild(markerIcon); - - // Setup the click event listeners - controlUI.addEventListener('click', () => { - if (this.toggleAddMarker === true) { - this.toggleAddMarker = false; - console.log('add marker button status:' + this.toggleAddMarker); - controlUI.style.backgroundColor = '#fff'; - markerIcon.style.color = 'rgb(25,25,25)'; - } else { - this.toggleAddMarker = true; - console.log('add marker button status:' + this.toggleAddMarker); - controlUI.style.backgroundColor = '#4476f7'; - markerIcon.style.color = 'rgb(255,255,255)'; - } - }); - controlDiv.appendChild(controlUI); - return controlDiv; + @action + private markerLoadHandler = (marker: google.maps.Marker, place: Doc) => { + place[Id] ? (this.markerMap[place[Id]] = marker) : null; + }; + + /** + * on clicking the map marker, set the selected place to the marker document & set infowindowopen to be true + * @param e + * @param place + */ + @action + private markerClickHandler = (e: google.maps.MapMouseEvent, place: Doc) => { + // set which place was clicked + this.selectedPlace = place; + // place.infoWindowOpen = true; }; /** @@ -231,52 +177,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps map: map, }); map.panTo(position); - const mapMarker = Docs.Create.MapMarkerDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {}); + const mapMarker = Docs.Create.PushpinDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {}); this.addDocument(mapMarker, this.annotationKey); }; _loadPending = true; - /** - * store a reference to google map instance - * setup the drawing manager on the top right corner of map - * fit map bounds to contain all markers - * @param map - */ - @action - private loadHandler = (map: google.maps.Map) => { - this._map = map; - this._loadPending = true; - const centerControlDiv = this.CenterControl(); - map.controls[google.maps.ControlPosition.TOP_RIGHT].push(centerControlDiv); - //drawingManager.setMap(map); - // if (navigator.geolocation) { - // navigator.geolocation.getCurrentPosition( - // (position: Position) => { - // const pos = { - // lat: position.coords.latitude, - // lng: position.coords.longitude, - // }; - // this._map.setCenter(pos); - // } - // ); - // } else { - // alert("Your geolocation is not supported by browser.") - // }; - map.setZoom(NumCast(this.dataDoc.mapZoom, 2.5)); - map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng))); - setTimeout(() => { - if (this._loadPending && this._map.getBounds()) { - this._loadPending = false; - this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map); - } - }, 250); - // listener to addmarker event - this._map.addListener('click', (e: MouseEvent) => { - if (this.toggleAddMarker === true) { - this.placeMarker((e as any).latLng, map); - } - }); - }; @action centered = () => { @@ -298,25 +203,49 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; /** - * Load and render all map markers - * @param marker - * @param place + * function that reads the place inputed from searchbox, then zoom in on the location that's been autocompleted; + * add a customized temporary marker on the map */ @action - private markerLoadHandler = (marker: google.maps.Marker, place: Doc) => { - place[Id] ? (this.markerMap[place[Id]] = marker) : null; - }; + private handlePlaceChanged = () => { + const place = this.searchBox.getPlace(); - /** - * on clicking the map marker, set the selected place to the marker document & set infowindowopen to be true - * @param e - * @param place - */ - @action - private markerClickHandler = (e: google.maps.MapMouseEvent, place: Doc) => { - // set which place was clicked - this.selectedPlace = place; - place.infoWindowOpen = true; + 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; + } + + // zoom in on the location of the search result + if (place.geometry.viewport) { + this._map.fitBounds(place.geometry.viewport); + } else { + this._map.setCenter(place.geometry.location); + this._map.setZoom(17); + } + + // customize icon => customized icon for the nature of the location selected + 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), + }; + + // put temporary cutomized marker on searched location + this.searchMarkers.forEach(marker => { + marker.setMap(null); + }); + this.searchMarkers = []; + this.searchMarkers.push( + new window.google.maps.Marker({ + map: this._map, + icon, + title: place.name, + position: place.geometry.location, + }) + ); }; /** @@ -335,7 +264,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (existingMarker) { Doc.AddDocToList(existingMarker, 'data', doc); } else { - const marker = Docs.Create.MapMarkerDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {}); + const marker = Docs.Create.PushpinDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {}); this.addDocument(marker, this.annotationKey); } } @@ -351,7 +280,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps * @returns */ sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => { - if (this.layoutDoc._layout_showSidebar) this.toggleSidebar(); + // if (this.layoutDoc._layout_showSidebar) this.toggleSidebar(); const docs = doc instanceof Doc ? [doc] : doc; return this.removeDocument(doc, sidebarKey); }; @@ -373,11 +302,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const fullWidth = this.layoutDoc[Width](); const mapWidth = fullWidth - this.sidebarWidth(); if (this.sidebarWidth() + localDelta[0] > 0) { - this._showSidebar = true; + this.layoutDoc._layout_showSidebar = true; this.layoutDoc._width = fullWidth + localDelta[0]; this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%'; } else { - this._showSidebar = false; + this.layoutDoc._layout_showSidebar = false; this.layoutDoc._width = mapWidth; this.layoutDoc._layout_sidebarWidthPercent = '0%'; } @@ -397,52 +326,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } /** - * function that reads the place inputed from searchbox, then zoom in on the location that's been autocompleted; - * add a customized temporary marker on the map - */ - @action - private handlePlaceChanged = () => { - 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; - } - - // zoom in on the location of the search result - if (place.geometry.viewport) { - this._map.fitBounds(place.geometry.viewport); - } else { - this._map.setCenter(place.geometry.location); - this._map.setZoom(17); - } - - // customize icon => customized icon for the nature of the location selected - 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), - }; - - // put temporary cutomized marker on searched location - this.searchMarkers.forEach(marker => { - marker.setMap(null); - }); - this.searchMarkers = []; - this.searchMarkers.push( - new window.google.maps.Marker({ - map: this._map, - icon, - title: place.name, - position: place.geometry.location, - }) - ); - }; - - /** * Handles toggle of sidebar on click the little comment button */ @computed get sidebarHandle() { @@ -471,6 +354,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); }; + createNoteAnnotation = () => { + !this.layoutDoc.layout_showSidebar && this.toggleSidebar(); + + setTimeout(undoable(() => { + const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false)); + if (note && this.selectedPin) { + note.latitude = this.selectedPin.latitude; + note.longitude = this.selectedPin.latitude; + } + },"create note annotation")) + + } sidebarDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true); }; @@ -503,7 +398,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; @action finishMarquee = (x?: number, y?: number) => { this._marqueeing = undefined; - this._isAnnotating = false; x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false); }; @@ -525,7 +419,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ); } - getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => AnchorMenu.Instance?.GetAnchor(this._savedAnnotations, addAsAnnotation) ?? this.rootDoc; + // Old get anchor function + // getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => AnchorMenu.Instance?.GetAnchor(this._savedAnnotations, addAsAnnotation) ?? this.rootDoc; /** * render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker @@ -537,14 +432,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps )); }; - // TODO: auto center on select a document in the sidebar - private handleMapCenter = (map: google.maps.Map) => { - // console.log("print the selected views in selectionManager:") - // if (SelectionManager.Views().lastElement()) { - // console.log(SelectionManager.Views().lastElement()); - // } - }; - panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); @@ -590,14 +477,460 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }); }; + /** + * + * + * ERIC'S BING MAP CODE BELOW + * + * + * + **/ + @observable + bingSearchBarContents: any = 'enter city/zip/...'; // For Bing Maps: The contents of the Bing search bar (string) + + geoDataRequestOptions = { + entityType: 'PopulatedPlace', + }; + + // incrementer: number = 0; + /* + * Creates Pushpin doc and adds it to the list of annotations + */ + @action + createPushpin = undoable((latitude: number, longitude: number) => { + // Stores the pushpin as a MapMarkerDocument + const mapMarker = Docs.Create.PushpinDocument( + NumCast(latitude), + NumCast(longitude), + false, + [], + {} + // ,'pushpinIDamongus'+ this.incrementer++ + ); + this.addDocument(mapMarker, this.annotationKey); + + // mapMarker.infoWindowOpen = true; + },"createpin"); + + /* + * Pushpin dblclick + */ + // @action + // pushpinDblClicked = (pin: any, pinDoc?: Doc) => { + // if (pinDoc) this.removePushpin(pinDoc); + // else this._bingMap.current.entities.remove(pin); + // }; + + // The pin that is selected + @observable + selectedPin: Doc | undefined; + + @action + deselectPin = () => { + if (this.selectedPin) { + // Removes filter + Doc.setDocFilter(this.rootDoc, "latitude", this.selectedPin.latitude, "remove"); + Doc.setDocFilter(this.rootDoc, "longitude", this.selectedPin.longitude, "remove"); + + const temp = this.selectedPin; + this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp)); + const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude)); + this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(temp as Doc)); + this._bingMap.current.entities.push(newpin); + this.map_docToPinMap.set(temp, newpin); + this.selectedPin = undefined; + + + } + + }; + + getView = async (doc: Doc) => { + if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(); + return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); + }; + /* + * Pushpin onclick + */ + @action + pushpinClicked = (pinDoc: Doc) => { + this.deselectPin(); + this.selectedPin = pinDoc; + + Doc.setDocFilter(this.rootDoc, "latitude", this.selectedPin.latitude, "match"); + Doc.setDocFilter(this.rootDoc, "longitude", this.selectedPin.latitude, "match"); + + this._bingMap.current.entities.remove(this.map_docToPinMap.get(this.selectedPin)); + const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude), { + color: 'green', + }); + this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(this.selectedPin as Doc)); + this._bingMap.current.entities.push(newpin); + this.map_docToPinMap.set(this.selectedPin, newpin); + + MapAnchorMenu.Instance.Delete = this.deleteSelectedPin; + MapAnchorMenu.Instance.Center = this.centerOnSelectedPin; + MapAnchorMenu.Instance.LinkNote = this.createNoteAnnotation; + + const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude)); + const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2; + const y = point.y + this.props.PanelHeight() / 2 + 32; + const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y); + MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true); + + document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true); + this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'click', this.mapOnClick); + + // Filter sidebar: + + }; + + /** + * Returns a list of Pushpin docs + */ + @computed get allMapPushpins() { + return DocListCast(this.dataDoc[this.annotationKey]); + } + + /** + * Map OnClick + */ + @action + mapOnClick = (e: { location: { latitude: any; longitude: any } }) => { + if (this.selectedPin) { + this.deselectPin(); + this.MicrosoftMaps.Events.removeEventListener(this._bingMap.current, 'click', this.mapOnClick); + } + }; + + /* + * Updates values of layout doc to match the current map + */ + @action + updateLayout = () => { + this.dataDoc.latitude = this._bingMap.current.getCenter().latitude; + this.dataDoc.longitude = this._bingMap.current.getCenter().longitude; + this.dataDoc.mapZoom = this._bingMap.current.getZoom(); + // if(this.dataDoc.mapType == 'x'){ + // this.dataDoc.locationToLookAt + // } + // this.dataDoc.mapType = new this.MicrosoftMaps.MapTypeId(); + }; + /* + * Updates maptype + */ + @action + updateMapType = () => { + this.dataDoc.mapType = this._bingMap.current.getMapTypeId(); + }; + + searched_pin: any; + /* + * For Bing Maps + * Called by search button's onClick + * Finds the geocode of the searched contents and sets location to that location + **/ + @action + bingSearch = async () => { + const location = await this.bingGeocode(this._bingMap, this.bingSearchBarContents); + this.dataDoc.latitude = location.latitude; + this.dataDoc.longitude = location.longitude; + this.dataDoc.mapZoom = this._bingMap.current.getZoom(); + // Creates a temporary pin but does not add it to the dataDoc + this.createPushpin(this.dataDoc.latitude, this.dataDoc.longitude); + }; + + /** + * Adds all pushpins in dataDoc onto the map (render) - OLD & UNUSED + */ + // @action + // addAllPins = () => { + // this._bingMap.current.entities.clear(); + // if (this.searched_pin) this._bingMap.current.entities.push(this.searched_pin); + // // this.allMapPushpins.map(pin => this.addPushpin(pin)); + // }; + + /* + * Returns doc w/ relevant info + */ + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { + /// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER + const anchor = Docs.Create.MapanchorDocument({ + title: 'MapAnchor:' + this.rootDoc.title, + config_latitude: NumCast(this.selectedPin?.latitude ?? this.dataDoc.latitude), + config_longitude: NumCast(this.selectedPin?.longitude ?? this.dataDoc.longitude), + config_mapZoom: NumCast(this.dataDoc.mapZoom), + config_mapType: StrCast(this.dataDoc.mapType), + // preslocationToLookAt:this.dataDoc.locationToLookAt, + // presType: + layout_unrendered: true, + annotationOn: this.rootDoc, + }); + if (anchor) { + if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; + /* addAsAnnotation &&*/ this.addDocument(anchor); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.rootDoc); + return anchor; + } + return this.rootDoc; + }; + + map_docToPinMap = new Map<Doc, any>(); + /* + * Input: pin doc + * Adds MicrosoftMaps Pushpin to the map (render) + */ + @action + addPushpin = (pin: Doc) => { + const pushPin = pin.infoWindowOpen + ? new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), {}) + : new this.MicrosoftMaps.Pushpin( + new this.MicrosoftMaps.Location(pin.latitude, pin.longitude) + // {icon: 'http://icons.iconarchive.com/icons/icons-land/vista-map-markers/24/Map-Marker-Marker-Outside-Chartreuse-icon.png'} + ); + + this._bingMap.current.entities.push(pushPin); + + this.MicrosoftMaps.Events.addHandler(pushPin, 'click', (e: any) => this.pushpinClicked(pin)); + // this.MicrosoftMaps.Events.addHandler(pushPin, 'dblclick', (e: any) => this.pushpinDblClicked(pushPin, pin)); + this.map_docToPinMap.set(pin, pushPin); + }; + + @observable + pinIsSelected_TEMPORARY: boolean = false; // toggles if remove pin button appears + + /* + * Input: pin doc + * Removes pin from annotations + */ + @action + removePushpin = (pinDoc: Doc) => { + // this.allMapPushpins + // this.allMapPushpins.map(pin => this.addPushpin(pin)); + // this._bingMap.current.entities.clear(); + + this.removeDocument(pinDoc, this.annotationKey); + + // this.dataDoc[this.annotationKey] + }; + /* + * Removes pushpin from map render + */ + deletePushpin = (pinDoc: Doc) => { + this._bingMap.current.entities.remove(this.map_docToPinMap.get(pinDoc)); + this.map_docToPinMap.delete(pinDoc); + this.selectedPin = undefined; + + }; + + @action + deleteSelectedPin = undoable(() => { + if (this.selectedPin) { + // Removes filter + Doc.setDocFilter(this.rootDoc, "latitude", this.selectedPin.latitude, "remove"); + Doc.setDocFilter(this.rootDoc, "longitude", this.selectedPin.longitude, "remove"); + + this.removePushpin(this.selectedPin); + } + MapAnchorMenu.Instance.fadeOut(true); + document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); + }, 'delete pin'); + + tryHideMapAnchorMenu = (e: PointerEvent) => { + let target = document.elementFromPoint(e.x, e.y); + + while (target != null) { + if (target === MapAnchorMenu.top.current) { + return; + } + target = target.parentElement; + } + e.stopPropagation(); + e.preventDefault(); + MapAnchorMenu.Instance.fadeOut(true); + document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); + }; + + // tryhidemenu = e => if( e.parent... == mapanchormenu.top.currrent) do nothing; else hide menu + + @action + centerOnSelectedPin = () => { + if (this.selectedPin) { + this.dataDoc.latitude = this.selectedPin.latitude; + this.dataDoc.longitude = this.selectedPin.longitude; + } + MapAnchorMenu.Instance.fadeOut(true); + document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu); + }; + + /** + * View options for bing maps + */ bingViewOptions = { - center: { latitude: defaultCenter.lat, longitude: defaultCenter.lng }, + // center: { latitude: this.dataDoc.latitude ?? defaultCenter.lat, longitude: this.dataDoc.longitude ?? defaultCenter.lng }, + zoom: this.dataDoc.latitude ?? 10, mapTypeId: 'grayscale', }; + + /** + * Map options + */ bingMapOptions = { navigationBarMode: 'square', + backgroundColor: '#f1f3f4', + enableInertia: true, + supportedMapTypes: ['grayscale', 'canvasLight'], + disableMapTypeSelectorMouseOver: true, + // showScalebar:true + // disableRoadView:true, + // disableBirdseye:true + streetsideOptions: { + showProblemReporting: false, + showCurrentAddress: false, + }, + }; + + /** + * Toggles mode for placing a pin down on map + */ + @observable + placePinOn = false; + @action + togglePlacePin = () => { + if (this.placePinOn) this.placePinOn = false; + else this.placePinOn = true; + }; + + @action + searchbarOnEdit = (newText: string) => { + this.bingSearchBarContents = newText; + }; + + /* + * Called when BingMap is first rendered + * Initializes starting values + */ + @observable _mapReady = false; + @action + bingMapReady = (map: any) => { + this._mapReady = true; + this._bingMap = map.map; + if (!this._bingMap.current) { + alert('NO Map!?'); + } + this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'viewchangeend', undoable(this.updateLayout, 'Map Layout Change')); + this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'maptypechanged', undoable(this.updateMapType, 'Map ViewType Change')); + this.updateLayout(); + this._disposer.highlight = reaction( () => this.allMapPushpins.map(doc => doc[Highlight]), + () => this.allMapPushpins.forEach(doc => { + + // if(doc[Highlight]){ + // this.deselectPin(); + // this.selectedPin = doc; + + // Doc.setDocFilter(this.rootDoc, "latitude", this.selectedPin.latitude, "match"); + // Doc.setDocFilter(this.rootDoc, "longitude", this.selectedPin.latitude, "match"); + + // this._bingMap.current.entities.remove(this.map_docToPinMap.get(this.selectedPin)); + // const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude), { + // color: 'green', + // }); + // this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(this.selectedPin as Doc)); + // this._bingMap.current.entities.push(newpin); + // this.map_docToPinMap.set(this.selectedPin, newpin); + + // MapAnchorMenu.Instance.Delete = this.deleteSelectedPin; + // MapAnchorMenu.Instance.Center = this.centerOnSelectedPin; + // MapAnchorMenu.Instance.LinkNote = this.createNoteAnnotation; + // } + // if (doc[Highlight]) { + // this._bingMap.current.entities.remove(this.map_docToPinMap.get(doc)); + // const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(doc.latitude, doc.longitude), { + // color: 'orange', + // }); + // this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(doc)); + // this._bingMap.current.entities.push(newpin); + // this.map_docToPinMap.set(doc, newpin); + + // } + // if (this.map_docToPinMap.get(doc).getColor() == 'orange' && !doc[Highlight]) { + // this._bingMap.current.entities.remove(this.map_docToPinMap.get(doc)); + // const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(doc.latitude, doc.longitude), {}); + // this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(doc)); + // this._bingMap.current.entities.push(newpin); + // this.map_docToPinMap.set(doc, newpin); + + // } + // else if (this.map_docToPinMap.get(doc).getColor() != 'orange' && doc[Highlight]) { + // this._bingMap.current.entities.remove(this.map_docToPinMap.get(doc)); + // const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(doc.latitude, doc.longitude), { + // color: 'orange', + // }); + // this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(doc)); + // this._bingMap.current.entities.push(newpin); + // this.map_docToPinMap.set(doc, newpin); + // } + + + }) + , {fireImmediately: true}) + // this.updateMapType(); + this._disposer.location = reaction( + () => ({ lat: this.rootDoc.latitude, lng: this.rootDoc.longitude, zoom: this.rootDoc.mapZoom, mapType: this.rootDoc.mapType }), + locationObject => { + // if (this._bingMap.current) + try { + locationObject?.zoom && + this._bingMap.current?.setView({ + mapTypeId: locationObject.mapType, + zoom: locationObject.zoom, + center: new this.MicrosoftMaps.Location(locationObject.lat, locationObject.lng), + }); + } catch (e) { + console.log(e); + } + }, + { fireImmediately: true } + ); + }; + + // Keeps track of when dragging a pin onto map + draggingPin = false; + dragToggle = (e: React.PointerEvent) => { + // console.log('DRAGGING TOGGLE'); + document.addEventListener('drop', this.dropPin, true); + document.addEventListener('pointermove', this.pinMove, true); + e.stopPropagation(); + }; + pinMove = (e: PointerEvent) => { + // console.log('MOVING'); + e.stopPropagation(); + }; + dropPin = (e: DragEvent) => { + e.stopPropagation(); + e.preventDefault(); + document.removeEventListener('drop', this.dropPin, true); + document.removeEventListener('pointermove', this.pinMove, true); + let target = document.elementFromPoint(e.x, e.y); + + while (target != null) { + if (target === this._ref.current) { + const cpt = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); + const x = cpt[0] - (this.props.PanelWidth() - this.sidebarWidth()) / 2; + const y = cpt[1] - 32 /* height of search bar */ - this.props.PanelHeight() / 2; + const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y)); + this.createPushpin(location.latitude, location.longitude); + break; + } + target = target.parentElement; + } + }; + + searchbarKeyDown = (e: any) => { + if (e.key === 'Enter') { + this.bingSearch(); + } }; - bingMapReady = (map: any) => (this._bingMap = map.map); render() { const renderAnnotations = (childFilters?: () => string[]) => null; return ( @@ -621,33 +954,85 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps {SnappingManager.GetIsDragging() ? null : renderAnnotations()} {this.annotationLayer} - {!MapBox.UseBing ? null : <BingMapsReact onMapReady={this.bingMapReady} bingMapsKey={bingApiKey} height="100%" mapOptions={this.bingMapOptions} width="100%" viewOptions={this.bingViewOptions} />} - <div style={{ display: MapBox.UseBing ? 'none' : undefined }}> - <GoogleMap mapContainerStyle={mapContainerStyle} onZoomChanged={this.zoomChanged} onCenterChanged={this.centered} onLoad={this.loadHandler} options={mapOptions}> - <Autocomplete onLoad={this.setSearchBox} onPlaceChanged={this.handlePlaceChanged}> - <input className="mapBox-input" ref={this.inputRef} type="text" onKeyDown={e => e.stopPropagation()} placeholder="Enter location" /> - </Autocomplete> - - {this.renderMarkers()} - {this.allMapMarkers - .filter(marker => marker.infoWindowOpen) - .map(marker => ( - <MapBoxInfoWindow - key={marker[Id]} - {...this.props} - setContentView={emptyFunction} - place={marker} - markerMap={this.markerMap} - PanelWidth={this.infoWidth} - PanelHeight={this.infoHeight} - moveDocument={this.moveDocument} - isAnyChildContentActive={this.isAnyChildContentActive} - whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - /> - ))} - {/* {this.handleMapCenter(this._map)} */} - </GoogleMap> + <div className="mapBox-searchbar"> + <EditableText + // editing + setVal={(newText: string | number) => typeof newText === 'string' && this.searchbarOnEdit(newText)} + onEnter={e => this.bingSearch()} + placeholder={this.bingSearchBarContents} + textAlign="center" + /> + <IconButton + icon={ + <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF"> + <path + fill="currentColor" + d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path> + </svg> + } + onClick={this.bingSearch} + onDoubleClick={function noRefCheck() {}} + onPointerDown={function noRefCheck() {}} + onPointerDownCapture={function noRefCheck() {}} + onPointerMove={function noRefCheck() {}} + onPointerMoveCapture={function noRefCheck() {}} + onPointerUp={function noRefCheck() {}} + type={Type.TERT} + /> + <div draggable={true} style={{ width: 30, height: 30 }} onPointerDown={this.dragToggle}> + <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} /> + </div> + </div> + + <BingMapsReact + onMapReady={this.bingMapReady} + bingMapsKey={bingApiKey} + height="100%" + mapOptions={this.bingMapOptions} + width="100%" + viewOptions={this.bingViewOptions} + onPointerUp + {...() => (this.draggingPin = false)}></BingMapsReact> + <div> + {!this._mapReady + ? null + : this.allMapPushpins.map(pushpin => ( + <DocumentView + {...this.props} + Document={pushpin} + DataDoc={undefined} + PanelWidth={returnOne} + PanelHeight={returnOne} + NativeWidth={returnOne} + NativeHeight={returnOne} + onClick={() => new ScriptField(undefined)} + onKey={undefined} + onDoubleClick={undefined} + onBrowseClick={undefined} + childFilters={returnEmptyFilter} + childFiltersByRanges={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + isDocumentActive={returnFalse} + isContentActive={returnFalse} + addDocTab={returnFalse} + ScreenToLocalTransform={() => new Transform(0, 0, 0)} + fitContentsToBox={undefined} + focus={returnOne} + /> + ))} </div> + {/* <MapBoxInfoWindow + key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]} + {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} + place={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})} + markerMap={this.markerMap} + PanelWidth={this.infoWidth} + PanelHeight={this.infoHeight} + moveDocument={this.moveDocument} + isAnyChildContentActive={this.isAnyChildContentActive} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} + /> */} + {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( <MarqueeAnnotator rootDoc={this.rootDoc} @@ -666,7 +1051,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps )} </div> {/* </LoadScript > */} - <div className="mapBox-sidebar" style={{ position: 'absolute', right: 0, height: '100%', width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> + <div className="mapBox-sidebar" style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> <SidebarAnnos ref={this._sidebarRef} {...this.props} |