From 44f1d9d08489f37c081e1bd87ca948d625d1814c Mon Sep 17 00:00:00 2001 From: Amanda Anderson Date: Thu, 12 Dec 2024 12:34:35 +0100 Subject: [PATCH 1/2] Move code into separate components or util functions --- front/app/components/IdeasMap/index.tsx | 318 +++++------------- .../IdeasMap/mobile/MobileIdeaOverlay.tsx | 77 +++++ front/app/components/IdeasMap/utils.tsx | 225 ++++++++++++- 3 files changed, 375 insertions(+), 245 deletions(-) create mode 100644 front/app/components/IdeasMap/mobile/MobileIdeaOverlay.tsx diff --git a/front/app/components/IdeasMap/index.tsx b/front/app/components/IdeasMap/index.tsx index 215de79f4399..a84ed44b3f0e 100644 --- a/front/app/components/IdeasMap/index.tsx +++ b/front/app/components/IdeasMap/index.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unnecessary-condition */ import React, { memo, useCallback, @@ -9,20 +8,14 @@ import React, { useState, } from 'react'; -import Point from '@arcgis/core/geometry/Point'; -import Graphic from '@arcgis/core/Graphic'; import FeatureLayer from '@arcgis/core/layers/FeatureLayer'; -import Renderer from '@arcgis/core/renderers/SimpleRenderer'; import MapView from '@arcgis/core/views/MapView'; import { Box, - colors, useBreakpoint, useWindowSize, - viewportWidths, } from '@citizenlab/cl2-component-library'; import { useSearchParams } from 'react-router-dom'; -import { CSSTransition } from 'react-transition-group'; import styled, { useTheme } from 'styled-components'; import { IIdeaMarkers } from 'api/idea_markers/types'; @@ -35,58 +28,39 @@ import useLocalize from 'hooks/useLocalize'; import LayerHoverLabel from 'components/ConfigurationMap/components/LayerHoverLabel'; import EsriMap from 'components/EsriMap'; import { - getClusterConfiguration, showAddInputPopup, goToMapLocation, esriPointToGeoJson, changeCursorOnHover, parseLayers, - getShapeSymbol, } from 'components/EsriMap/utils'; import { Props as InputFiltersProps } from 'components/IdeaCards/IdeasWithFiltersSidebar/InputFilters'; import { useIntl } from 'utils/cl-intl'; -import clHistory from 'utils/cl-router/history'; import { removeSearchParams } from 'utils/cl-router/removeSearchParams'; import { updateSearchParams } from 'utils/cl-router/updateSearchParams'; import { isAdmin } from 'utils/permissions/roles'; import IdeaMapOverlay from './desktop/IdeaMapOverlay'; -import IdeaMapCard from './IdeaMapCard'; import IdeasAtLocationPopup from './IdeasAtLocationPopup'; import InstructionMessage from './InstructionMessage'; import messages from './messages'; +import MobileIdeaOverlay from './mobile/MobileIdeaOverlay'; import StartIdeaButton from './StartIdeaButton'; import { InnerContainer, + generateIdeaFeatureLayer, + generateIdeaMapGraphics, + getIdeaSymbol, getInnerContainerLeftMargin, + handleListIdeaSelection, initialContainerWidth, initialInnerContainerLeftMargin, mapHeightDesktop, - mapHeightMobile, + openIdeaSelectionPopup, + openSelectedIdeaPanel, } from './utils'; -// Note: Existing custom styling -const StyledIdeaMapCard = styled(IdeaMapCard)<{ isClickable: boolean }>` - width: calc(100% - 24px); - position: absolute; - top: calc(${mapHeightMobile} - 220px - 24px); - left: 12px; - right: 12px; - z-index: 1001; - pointer-events: ${(props) => (props.isClickable ? 'auto' : 'none')}; - transition: opacity 300ms cubic-bezier(0.19, 1, 0.22, 1), - top 300ms cubic-bezier(0.19, 1, 0.22, 1); - - &.animation-enter { - opacity: 0; - - &.animation-enter-active { - opacity: 1; - } - } -`; - // Custom styling for Esri map const StyledMapContainer = styled(Box)` position: relative; @@ -122,10 +96,14 @@ const IdeasMap = memo( const theme = useTheme(); const localize = useLocalize(); const { formatMessage } = useIntl(); + const isMobileOrSmaller = useBreakpoint('phone'); + const { data: phase } = usePhase(phaseId); const { data: authUser } = useAuthUser(); + + // Data from search params const [searchParams] = useSearchParams(); - const isMobileOrSmaller = useBreakpoint('phone'); + const selectedIdea = searchParams.get('idea_map_id'); // Create div elements to use for inserting React components into Esri map popup // Docs: https://developers.arcgis.com/javascript/latest/custom-ui/#introduction @@ -142,10 +120,6 @@ const IdeasMap = memo( const [clickedMapLocation, setClickedMapLocation] = useState(null); - const selectedIdea = searchParams.get('idea_map_id'); - - const ideaData = ideaMarkers?.data.find((idea) => idea.id === selectedIdea); - const setSelectedIdea = useCallback((ideaId: string | null) => { if (ideaId) { updateSearchParams({ idea_map_id: ideaId }); @@ -158,39 +132,29 @@ const IdeasMap = memo( string[] | null >(null); - // Map icon for ideas + const selectedIdeaData = ideaMarkers?.data.find( + (idea) => idea.id === selectedIdea + ); + + // Map icons for ideas const ideaIcon = useMemo(() => { - return getShapeSymbol({ - shape: 'circle', - color: theme.colors.tenantPrimary, - outlineColor: colors.white, - outlineWidth: 2, - sizeInPx: 18, - }); - }, [theme.colors.tenantPrimary]); - - const ideaIconSecondary = useMemo(() => { - return getShapeSymbol({ - shape: 'circle', - color: theme.colors.tenantSecondary, - outlineColor: colors.white, - outlineWidth: 2, - sizeInPx: 18, - }); - }, [theme.colors.tenantSecondary]); + return getIdeaSymbol('primary', theme); + }, [theme]); + const ideaIconSelected = useMemo(() => { + return getIdeaSymbol('selected', theme); + }, [theme]); // Existing handling for dynamic container width const { windowWidth } = useWindowSize(); - const tablet = useMemo(() => { - return windowWidth <= viewportWidths.tablet; - }, [windowWidth]); + // Container width state (old code). TODO: Clean this up. const containerRef = useRef(null); const [containerWidth, setContainerWidth] = useState(initialContainerWidth); const [innerContainerLeftMargin, setInnerContainerLeftMargin] = useState( initialInnerContainerLeftMargin ); + // Update container width when window width changes (old code). TODO: Clean this up. useLayoutEffect(() => { const newContainerWidth = containerRef.current ?.getBoundingClientRect() @@ -205,7 +169,7 @@ const IdeasMap = memo( setInnerContainerLeftMargin( getInnerContainerLeftMargin(windowWidth, containerWidth) ); - }, [windowWidth, containerWidth, tablet]); + }, [windowWidth, containerWidth]); // Create Esri layers from mapConfig layers const mapLayers = useMemo(() => { @@ -214,74 +178,30 @@ const IdeasMap = memo( // Create a point graphics layer for idea pins const graphics = useMemo(() => { - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const ideasWithLocations = ideaMarkers?.data?.filter( - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - (idea) => idea?.attributes?.location_point_geojson - ); - return ideasWithLocations?.map((idea) => { - const coordinates = - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - idea?.attributes?.location_point_geojson?.coordinates; - return new Graphic({ - geometry: new Point({ - longitude: coordinates?.[0], - latitude: coordinates?.[1], - }), - attributes: { - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - ideaId: idea?.id, - }, - }); - }); + return generateIdeaMapGraphics(ideaMarkers); }, [ideaMarkers]); // Create an Esri feature layer from the idea pin graphics so we can add a cluster display const ideasLayer = useMemo(() => { if (graphics) { - return new FeatureLayer({ - source: graphics, // Array of idea graphics - title: formatMessage(messages.userInputs), - id: 'ideasLayer', - outFields: ['*'], - objectIdField: 'ID', - fields: [ - { - name: 'ID', - type: 'oid', - }, - { - name: 'ideaId', // From the graphics attributes - type: 'string', - }, - ], - // Set the symbol used to render the graphics - renderer: new Renderer({ - symbol: ideaIcon, - }), - legendEnabled: false, - // Add cluster display to this layer - featureReduction: getClusterConfiguration(theme.colors.tenantPrimary), - // Add a popup template which is used when multiple ideas share a single location - popupTemplate: { - title: formatMessage(messages.multipleInputsAtLocation), - content: () => { - return ideasAtLocationNode; - }, - }, + return generateIdeaFeatureLayer({ + FeatureLayer, + graphics, + ideaIcon, + ideaIconSelected, + ideasAtLocationNode, + theme, + formatMessage, }); } return undefined; }, [ - formatMessage, graphics, - ideasAtLocationNode, - theme.colors.tenantPrimary, ideaIcon, + ideaIconSelected, + ideasAtLocationNode, + theme, + formatMessage, ]); const layers = useMemo(() => { @@ -296,9 +216,7 @@ const IdeasMap = memo( // If an idea was selected in the URL params, move map to that idea if (selectedIdea) { - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const point = ideaMarkers?.data?.find( + const point = ideaMarkers?.data.find( (idea) => idea.id === selectedIdea )?.attributes.location_point_geojson; @@ -328,17 +246,13 @@ const IdeasMap = memo( // Get any map elements underneath map click const elements = result.results; if (elements.length > 0) { - // There are map elements - user clicked a layer, idea pin OR a cluster + // There are map elements, which means the user clicked a layer, idea pin OR a cluster const topElement = elements[0]; if (topElement.type === 'graphic') { - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const graphicId = topElement?.graphic?.attributes?.ID; - const clusterCount = - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - topElement?.graphic?.attributes?.cluster_count; + const graphicId = topElement.graphic.attributes.ID; + const clusterCount = topElement.graphic.attributes.cluster_count; + if (clusterCount) { // User clicked a cluster. Zoom in on the cluster. goToMapLocation( @@ -348,68 +262,30 @@ const IdeasMap = memo( ); } else if (graphicId) { // User clicked an idea pin or layer. - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const ideaId = topElement?.graphic?.attributes?.ideaId; - + const ideaId = topElement.graphic.attributes.ideaId; const ideasAtClickCount = elements.filter( (element) => element.type === 'graphic' && - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - element?.graphic?.layer.id === 'ideasLayer' + element.graphic.layer.id === 'ideasLayer' ).length; // If there are multiple ideas at this same location (overlapping pins), show the idea selection popup. if (ideasAtClickCount > 1 && mapView.zoom >= 19) { - goToMapLocation( - esriPointToGeoJson(topElement.mapPoint), - mapView - ).then(() => { - const ideaIds = elements.map((element) => { - // Get list of idea ids at this location - if (element.type === 'graphic') { - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const layerId = element?.graphic?.layer?.id; - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const ideaId = element?.graphic?.attributes?.ideaId; - if (ideaId && layerId === 'ideasLayer') { - return ideaId; - } - } - }); - // Set state and open the idea selection popup - setIdeasSharingLocation(ideaIds); - mapView.popup.open({ - features: [topElement.graphic], - location: topElement.mapPoint, - }); + openIdeaSelectionPopup({ + setIdeasSharingLocation, + topElement, + mapView, + elements, }); } else { // Otherwise, open the selected idea in the information panel if (ideaId) { - goToMapLocation( - esriPointToGeoJson(topElement.mapPoint), - mapView - ).then(() => { - setSelectedIdea(ideaId); - // Add a graphic symbol to highlight which point was clicked - const geometry = topElement.graphic.geometry; - if (geometry.type === 'point') { - const graphic = new Graphic({ - geometry, - symbol: ideaIconSecondary, - }); - mapView.graphics.removeAll(); - - // Add the graphic to the map for a few seconds to highlight the clicked point - mapView.graphics.add(graphic); - setTimeout(() => { - mapView.graphics.removeAll(); - }, 2000); - } + openSelectedIdeaPanel({ + ideaId, + mapView, + topElement, + ideaIconSelected, + setSelectedIdea, }); } } @@ -446,7 +322,7 @@ const IdeasMap = memo( phase?.data.attributes.submission_enabled, startIdeaButtonNode, formatMessage, - ideaIconSecondary, + ideaIconSelected, ] ); @@ -462,10 +338,7 @@ const IdeasMap = memo( const topElement = elements[0]; if (topElement.type === 'graphic') { // Set the hovered layer id - const customParameters = - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - topElement.layer && topElement.layer['customParameters']; + const customParameters = topElement.layer['customParameters']; setHoveredLayerId(customParameters?.layerId || null); } } else { @@ -476,36 +349,22 @@ const IdeasMap = memo( const onSelectIdeaFromList = useCallback( (selectedIdeaId: string | null) => { - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const ideaPoint = ideaMarkers?.data?.find( + const ideaPoint = ideaMarkers?.data.find( (idea) => idea.id === selectedIdeaId - )?.attributes?.location_point_geojson; + )?.attributes.location_point_geojson; if (selectedIdeaId && ideaPoint && esriMapView) { - goToMapLocation(ideaPoint, esriMapView).then(() => { - // Create a graphic symbol to highlight the selected point - const graphic = new Graphic({ - geometry: new Point({ - latitude: ideaPoint.coordinates[1], - longitude: ideaPoint.coordinates[0], - }), - symbol: ideaIconSecondary, - }); - esriMapView.graphics.removeAll(); - // Show the graphic on the map for a few seconds to highlight the selected point - esriMapView.graphics.add(graphic); - setTimeout(() => { - esriMapView.graphics.removeAll(); - }, 2000); - - setSelectedIdea(selectedIdeaId); - return; + handleListIdeaSelection({ + ideaPoint, + selectedIdeaId, + ideaIconSelected, + esriMapView, + setSelectedIdea, }); } setSelectedIdea(selectedIdeaId); }, - [ideaMarkers, esriMapView, ideaIconSecondary, setSelectedIdea] + [ideaMarkers, esriMapView, ideaIconSelected, setSelectedIdea] ); return ( @@ -525,9 +384,7 @@ const IdeasMap = memo( zoomWidgetLocation: 'right', onInit: onMapInit, }} - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - webMapId={mapConfig?.data?.attributes?.esri_web_map_id} + webMapId={mapConfig?.data.attributes.esri_web_map_id} height={isMobileOrSmaller ? '68vh' : '80vh'} layers={layers} onHover={onMapHover} @@ -550,42 +407,19 @@ const IdeasMap = memo( + ideas={ideaMarkers?.data.filter((idea) => ideasSharingLocation?.includes(idea.id) )} mapView={esriMapView} /> {isMobileOrSmaller && ( - - - {ideaData && ( - { - setSelectedIdea(null); - }} - onSelectIdea={(ideaId: string) => { - clHistory.push( - `/ideas/${ideaData.attributes.slug}?go_back=true`, - { - scrollToTop: true, - } - ); - setSelectedIdea(ideaId); - }} - isClickable={true} - projectId={projectId} - phaseId={phaseId} - /> - )} - - + )} {!isMobileOrSmaller && ( ` + width: calc(100% - 24px); + position: absolute; + top: calc(${mapHeightMobile} - 220px - 24px); + left: 12px; + right: 12px; + z-index: 1001; + pointer-events: ${(props) => (props.isClickable ? 'auto' : 'none')}; + transition: opacity 300ms cubic-bezier(0.19, 1, 0.22, 1), + top 300ms cubic-bezier(0.19, 1, 0.22, 1); + + &.animation-enter { + opacity: 0; + + &.animation-enter-active { + opacity: 1; + } + } +`; + +type Props = { + selectedIdea: string | null; + setSelectedIdea: (ideaId: string | null) => void; + selectedIdeaData: IIdeaMarkerData | undefined; + projectId: string; + phaseId?: string; +}; +const MobileIdeaOverlay = ({ + selectedIdea, + setSelectedIdea, + selectedIdeaData, + projectId, + phaseId, +}: Props) => { + return ( + + + {selectedIdeaData && ( + { + setSelectedIdea(null); + }} + onSelectIdea={(ideaId: string) => { + clHistory.push( + `/ideas/${selectedIdeaData.attributes.slug}?go_back=true`, + { + scrollToTop: true, + } + ); + setSelectedIdea(ideaId); + }} + isClickable={true} + projectId={projectId} + phaseId={phaseId} + /> + )} + + + ); +}; + +export default MobileIdeaOverlay; diff --git a/front/app/components/IdeasMap/utils.tsx b/front/app/components/IdeasMap/utils.tsx index e7881fad03a9..05a1ef8b98cf 100644 --- a/front/app/components/IdeasMap/utils.tsx +++ b/front/app/components/IdeasMap/utils.tsx @@ -1,8 +1,26 @@ +import Point from '@arcgis/core/geometry/Point'; +import Graphic from '@arcgis/core/Graphic'; +import FeatureLayer from '@arcgis/core/layers/FeatureLayer'; +import Renderer from '@arcgis/core/renderers/SimpleRenderer'; import { colors, media } from '@citizenlab/cl2-component-library'; import styled from 'styled-components'; +import { IIdeaMarkers } from 'api/idea_markers/types'; + import { maxPageWidth } from 'containers/ProjectsShowPage/styles'; +import { + esriPointToGeoJson, + getClusterConfiguration, + getShapeSymbol, + goToMapLocation, +} from 'components/EsriMap/utils'; + +import { MessageDescriptor } from 'utils/cl-intl'; +import { FormatMessageValues } from 'utils/cl-intl/useIntl'; + +import messages from './messages'; + // BELOW: Custom handling for idea map width // Description: This was existing styling prior to Esri migration. // TODO: Cleanup these styles @@ -25,9 +43,7 @@ export const initialWindowWidth = Math.max( window.innerWidth || 0 ); export const initialContainerWidth = - // TODO: Fix this the next time the file is edited. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - document?.getElementById('e2e-ideas-container')?.offsetWidth || + document.getElementById('e2e-ideas-container')?.offsetWidth || initialWindowWidth < maxPageWidth ? initialWindowWidth - 40 : maxPageWidth; @@ -75,3 +91,206 @@ export const InnerContainer = styled.div<{ } `} `; + +// Generate Esri point graphics from the ideaMarkers +export const generateIdeaMapGraphics = ( + ideaMarkers: IIdeaMarkers | undefined +) => { + // Filter out ideas without location + const ideasWithLocations = ideaMarkers?.data.filter( + (idea) => idea.attributes.location_point_geojson + ); + // Generate Esri point graphics + return ideasWithLocations?.map((idea) => { + const coordinates = idea.attributes.location_point_geojson?.coordinates; + return new Graphic({ + geometry: new Point({ + longitude: coordinates?.[0], + latitude: coordinates?.[1], + }), + attributes: { + ideaId: idea.id, + }, + }); + }); +}; + +type GenerateFeatureLayerProps = { + FeatureLayer: any; + graphics: __esri.Graphic[]; + ideaIcon: __esri.SimpleMarkerSymbol; + ideaIconSelected: __esri.SimpleMarkerSymbol; + ideasAtLocationNode: HTMLDivElement; + theme: any; + formatMessage: ( + messageDescriptor: MessageDescriptor, + values?: FormatMessageValues + ) => string; +}; + +// Generate Esri feature layer for idea graphics +export const generateIdeaFeatureLayer = ({ + graphics, + formatMessage, + ideaIcon, + ideasAtLocationNode, + theme, +}: GenerateFeatureLayerProps) => { + return new FeatureLayer({ + source: graphics, // Array of idea graphics + title: formatMessage(messages.userInputs), + id: 'ideasLayer', + outFields: ['*'], + objectIdField: 'ID', + fields: [ + { + name: 'ID', + type: 'oid', + }, + { + name: 'ideaId', // From the graphics attributes + type: 'string', + }, + ], + // Set the symbol used to render the graphics + renderer: new Renderer({ + symbol: ideaIcon, + }), + legendEnabled: false, + // Add cluster display to this layer + featureReduction: getClusterConfiguration(theme.colors.tenantPrimary), + // Add a popup template which is used when multiple ideas share a single location + popupTemplate: { + title: formatMessage(messages.multipleInputsAtLocation), + content: () => { + return ideasAtLocationNode; + }, + }, + }); +}; + +// Generate Esri circle icons for the idea map +export const getIdeaSymbol = (variant: 'primary' | 'selected', theme) => { + if (variant === 'primary') { + return getShapeSymbol({ + shape: 'circle', + color: theme.colors.tenantPrimary, + outlineColor: colors.white, + outlineWidth: 2, + sizeInPx: 18, + }); + } else { + return getShapeSymbol({ + shape: 'circle', + color: theme.colors.tenantSecondary, + outlineColor: colors.white, + outlineWidth: 2, + sizeInPx: 18, + }); + } +}; + +type OpenSelectedIdeaProps = { + ideaId: string; + mapView: __esri.MapView; + topElement: __esri.GraphicHit; + ideaIconSelected: __esri.SimpleMarkerSymbol; + setSelectedIdea: (ideaId: string | null) => void; +}; + +type IdeaSelectionProps = { + ideaPoint: GeoJSON.Point; + esriMapView: __esri.MapView; + ideaIconSelected: __esri.SimpleMarkerSymbol; + selectedIdeaId: string | null; + setSelectedIdea: (ideaId: string | null) => void; +}; +// Function to handle when an idea is selected from the ideas list +export const handleListIdeaSelection = ({ + esriMapView, + ideaPoint, + ideaIconSelected, + setSelectedIdea, + selectedIdeaId, +}: IdeaSelectionProps) => { + goToMapLocation(ideaPoint, esriMapView).then(() => { + // Create a graphic symbol to highlight the selected point + const graphic = new Graphic({ + geometry: new Point({ + latitude: ideaPoint.coordinates[1], + longitude: ideaPoint.coordinates[0], + }), + symbol: ideaIconSelected, + }); + esriMapView.graphics.removeAll(); + // Show the graphic on the map for a few seconds to highlight the selected point + esriMapView.graphics.add(graphic); + setTimeout(() => { + esriMapView.graphics.removeAll(); + }, 2000); + + setSelectedIdea(selectedIdeaId); + return; + }); +}; + +// Function to open the selected idea panel view and highlight the clicked point +export const openSelectedIdeaPanel = ({ + ideaId, + mapView, + topElement, + ideaIconSelected, + setSelectedIdea, +}: OpenSelectedIdeaProps) => { + goToMapLocation(esriPointToGeoJson(topElement.mapPoint), mapView).then(() => { + setSelectedIdea(ideaId); + // Add a graphic symbol to highlight which point was clicked + const geometry = topElement.graphic.geometry; + if (geometry.type === 'point') { + const graphic = new Graphic({ + geometry, + symbol: ideaIconSelected, + }); + mapView.graphics.removeAll(); + + // Add the graphic to the map for a few seconds to highlight the clicked point + mapView.graphics.add(graphic); + setTimeout(() => { + mapView.graphics.removeAll(); + }, 2000); + } + }); +}; + +type OpenSelectionPopupProps = { + elements: __esri.ViewHit[]; + topElement: __esri.GraphicHit; + mapView: __esri.MapView; + setIdeasSharingLocation: (ideaIds: string[]) => void; +}; + +export const openIdeaSelectionPopup = ({ + setIdeasSharingLocation, + topElement, + mapView, + elements, +}: OpenSelectionPopupProps) => { + goToMapLocation(esriPointToGeoJson(topElement.mapPoint), mapView).then(() => { + const ideaIds = elements.map((element) => { + // Get list of idea ids at this location + if (element.type === 'graphic') { + const layerId = element.graphic.layer.id; + const ideaId = element.graphic.attributes.ideaId; + if (ideaId && layerId === 'ideasLayer') { + return ideaId; + } + } + }); + // Set state and open the idea selection popup + setIdeasSharingLocation(ideaIds); + mapView.popup.open({ + features: [topElement.graphic], + location: topElement.mapPoint, + }); + }); +}; From 08c53ee5146d3c1ad4241209046ef4093bda0d60 Mon Sep 17 00:00:00 2001 From: Amanda Anderson Date: Thu, 12 Dec 2024 12:38:34 +0100 Subject: [PATCH 2/2] Improve comments --- front/app/components/IdeasMap/index.tsx | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/front/app/components/IdeasMap/index.tsx b/front/app/components/IdeasMap/index.tsx index a84ed44b3f0e..94ddb689c0fd 100644 --- a/front/app/components/IdeasMap/index.tsx +++ b/front/app/components/IdeasMap/index.tsx @@ -101,11 +101,11 @@ const IdeasMap = memo( const { data: phase } = usePhase(phaseId); const { data: authUser } = useAuthUser(); - // Data from search params + // Get selected idea data from search params const [searchParams] = useSearchParams(); const selectedIdea = searchParams.get('idea_map_id'); - // Create div elements to use for inserting React components into Esri map popup + // Create two div elements to use for inserting custom React components into Esri map popups // Docs: https://developers.arcgis.com/javascript/latest/custom-ui/#introduction const startIdeaButtonNode = useMemo(() => { return document.createElement('div'); @@ -128,14 +128,15 @@ const IdeasMap = memo( } }, []); - const [ideasSharingLocation, setIdeasSharingLocation] = useState< - string[] | null - >(null); - const selectedIdeaData = ideaMarkers?.data.find( (idea) => idea.id === selectedIdea ); + // State for storing ideas that are at the exact same location + const [ideasSharingLocation, setIdeasSharingLocation] = useState< + string[] | null + >(null); + // Map icons for ideas const ideaIcon = useMemo(() => { return getIdeaSymbol('primary', theme); @@ -144,17 +145,17 @@ const IdeasMap = memo( return getIdeaSymbol('selected', theme); }, [theme]); - // Existing handling for dynamic container width + // Container width state (old code prior to Esri integration). + // TODO: Clean this up. const { windowWidth } = useWindowSize(); - - // Container width state (old code). TODO: Clean this up. const containerRef = useRef(null); const [containerWidth, setContainerWidth] = useState(initialContainerWidth); const [innerContainerLeftMargin, setInnerContainerLeftMargin] = useState( initialInnerContainerLeftMargin ); - // Update container width when window width changes (old code). TODO: Clean this up. + // Update container width when window width changes (old code prior to Esri integration). + // TODO: Clean this up. useLayoutEffect(() => { const newContainerWidth = containerRef.current ?.getBoundingClientRect() @@ -171,7 +172,7 @@ const IdeasMap = memo( ); }, [windowWidth, containerWidth]); - // Create Esri layers from mapConfig layers + // Create Esri layers from any mapConfig layers const mapLayers = useMemo(() => { return parseLayers(mapConfig, localize); }, [mapConfig, localize]); @@ -181,7 +182,7 @@ const IdeasMap = memo( return generateIdeaMapGraphics(ideaMarkers); }, [ideaMarkers]); - // Create an Esri feature layer from the idea pin graphics so we can add a cluster display + // Create an Esri feature layer from the idea pin graphics (so we can add a cluster display) const ideasLayer = useMemo(() => { if (graphics) { return generateIdeaFeatureLayer({