diff options
Diffstat (limited to 'src/client/views/nodes/MapBox/MapBox.tsx')
-rw-r--r-- | src/client/views/nodes/MapBox/MapBox.tsx | 992 |
1 files changed, 425 insertions, 567 deletions
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index cde68a2e6..8b5858e28 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -3,58 +3,56 @@ import BingMapsReact from 'bingmaps-react'; // import 'mapbox-gl/dist/mapbox-gl.css'; import { Button, EditableText, IconButton, Size, Type } from 'browndash-components'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS} from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, makeObservable, flow, toJS } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils'; import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc'; import { DocCss, Highlight } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils'; import { Docs, DocUtils } from '../../../documents/Documents'; import { DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { LinkManager } from '../../../util/LinkManager'; -import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; -import { undoable, UndoManager } from '../../../util/UndoManager'; -import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; +import { UndoManager, undoable } from '../../../util/UndoManager'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent'; -import { Colors } from '../../global/globalEnums'; import { SidebarAnnos } from '../../SidebarAnnos'; +import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; +import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { FormattedTextBox } from '../formattedText/FormattedTextBox'; import { PinProps, PresBox } from '../trails'; import { MapAnchorMenu } from './MapAnchorMenu'; -import { +import { Map as MapboxMap, MapRef, - Marker, - ControlPosition, - FullscreenControl, - MapProvider, - MarkerProps, - NavigationControl, - ScaleControl, - ViewState, + Marker, + ControlPosition, + FullscreenControl, + MapProvider, + MarkerProps, + NavigationControl, + ScaleControl, + ViewState, ViewStateChangeEvent, useControl, GeolocateControl, Popup, MapEvent, Source, - Layer} from 'react-map-gl'; -import MapboxGeocoder, {GeocoderOptions} from '@mapbox/mapbox-gl-geocoder'; -import debounce from 'debounce'; + Layer, +} from 'react-map-gl'; import './MapBox.scss'; import { NumberLiteralType } from 'typescript'; // import { GeocoderControl } from './GeocoderControl'; -import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent, MercatorCoordinate } from 'mapbox-gl'; +import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent, MercatorCoordinate } from 'mapbox-gl!'; import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, MultiLineString, Position } from 'geojson'; import { MarkerEvent } from 'react-map-gl/dist/esm/types'; -import { MapboxApiUtility, TransportationType} from './MapboxApiUtility'; +import { MapboxApiUtility, TransportationType } from './MapboxApiUtility'; import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; @@ -62,7 +60,7 @@ import { IconLookup, faCircleXmark, faFileExport, faGear, faPause, faPlay, faRot import { MarkerIcons } from './MarkerIcons'; import { SettingsManager } from '../../../util/SettingsManager'; import * as turf from '@turf/turf'; -import * as d3 from "d3"; +import * as d3 from 'd3'; import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility'; import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons'; @@ -87,24 +85,24 @@ const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/map const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/'; type PopupInfo = { - longitude: number, - latitude: number, - title: string, - description: string -} + longitude: number; + latitude: number; + title: string; + description: string; +}; -export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & { - mapboxAccessToken: string, - marker?: Omit<MarkerProps, 'longitude' | 'latitude'>; - position: ControlPosition; +// export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & { +// mapboxAccessToken: string; +// marker?: Omit<MarkerProps, 'longitude' | 'latitude'>; +// position: ControlPosition; - onResult: (...args: any[]) => void; -} +// onResult: (...args: any[]) => void; +// }; type MapMarker = { - longitude: number, - latitude: number -} + longitude: number; + latitude: number; +}; /** * Consider integrating later: allows for drawing, circling, making shapes on map @@ -132,7 +130,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps private _ref: React.RefObject<HTMLDivElement> = React.createRef(); private _mapRef: React.RefObject<MapRef> = React.createRef(); private _disposers: { [key: string]: IReactionDisposer } = {}; - private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void); + + constructor(props: any) { + super(props); + makeObservable(this); + } + + _unmounting = false; @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @computed get allSidebarDocs() { @@ -166,7 +170,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return { type: 'Feature', properties: { - 'routeTitle': StrCast(this.routeToAnimate.title) + routeTitle: StrCast(this.routeToAnimate.title), }, geometry: geometry, }; @@ -175,15 +179,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const startCoord = originalCoordinates[startIndex]; const endCoord = originalCoordinates[endIndex]; const fraction = index - startIndex; - + // Interpolate the coordinates - const interpolatedCoord = [ - startCoord[0] + fraction * (endCoord[0] - startCoord[0]), - startCoord[1] + fraction * (endCoord[1] - startCoord[1]), - ]; - + const interpolatedCoord = [startCoord[0] + fraction * (endCoord[0] - startCoord[0]), startCoord[1] + fraction * (endCoord[1] - startCoord[1])]; + const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]); - + const geometry: LineString = { type: 'LineString', coordinates, @@ -191,7 +192,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return { type: 'Feature', properties: { - 'routeTitle': StrCast(this.routeToAnimate.title) + routeTitle: StrCast(this.routeToAnimate.title), }, geometry: geometry, }; @@ -202,13 +203,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps properties: {}, geometry: { type: 'LineString', - coordinates: [] + coordinates: [], }, }; } @computed get selectedRouteCoordinates(): Position[] { let coordinates: Position[] = []; - if (this.routeToAnimate?.routeCoordinates){ + if (this.routeToAnimate?.routeCoordinates) { coordinates = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates)); } return coordinates; @@ -224,7 +225,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return { type: 'Feature', properties: { - 'routeTitle': routeDoc.title}, + routeTitle: routeDoc.title, + }, geometry: geometry, }; }); @@ -242,7 +244,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } @computed get sidebarColor() { - return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4')); + return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4')); } @computed get SidebarKey() { return this.fieldKey + '_sidebar'; @@ -250,12 +252,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps componentDidMount() { this._unmounting = false; - this.props.setContentView?.(this); - + this._props.setContentView?.(this); } - - _unmounting = false; componentWillUnmount(): void { this._unmounting = true; this.deselectPinOrRoute(); @@ -317,9 +316,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps e, (e, down, delta) => runInAction(() => { - const localDelta = this.props + const localDelta = this._props .ScreenToLocalTransform() - .scale(this.props.NativeDimScaling?.() || 1) + .scale(this._props.NativeDimScaling?.() || 1) .transformDirection(delta[0], delta[1]); const fullWidth = NumCast(this.layoutDoc._width); const mapWidth = fullWidth - this.sidebarWidth(); @@ -338,7 +337,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map') ); }; - sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth(); + sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth(); /** * Handles toggle of sidebar on click the little comment button @@ -350,8 +349,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps key="sidebar" title="Toggle Sidebar" style={{ - display: !this.props.isContentActive() ? 'none' : undefined, - top: StrCast(this.rootDoc._layout_showTitle) === 'title' ? 20 : 5, + display: !this._props.isContentActive() ? 'none' : undefined, + top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5, backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK, }} onPointerDown={this.sidebarBtnDown}> @@ -383,16 +382,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }); const targetCreator = (annotationOn: Doc | undefined) => { - const target = DocUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow'); - FormattedTextBox.SelectOnLoad = target[Id]; + const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow'); + FormattedTextBox.SetSelectOnLoad(target); return target; }; - const docView = this.props.DocumentView?.(); + const docView = this._props.DocumentView?.(); docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { dragComplete: e => { if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { - e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document; + e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document; e.annoDragData.linkSourceDoc.followLinkZoom = false; } }, @@ -427,19 +426,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return false; }; - setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func); - addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey); - pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none'); + pointerEvents = () => (this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none'); - 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)); - transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter]; - opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter]; - infoWidth = () => this.props.PanelWidth() / 5; - infoHeight = () => this.props.PanelHeight() / 5; + 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)); + transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter]; + infoWidth = () => this._props.PanelWidth() / 5; + infoHeight = () => this._props.PanelHeight() / 5; anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; savedAnnotations = () => this._savedAnnotations; @@ -477,7 +474,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; @observable - bingSearchBarContents: any = this.rootDoc.map; // For Bing Maps: The contents of the Bing search bar (string) + bingSearchBarContents: any = this.dataDoc.map; // For Bing Maps: The contents of the Bing search bar (string) geoDataRequestOptions = { entityType: 'PopulatedPlace', @@ -486,15 +483,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // The pin that is selected @observable selectedPinOrRoute: Doc | undefined; - @action deselectPinOrRoute = () => { if (this.selectedPinOrRoute) { // // Removes filter - // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove'); - // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove'); - // Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove'); - + // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove'); + // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove'); + // Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove'); // const temp = this.selectedPin; // if (!this._unmounting) { // this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp)); @@ -506,11 +501,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // } // this.map_docToPinMap.set(temp, newpin); // this.selectedPin = undefined; - // this.bingSearchBarContents = this.rootDoc.map; + // this.bingSearchBarContents = this.Document.map; } }; - 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))); @@ -524,9 +518,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.selectedPinOrRoute = pinDoc; this.bingSearchBarContents = pinDoc.map; - // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match'); - // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match'); - Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); + // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match'); + // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match'); + Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); this.recolorPin(this.selectedPinOrRoute, 'green'); @@ -536,9 +530,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag; const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPinOrRoute.latitude, this.selectedPinOrRoute.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); + 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); @@ -549,7 +543,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps */ @action mapOnClick = (e: { location: { latitude: any; longitude: any } }) => { - this.props.select(false); + this._props.select(false); this.deselectPinOrRoute(); }; /* @@ -592,11 +586,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps /* * Returns doc w/ relevant info */ + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps, existingPin?: Doc) => { /// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER const anchor = Docs.Create.ConfigDocument({ - title: 'MapAnchor:' + this.rootDoc.title, - text: StrCast(this.selectedPinOrRoute?.map) || StrCast(this.rootDoc.map) || 'map location', + title: 'MapAnchor:' + this.Document.title, + text: StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location', config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude), config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude), config_map_zoom: NumCast(this.dataDoc.map_zoom), @@ -604,15 +599,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps config_map: StrCast((existingPin ?? this.selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map), layout_unrendered: true, mapPin: existingPin ?? this.selectedPinOrRoute, - annotationOn: this.rootDoc, + annotationOn: this.Document, }); if (anchor) { if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; addAsAnnotation && this.addDocument(anchor); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.rootDoc); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.Document); return anchor; } - return this.rootDoc; + return this.Document; }; map_docToPinMap = new Map<Doc, any>(); @@ -659,9 +654,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps deleteSelectedPinOrRoute = undoable(() => { if (this.selectedPinOrRoute) { // Removes filter - Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPinOrRoute.latitude, 'remove'); - Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPinOrRoute.longitude, 'remove'); - Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPinOrRoute))}`, 'remove'); + Doc.setDocFilter(this.Document, 'latitude', this.selectedPinOrRoute.latitude, 'remove'); + Doc.setDocFilter(this.Document, 'longitude', this.selectedPinOrRoute.longitude, 'remove'); + Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPinOrRoute))}`, 'remove'); this.removePushpinOrRoute(this.selectedPinOrRoute); } @@ -669,8 +664,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); }, 'delete pin'); - - tryHideMapAnchorMenu = (e: PointerEvent) => { let target = document.elementFromPoint(e.x, e.y); while (target) { @@ -688,8 +681,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps centerOnSelectedPin = () => { if (this.selectedPinOrRoute) { this._mapRef.current?.flyTo({ - center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)] - }) + center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)], + }); } // if (this.selectedPin) { // this.dataDoc.latitude = this.selectedPin.latitude; @@ -710,7 +703,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps mapTypeId: 'grayscale', }; - /** * Map options */ @@ -729,8 +721,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }, }; - - recolorPin = (pin: Doc, color?: string) => { // this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin)); // this.map_docToPinMap.delete(pin); @@ -745,6 +735,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps * Initializes starting values */ @observable _mapReady = false; + @action bingMapReady = (map: any) => { this._mapReady = true; @@ -757,7 +748,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'maptypechanged', undoable(this.updateMapType, 'Map ViewType Change')); this._disposers.mapLocation = reaction( - () => this.rootDoc.map, + () => this.dataDoc.map, mapLoc => (this.bingSearchBarContents = mapLoc), { fireImmediately: true } ); @@ -782,7 +773,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ); this._disposers.location = reaction( - () => ({ lat: this.rootDoc.latitude, lng: this.rootDoc.longitude, zoom: this.rootDoc.map_zoom, mapType: this.rootDoc.map_type }), + () => ({ lat: this.dataDoc.latitude, lng: this.dataDoc.longitude, zoom: this.dataDoc.map_zoom, mapType: this.dataDoc.map_type }), locationObject => { // if (this._bingMap.current) try { @@ -806,25 +797,27 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps setupMoveUpEvents( e, e, - e => { // move event + e => { + // move event if (!dragClone) { - dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin + dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin dragClone.style.position = 'absolute'; dragClone.style.zIndex = '10000'; - DragManager.Root().appendChild(dragClone); // add clone to root + DragManager.Root().appendChild(dragClone); // add clone to root } dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`; return false; }, - e => { // up event + e => { + // up event if (!dragClone) return; DragManager.Root().removeChild(dragClone); let target = document.elementFromPoint(e.x, e.y); // element for specified x and y coordinates while (target) { - 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] - 20 /* height of search bar */ - this.props.PanelHeight() / 2; + 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] - 20 /* 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; @@ -833,7 +826,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } }, e => { - const createPin = () => this.createPushpin(this.rootDoc.latitude, this.rootDoc.longitude, this.rootDoc.map); + const createPin = () => this.createPushpin(this.dataDoc.latitude, this.dataDoc.longitude, this.dataDoc.map); if (this.bingSearchBarContents) { this.bingSearch().then(createPin); } else createPin(); @@ -841,7 +834,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ); }; - // incrementer: number = 0; + // incrementer: number = 0; /* * Creates Pushpin doc and adds it to the list of annotations */ @@ -854,13 +847,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps false, [], { - title: location ?? `lat=${NumCast(latitude)},lng=${NumCast(longitude)}`, - map: location, - description: "", + title: location ?? `lat=${NumCast(latitude)},lng=${NumCast(longitude)}`, + map: location, + description: '', wikiData: wikiData, markerType: 'MAP_PIN', - markerColor: '#ff5722' - }, + markerColor: '#ff5722', + } // { title: map ?? `lat=${latitude},lng=${longitude}`, map: map }, // ,'pushpinIDamongus'+ this.incrementer++ ); @@ -873,11 +866,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @action createMapRoute = undoable((coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => { - const mapRoute = Docs.Create.MapRouteDocument( - false, - [], - {title: `${origin} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates)}, - ); + const mapRoute = Docs.Create.MapRouteDocument(false, [], { title: `${origin} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates) }); this.addDocument(mapRoute, this.annotationKey); if (createPinForDestination) { this.createPushpin(destination.center[1], destination.center[0], destination.place_name); @@ -889,43 +878,29 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch(); - - - - - @observable featuresFromGeocodeResults: any[] = []; - @action addMarkerForFeature = (feature: any) => { const location = feature.place_name; - if (feature.center){ + if (feature.center) { const longitude = feature.center[0]; const latitude = feature.center[1]; const wikiData = feature.properties?.wikiData; - - this.createPushpin( - latitude, - longitude, - location, - wikiData - ) - - if (this._mapRef.current){ + + this.createPushpin(latitude, longitude, location, wikiData); + + if (this._mapRef.current) { this._mapRef.current.flyTo({ - center: feature.center + center: feature.center, }); } this.featuresFromGeocodeResults = []; - } else { // TODO: handle error } - } - - + }; /** * Makes a forward geocoding API call to Mapbox to retrieve locations based on the search input @@ -933,12 +908,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps */ handleSearchChange = async (searchText: string) => { const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText); - if (features && !this.isAnimating){ + if (features && !this.isAnimating) { runInAction(() => { - this.settingsOpen= false; + this.settingsOpen = false; this.featuresFromGeocodeResults = features; this.routeToAnimate = undefined; - }) + }); } // try { // const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`; @@ -948,34 +923,31 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // this.featuresFromGeocodeResults = data.features; // }) // } catch (error: any){ - // // TODO: handle error in better way + // // TODO: handle error in better way // console.log(error); // } - } + }; // @action // debouncedCall = React.useCallback(debounce(this.debouncedOnSearchBarChange, 300), []); - @action handleMapClick = (e: MapLayerMouseEvent) => { - if (this._mapRef.current){ - const features = this._mapRef.current.queryRenderedFeatures( - e.point, { - layers: ['map-routes-layer'] - } - ); + if (this._mapRef.current) { + const features = this._mapRef.current.queryRenderedFeatures(e.point, { + layers: ['map-routes-layer'], + }); console.error(features); if (features && features.length > 0 && features[0].properties && features[0].geometry) { const geometry = features[0].geometry as LineString; const routeTitle: string = features[0].properties['routeTitle']; - const routeDoc: Doc | undefined = this.allRoutes.find((routeDoc) => routeDoc.title === routeTitle); + const routeDoc: Doc | undefined = this.allRoutes.find(routeDoc => routeDoc.title === routeTitle); this.deselectPinOrRoute(); // TODO: Also deselect route if selected - if (routeDoc){ + if (routeDoc) { this.selectedPinOrRoute = routeDoc; - Doc.setDocFilter(this.rootDoc, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); + Doc.setDocFilter(this.Document, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); - // TODO: Recolor route + // TODO: Recolor route MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute; MapAnchorMenu.Instance.Center = this.centerOnSelectedPin; @@ -985,9 +957,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps MapAnchorMenu.Instance.setRouteDoc(routeDoc); // TODO: Subject to change - MapAnchorMenu.Instance.setAllMapboxPins( - this.allAnnotations.filter(anno => !anno.layout_unrendered) - ) + MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered)); MapAnchorMenu.Instance.DisplayRoute = this.displayRoute; MapAnchorMenu.Instance.HideRoute = this.hideRoute; @@ -1002,16 +972,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true); document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true); - } + } } } - } - + }; /** * Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude - * and latitude). Sets the search results accordingly. - * @param e + * and latitude). Sets the search results accordingly. + * @param e */ handleMapDblClick = async (e: MapLayerMouseEvent) => { e.preventDefault(); @@ -1020,13 +989,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const latitude: number = lngLat.lat; const features = await MapboxApiUtility.reverseGeocodeForFeatures(longitude, latitude); - if (features){ + if (features) { runInAction(() => { this.featuresFromGeocodeResults = features; - }) + }); } - // // REVERSE GEOCODE TO GET LOCATION DETAILS + // // REVERSE GEOCODE TO GET LOCATION DETAILS // try { // const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' + // `?access_token=${MAPBOX_ACCESS_TOKEN}`; @@ -1040,9 +1009,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // // TODO: handle error in better way // console.log(error); // } - } + }; - @observable + @observable currentPopup: PopupInfo | undefined = undefined; @action @@ -1052,23 +1021,20 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.selectedPinOrRoute = pinDoc; // this.bingSearchBarContents = pinDoc.map; - // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match'); - // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match'); - Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); + // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match'); + // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match'); + Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); this.recolorPin(this.selectedPinOrRoute, 'green'); // TODO: check this method - - + MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute; MapAnchorMenu.Instance.Center = this.centerOnSelectedPin; MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation; MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag; - // pass in the pinDoc + // pass in the pinDoc MapAnchorMenu.Instance.setPinDoc(pinDoc); - MapAnchorMenu.Instance.setAllMapboxPins( - this.allAnnotations.filter(anno => !anno.layout_unrendered) - ) + MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered)); MapAnchorMenu.Instance.DisplayRoute = this.displayRoute; MapAnchorMenu.Instance.HideRoute = this.hideRoute; @@ -1085,12 +1051,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable temporaryRouteSource: FeatureCollection = { type: 'FeatureCollection', - features: [] - } + features: [], + }; @action displayRoute = (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => { - if (routeInfoMap){ + if (routeInfoMap) { const newTempRouteSource: FeatureCollection = { type: 'FeatureCollection', features: [ @@ -1099,16 +1065,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps properties: {}, geometry: { type: 'LineString', - coordinates: routeInfoMap[type].coordinates - } - } - ] - } - // TODO: Create pin for destination - // TODO: Fly to point where full route will be shown + coordinates: routeInfoMap[type].coordinates, + }, + }, + ], + }; + // TODO: Create pin for destination + // TODO: Fly to point where full route will be shown this.temporaryRouteSource = newTempRouteSource; } - } + }; @observable isAnimating: boolean = false; @@ -1119,10 +1085,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable routeToAnimate: Doc | undefined = undefined; - @observable + @observable animationPhase: number = 0; - @observable + @observable finishedFlyTo: boolean = false; @action @@ -1130,22 +1096,22 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.animationPhase = newValue; }; - @observable + @observable frameId: number | null = null; @action setFrameId = (frameId: number) => { this.frameId = frameId; - } + }; @action openAnimationPanel = (routeDoc: Doc | undefined) => { - if (routeDoc){ + if (routeDoc) { MapAnchorMenu.Instance.fadeOut(true); document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); this.routeToAnimate = routeDoc; } - } + }; @observable animationDuration = 40000; @@ -1156,15 +1122,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable pathDistance = 0; - @observable + @observable isStreetViewAnimation: boolean = false; - @observable + @observable animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM; @action updateAnimationSpeed = () => { - switch (this.animationSpeed){ + switch (this.animationSpeed) { case AnimationSpeed.SLOW: this.animationSpeed = AnimationSpeed.MEDIUM; break; @@ -1178,56 +1144,55 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.animationSpeed = AnimationSpeed.MEDIUM; break; } - } + }; @computed get animationSpeedTooltipText(): string { switch (this.animationSpeed) { - case AnimationSpeed.SLOW: - return '1x speed'; - case AnimationSpeed.MEDIUM: - return '2x speed'; - case AnimationSpeed.FAST: - return '3x speed'; - default: - return '2x speed'; + case AnimationSpeed.SLOW: + return '1x speed'; + case AnimationSpeed.MEDIUM: + return '2x speed'; + case AnimationSpeed.FAST: + return '3x speed'; + default: + return '2x speed'; } } - @computed get animationSpeedIcon(): JSX.Element{ + @computed get animationSpeedIcon(): JSX.Element { switch (this.animationSpeed) { case AnimationSpeed.SLOW: - return slowSpeedIcon; + return slowSpeedIcon; case AnimationSpeed.MEDIUM: - return mediumSpeedIcon; + return mediumSpeedIcon; case AnimationSpeed.FAST: - return fastSpeedIcon; + return fastSpeedIcon; default: - return mediumSpeedIcon; - } + return mediumSpeedIcon; + } } @action toggleIsStreetViewAnimation = () => { this.isStreetViewAnimation = !this.isStreetViewAnimation; - } + }; - @observable + @observable dynamicRouteFeature: Feature<Geometry, GeoJsonProperties> = { type: 'Feature', properties: {}, geometry: { type: 'LineString', - coordinates: [] - } + coordinates: [], + }, }; - - @observable + @observable path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = { type: 'Feature', geometry: { type: 'LineString', - coordinates: [] + coordinates: [], }, - properties: {} + properties: {}, }; getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => { @@ -1238,18 +1203,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return { type: 'Feature', properties: { - 'routeTitle': routeDoc.title}, + routeTitle: routeDoc.title, + }, geometry: geometry, }; - } + }; - @action + @action playAnimation = (status: AnimationStatus) => { - if (!this._mapRef.current || !this.routeToAnimate){ + if (!this._mapRef.current || !this.routeToAnimate) { return; } - if (this.isAnimating){ + if (this.isAnimating) { return; } this.animationPhase = status === AnimationStatus.RESUME ? this.animationPhase : 0; @@ -1257,112 +1223,99 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.finishedFlyTo = AnimationStatus.RESUME ? this.finishedFlyTo : false; const path = turf.lineString(this.selectedRouteCoordinates); - + this.settingsOpen = false; this.path = path; this.pathDistance = turf.lineDistance(path); this.isAnimating = true; runInAction(() => { - return new Promise<void>(async (resolve) => { + return new Promise<void>(async resolve => { let animationUtil; try { - const targetLngLat = { - lng: this.selectedRouteCoordinates[0][0], - lat: this.selectedRouteCoordinates[0][1], - }; - - animationUtil = new AnimationUtility( - targetLngLat, - this.selectedRouteCoordinates, - this.isStreetViewAnimation, - this.animationSpeed - ); - + const targetLngLat = { + lng: this.selectedRouteCoordinates[0][0], + lat: this.selectedRouteCoordinates[0][1], + }; + + animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this.isStreetViewAnimation, this.animationSpeed); + + const updateFrameId = (newFrameId: number) => { + this.setFrameId(newFrameId); + }; + + const updateAnimationPhase = (newAnimationPhase: number) => { + this.setAnimationPhase(newAnimationPhase); + }; + + if (status !== AnimationStatus.RESUME) { + const result = await animationUtil.flyInAndRotate({ + map: this._mapRef.current!, + // targetLngLat, + // duration 3000 + // startAltitude: 3000000, + // endAltitude: this.isStreetViewAnimation ? 80 : 12000, + // startBearing: 0, + // endBearing: -20, + // startPitch: 40, + // endPitch: this.isStreetViewAnimation ? 80 : 50, + updateFrameId, + }); - const updateFrameId = (newFrameId: number) => { - this.setFrameId(newFrameId); - } + console.log('Bearing: ', result.bearing); + console.log('Altitude: ', result.altitude); + } - const updateAnimationPhase = ( - newAnimationPhase: number, - ) => { - this.setAnimationPhase(newAnimationPhase); - }; - - if (status !== AnimationStatus.RESUME) { + runInAction(() => { + this.finishedFlyTo = true; + }); - const result = await animationUtil.flyInAndRotate({ + // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation + await animationUtil.animatePath({ map: this._mapRef.current!, - // targetLngLat, - // duration 3000 - // startAltitude: 3000000, - // endAltitude: this.isStreetViewAnimation ? 80 : 12000, - // startBearing: 0, - // endBearing: -20, - // startPitch: 40, - // endPitch: this.isStreetViewAnimation ? 80 : 50, + // path: this.path, + // startBearing: -20, + // startAltitude: this.isStreetViewAnimation ? 80 : 12000, + // pitch: this.isStreetViewAnimation ? 80: 50, + currentAnimationPhase: this.animationPhase, + updateAnimationPhase, updateFrameId, }); - console.log("Bearing: ", result.bearing); - console.log("Altitude: ", result.altitude); + // get the bounds of the linestring, use fitBounds() to animate to a final view + const bbox3d = turf.bbox(this.path); - } + const bbox2d: LngLatBoundsLike = [bbox3d[0], bbox3d[1], bbox3d[2], bbox3d[3]]; - runInAction(() => { - this.finishedFlyTo = true; - }) - - // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation - await animationUtil.animatePath({ - map: this._mapRef.current!, - // path: this.path, - // startBearing: -20, - // startAltitude: this.isStreetViewAnimation ? 80 : 12000, - // pitch: this.isStreetViewAnimation ? 80: 50, - currentAnimationPhase: this.animationPhase, - updateAnimationPhase, - updateFrameId, - }); - - // get the bounds of the linestring, use fitBounds() to animate to a final view - const bbox3d = turf.bbox(this.path); - - const bbox2d: LngLatBoundsLike = [bbox3d[0], bbox3d[1], bbox3d[2], bbox3d[3]]; - - this._mapRef.current!.fitBounds(bbox2d, { - duration: 3000, - pitch: 30, - bearing: 0, - padding: 120, - }); - - setTimeout(() => { - resolve(); - }, 10000); - } catch (error: any){ + this._mapRef.current!.fitBounds(bbox2d, { + duration: 3000, + pitch: 30, + bearing: 0, + padding: 120, + }); + + setTimeout(() => { + resolve(); + }, 10000); + } catch (error: any) { console.log(error); console.log('animation util: ', animationUtil); - }}); - - }) - - - } - + } + }); + }); + }; - @action + @action pauseAnimation = () => { - if (this.frameId && this.animationPhase > 0){ + if (this.frameId && this.animationPhase > 0) { window.cancelAnimationFrame(this.frameId); this.frameId = null; this.isAnimating = false; } - } + }; @action stopAndCloseAnimation = () => { - if (this.frameId){ + if (this.frameId) { window.cancelAnimationFrame(this.frameId); this.frameId = null; this.finishedFlyTo = false; @@ -1371,18 +1324,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.routeToAnimate = undefined; // this.selectedRouteCoordinates = []; } - // reset bearing and pitch to original, zoom out - } + // reset bearing and pitch to original, zoom out + }; @action - exportAnimationToVideo = () => { - - } + exportAnimationToVideo = () => {}; getRouteAnimationOptions = (): JSX.Element => { return ( <> - <IconButton + <IconButton tooltip={this.isAnimating && this.finishedFlyTo ? 'Pause Animation' : 'Play Animation'} onPointerDown={() => { if (this.isAnimating && this.finishedFlyTo) { @@ -1393,82 +1344,41 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.playAnimation(AnimationStatus.START); // Play from the beginning } }} - icon={this.isAnimating && this.finishedFlyTo ? - <FontAwesomeIcon icon={faPause as IconLookup}/> - : - <FontAwesomeIcon icon={faPlay as IconLookup}/> - } - color='black' - size={Size.MEDIUM} - /> - {this.isAnimating && this.finishedFlyTo && - <IconButton - tooltip='Restart animation' - onPointerDown={() => this.playAnimation(AnimationStatus.RESTART)} - icon={<FontAwesomeIcon icon={faRotate as IconLookup}/>} - color='black' - size={Size.MEDIUM} - /> - - } - <IconButton - tooltip='Stop and close animation' - onPointerDown={this.stopAndCloseAnimation} - icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup}/>} - color='black' - size={Size.MEDIUM} - /> - <IconButton - style={{marginRight: '10px'}} - tooltip='Export to video' - onPointerDown={this.exportAnimationToVideo} - icon={<FontAwesomeIcon icon={faFileExport as IconLookup}/>} - color='black' + icon={this.isAnimating && this.finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />} + color="black" size={Size.MEDIUM} /> - {!this.isAnimating && + {this.isAnimating && this.finishedFlyTo && ( + <IconButton tooltip="Restart animation" onPointerDown={() => this.playAnimation(AnimationStatus.RESTART)} icon={<FontAwesomeIcon icon={faRotate as IconLookup} />} color="black" size={Size.MEDIUM} /> + )} + <IconButton tooltip="Stop and close animation" onPointerDown={this.stopAndCloseAnimation} icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup} />} color="black" size={Size.MEDIUM} /> + <IconButton style={{ marginRight: '10px' }} tooltip="Export to video" onPointerDown={this.exportAnimationToVideo} icon={<FontAwesomeIcon icon={faFileExport as IconLookup} />} color="black" size={Size.MEDIUM} /> + {!this.isAnimating && ( <> - <div className='animation-suboptions'> + <div className="animation-suboptions"> <div>|</div> - <FormControlLabel - label='Street view animation' - labelPlacement='start' - control={ - <Checkbox - color='success' - checked={this.isStreetViewAnimation} - onChange={this.toggleIsStreetViewAnimation} - /> - } - /> - <div id='last-divider'>|</div> - <IconButton - tooltip={this.animationSpeedTooltipText} - onPointerDown={this.updateAnimationSpeed} - icon={this.animationSpeedIcon} - size={Size.MEDIUM} - /> + <FormControlLabel label="Street view animation" labelPlacement="start" control={<Checkbox color="success" checked={this.isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />} /> + <div id="last-divider">|</div> + <IconButton tooltip={this.animationSpeedTooltipText} onPointerDown={this.updateAnimationSpeed} icon={this.animationSpeedIcon} size={Size.MEDIUM} /> </div> </> - } + )} </> - ) - } + ); + }; @action hideRoute = () => { this.temporaryRouteSource = { type: 'FeatureCollection', - features: [] - } - } - - + features: [], + }; + }; @observable mapboxMapViewState: ViewState = { zoom: 9, - longitude: -71.45, + longitude: -71.45, latitude: 41.82, pitch: 0, bearing: 0, @@ -1476,15 +1386,15 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps top: 0, bottom: 0, left: 0, - right: 0 - } - } + right: 0, + }, + }; - @observable + @observable settingsOpen: boolean = false; - @observable - mapStyle: string = 'mapbox://styles/mapbox/streets-v12' + @observable + mapStyle: string = 'mapbox://styles/mapbox/streets-v12'; @observable showTerrain: boolean = false; @@ -1495,57 +1405,51 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.featuresFromGeocodeResults = []; this.settingsOpen = !this.settingsOpen; } - } + }; @action changeMapStyle = (e: React.ChangeEvent<HTMLSelectElement>) => { - this.mapStyle = `mapbox://styles/mapbox/${e.target.value}` - } + this.mapStyle = `mapbox://styles/mapbox/${e.target.value}`; + }; @action onMapMove = (e: ViewStateChangeEvent) => { this.mapboxMapViewState = e.viewState; - } + }; @action toggleShowTerrain = () => { this.showTerrain = !this.showTerrain; - } + }; @action onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => { - const newVal = parseInt(e.target.value) - if (!isNaN(newVal) && newVal >= 0){ + const newVal = parseInt(e.target.value); + if (!isNaN(newVal) && newVal >= 0) { this.mapboxMapViewState = { ...this.mapboxMapViewState, - bearing: parseInt(e.target.value) - } + bearing: parseInt(e.target.value), + }; } - } + }; @action onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => { const newVal = parseInt(e.target.value); - if (!isNaN(newVal) && newVal >= 0){ + if (!isNaN(newVal) && newVal >= 0) { this.mapboxMapViewState = { ...this.mapboxMapViewState, - pitch: parseInt(e.target.value) - } + pitch: parseInt(e.target.value), + }; } - } + }; getMarkerIcon = (pinDoc: Doc): JSX.Element | null => { const markerType = StrCast(pinDoc.markerType); const markerColor = StrCast(pinDoc.markerColor); return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null; - - } - - - - - + }; static _firstRender = true; static _rerenderDelay = 500; @@ -1553,7 +1457,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps render() { // bcz: no idea what's going on here, but bings maps have some kind of bug // such that we need to delay rendering a second map on startup until the first map is rendered. - this.rootDoc[DocCss]; + this.Document[DocCss]; if (MapBox._rerenderDelay) { // prettier-ignore this._rerenderTimeout = this._rerenderTimeout ?? @@ -1562,12 +1466,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps MapBox._rerenderDelay = 0; } this._rerenderTimeout = undefined; - this.rootDoc[DocCss] = this.rootDoc[DocCss] + 1; + this.Document[DocCss] = this.Document[DocCss] + 1; }), MapBox._rerenderDelay); return null; } - const renderAnnotations = (childFilters?: () => string[]) => null; + const scale = this.props.NativeDimScaling?.() || 1; return ( <div className="mapBox" ref={this._ref}> <div @@ -1576,216 +1480,164 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps onPointerDown={async e => { e.button === 0 && !e.ctrlKey && e.stopPropagation(); }} - style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}> - <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div> - {renderAnnotations(this.opaqueFilter)} - {SnappingManager.GetIsDragging() ? null : renderAnnotations()} - {!this.routeToAnimate && + style={{ transformOrigin: 'top left', transform: `scale(${scale})`, width: `${100 / scale}%`, height: `${100 / scale}%`, pointerEvents: this.pointerEvents() }}> + {!this.routeToAnimate && ( <div className="mapBox-searchbar"> - <TextField - fullWidth - placeholder='Enter a location' - onChange={(e) => this.handleSearchChange(e.target.value)} - /> - <IconButton - icon={<FontAwesomeIcon icon={faGear as IconLookup} size='1x'/>} - type={Type.TERT} - onClick={(e) => this.toggleSettings()} - - /> - </div> - } - {this.settingsOpen && !this.routeToAnimate && - <div className='mapbox-settings-panel' style={{right: `${0+ this.sidebarWidth()}px`}}> - <div className='mapbox-style-select'> - <div> - Map Style: - </div> + <TextField fullWidth placeholder="Enter a location" onChange={(e: any) => this.handleSearchChange(e.target.value)} /> + <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} /> + </div> + )} + {this.settingsOpen && !this.routeToAnimate && ( + <div className="mapbox-settings-panel" style={{ right: `${0 + this.sidebarWidth()}px` }}> + <div className="mapbox-style-select"> + <div>Map Style:</div> <div> <select onChange={this.changeMapStyle}> - <option value='streets-v12'>Streets</option> - <option value='outdoors-v12'>Outdoors</option> - <option value='light-v11'>Light</option> - <option value='dark-v11'>Dark</option> - <option value='satellite-v9'>Satellite</option> - <option value='satellite-streets-v12'>Satellite Streets</option> - <option value='navigation-day-v1'>Navigation Day</option> - <option value='navigation-night-v1'>Navigation Night</option> + <option value="streets-v12">Streets</option> + <option value="outdoors-v12">Outdoors</option> + <option value="light-v11">Light</option> + <option value="dark-v11">Dark</option> + <option value="satellite-v9">Satellite</option> + <option value="satellite-streets-v12">Satellite Streets</option> + <option value="navigation-day-v1">Navigation Day</option> + <option value="navigation-night-v1">Navigation Night</option> </select> </div> </div> - <div className='mapbox-bearing-selection'> + <div className="mapbox-bearing-selection"> <div>Bearing: </div> - <input - value={this.mapboxMapViewState.bearing} - min={0} - type='number' - onChange={this.onBearingChange}/> + <input value={this.mapboxMapViewState.bearing} min={0} type="number" onChange={this.onBearingChange} /> </div> - <div className='mapbox-pitch-selection'> + <div className="mapbox-pitch-selection"> <div>Pitch: </div> - <input - value={this.mapboxMapViewState.pitch} - min={0} - type='number' - onChange={this.onPitchChange}/> - </div> - <div className='mapbox-terrain-selection'> + <input value={this.mapboxMapViewState.pitch} min={0} type="number" onChange={this.onPitchChange} /> + </div> + <div className="mapbox-terrain-selection"> <div>Show terrain: </div> - <input - type='checkbox' - checked={this.showTerrain} - onChange={this.toggleShowTerrain} - /> + <input type="checkbox" checked={this.showTerrain} onChange={this.toggleShowTerrain} /> </div> </div> - } - {this.routeToAnimate && - <div className='animation-panel'> - <div id='route-to-animate-title'> - {StrCast(this.routeToAnimate.title)} - </div> - <div className='route-animation-options'> - {this.getRouteAnimationOptions()} - </div> + )} + {this.routeToAnimate && ( + <div className="animation-panel"> + <div id="route-to-animate-title">{StrCast(this.routeToAnimate.title)}</div> + <div className="route-animation-options">{this.getRouteAnimationOptions()}</div> </div> - } + )} {this.featuresFromGeocodeResults.length > 0 && ( - <div className='mapbox-geocoding-search-results'> + <div className="mapbox-geocoding-search-results"> <React.Fragment> <h4>Choose a location for your pin: </h4> {this.featuresFromGeocodeResults .filter(feature => feature.place_name) .map((feature, idx) => ( - <div - key={idx} - className='search-result-container' - onClick={() => { - this.handleSearchChange(""); - this.addMarkerForFeature(feature); - }} - > - <div className='search-result-place-name'> - {feature.place_name} + <div + key={idx} + className="search-result-container" + onClick={() => { + this.handleSearchChange(''); + this.addMarkerForFeature(feature); + }}> + <div className="search-result-place-name">{feature.place_name}</div> </div> - </div> - ))} + ))} </React.Fragment> - - </div> - )} + </div> + )} <MapProvider> <MapboxMap ref={this._mapRef} mapboxAccessToken={MAPBOX_ACCESS_TOKEN} id="mapbox-map" mapStyle={this.mapStyle} - style={{height: '100%', width: '100%'}} + style={{ height: '100%', width: '100%' }} initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState} // {...this.mapboxMapViewState} onMove={this.onMapMove} onClick={this.handleMapClick} onDblClick={this.handleMapDblClick} - terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined} - - - > - <Source - id="mapbox-dem" - type="raster-dem" - url="mapbox://mapbox.mapbox-terrain-dem-v1" - tileSize={512} - maxzoom={14} - /> - <Source id='temporary-route' type='geojson' data={this.temporaryRouteSource}/> - <Source id='map-routes' type='geojson' data={this.allRoutesGeoJson}/> - <Layer - id='temporary-route-layer' - type='line' - source='temporary-route' - layout={{"line-join": "round", "line-cap": "round"}} - paint={{"line-color": "#36454F", "line-width": 4, "line-dasharray": [1,1]}} - /> - {!this.isAnimating && this.animationPhase == 0 && - <Layer - id='map-routes-layer' - type='line' - source='map-routes' - layout={{"line-join": "round", "line-cap": "round"}} - paint={{"line-color": "#FF0000", "line-width": 4}} - /> - } - {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) && + terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}> + <Source id="mapbox-dem" type="raster-dem" url="mapbox://mapbox.mapbox-terrain-dem-v1" tileSize={512} maxzoom={14} /> + <Source id="temporary-route" type="geojson" data={this.temporaryRouteSource} /> + <Source id="map-routes" type="geojson" data={this.allRoutesGeoJson} /> + <Layer id="temporary-route-layer" type="line" source="temporary-route" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#36454F', 'line-width': 4, 'line-dasharray': [1, 1] }} /> + {!this.isAnimating && this.animationPhase == 0 && <Layer id="map-routes-layer" type="line" source="map-routes" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#FF0000', 'line-width': 4 }} />} + {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) && ( <> - {!this.isStreetViewAnimation && + {!this.isStreetViewAnimation && ( <> - <Source id='animated-route' type='geojson' data={this.updatedRouteCoordinates}/> - <Layer - id='dynamic-animation-line' - type='line' - source='animated-route' - paint={{ - 'line-color': 'yellow', - 'line-width': 4, - }} + <Source id="animated-route" type="geojson" data={this.updatedRouteCoordinates} /> + <Layer + id="dynamic-animation-line" + type="line" + source="animated-route" + paint={{ + 'line-color': 'yellow', + 'line-width': 4, + }} /> </> - } - <Source id='start-pin-base' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.04)}/> - <Source id='start-pin-top' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.25)}/> - <Source id='end-pin-base' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.04)}/> - <Source id='end-pin-top' type='geojson' data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.25)}/> - <Layer id='start-fill-pin-base' type='fill-extrusion' source='start-pin-base' + )} + <Source id="start-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.04)} /> + <Source id="start-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.25)} /> + <Source id="end-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.04)} /> + <Source id="end-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.25)} /> + <Layer + id="start-fill-pin-base" + type="fill-extrusion" + source="start-pin-base" paint={{ 'fill-extrusion-color': '#0bfc03', - 'fill-extrusion-height': 1000 + 'fill-extrusion-height': 1000, }} /> - <Layer id='start-fill-pin-top' type='fill-extrusion' source='start-pin-top' + <Layer + id="start-fill-pin-top" + type="fill-extrusion" + source="start-pin-top" paint={{ 'fill-extrusion-color': '#0bfc03', 'fill-extrusion-base': 1000, - 'fill-extrusion-height': 1200 + 'fill-extrusion-height': 1200, }} /> - <Layer id='end-fill-pin-base' type='fill-extrusion' source='end-pin-base' + <Layer + id="end-fill-pin-base" + type="fill-extrusion" + source="end-pin-base" paint={{ 'fill-extrusion-color': '#eb1c1c', - 'fill-extrusion-height': 1000 + 'fill-extrusion-height': 1000, }} /> - <Layer id='end-fill-pin-top' type='fill-extrusion' source='end-pin-top' + <Layer + id="end-fill-pin-top" + type="fill-extrusion" + source="end-pin-top" paint={{ 'fill-extrusion-color': '#eb1c1c', 'fill-extrusion-base': 1000, - 'fill-extrusion-height': 1200 + 'fill-extrusion-height': 1200, }} /> - </> - } - + )} <> - {!this.isAnimating && this.animationPhase == 0 && this.allPushpins - // .filter(anno => !anno.layout_unrendered) - .map((pushpin, idx) => ( - <Marker - key={idx} - longitude={NumCast(pushpin.longitude)} - latitude={NumCast(pushpin.latitude)} - anchor='bottom' - onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)} - > - {this.getMarkerIcon(pushpin)} - </Marker> - ))} + {!this.isAnimating && + this.animationPhase == 0 && + this.allPushpins + // .filter(anno => !anno.layout_unrendered) + .map((pushpin, idx) => ( + <Marker key={idx} longitude={NumCast(pushpin.longitude)} latitude={NumCast(pushpin.latitude)} anchor="bottom" onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)}> + {this.getMarkerIcon(pushpin)} + </Marker> + ))} </> - + {/* {this.mapMarkers.length > 0 && this.mapMarkers.map((marker, idx) => ( <Marker key={idx} longitude={marker.longitude} latitude={marker.latitude}/> ))} */} - </MapboxMap> </MapProvider> @@ -1805,10 +1657,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps .map((pushpin, i) => ( <DocumentView key={i} - {...this.props} - renderDepth={this.props.renderDepth + 1} + {...this._props} + renderDepth={this._props.renderDepth + 1} Document={pushpin} - DataDoc={undefined} + TemplateDataDocument={undefined} PanelWidth={returnOne} PanelHeight={returnOne} NativeWidth={returnOne} @@ -1830,7 +1682,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps </div> */} {/* <MapBoxInfoWindow key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]} - {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} + {...OmitKeys(this._props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} place={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})} markerMap={this.markerMap} PanelWidth={this.infoWidth} @@ -1844,9 +1696,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> <SidebarAnnos ref={this._sidebarRef} - {...this.props} + {...this._props} fieldKey={this.fieldKey} - rootDoc={this.rootDoc} + Document={this.Document} layoutDoc={this.layoutDoc} dataDoc={this.dataDoc} usePanelWidth={true} @@ -1865,7 +1717,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } -{/* <Autocomplete +{ + /* <Autocomplete fullWidth id="map-location-searcher" freeSolo @@ -1884,8 +1737,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps placeholder='Enter a location' /> )} - /> */} - {/* <EditableText + /> */ +} +{ + /* <EditableText // editing setVal={(newText: string | number) => typeof newText === 'string' && this.handleSearchChange(newText)} // onEnter={e => this.bingSearch()} @@ -1894,8 +1749,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // placeholder={this.bingSearchBarContents || 'Enter a location'} placeholder='Enter a location' textAlign="center" - /> */} - {/* <IconButton + /> */ +} +{ + /* <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 @@ -1908,4 +1765,5 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps /> <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}> <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} /> - </div> */}
\ No newline at end of file + </div> */ +} |