Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/components/MapComponent/FeatureMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface FeatureMapProps extends MapComponentProps {
export const FeatureMap = ({ geoJsonOverlaySources, drawerOpen, closeDrawer, ...mapProps }: FeatureMapProps): React.ReactNode => {
const { addFeature, savedFeatures } = useContext(SavedFeaturesContext)!

const [dynamicCenter, setDynamicCenter] = useState<[number, number] | undefined>(mapProps.center)
const [contextMenuPosition, setContextMenuPosition] = useState<L.LatLng | null>(null)
const [selectedFeature, setSelectedFeature] = useState<GeoJsonFeature | null>(null)
const [fixedOverlays, setFixedOverlays] = useState<TLayerOverlay[]>([])
Expand All @@ -40,6 +41,10 @@ export const FeatureMap = ({ geoJsonOverlaySources, drawerOpen, closeDrawer, ...
const [popupProps, setPopupProps] = useState<PopupOptions>({})
const [popupContainerProps, setPopupContainerProps] = useState<iPopupContainerProps>({})

useEffect(() => {
setDynamicCenter(mapProps.center)
}, [mapProps.center])

useEffect(() => {
const width = isMd ? 600 : isSm ? 450 : isXs ? 275 : 900
const height = isMd ? 450 : isSm ? 400 : isXs ? 350 : 500
Expand Down Expand Up @@ -124,14 +129,26 @@ export const FeatureMap = ({ geoJsonOverlaySources, drawerOpen, closeDrawer, ...
}))
}, [savedFeatures, onFeatureContextMenuHandler, popupProps, popupContainerProps])

const handleNavigateToCoordinates = useCallback((coords: [number, number]) => {
// Leaflet expects [lat, lng]. Ensure coords are in this format.
// GeoJSON is typically [lng, lat]. This will be handled when the callback is called.
setDynamicCenter(coords)
}, [])

return (
<>
<MapComponent overlays={[...fixedOverlays, ...dynamicOverlays]} contextMenuHandler={onMapContextMenuHandler} {...mapProps}>
<MapComponent
overlays={[...fixedOverlays, ...dynamicOverlays]}
contextMenuHandler={onMapContextMenuHandler}
{...mapProps}
center={dynamicCenter}
>
<FeatureMapContextMenu selectedFeature={selectedFeature} menuLatLng={contextMenuPosition} />
</MapComponent>
<SavedFeaturesDrawer
drawerOpen={drawerOpen}
onClose={closeDrawer}
navigateToCoordinates={handleNavigateToCoordinates}
/>
</>
)
Expand Down
11 changes: 11 additions & 0 deletions src/components/MapComponent/MapComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ const MapComponent = ({
localStorage.setItem("activeBaseLayer", activeBaseLayer)
}, [activeBaseLayer])

// useEffect to update mapState.center when the center prop changes
useEffect(() => {
// Ensure 'center' prop is defined and different from current mapState.center
// This prevents potential unnecessary updates or loops.
if (center && (center[0] !== mapState.center[0] || center[1] !== mapState.center[1])) {
setMapState(prevState => ({ ...prevState, center: center }));
}
// mapState.center is implicitly part of the closure for setMapState,
// but adding 'center' (the prop) is crucial for the dependency array.
}, [center]); // Only 'center' (the prop) is needed as a dependency. mapState.center is not needed here.

const handleMapMove = useCallback((center: [number, number], zoom: number) => {
setMapState({ center, zoom })
}, [])
Expand Down
44 changes: 28 additions & 16 deletions src/components/SavedFeaturesDrawer/FeatureList/FeatureList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,32 @@ import NoteEditor from "../../NoteEditor/NoteEditor"
import { SortableFeatureItem } from "./SortableFeatureItem"

interface FeatureListProps {
features: GeoJsonFeature[]
items: Array<{ feature: GeoJsonFeature; originalIndex: number }> // Updated to items
setSavedFeatures: {
(newState: SavedFeaturesStateType): void
(updater: (prev: SavedFeaturesStateType) => SavedFeaturesStateType): void
}
selectedTab: string
selectedFeature: selectionInfo | null
setSelectedFeature: (selection: selectionInfo | null) => void
handleContextMenu: (event: React.MouseEvent, selection: selectionInfo) => void
handleContextMenu: (event: React.MouseEvent | React.TouchEvent, selection: selectionInfo) => void
excludedProperties: string[]
// searchQuery prop removed
navigateToCoordinates?: (coords: [number, number]) => void
onClose?: () => void
}

export const FeatureList = ({
features,
items, // Destructure items
setSavedFeatures,
selectedTab,
selectedFeature,
setSelectedFeature,
handleContextMenu,
excludedProperties,
// searchQuery, // Removed from destructuring
navigateToCoordinates,
onClose,
}: FeatureListProps) => {
const [editorVisible, setEditorVisible] = useState(false)
const [notes, setNotes] = useState("")
Expand All @@ -51,11 +57,12 @@ export const FeatureList = ({
if (selectedFeature) {
setSavedFeatures((prev) => {
const newFeatures = [...prev[selectedTab]]
const index = newFeatures.findIndex((f, index) => idxFeat(index, f) === idxSel(selectedFeature))
if (index !== -1 && newFeatures[index].properties) {
newFeatures[index].properties.tripNotes = notes
// Note: selectedFeature.index is originalIndex. Find by that.
const featureToUpdate = newFeatures[selectedFeature.index];
if (featureToUpdate && featureToUpdate.properties) {
featureToUpdate.properties.tripNotes = notes;
}
return { ...prev, [selectedTab]: newFeatures }
return { ...prev, [selectedTab]: newFeatures };
})
}
}
Expand All @@ -69,31 +76,36 @@ export const FeatureList = ({

return (
<List>
{features.map((feature, index) => (
<React.Fragment key={idxFeat(index, feature)}>
{items.map((item) => ( // Changed to items.map; mapIndex is not strictly needed if key is stable
<React.Fragment key={idxFeat(item.originalIndex, item.feature)}>
<SortableFeatureItem
feature={feature}
id={idxFeat(index, feature)}
index={index}
feature={item.feature}
id={idxFeat(item.originalIndex, item.feature)} // Use originalIndex for stable ID
index={item.originalIndex} // Pass originalIndex as index
selectedTab={selectedTab}
selectedFeature={selectedFeature}
setSelectedFeature={setSelectedFeature}
handleContextMenu={handleContextMenu}
// searchQuery prop removed
navigateToCoordinates={navigateToCoordinates}
onClose={onClose}
/>
<Collapse in={idxSel(selectedFeature) === idxFeat(index, feature)} timeout="auto" unmountOnExit>
<Collapse in={idxSel(selectedFeature) === idxFeat(item.originalIndex, item.feature)} timeout="auto" unmountOnExit>
<ListItem sx={{ pl: 4 }}>
<Button onClick={() => openCloseEditor(feature)}>Add/edit notes</Button>
<Button onClick={() => openCloseEditor(item.feature)}>Add/edit notes</Button>
</ListItem>
{/* The "Go to on Map" button ListItem has been removed from here. */}
{editorVisible && (
<>
<ListItem sx={{ pl: 4 }}>
<NoteEditor key={idxFeat(index, feature)} initialText={notes} onChange={handleNotesChange} />
{/* Key for NoteEditor should be stable with the selected feature if possible */}
<NoteEditor key={idxFeat(item.originalIndex, item.feature)} initialText={notes} onChange={handleNotesChange} />
</ListItem>
<ListItem sx={{ pl: 4 }}><Button onClick={handleSaveNotes}>Save notes</Button></ListItem>
</>
)}
<List component="div" disablePadding>
{Object.entries(feature.properties || {})
{Object.entries(item.feature.properties || {})
.filter(([key]) => !excludedProperties.includes(key))
.map(([key, value]) => (
<ListItem key={key} sx={{ pl: 4 }}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { useSortable } from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { ListItem, ListItemText, ListItemIcon, IconButton } from "@mui/material"
import React from "react"
import { MdDragIndicator } from "react-icons/md"
import { ListItem, ListItemText, ListItemIcon, IconButton, ListItemSecondaryAction } from "@mui/material" // Added ListItemSecondaryAction
import React from "react" // Keep React import
import { MdDragIndicator, MdNearMe } from "react-icons/md" // Added MdNearMe

import { useLongPress } from "../../../hooks/useLongPress"
import { selectionInfo } from "../../../contexts/SavedFeaturesContext"
import { GeoJsonFeature } from "../../../data/types"
import idxFeat, { idxSel } from "../../../utils/idxFeat"
// filterFeatures import removed as it's not used
import { getCoordinatesForNavigation } from "../../../utils/navigationUtils" // Import the new utility function

interface SortableFeatureItemProps {
feature: GeoJsonFeature
Expand All @@ -15,10 +18,24 @@ interface SortableFeatureItemProps {
selectedTab: string
selectedFeature: selectionInfo | null
setSelectedFeature: (selection: selectionInfo | null) => void
handleContextMenu: (event: React.MouseEvent, selection: selectionInfo) => void
handleContextMenu: (event: React.MouseEvent | React.TouchEvent, selection: selectionInfo) => void
// searchQuery: string // Removed searchQuery prop
navigateToCoordinates?: (coords: [number, number]) => void
onClose?: () => void
}

export const SortableFeatureItem = ({ feature, id, index, selectedTab, selectedFeature, setSelectedFeature, handleContextMenu }: SortableFeatureItemProps) => {
export const SortableFeatureItem = ({
feature,
id,
index, // This will now be originalIndex
selectedTab,
selectedFeature,
setSelectedFeature,
handleContextMenu,
// searchQuery, // Removed from destructuring
navigateToCoordinates,
onClose,
}: SortableFeatureItemProps) => {
const {
attributes,
listeners,
Expand All @@ -36,22 +53,35 @@ export const SortableFeatureItem = ({ feature, id, index, selectedTab, selectedF
border: isDragging ? "dashed" : "",
}

const isSelected = idxSel(selectedFeature) === idxFeat(index, feature)
const selection: selectionInfo = { feature: feature, index: index, category: selectedTab }
const isSelected = idxSel(selectedFeature) === idxFeat(index, feature) // index is originalIndex
const selection: selectionInfo = { feature: feature, index: index, category: selectedTab } // index is originalIndex

// Removed isVisible calculation and conditional rendering
// const isVisible = searchQuery ? filterFeatures([feature], searchQuery).length > 0 : true
// if (!isVisible) {
// return null;
// }

// Integrate useLongPress
const longPressProps = useLongPress(
(event) => {
handleContextMenu(event, selection)
// event.stopPropagation() is called by useLongPress if the long press is successful
},
500 // Duration for long press
)

return (
<ListItem
ref={setNodeRef}
style={style}
onClick={(event: React.MouseEvent) => {
event.stopPropagation()
event.stopPropagation() // Keep stopPropagation for regular clicks
setSelectedFeature(isSelected ? null : selection)
}}
onContextMenu={(event) => {
handleContextMenu(event, selection)
event.stopPropagation()
}}
{...{ button: "true" }}
// Spread longPressProps here. This replaces the old onContextMenu.
{...longPressProps}
button // Use standard 'button' prop for clickability
>
<ListItemIcon>
<IconButton
Expand All @@ -71,6 +101,36 @@ export const SortableFeatureItem = ({ feature, id, index, selectedTab, selectedF
<ListItemText
primary={feature.properties?.name || "Unnamed Feature"}
/>
{/* Conditional rendering of the navigation icon button */}
{navigateToCoordinates && feature.geometry && (
<ListItemSecondaryAction>
<IconButton
edge="end" // Standard for secondary actions
aria-label="Navigate to POI"
onClick={(event: React.MouseEvent) => {
event.stopPropagation(); // Prevent ListItem's onClick

// The check for navigateToCoordinates and feature.geometry is removed
// as it's covered by the conditional rendering of the button
// and getCoordinatesForNavigation handles null feature/geometry.

const leafletCoords = getCoordinatesForNavigation(feature);

if (leafletCoords) {
// navigateToCoordinates is guaranteed to be defined here
// due to the conditional rendering: {navigateToCoordinates && feature.geometry && ...}
navigateToCoordinates(leafletCoords);
if (onClose) {
onClose();
}
}
// If leafletCoords is null, getCoordinatesForNavigation has already logged the error/warning.
}}
>
<MdNearMe />
</IconButton>
</ListItemSecondaryAction>
)}
</ListItem>
)
}
49 changes: 45 additions & 4 deletions src/components/SavedFeaturesDrawer/SavedFeaturesDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import {
Drawer,
Box,
TextField,
useTheme,
useMediaQuery,
} from "@mui/material"
import React, { useState, useContext, useCallback, useEffect } from "react"
import React, { useState, useContext, useCallback, useEffect, useMemo } from "react" // Added useMemo

import SavedFeaturesContext, { DEFAULT_CATEGORY } from "../../contexts/SavedFeaturesContext"

import { CategoryContextMenu } from "./ContextMenu/CategoryContextMenu"
import { FeatureContextMenu } from "./ContextMenu/FeatureContextMenu"
import { FeatureDragContext } from "./FeatureList/FeatureDragContext"
import { FeatureList } from "./FeatureList/FeatureList"
import { filterFeatures } from "./filterUtils" // Ensure this import is active
import { useCategoryManagement } from "./hooks/useCategoryManagement"
import { useContextMenu } from "./hooks/useContextMenu"
import { useFeatureManagement } from "./hooks/useFeatureManagement"
Expand All @@ -22,12 +24,19 @@ interface SavedFeaturesDrawerProps {
drawerOpen: boolean
onClose: () => void
setCurrentCategory?: (newState: string) => void
navigateToCoordinates?: (coords: [number, number]) => void // Added navigateToCoordinates prop
}

const excludedProperties = ["id", "images", "style"] as const

const SavedFeaturesDrawer: React.FC<SavedFeaturesDrawerProps> = ({ drawerOpen, onClose, setCurrentCategory }) => {
const SavedFeaturesDrawer: React.FC<SavedFeaturesDrawerProps> = ({
drawerOpen,
onClose,
setCurrentCategory,
navigateToCoordinates, // Destructured navigateToCoordinates
}) => {
const [selectedTab, setSelectedTab] = useState<string>(DEFAULT_CATEGORY)
const [searchQuery, setSearchQuery] = useState<string>("")

const { savedFeatures, setSavedFeatures, removeFeature } = useContext(SavedFeaturesContext)!
const { selectedFeature, setSelectedFeature } = useFeatureSelection()
Expand All @@ -49,12 +58,33 @@ const SavedFeaturesDrawer: React.FC<SavedFeaturesDrawerProps> = ({ drawerOpen, o
setSelectedFeature(null)
}, [setSelectedFeature])

const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(event.target.value)
}

useEffect(() => {
if (setCurrentCategory) {
setCurrentCategory(selectedTab)
}
}, [selectedTab, setCurrentCategory])

// Create itemsWithOriginalIndex
const itemsWithOriginalIndex = useMemo(() => {
return (savedFeatures[selectedTab] || []).map((feature, index) => ({
feature,
originalIndex: index,
}));
}, [savedFeatures, selectedTab]);

// Create filteredItems
const filteredItems = useMemo(() => {
// The itemsWithOriginalIndex already handles the case where savedFeatures[selectedTab] might be empty.
// filterFeatures will handle the empty searchQuery case.
return filterFeatures(itemsWithOriginalIndex, searchQuery);
}, [itemsWithOriginalIndex, searchQuery]);

// Dead code related to currentFeatures and filteredFeatures has been removed.

return (
<>
<FeatureDragContext savedFeatures={savedFeatures} selectedTab={selectedTab} setSavedFeatures={setSavedFeatures}>
Expand All @@ -81,15 +111,26 @@ const SavedFeaturesDrawer: React.FC<SavedFeaturesDrawerProps> = ({ drawerOpen, o
handleTabContextMenu={handleTabContextMenu}
/>
</Box>
<Box sx={{ flexGrow: 1, overflowY: "auto" }}>
<Box sx={{ flexGrow: 1, overflowY: "auto", p: 2 }}>
<TextField
fullWidth
label="Search Features"
variant="outlined"
value={searchQuery}
onChange={handleSearchChange}
sx={{ mb: 2 }}
/>
<FeatureList
features={savedFeatures[selectedTab] || []}
items={filteredItems} // New prop
// features and searchQuery props removed
setSavedFeatures={setSavedFeatures}
selectedTab={selectedTab}
selectedFeature={selectedFeature}
setSelectedFeature={setSelectedFeature}
handleContextMenu={handleContextMenu}
excludedProperties={Array.from(excludedProperties)}
navigateToCoordinates={navigateToCoordinates}
onClose={onClose}
/>
</Box>
</Box>
Expand Down
Loading