From 6f0ef8aa44ba2e8058afe8afc24ccb8062ec560a Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Wed, 15 May 2024 22:10:33 -0400 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=8E=A8=20refactoring=20map=20hook=20i?= =?UTF-8?q?nto=20smaller=20pieces=20for=20debugging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/hooks/map/useWebMap.ts | 416 ++++++++++++++++------------ 1 file changed, 240 insertions(+), 176 deletions(-) diff --git a/packages/app/hooks/map/useWebMap.ts b/packages/app/hooks/map/useWebMap.ts index 46b8f89d6..aea4ba330 100644 --- a/packages/app/hooks/map/useWebMap.ts +++ b/packages/app/hooks/map/useWebMap.ts @@ -2,7 +2,6 @@ import { useState, useEffect, useRef, useMemo, useCallback } from 'react'; import { Dimensions } from 'react-native'; import mapboxgl from 'mapbox-gl'; import togpx from 'togpx'; - import { calculateZoomLevel, findTrailCenter, @@ -17,79 +16,111 @@ import { } from 'app/utils/mapFunctions'; import { saveFile } from 'app/utils/fileSaver/fileSaver.web'; -export const useWebMap = ({ shape: shapeProp }) => { - // useEffect(() => { - // // temporary solution to fix mapbox-gl-js missing css error - // if (Platform.OS === 'web') { - // // inject mapbox css into head - // const link = document.createElement('link'); - // link.href = 'https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.css'; - // link.rel = 'stylesheet'; - // document.head.appendChild(link); - - // // inject mapbox js into head - // const script = document.createElement('script'); - // script.src = 'https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js'; - // script.async = true; - // document.head.appendChild(script); - // } - // }, []); - +// Hook to handle state management +const useMapState = (shapeProp) => { + // State to store the shape data const [shape, setShape] = useState(shapeProp); - console.log('WebMap shape', shape); - + // Refs for map container and instance const mapContainer = useRef(null); const map = useRef(null); + // States for map coordinates and zoom level const [lng, setLng] = useState(-77.0369); const [lat, setLat] = useState(38.9072); - - // consts - const dw = Dimensions.get('screen').width; - const dh = Dimensions.get('screen').height; - const fullMapDiemention = useMemo(() => ({ width: dw, height: 360 }), [dw]); - const previewMapDiemension = { width: dw * 0.9, height: 220 }; - const [zoomLevel, setZoomLevel] = useState(10); const [trailCenterPoint, setTrailCenterPoint] = useState(null); const zoomLevelRef = useRef(10); const trailCenterPointRef = useRef(null); - + // States for UI and user interaction const [mapFullscreen, setMapFullscreen] = useState(false); const [downloading, setDownloading] = useState(false); - const [showModal, setShowModal] = useState(false); - const [mapStyle, setMapStyle] = useState(mapboxStyles[0].style); const [showUserLocation, setShowUserLocation] = useState(false); const [userLng, setUserLng] = useState(null); const [userLat, setUserLat] = useState(null); - - // download variables const [downloadable, setDownloadable] = useState(false); + // Dimensions for full and preview map + const dw = Dimensions.get('screen').width; + const fullMapDiemention = useMemo(() => ({ width: dw, height: 360 }), [dw]); + const previewMapDiemension = { width: dw * 0.9, height: 220 }; + + // Effect to update shape when shapeProp changes useEffect(() => { - // update the shape state when a new shapeProp gets passed if (shapeProp !== shape) setShape(shapeProp); }, [shapeProp]); + return { + shape, + setShape, + mapContainer, + map, + lng, + setLng, + lat, + setLat, + zoomLevel, + setZoomLevel, + trailCenterPoint, + setTrailCenterPoint, + zoomLevelRef, + trailCenterPointRef, + mapFullscreen, + setMapFullscreen, + downloading, + setDownloading, + showModal, + setShowModal, + mapStyle, + setMapStyle, + showUserLocation, + setShowUserLocation, + userLng, + setUserLng, + userLat, + setUserLat, + downloadable, + setDownloadable, + fullMapDiemention, + previewMapDiemension, + }; +}; + +// Hook to handle side effects +const useMapEffects = ({ + shape, + map, + mapContainer, + mapStyle, + lng, + lat, + zoomLevel, + zoomLevelRef, + trailCenterPointRef, + mapFullscreen, + showUserLocation, + setLng, + setLat, + setZoomLevel, + setDownloadable, + fullMapDiemention, +}) => { + // Effect to handle shape data and calculate bounds useEffect(() => { if (shape?.features[0]?.geometry?.coordinates?.length >= 1) { let bounds = getShapeSourceBounds(shape); bounds = bounds[0].concat(bounds[1]); - const mapDim = fullMapDiemention; - const latZoom = calculateZoomLevel(bounds, mapDim); const trailCenter = findTrailCenter(shape); console.log('trailCenter in useEffect', trailCenter); - zoomLevelRef.current = latZoom; trailCenterPointRef.current = trailCenter; - setDownloadable(isShapeDownloadable(shape)); } }, [shape, fullMapDiemention]); + // Effect to initialize and configure Mapbox map useEffect(() => { console.log( !mapFullscreen || !isPolygonOrMultiPolygon(shape), @@ -101,7 +132,6 @@ export const useWebMap = ({ shape: shapeProp }) => { const mapInstance = new mapboxgl.Map({ container: mapContainer.current, style: mapStyle, - // center: [lng, lat], center: trailCenterPointRef.current && !isNaN(trailCenterPointRef.current[0]) && @@ -112,6 +142,7 @@ export const useWebMap = ({ shape: shapeProp }) => { interactive: mapFullscreen, }); + // Map load event handler mapInstance.on('load', () => { if (isPoint(shape)) { addPoints(mapInstance); @@ -139,10 +170,7 @@ export const useWebMap = ({ shape: shapeProp }) => { }); } - // const marker = new mapboxgl.Marker() - // .setLngLat([lng, lat]) - // .addTo(mapInstance); - + // Map move event handler mapInstance.on('move', () => { const { lng, lat } = mapInstance.getCenter(); setLng(lng.toFixed(4)); @@ -157,6 +185,7 @@ export const useWebMap = ({ shape: shapeProp }) => { } }, [mapFullscreen]); + // Effect to handle shape changes and update map layers useEffect(() => { if (map.current && isPoint(shape)) { addPoints(map.current); @@ -166,50 +195,46 @@ export const useWebMap = ({ shape: shapeProp }) => { map.current.setCenter(trailCenterPointRef.current); map.current.setZoom(zoomLevelRef.current); } - console.log('trailCenterPointRef.current', trailCenterPointRef.current); - - // console.log("mapInstance", mapInstance); }, [shape]); +}; - /** - * Removes the existing source and layers for the trail-cap and trail from the map instance. - * - * @param {object} mapInstance - The map instance to remove the layers and source from. - */ +// Hook to handle actions +const useMapActions = ({ + map, + shape, + setDownloading, + userLng, + userLat, + setUserLng, + setUserLat, + setShowUserLocation, + setMapFullscreen, + setShowModal, +}) => { + // Function to remove trail layer from map const removeTrailLayer = (mapInstance) => { - // Remove existing source and layers if they exist if (mapInstance.getLayer('trail-cap')) { mapInstance.removeLayer('trail-cap'); } - if (mapInstance.getSource('trail-cap')) { mapInstance.removeSource('trail-cap'); } - if (mapInstance.getLayer('trail')) { mapInstance.removeLayer('trail'); } - if (mapInstance.getSource('trail')) { mapInstance.removeSource('trail'); } }; - /** - * Adds a trail layer to the given map instance. - * - * @param {Object} mapInstance - The map instance to add the trail layer to. - */ + // Function to add trail layer to map const addTrailLayer = (mapInstance) => { const processedShape = processShapeData(shape); - - // Add new source and layers mapInstance.addSource('trail', { type: 'geojson', data: processedShape || shape, }); - mapInstance.addLayer({ id: 'trail', type: 'line', @@ -220,8 +245,6 @@ export const useWebMap = ({ shape: shapeProp }) => { 'line-opacity': 1, }, }); - - // Add circle cap to the line ends mapInstance.addLayer({ id: 'trail-cap', type: 'circle', @@ -234,12 +257,7 @@ export const useWebMap = ({ shape: shapeProp }) => { }); }; - /** - * Adds points to the map instance. - * - * @param {type} mapInstance - The map instance to add points to. - * @return {type} None - */ + // Function to add point markers to map const addPoints = (mapInstance) => { if (mapInstance) { const pointLatLong = shape?.features[0]?.geometry?.coordinates; @@ -258,11 +276,7 @@ export const useWebMap = ({ shape: shapeProp }) => { } }; - /** - * Adds polygons to the map instance. - * - * @param {object} mapInstance - The map instance to add the polygons to. - */ + // Function to add polygon layers to map const addPolygons = (mapInstance) => { if (mapInstance) { mapInstance.addLayer({ @@ -280,31 +294,20 @@ export const useWebMap = ({ shape: shapeProp }) => { mapInstance.setCenter(multiPolygonBounds(shape.features[0])); } }; - /** - * Fetches the GPX download and handles the download process. - * This function sets the state of 'downloading' to true and then tries to fetch the GPX data - * using the provided shape and options. After receiving the GPX data, it calls the 'handleGpxDownload' - * function to handle the download. If there is an error during the process, it logs the error to the console. - * - * @return {Promise} A promise that resolves when the GPX download is complete. - */ + + // Function to fetch and download GPX file const fetchGpxDownload = async () => { setDownloading(true); - try { const options = { - creator: 'PackRat', // Hardcoded creator option + creator: 'PackRat', metadata: { - name: shape.name || '', // Extract name from geoJSON (if available) - desc: shape.description || '', // Extract description from geoJSON (if available) + name: shape.name || '', + desc: shape.description || '', }, - // featureTitle: (properties) => properties.name || "", // Extract feature title from properties (if available) - // featureDescription: (properties) => properties.description || "", // Extract feature description from properties (if available) }; const gpx = togpx(shape, options); - await handleGpxDownload(gpx); - setDownloading(false); } catch (error) { console.log('error', error); @@ -312,88 +315,19 @@ export const useWebMap = ({ shape: shapeProp }) => { } }; - /** - * Enables full screen mode. - * - * @return {void} - */ + // Function to enable fullscreen mode const enableFullScreen = () => { setMapFullscreen(true); setShowModal(true); }; - /** - * Disable full screen. - * - * @return {undefined} No return value. - */ + // Function to disable fullscreen mode const disableFullScreen = () => { setMapFullscreen(false); setShowModal(false); }; - const setMapboxStyle = useCallback( - (style) => { - if (map.current) { - // Step 1: remove sources, layers, etc. - removeTrailLayer(map.current); - - // Step 2: change the style - map.current.setStyle(style); - - // Step 3: add the sources, layers, etc. back once the style has loaded - if (isPoint(shape)) { - map.current.on('style.load', () => addPoints(map.current)); - } else if (isPolygonOrMultiPolygon) { - // Add Polygon - } else { - map.current.on('style.load', () => { - addTrailLayer(map.current); - }); - } - } - }, - [addTrailLayer, removeTrailLayer], - ); - - /** - * Updates the map style and mapbox style to the specified style. - * - * @param {style} style - The style to set for the map and mapbox. - * @return {void} This function does not return a value. - */ - const handleChangeMapStyle = (style) => { - setMapStyle(style); - setMapboxStyle(style); - }; - - const openMaps = () => { - const pointLatLong = shape?.features[0]?.geometry?.coordinates; - const { type } = shape.features[0].geometry; - if (type !== 'Point') { - const [latlng] = pointLatLong; - window.open(`https://maps.google.com?q=${latlng[1]},${latlng[0]}`); - } else { - const [lng, lat] = pointLatLong; - window.open(`https://maps.google.com?q=${lat},${lng}`); - } - - // console.log() - // if(type !== 'Point') { - - // } else { - // window.open(`https://maps.google.com?q=${lat},${lng}`); - // } - }; - - /** - * Handles the download of a GPX file. - * - * @param {Object} gpxData - The GPX data to be downloaded. - * @param {string} [filename="trail"] - The name of the file to be downloaded. - * @param {string} [extension="gpx"] - The extension of the file to be downloaded. - * @return {Promise} - A promise that resolves when the download is complete. - */ + // Function to handle GPX file download const handleGpxDownload = async ( gpxData, filename = shape?.features[0]?.properties?.name ?? 'trail', @@ -405,36 +339,26 @@ export const useWebMap = ({ shape: shapeProp }) => { } }; - /** - * Fetches the user's location and updates the map accordingly. - * - * @return {Promise} A Promise that resolves when the location is fetched and the map is updated. - */ + // Function to fetch user location const fetchLocation = async () => { try { const location = await getLocation(); - if (location) { const { latitude, longitude } = location.coords; setUserLng(longitude); setUserLat(latitude); setShowUserLocation(true); - if (map.current) { map.current.flyTo({ center: [longitude, latitude], zoom: 14, }); - - // Remove existing user location layer if it exists if (map.current.getLayer('user-location')) { map.current.removeLayer('user-location'); } if (map.current.getSource('user-location')) { map.current.removeSource('user-location'); } - - // Add new user location layer map.current.addLayer({ id: 'user-location', type: 'circle', @@ -456,7 +380,147 @@ export const useWebMap = ({ shape: shapeProp }) => { console.log('error', error); } }; - console.log(isPolygonOrMultiPolygon(shape) || showModal, 'polygon or not'); + + // Function to open location in Google Maps + const openMaps = () => { + const pointLatLong = shape?.features[0]?.geometry?.coordinates; + const { type } = shape.features[0].geometry; + if (type !== 'Point') { + const [latlng] = pointLatLong; + window.open(`https://maps.google.com?q=${latlng[1]},${latlng[0]}`); + } else { + const [lng, lat] = pointLatLong; + window.open(`https://maps.google.com?q=${lat},${lng}`); + } + }; + + // Function to set Mapbox style + const setMapboxStyle = useCallback( + (style) => { + if (map.current) { + removeTrailLayer(map.current); + map.current.setStyle(style); + if (isPoint(shape)) { + map.current.on('style.load', () => addPoints(map.current)); + } else if (isPolygonOrMultiPolygon) { + // Add Polygon + } else { + map.current.on('style.load', () => { + addTrailLayer(map.current); + }); + } + } + }, + [addTrailLayer, removeTrailLayer], + ); + + // Function to change map style + const handleChangeMapStyle = (style) => { + setMapStyle(style); + setMapboxStyle(style); + }; + + return { + removeTrailLayer, + addTrailLayer, + addPoints, + addPolygons, + fetchGpxDownload, + enableFullScreen, + disableFullScreen, + handleGpxDownload, + fetchLocation, + openMaps, + setMapboxStyle, + handleChangeMapStyle, + }; +}; + +// Main hook to combine state, effects, and actions +export const useWebMap = ({ shape: shapeProp }) => { + // State management hook + const { + shape, + setShape, + mapContainer, + map, + lng, + setLng, + lat, + setLat, + zoomLevel, + setZoomLevel, + trailCenterPoint, + setTrailCenterPoint, + zoomLevelRef, + trailCenterPointRef, + mapFullscreen, + setMapFullscreen, + downloading, + setDownloading, + showModal, + setShowModal, + mapStyle, + setMapStyle, + showUserLocation, + setShowUserLocation, + userLng, + setUserLng, + userLat, + setUserLat, + downloadable, + setDownloadable, + fullMapDiemention, + previewMapDiemension, + } = useMapState(shapeProp); + + // Side effects hook + useMapEffects({ + shape, + map, + mapContainer, + mapStyle, + lng, + lat, + zoomLevel, + zoomLevelRef, + trailCenterPointRef, + mapFullscreen, + showUserLocation, + setLng, + setLat, + setZoomLevel, + setDownloadable, + fullMapDiemention, + }); + + // Actions hook + const { + removeTrailLayer, + addTrailLayer, + addPoints, + addPolygons, + fetchGpxDownload, + enableFullScreen, + disableFullScreen, + handleGpxDownload, + fetchLocation, + openMaps, + setMapboxStyle, + handleChangeMapStyle, + } = useMapActions({ + map, + shape, + setDownloading, + userLng, + userLat, + setUserLng, + setUserLat, + setShowUserLocation, + setMapFullscreen, + setShowModal, + }); + return { mapContainer, lng, From 953e45f1ec3c030b9d4b2f7f5e7a580f28a9a7a5 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Thu, 16 May 2024 21:45:57 -0400 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=90=9B=20fix=20missing=20params?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/hooks/map/useWebMap.ts | 63 ++++++++++++++++------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/packages/app/hooks/map/useWebMap.ts b/packages/app/hooks/map/useWebMap.ts index 3a05d4647..d1076851e 100644 --- a/packages/app/hooks/map/useWebMap.ts +++ b/packages/app/hooks/map/useWebMap.ts @@ -92,6 +92,7 @@ const useMapEffects = ({ map, mapContainer, mapStyle, + setMapStyle, lng, lat, zoomLevel, @@ -104,6 +105,10 @@ const useMapEffects = ({ setZoomLevel, setDownloadable, fullMapDiemention, + addPoints, + addPolygons, + addTrailLayer, + removeTrailLayer, }) => { // Effect to handle shape data and calculate bounds useEffect(() => { @@ -113,7 +118,7 @@ const useMapEffects = ({ const mapDim = fullMapDiemention; const latZoom = calculateZoomLevel(bounds, mapDim); const trailCenter = findTrailCenter(shape); - + zoomLevelRef.current = latZoom; trailCenterPointRef.current = trailCenter; setDownloadable(isShapeDownloadable(shape)); @@ -147,7 +152,6 @@ const useMapEffects = ({ if (isPoint(shape)) { addPoints(mapInstance); } else if (isPolygonOrMultiPolygon(shape)) { - addPolygons(mapInstance); } else { addTrailLayer(mapInstance); @@ -195,7 +199,6 @@ const useMapEffects = ({ map.current.setCenter(trailCenterPointRef.current); map.current.setZoom(zoomLevelRef.current); } - }, [shape]); }; @@ -203,6 +206,8 @@ const useMapEffects = ({ const useMapActions = ({ map, shape, + mapStyle, + setMapStyle, setDownloading, userLng, userLat, @@ -310,7 +315,6 @@ const useMapActions = ({ await handleGpxDownload(gpx); setDownloading(false); } catch (error) { - setDownloading(false); } }; @@ -376,9 +380,7 @@ const useMapActions = ({ }); } } - } catch (error) { - - } + } catch (error) {} }; // Function to open location in Google Maps @@ -474,26 +476,6 @@ export const useWebMap = ({ shape: shapeProp }) => { previewMapDiemension, } = useMapState(shapeProp); - // Side effects hook - useMapEffects({ - shape, - map, - mapContainer, - mapStyle, - lng, - lat, - zoomLevel, - zoomLevelRef, - trailCenterPointRef, - mapFullscreen, - showUserLocation, - setLng, - setLat, - setZoomLevel, - setDownloadable, - fullMapDiemention, - }); - // Actions hook const { removeTrailLayer, @@ -511,6 +493,8 @@ export const useWebMap = ({ shape: shapeProp }) => { } = useMapActions({ map, shape, + mapStyle, + setMapStyle, setDownloading, userLng, userLat, @@ -521,6 +505,31 @@ export const useWebMap = ({ shape: shapeProp }) => { setShowModal, }); + // Side effects hook + useMapEffects({ + shape, + map, + mapContainer, + mapStyle, + setMapStyle, + lng, + lat, + zoomLevel, + zoomLevelRef, + trailCenterPointRef, + mapFullscreen, + showUserLocation, + setLng, + setLat, + setZoomLevel, + setDownloadable, + fullMapDiemention, + addPoints, + addPolygons, + addTrailLayer, + removeTrailLayer, + }); + return { mapContainer, lng, From e41dda16c2b05e6e554829e6575cf181c77f8803 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Thu, 16 May 2024 21:48:24 -0400 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=90=9B=20remove=20log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/hooks/map/useWebMap.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/app/hooks/map/useWebMap.ts b/packages/app/hooks/map/useWebMap.ts index d1076851e..e8cfdedec 100644 --- a/packages/app/hooks/map/useWebMap.ts +++ b/packages/app/hooks/map/useWebMap.ts @@ -127,10 +127,6 @@ const useMapEffects = ({ // Effect to initialize and configure Mapbox map useEffect(() => { - console.log( - !mapFullscreen || !isPolygonOrMultiPolygon(shape), - 'is polygon or not', - ); if (!mapFullscreen && !isPolygonOrMultiPolygon(shape)) return; if (!lng || !lat) return; try { From e6637118d435d71f583841095762e417b1c93f8c Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Thu, 16 May 2024 21:53:57 -0400 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=9A=91=20temp=20fix=20due=20to=20brok?= =?UTF-8?q?en=20modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/components/map/WebMap.web.tsx | 27 ++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/app/components/map/WebMap.web.tsx b/packages/app/components/map/WebMap.web.tsx index 53baff588..dd2657688 100644 --- a/packages/app/components/map/WebMap.web.tsx +++ b/packages/app/components/map/WebMap.web.tsx @@ -1,13 +1,13 @@ -import React, { useState } from 'react'; +import { MAPBOX_ACCESS_TOKEN } from '@packrat/config'; +import { useWebMap } from 'app/hooks/map/useWebMap'; +import useCustomStyles from 'app/hooks/useCustomStyles'; import mapboxgl from 'mapbox-gl'; -import { View, Modal, Alert } from 'react-native'; +import React, { useState } from 'react'; +import { View } from 'react-native'; import { isPolygonOrMultiPolygon } from '../../utils/mapFunctions'; import MapButtonsOverlay from './MapButtonsOverlay'; import MapPreview from './MapPreview'; -import useCustomStyles from 'app/hooks/useCustomStyles'; -import { useWebMap } from 'app/hooks/map/useWebMap'; import useGpxUpload from './useGpxUpload'; -import { MAPBOX_ACCESS_TOKEN } from '@packrat/config'; mapboxgl.accessToken = MAPBOX_ACCESS_TOKEN; @@ -64,13 +64,16 @@ const WebMap = ({ shape: shapeProp }) => { ); - return showModal ? ( - - {element} - - ) : ( - element - ); + // TODO: Fix this. The modal is not working as expected. + return element; + + // return showModal ? ( + // + // {element} + // + // ) : ( + // element + // ); }; const loadStyles = () => ({