diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 28 | ||||
-rw-r--r-- | src/client/views/nodes/MapBox/MapBox.tsx | 163 | ||||
-rw-r--r-- | src/client/views/nodes/MapBox/MapMarker.tsx | 4 |
3 files changed, 149 insertions, 46 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9740f81ef..c2d1fec51 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -156,6 +156,8 @@ export class DocumentOptions { x?: number; y?: number; z?: number; // whether document is in overlay (1) or not (0 or undefined) + lat?: number; + lng?: number; author?: string; _layoutKey?: string; type?: string; @@ -726,8 +728,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.MAP), new List(documents), options); } - export function MapMarkerDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), options, id); + export function MapMarkerDocument(lat: number, lng: number, documents: Array<Doc>, options: DocumentOptions, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), {lat, lng, ...options}, id); } export function KVPDocument(document: Doc, options: DocumentOptions = {}) { @@ -1325,6 +1327,25 @@ export namespace DocUtils { return optionsCollection; } + /** + * + * @param dms Degree Minute Second format exif gps data + * @param ref ref that determines negativity of decimal coordinates + * @returns a decimal format of gps latitude / longitude + */ + function getDecimalfromDMS(dms?: number[], ref?: string) { + if (dms && ref) { + let degrees = dms[0] / dms[1]; + let minutes = dms[2] / dms[3] / 60.0; + let seconds = dms[4] / dms[5] / 3600.0; + + if (['S', 'W'].includes(ref)) { + degrees = -degrees; minutes = -minutes; seconds = -seconds + } + return (degrees + minutes + seconds).toFixed(5); + } + } + async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions) { if (result instanceof Error) { alert(`Upload failed: ${result.message}`); @@ -1347,6 +1368,9 @@ export namespace DocUtils { proto["data-nativeWidth"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); } proto.contentSize = result.contentSize; + // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates + proto.lat = getDecimalfromDMS(result.exifData?.data?.gps.GPSLatitude, result.exifData?.data?.gps.GPSLatitudeRef); + proto.lng = getDecimalfromDMS(result.exifData?.data?.gps.GPSLongitude, result.exifData?.data?.gps.GPSLongitudeRef); } generatedDocuments.push(doc); } diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 56203f3ae..e62f835b2 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -1,19 +1,24 @@ import { Autocomplete, GoogleMap, GoogleMapProps, InfoWindow, Marker } from '@react-google-maps/api'; -import { action, computed, IReactionDisposer, observable } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap } from 'mobx'; import { observer } from "mobx-react"; import * as React from "react"; -import { Doc, DocListCast, WidthSym } from '../../../../fields/Doc'; +import { DataSym, Doc, DocListCast, FieldsSym, WidthSym } from '../../../../fields/Doc'; import { documentSchema } from '../../../../fields/documentSchemas'; import { makeInterface } from '../../../../fields/Schema'; import { NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, setupMoveUpEvents } from '../../../../Utils'; +import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent'; +import { AnchorMenu } from '../../pdf/AnchorMenu'; import { SidebarAnnos } from '../../SidebarAnnos'; import { StyleProp } from '../../StyleProvider'; import { FieldView, FieldViewProps } from '../FieldView'; import "./MapBox.scss"; import { MapMarker } from './MapMarker'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { identity } from 'lodash'; +import { Id } from '../../../../fields/FieldSymbols'; type MapDocument = makeInterface<[typeof documentSchema]>; const MapDocument = makeInterface(documentSchema); @@ -76,7 +81,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @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 childDocs: MapMarker[] = []; + @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); + @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); }; + @computed get allMapMarkers() { return DocListCast(this.dataDoc[this.annotationKey]); }; + @observable private allMarkers: Doc[] = []; @observable _showSidebar = false; @@ -96,17 +104,43 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.searchBox = searchBox; } - // iterate childDocs to size, center, and zoom map to contain all markers + // iterate allMarkers to size, center, and zoom map to contain all markers private fitBounds = (map: google.maps.Map) => { console.log('map bound is:' + this.bounds); - this.childDocs.map(place => { - this.bounds.extend(place._latlngLocation); + this.allMarkers.map(place => { + this.bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }); return place._markerId; }); map.fitBounds(this.bounds) } - // store a reference to google map instance; fit map bounds to contain all markers + + private hasGeolocation = (doc: Doc) => { + return doc.type === DocumentType.IMG + + } + + + /** + * A function that examines allMapMarkers docs in the map node and form MapMarkers + */ + private fillMarkers = () => { + this.allMapMarkers?.forEach(doc => { + // search for if the map marker exists, else create marker + if (doc.lat !== undefined && doc.lng !== undefined) { + const marker = Docs.Create.MapMarkerDocument(NumCast(doc.lat), NumCast(doc.lng), [], {}) + this.allMarkers.push(marker) + } + }) + } + + + /** + * 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; @@ -125,11 +159,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps alert("Your geolocation is not supported by browser.") } this.fitBounds(map); + this.fillMarkers(); } @action - private markerLoadHandler = (marker: google.maps.Marker, place: MapMarker) => { - place._markerId ? this.markerMap[place._markerId] = marker : null; + private markerLoadHandler = (marker: google.maps.Marker, place: Doc) => { + place[Id] ? this.markerMap[place[Id]] = marker : null; } @action @@ -156,7 +191,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps */ sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { if (!this.layoutDoc._showSidebar) this.toggleSidebar(); - return this.addDocument(doc, sidebarKey); + const docs = doc instanceof Doc ? [doc] : doc + docs.forEach(doc => { + if (doc.lat !== undefined && doc.lng !== undefined) { + const marker = Docs.Create.MapMarkerDocument(NumCast(doc.lat), NumCast(doc.lng), [], {}) + this.addDocument(marker, this.annotationKey) + } + }) //add to annotation list + return this.addDocument(doc, sidebarKey); // add to sidebar list } /** @@ -183,6 +225,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } + /** + * 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 = () => { console.log(this.searchBox); @@ -228,6 +274,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ) } + @action private handleInfoWindowClose = () => { if (this.infoWindowOpen) { @@ -246,6 +293,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } public get SidebarKey() { return this.fieldKey + "-sidebar"; } + @computed get sidebarHandle() { const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length; return (!annotated && !this.isContentActive()) ? (null) : <div className="mapBox-sidebar-handle" onPointerDown={this.sidebarDown} @@ -254,6 +302,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps background: this.props.styleProvider?.(this.rootDoc, this.props as any, StyleProp.WidgetColor + (annotated ? ":annotated" : "")) }} />; } + @action toggleSidebar = () => { const prevWidth = this.sidebarWidth(); @@ -271,6 +320,65 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return false; } + // TODO what's the difference between savedAnnotations & allMapMarkers? + getAnchor = () => { + const anchor = + AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ?? + this.rootDoc // if anchormenu pops up else return rootDoc (map) + // Docs.Create.MapMarkerDocument(this.allMapMarkers, { + // title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop), + // annotationOn: this.rootDoc, + // y: NumCast(this.layoutDoc._scrollTop), + // }); + // this.addDocument(anchor); + return anchor; + } + + private saveMarkerInfo = () => { + + } + // create marker prop --> func that + private renderMarkers = () => { + this.allMarkers.map(place => ( + <Marker + key={place[Id]} + position={{ lat: NumCast(place.lat), lng: NumCast(place.lng) }} + onLoad={marker => this.markerLoadHandler(marker, place)} + onClick={e => this.markerClickHandler(e, place)} + /> + )) + } + + private renderInfoWindow = (marker: MapMarker) => { + return this.infoWindowOpen && this.selectedPlace && ( + <InfoWindow + anchor={this.markerMap[this.selectedPlace._markerId!]} + onCloseClick={this.handleInfoWindowClose} + > + <div style={{ backgroundColor: 'white', opacity: 0.75, padding: 12 }}> + <div style={{ fontSize: 16 }}> + <div> + {// TODO need to figure out how to render these childDocs of the MapMarker in InfoWindow + marker.childDocs} + <hr /> + <form> + <label>Title: </label><br /> + <input type="text" id="title" name="title"></input><br /> + <label>Desription: </label><br /> + <textarea style={{ height: 150 }} id="description" name="description" placeholder="Notes, a short description of this location, a brief comment, etc."></textarea> + <button type="submit">Save</button> + </form> + <hr /> + <div> + <button>New link+</button> + </div> + </div> + </div> + </div> + </InfoWindow> + ) + } + render() { return <div className="mapBox" ref={this._ref} style={{ pointerEvents: this.isContentActive() ? undefined : "none" }} > @@ -295,39 +403,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps <input ref={this.inputRef} className="searchbox" type="text" placeholder="Search anywhere:" /> </Autocomplete> - {this.childDocs.map(place => ( - <Marker - key={place._markerId} - position={place._latlngLocation} - onLoad={marker => this.markerLoadHandler(marker, place)} - onClick={e => this.markerClickHandler(e, place)} - /> - ))} - {this.infoWindowOpen && this.selectedPlace && ( - <InfoWindow - anchor={this.markerMap[this.selectedPlace._markerId!]} - onCloseClick={this.handleInfoWindowClose} - > - <div style={{ backgroundColor: 'white', opacity: 0.75, padding: 12 }}> - <div style={{ fontSize: 16 }}> - <div> - <img src="http://placekitten.com/200/300" /> - <hr /> - <form> - <label>Title: </label><br /> - <input type="text" id="fname" name="fname"></input><br /> - <label>Desription: </label><br /> - <textarea style={{ height: 150 }} id="lname" name="lname" placeholder="Notes, a short description of this location, a brief comment, etc."></textarea> - </form> - <hr /> - <div> - <button>New link+</button> - </div> - </div> - </div> - </div> - </InfoWindow> - )} + {this.renderMarkers} + {this.renderInfoWindow} </GoogleMap> </div> {/* {/* </LoadScript > */} diff --git a/src/client/views/nodes/MapBox/MapMarker.tsx b/src/client/views/nodes/MapBox/MapMarker.tsx index 34057cf48..fbad0cf65 100644 --- a/src/client/views/nodes/MapBox/MapMarker.tsx +++ b/src/client/views/nodes/MapBox/MapMarker.tsx @@ -17,7 +17,6 @@ import { AnchorMenu } from "../../pdf/AnchorMenu"; import { FieldView, FieldViewProps } from "../FieldView"; import { FormattedTextBox } from "../formattedText/FormattedTextBox"; import { RichTextMenu } from "../formattedText/RichTextMenu"; -import { PresMovement } from "../PresBox"; type MarkerDocument = makeInterface<[typeof documentSchema]>; const MarkerDocument = makeInterface(documentSchema); @@ -27,6 +26,7 @@ export type Coordinates = { lng: number, } +//TODO: MapMarkerBox @observer export class MapMarker extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, MarkerDocument>(MarkerDocument) { makeLinkAnchor(arg1: string, undefined: undefined, arg3: string) { @@ -52,6 +52,8 @@ export class MapMarker extends ViewBoxAnnotatableComponent<ViewBoxAnnotatablePro return tagDocs; } + @computed get lat() { return NumCast(this.dataDoc.lat) } + @computed get lng() { return NumCast(this.dataDoc.lng) } /** * Methods |