8000 feat: map explore page by ChrAlpha · Pull Request #29 · Afilmory/afilmory · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: map explore page #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Jul 7, 2025
Merged

feat: map explore page #29

merged 25 commits into from
Jul 7, 2025

Conversation

ChrAlpha
Copy link
Contributor
@ChrAlpha ChrAlpha commented Jun 29, 2025

Kapture 2025-06-29 at 16 14 29

This PR introduces a new map feature for displaying photo locations using Mapbox. If this feature isn't welcomed to afilmory, just close this PR directly and I'd respect your decision.

TODO

ChrAlpha added 8 commits June 28, 2025 20:41
- Implemented GeoJsonLayer component for rendering GeoJSON data on the map.
- Created MapBackButton for easy navigation back to the gallery.
- Added MapControls component for user geolocation and navigation controls.
- Developed MapInfoPanel to display information about markers and bounds.
- Introduced MapLoadingState to show loading indicators while fetching data.
- Built Mapbox component to integrate Mapbox GL with custom markers and popups.
- Created PhotoMarkerPin for displaying photo markers on the map.
- Developed PhotoPopup for showing detailed information about selected photos.
- Added PureMapbox component for a simplified Mapbox integration.
- Updated index files to export new components and hooks.
- Implemented hooks for managing map bounds, marker selection, and loading photo markers.
- Created utility functions for handling GPS coordinates and calculating map bounds.
- Built MapSection and MapboxContainer components to manage map state and interactions.
- Updated Explory page to utilize new map components and improve loading/error handling.
- Defined TypeScript types for map-related data structures.
@MaxtuneLee
Copy link
Contributor

Thanks for your wonderful work 🚀. However, I was thinking that maybe we could abstract the map functionality into providers, rather than tying it specifically to Mapbox. This way, developers could easily switch to another map provider if needed. After all, using Mapbox requires registration and obtaining a token, and it may also incur fees if usage is high. 🤔

@ChrAlpha
Copy link
Contributor Author
ChrAlpha commented Jul 4, 2025

abstract the map functionality into providers, rather than tying it specifically to Mapbox. This way, developers could easily switch to another map provider if needed.

Thank you for your advice. After 523a37c (#29), the map section is following the hierarchy MapSection > MapProvider > MapAdapter > MapComponent, utilizing the Adapter and Strategy patterns to flexibly switch between different map service providers.

1. MapSection

This is the main entry component for the map feature. Its primary responsibilities are:

  • Data Preparation: It fetches the necessary data (e.g., photos with geotags) and transforms it into a format suitable for map markers. It also manages loading and error states during this process.
  • Context Provision: It wraps the entire map UI with the MapProvider, which is the key to enabling the map-switching strategy.
  • Suspend UI Rendering: It renders loading indicators or the final map component, passing the prepared data (like markers and the initial view) to it.

2. MapProvider

This component acts as the "brain" of the module. It doesn't render the map directly but decides which map service to use.

  • Strategy Selection: Its core logic determines the preferred map service based on application configuration. It can be configured to use a specific provider or to automatically select the first available one from a prioritized list (e.g., by checking for valid API keys).
  • Adapter Provision: After selecting a service, it uses a React Context to provide the chosen adapter and its corresponding map component to all child components. This allows downstream components to use the selected map service without needing to know which one it is.

3. MapAdapter (The Executor)

This file is the concrete implementation for a specific map service. It adapts a third-party map library to a standardized MapAdapter interface that the rest of the application can understand.

  • Interface Implementation: It conforms to a standard interface, which includes its name and a function to check if it's isAvailable. This availability check is used by the MapProvider to make its decision.
  • Component Wrapping: It provides a React component that acts as a "middleman." This component receives generic props from the application and translates them into the specific format required by the underlying map library (e.g., Mapbox). It handles library-specific logic and event handling.

4. MapComponent (The Pure UI)

This is the lowest-level component, responsible only for rendering the user interface of the map.

  • Highly Reusable: It is a presentational component that contains no business logic.
  • Concrete Rendering: It takes props and uses the actual components from the map library (e.g., Map, Marker, NavigationControl) to build the final map UI.

  • Easy to Extend: To support a new map service (e.g., Google Maps), you only need to:
    1. Create a new GoogleMapsAdapter that implements the standard interface.
    2. Register the new adapter in the MapProvider.
    3. Update the configuration to select it.

No changes are required in the core business logic components, making the system highly maintainable and scalable.

After all, using Mapbox requires registration and obtaining a token, and it may also incur fees if usage is high. 🤔

MapLibre is an open-source mapping library that does not require an API. react-map-gl provides MapLibre with a drop-in replacement of Mapbox. Therefore, I set MapLibre as default map provider to avoid high usage fee, while simultaneously offering Mapbox as an alternative option.

@ChrAlpha ChrAlpha changed the title [WIP] feat: map explore page feat: map explore page Jul 6, 2025
@ChrAlpha
Copy link
Contributor Author
ChrAlpha commented Jul 6, 2025

Kapture 2025-07-06 at 15 17 54

I believe the functionality for the standalone map page is now complete. To keep this PR focused, I'd like to avoid adding more complexity here.

Could the maintainers please review and consider merging? @MaxtuneLee @Innei

@MaxtuneLee
Copy link
Contributor

Very cool work! 🔥 However, I noticed a small issue — when two photos are located at the same (or very close) spot, it's not possible to click on the one behind. Maybe the interaction could be improved when photos are too close together?

image

⬆️ There are actually two photos here.

image

⬆️ When zoomed in, the two images overlap so tightly that I can't click the one underneath.

@Innei Innei requested a review from Copilot July 7, 2025 07:03
Copy link
Contributor
@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new “map explore” feature to display photo locations using Mapbox and MapLibre.

  • Defines a map configuration in site.config.ts and updates config.example.json
  • Adds localization keys for the map UI in all supported languages
  • Implements a generic map provider system (MapProvider, adapters, types, utilities) and integrates it into a new /explory page and gallery action

Reviewed Changes

Copilot reviewed 31 out of 33 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
site.config.ts Added MapConfig type and optional map field
config.example.json Provided example map settings
locales/app/*.json Added action.map.explore and explory.* keys
apps/web/src/types/map/*.ts Defined map-related types and provider interfaces
apps/web/src/modules/map/ Implemented MapProvider, adapters, utils, and UI
apps/web/src/pages/(main)/explory/index.tsx Created map exploration page component
apps/web/src/modules/gallery/ActionGroup.tsx Added “map explore” button to gallery actions
Comments suppressed due to low confidence (3)

apps/web/src/lib/map-utils.ts:101

  • Consider adding unit tests for convertPhotosToMarkersFromEXIF (and related GPS parsing utilities) to ensure correct behavior with various EXIF data formats and edge cases.
export function convertPhotosToMarkersFromEXIF(

locales/app/en.json:4

  • [nitpick] The label 'Map Explore' reads awkwardly; consider using 'Explore Map' for better clarity and consistency with other UI text.
  "action.map.explore": "Map Explore",

apps/web/src/modules/gallery/ActionGroup.tsx:320

  • The Button component is used here but not imported; please add import { Button } from '~/components/ui/button'.
      <Button

* Hook to get the current map adapter from the context.
*/
export const useMapAdapter = () => {
const context = use(MapContext)
Copy link
Preview
Copilot AI Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hook use(MapContext) is incorrect for consuming React context; please use React's useContext(MapContext) instead of use().

Suggested change
const context = use(MapContext)
const context = useContext(MapContext)

Copilot uses AI. Check for mistakes.


const value = useMemo(() => ({ adapter }), [adapter])

return <MapContext value={value}>{children}</MapContext>
Copy link
Preview
Copilot AI Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context provider usage is incorrect; wrap children in <MapContext.Provider value={value}> rather than using MapContext directly as a component.

Suggested change
return <MapContext value={value}>{children}</MapContext>
return <MapContext.Provider value={value}>{children}</MapContext.Provider>

Copilot uses AI. Check for mistakes.

}

// Simple clustering algorithm for small datasets
function clusterMarkers(markers: PhotoMarker[], zoom: number): ClusterPoint[] {
Copy link
Preview
Copilot AI Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simple distance-based clustering algorithm is O(n²) and may not scale well for large marker sets. Consider using a spatial index or a library like Supercluster for more efficient clustering.

Copilot uses AI. Check for mistakes.

…teractions

- Introduced a new HoverCard component for displaying additional information on photo markers.
- Implemented a GlassButton component for improved button aesthetics and interactions.
- Updated Map components to utilize the new HoverCard and GlassButton, enhancing user experience.
- Added new styles and refactored existing components for better visual consistency.

Signed-off-by: Innei <tukon479@gmail.com>
cursor[bot]

This comment was marked as outdated.

- Added zoom in, zoom out, compass, and geolocation buttons to Mapbox and MapLibre components for better user interaction.
- Refactored MapInfoPanel to improve layout and styling, including enhanced coordinate display and coverage area calculation.
- Updated animations and styles for a more cohesive visual experience across map components.

Signed-off-by: Innei <tukon479@gmail.com>
cursor[bot]

This comment was marked as outdated.

…ared utilities

- Introduced clustering functionality for photo markers, improving the display of grouped photos on the map.
- Added shared components for GeoJSON layers, cluster markers, and photo marker pins to streamline code and enhance reusability.
- Updated Mapbox and MapLibre components to utilize new shared utilities, improving maintainability and consistency across the application.
- Enhanced styling and interactions for cluster markers and photo markers, providing a better user experience.

Signed-off-by: Innei <tukon479@gmail.com>
cursor[bot]

This comment was marked as outdated.

Signed-off-by: Innei <tukon479@gmail.com>
cursor[bot]

This comment was marked as outdated.

- Added autoFitBounds prop to MapLibre and GenericMap components to automatically adjust the map view to fit all markers.
- Updated MapInfoPanel to include collapsible sections for better organization of coordinate and coverage area information.
- Introduced a new MapLibreStyle.json for improved map styling.
- Refactored MapControls to update the location icon for better visual consistency.

Signed-off-by: Innei <tukon479@gmail.com>
Copy link
@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: ClusterPhotoGrid Fails Date Validation

The ClusterPhotoGrid component's date range calculation is flawed. It uses DateTimeOriginal EXIF data without validating date string formats. Invalid date strings result in new Date() creating 'Invalid Date' objects, which return NaN for getTime(). This leads to unpredictable sorting and incorrect display of the earliest and latest dates.

apps/web/src/components/ui/map/ClusterPhotoGrid.tsx#L127-L133

{(() => {
const dates = photos
.map((p) => p.photo.exif?.DateTimeOriginal)
.filter(Boolean)
.map((d) => new Date(d!))
.sort((a, b) => a.getTime() - b.getTime())

apps/web/src/lib/map-utils.ts#L126-L132

)
return null
}
}
/**
* Convert array of PhotoManifestItem to PhotoMarker array using EXIF data

Fix in CursorFix in Web


Was this report helpful? Give feedback by reacting with 👍 or 👎

@Innei
Copy link
Contributor
Innei commented Jul 7, 2025

I removed mapbox support because I didn't have the energy to maintain the UI/UX for both maps. I redesigned the UI/UX for the current map, thanks for your contribution!

@Innei Innei merged commit 7310555 into Afilmory:main Jul 7, 2025
1 check passed
@ChrAlpha ChrAlpha mentioned this pull request Jul 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0