diff options
Diffstat (limited to 'src/client/views/nodes/MapBox/MapAnchorMenu.tsx')
-rw-r--r-- | src/client/views/nodes/MapBox/MapAnchorMenu.tsx | 401 |
1 files changed, 370 insertions, 31 deletions
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx index 66c4dc7b8..b1fb3368c 100644 --- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx +++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx @@ -1,15 +1,27 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { IReactionDisposer, ObservableMap, makeObservable, reaction } from 'mobx'; -import { observer } from 'mobx-react'; import * as React from 'react'; +import { IReactionDisposer, ObservableMap, action, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, NumListCast, Opt } from '../../../../fields/Doc'; import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils'; -import { Doc, Opt } from '../../../../fields/Doc'; import { SelectionManager } from '../../../util/SelectionManager'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; // import { GPTPopup, GPTPopupMode } from './../../GPTPopup/GPTPopup'; -import { IconButton } from 'browndash-components'; +import { Button, IconButton } from 'browndash-components'; import { SettingsManager } from '../../../util/SettingsManager'; import './MapAnchorMenu.scss'; +import { NumCast, StrCast } from '../../../../fields/Types'; +import { IconLookup, faDiamondTurnRight, faCalendarDays, faEdit, faAdd, faRoute, faArrowLeft, faLocationDot, faArrowDown, faCar, faBicycle, faPersonWalking, faUpload, faArrowsRotate } from '@fortawesome/free-solid-svg-icons'; +import { DirectionsAnchorMenu } from './DirectionsAnchorMenu'; +import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material'; +import { MapboxApiUtility, TransportationType } from './MapboxApiUtility'; +import { MapBox } from './MapBox'; +import { List } from '../../../../fields/List'; +import { MarkerIcons } from './MarkerIcons'; +import { CirclePicker, ColorResult } from 'react-color'; +import { Position } from 'geojson'; + +type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize' | 'route'; @observer export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -17,6 +29,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { private _disposer: IReactionDisposer | undefined; private _commentRef = React.createRef<HTMLDivElement>(); + private _fileInputRef = React.createRef<HTMLInputElement>(); public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search @@ -30,6 +43,50 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { // public MakeTargetToggle: () => void = unimplementedFunction; // public ShowTargetTrail: () => void = unimplementedFunction; public IsTargetToggler: () => boolean = returnFalse; + + public DisplayRoute: (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => void = unimplementedFunction; + public HideRoute: () => void = unimplementedFunction; + public AddNewRouteToMap: (coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => void = unimplementedFunction; + public CreatePin: (feature: any) => void = unimplementedFunction; + + public UpdateMarkerColor: (color: string) => void = unimplementedFunction; + public UpdateMarkerIcon: (iconKey: string) => void = unimplementedFunction; + + public OpenAnimationPanel: (routeDoc: Doc | undefined) => void = unimplementedFunction; + + @observable + menuType: MapAnchorMenuType = 'standard'; + + @action + public setMenuType = (menuType: MapAnchorMenuType) => { + this.menuType = menuType; + }; + + private allMapPinDocs: Doc[] = []; + + private pinDoc: Doc | undefined = undefined; + + private routeDoc: Doc | undefined = undefined; + + private title: string | undefined = undefined; + + public setPinDoc(pinDoc: Doc) { + this.pinDoc = pinDoc; + this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`); + } + + public setRouteDoc(routeDoc: Doc) { + this.routeDoc = routeDoc; + this.title = StrCast(routeDoc.title ?? 'Map route'); + } + + public setAllMapboxPins(pinDocs: Doc[]) { + this.allMapPinDocs = pinDocs; + pinDocs.forEach((p, idx) => { + console.log(`Pin ${idx}: ${p.title}`); + }); + } + public get Active() { return this._left > 0; } @@ -42,6 +99,10 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { } componentWillUnmount() { + this.destinationFeatures = []; + this.destinationSelected = false; + this.selectedDestinationFeature = undefined; + this.currentRouteInfoMap = undefined; this._disposer?.(); } @@ -81,39 +142,224 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { }; static top = React.createRef<HTMLDivElement>(); + // public get Top(){ // return this.top // } + @action + DirectionsClick = () => { + this.menuType = 'routeCreation'; + }; + + @action + CustomizeClick = () => { + this.currentRouteInfoMap = undefined; + this.menuType = 'customize'; + }; + + @action + BackClick = () => { + this.currentRouteInfoMap = undefined; + this.menuType = 'standard'; + }; + + @action + TriggerFileInputClick = () => { + if (this._fileInputRef) { + this._fileInputRef.current?.click(); // Trigger the file input click event + } + }; + + @action + onMarkerColorChange = (color: ColorResult) => { + if (this.pinDoc) { + this.pinDoc.markerColor = color.hex; + } + }; + + revertToOriginalMarker = () => { + if (this.pinDoc) { + this.pinDoc.markerType = 'MAP_PIN'; + this.pinDoc.markerColor = '#ff5722'; + } + }; + + onMarkerIconChange = (iconKey: string) => { + if (this.pinDoc) { + this.pinDoc.markerType = iconKey; + } + }; + + @observable + destinationFeatures: any[] = []; + + @observable + destinationSelected: boolean = false; + + @observable + selectedDestinationFeature: any = undefined; + + @observable + createPinForDestination: boolean = true; + + @observable + currentRouteInfoMap: Record<TransportationType, any> | undefined = undefined; + + @observable + selectedTransportationType: TransportationType = 'driving'; + + @action + handleTransportationTypeChange = (newType: TransportationType) => { + if (newType !== this.selectedTransportationType) { + this.selectedTransportationType = newType; + this.DisplayRoute(this.currentRouteInfoMap, newType); + } + }; + + @action + handleSelectedDestinationFeature = (destinationFeature: any) => { + this.selectedDestinationFeature = destinationFeature; + }; + + @action + toggleCreatePinForDestinationCheckbox = () => { + this.createPinForDestination = !this.createPinForDestination; + }; + + @action + handleDestinationSearchChange = async (searchText: string) => { + if (this.selectedDestinationFeature !== undefined) this.selectedDestinationFeature = undefined; + const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText); + if (features) { + runInAction(() => { + this.destinationFeatures = features; + }); + } + }; + + getRoutes = async (destinationFeature: any) => { + const currentPinLong: number = NumCast(this.pinDoc?.longitude); + const currentPinLat: number = NumCast(this.pinDoc?.latitude); + + if (currentPinLong && currentPinLat && destinationFeature.center) { + const routeInfoMap = await MapboxApiUtility.getDirections([currentPinLong, currentPinLat], destinationFeature.center); + if (routeInfoMap) { + runInAction(() => { + this.currentRouteInfoMap = routeInfoMap; + }); + this.DisplayRoute(routeInfoMap, 'driving'); + } + } + + // get route menu, set it equal to here + // create a temporary route + // create pin if createPinForDestination was clicked + }; + + HandleAddRouteClick = () => { + if (this.currentRouteInfoMap && this.selectedTransportationType && this.selectedDestinationFeature) { + const coordinates = this.currentRouteInfoMap[this.selectedTransportationType].coordinates; + console.log(coordinates); + console.log(this.selectedDestinationFeature); + this.AddNewRouteToMap(coordinates, this.title ?? '', this.selectedDestinationFeature, this.createPinForDestination); + this.HideRoute(); + } + }; + + getMarkerIcon = (): JSX.Element | undefined => { + if (this.pinDoc) { + const markerType = StrCast(this.pinDoc.markerType); + const markerColor = StrCast(this.pinDoc.markerColor); + + return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor); + } + return undefined; + }; + render() { const buttons = ( - <> - { - <IconButton - tooltip="Delete Pin" // - onPointerDown={this.Delete} - icon={<FontAwesomeIcon icon="trash-alt" />} - color={SettingsManager.userColor} - /> - } - { - <div ref={this._commentRef}> + <div className="menu-buttons" style={{ display: 'flex' }}> + {this.menuType === 'standard' && ( + <> <IconButton - tooltip="Link Note to Pin" // - onPointerDown={this.notePointerDown} - icon={<FontAwesomeIcon icon="sticky-note" />} + tooltip="Delete Pin" // + onPointerDown={this.Delete} + icon={<FontAwesomeIcon icon="trash-alt" />} color={SettingsManager.userColor} /> - </div> - } - { - <IconButton - tooltip="Center on pin" // - onPointerDown={this.Center} - icon={<FontAwesomeIcon icon="compress-arrows-alt" />} - color={SettingsManager.userColor} - /> - } + <IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} /**TODO: fix */ icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} /> + <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} /> + <div ref={this._commentRef}> + <IconButton + tooltip="Link Note to Pin" // + onPointerDown={this.notePointerDown} + icon={<FontAwesomeIcon icon="sticky-note" />} + color={SettingsManager.userColor} + /> + </div> + <IconButton tooltip="Customize pin" onPointerDown={this.CustomizeClick} icon={<FontAwesomeIcon icon={faEdit as IconLookup} />} color={SettingsManager.userColor} /> + <IconButton + tooltip="Center on pin" // + onPointerDown={this.Center} + icon={<FontAwesomeIcon icon="compress-arrows-alt" />} + color={SettingsManager.userColor} + /> + </> + )} + {this.menuType === 'routeCreation' && ( + <> + <IconButton + tooltip="Go back" // + onPointerDown={this.BackClick} + icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />} + color={SettingsManager.userColor} + /> + <IconButton + tooltip="Add route" // + onPointerDown={this.HandleAddRouteClick} + icon={<FontAwesomeIcon icon={faAdd as IconLookup} />} + color={SettingsManager.userColor} + /> + </> + )} + {this.menuType === 'route' && ( + <> + <IconButton + tooltip="Delete Route" // + onPointerDown={this.Delete} + icon={<FontAwesomeIcon icon="trash-alt" />} + color={SettingsManager.userColor} + /> + <IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} /**TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} /> + <div ref={this._commentRef}> + <IconButton + tooltip="Link Note to Pin" // + onPointerDown={this.notePointerDown} + icon={<FontAwesomeIcon icon="sticky-note" />} + color={SettingsManager.userColor} + /> + </div> + <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} /> + </> + )} + {this.menuType === 'customize' && ( + <> + <IconButton + tooltip="Go back" // + onPointerDown={this.BackClick} + icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />} + color={SettingsManager.userColor} + /> + <IconButton + tooltip="Revert to original" // + onPointerDown={() => this.revertToOriginalMarker()} + icon={<FontAwesomeIcon icon={faArrowsRotate as IconLookup} />} + color={SettingsManager.userColor} + /> + </> + )} + {/* {this.IsTargetToggler !== returnFalse && ( <Toggle tooltip={'Make target visibility toggle on click'} @@ -125,13 +371,106 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { color={SettingsManager.userColor} /> )} */} - </> + </div> ); + // return ( + // <div ref={MapAnchorMenu.top} style={{zIndex: 30000, width: '100%', height: '100px'}}> + // HELLO THIS IS ANCHOR MENU + // {this.getElement(buttons)} + // </div> + // ) return this.getElement( - <div ref={MapAnchorMenu.top} style={{ width: '100%', display: 'flex' }}> + <div ref={MapAnchorMenu.top} className="map-anchor-menu-container"> + {this.menuType === 'standard' && <div>{this.title}</div>} + {this.menuType === 'routeCreation' && ( + <div className="direction-inputs" style={{ display: 'flex', flexDirection: 'column' }}> + <TextField fullWidth disabled value={this.title} /> + <FontAwesomeIcon icon={faArrowDown as IconLookup} size="xs" /> + <Autocomplete + fullWidth + id="route-destination-searcher" + onInputChange={(e: any, searchText: any) => this.handleDestinationSearchChange(searchText)} + onChange={(e: any, feature: any, reason: any) => { + if (reason === 'clear') { + this.handleSelectedDestinationFeature(undefined); + } else if (reason === 'selectOption') { + this.handleSelectedDestinationFeature(feature); + } + }} + options={this.destinationFeatures.filter(feature => feature.place_name).map(feature => feature)} + getOptionLabel={(feature: any) => feature.place_name} + renderInput={(params: any) => <TextField {...params} placeholder="Enter a destination" />} + /> + {this.selectedDestinationFeature && ( + <> + {!this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && ( + <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px' }}> + <FormControlLabel label="Create pin for destination?" control={<Checkbox color="success" checked={this.createPinForDestination} onChange={this.toggleCreatePinForDestinationCheckbox} />} /> + </div> + )} + </> + )} + <button id="get-routes-button" disabled={this.selectedDestinationFeature ? false : true} onClick={() => this.getRoutes(this.selectedDestinationFeature)}> + Get routes + </button> + + {/* <input + placeholder="Origin" + /> */} + </div> + )} + {this.currentRouteInfoMap && ( + <div className="current-route-info-container"> + <div className="transportation-icons-container"> + <IconButton + tooltip="Driving route" + onPointerDown={() => this.handleTransportationTypeChange('driving')} + icon={<FontAwesomeIcon icon={faCar as IconLookup} />} + color={this.selectedTransportationType === 'driving' ? 'lightblue' : 'grey'} + /> + <IconButton + tooltip="Cycling route" + onPointerDown={() => this.handleTransportationTypeChange('cycling')} + icon={<FontAwesomeIcon icon={faBicycle as IconLookup} />} + color={this.selectedTransportationType === 'cycling' ? 'lightblue' : 'grey'} + /> + <IconButton + tooltip="Walking route" + onPointerDown={() => this.handleTransportationTypeChange('walking')} + icon={<FontAwesomeIcon icon={faPersonWalking as IconLookup} />} + color={this.selectedTransportationType === 'walking' ? 'lightblue' : 'grey'} + /> + </div> + <div className="selected-route-details-container"> + <div>Duration: {this.currentRouteInfoMap[this.selectedTransportationType].duration}</div> + <div>Distance: {this.currentRouteInfoMap[this.selectedTransportationType].distance}</div> + </div> + </div> + )} + {this.menuType === 'customize' && ( + <div className="customized-marker-container"> + <div className="current-marker-container"> + <div>Current Marker: </div> + <div>{this.getMarkerIcon()}</div> + </div> + <div className="color-picker-container" style={{ marginBottom: '10px' }}> + <CirclePicker circleSize={15} circleSpacing={7} width="100%" onChange={color => this.onMarkerColorChange(color)} /> + </div> + <div className="all-markers-container"> + {Object.keys(MarkerIcons.FAMarkerIconsMap).map(iconKey => ( + <div key={iconKey} className="marker-icon"> + <IconButton onPointerDown={() => this.onMarkerIconChange(iconKey)} icon={MarkerIcons.getFontAwesomeIcon(iconKey, '1x', 'white')} /> + </div> + ))} + </div> + <div style={{ width: '100%', height: '3px', color: 'white' }}></div> + </div> + )} + {this.menuType === 'route' && this.routeDoc && <div>{StrCast(this.routeDoc.title)}</div>} {buttons} - </div> + </div>, + true ); } } |