diff --git a/app/components/map.gjs b/app/components/map.gjs index 1a45d82..ffe817b 100644 --- a/app/components/map.gjs +++ b/app/components/map.gjs @@ -77,14 +77,17 @@ export default class MapComponent extends Component { // Listen to changes in the /places/ scope // keeping this as a backup or for future real-time sync support this.storage.rs.scope('/places/').on('change', (event) => { - console.log('RemoteStorage change detected:', event); - // this.loadBookmarks(); // Disabling auto-update for now per instructions, using explicit version action instead + console.log('RemoteStorage change detected:', event); + // this.loadBookmarks(); // Disabling auto-update for now per instructions, using explicit version action instead }); }); - // Re-fetch bookmarks when the version changes (triggered by parent action) - updateBookmarks = modifier((element, [version]) => { - this.loadBookmarks(); + // Re-fetch bookmarks when the version changes (triggered by parent action or service) + updateBookmarks = modifier(() => { + // Depend on the tracked storage.version + if (this.storage.version >= 0) { + this.loadBookmarks(); + } }); async loadBookmarks() { @@ -94,21 +97,21 @@ export default class MapComponent extends Component { this.bookmarkSource.clear(); if (places && Array.isArray(places)) { - places.forEach(place => { + places.forEach((place) => { if (place.lat && place.lon) { const feature = new Feature({ geometry: new Point(fromLonLat([place.lon, place.lat])), name: place.title, id: place.id, isBookmark: true, // Marker property to distinguish - originalPlace: place + originalPlace: place, }); this.bookmarkSource.addFeature(feature); } }); } } catch (e) { - console.error("Failed to load bookmarks:", e); + console.error('Failed to load bookmarks:', e); } } @@ -120,43 +123,46 @@ export default class MapComponent extends Component { let selectedFeatureType = null; if (features && features.length > 0) { - const bookmarkFeature = features.find(f => f.get('isBookmark')); - if (bookmarkFeature) { - clickedBookmark = bookmarkFeature.get('originalPlace'); - } - // Also get visual props for standard map click logic later - const props = features[0].getProperties(); - if (props.name) { - selectedFeatureName = props.name; - selectedFeatureType = props.class || props.subclass; - } + const bookmarkFeature = features.find((f) => f.get('isBookmark')); + if (bookmarkFeature) { + clickedBookmark = bookmarkFeature.get('originalPlace'); + } + // Also get visual props for standard map click logic later + const props = features[0].getProperties(); + if (props.name) { + selectedFeatureName = props.name; + selectedFeatureType = props.class || props.subclass; + } } // Special handling when sidebar is OPEN if (this.args.isSidebarOpen) { - // If it's a bookmark, we allow "switching" to it even if sidebar is open - if (clickedBookmark) { - console.log("Clicked bookmark while sidebar open (switching):", clickedBookmark); - if (this.args.onPlacesFound) { - this.args.onPlacesFound([], clickedBookmark); - } - return; - } + // If it's a bookmark, we allow "switching" to it even if sidebar is open + if (clickedBookmark) { + console.log( + 'Clicked bookmark while sidebar open (switching):', + clickedBookmark + ); + if (this.args.onPlacesFound) { + this.args.onPlacesFound([], clickedBookmark); + } + return; + } - // Otherwise (empty map or non-bookmark feature), close the sidebar - if (this.args.onOutsideClick) { - this.args.onOutsideClick(); - } - return; + // Otherwise (empty map or non-bookmark feature), close the sidebar + if (this.args.onOutsideClick) { + this.args.onOutsideClick(); + } + return; } // Normal behavior (sidebar is closed) if (clickedBookmark) { - console.log("Clicked bookmark:", clickedBookmark); - if (this.args.onPlacesFound) { - this.args.onPlacesFound([], clickedBookmark); - } - return; + console.log('Clicked bookmark:', clickedBookmark); + if (this.args.onPlacesFound) { + this.args.onPlacesFound([], clickedBookmark); + } + return; } const coords = toLonLat(event.coordinate); @@ -170,46 +176,63 @@ export default class MapComponent extends Component { let pois = await this.osm.getNearbyPois(lat, lon, searchRadius); // Sort by distance from click - pois = pois.map(p => { - // Use center lat/lon for ways/relations if available, else lat/lon - const pLat = p.lat || p.center?.lat; - const pLon = p.lon || p.center?.lon; - return { - ...p, - _distance: (pLat && pLon) ? getDistance(lat, lon, pLat, pLon) : 9999 - }; - }).sort((a, b) => a._distance - b._distance); + pois = pois + .map((p) => { + // Use center lat/lon for ways/relations if available, else lat/lon + const pLat = p.lat || p.center?.lat; + const pLon = p.lon || p.center?.lon; + return { + ...p, + _distance: pLat && pLon ? getDistance(lat, lon, pLat, pLon) : 9999, + }; + }) + .sort((a, b) => a._distance - b._distance); let matchedPlace = null; if (selectedFeatureName && pois.length > 0) { // Heuristic: // 1. Exact Name Match - matchedPlace = pois.find(p => p.tags && (p.tags.name === selectedFeatureName || p.tags['name:en'] === selectedFeatureName)); + matchedPlace = pois.find( + (p) => + p.tags && + (p.tags.name === selectedFeatureName || + p.tags['name:en'] === selectedFeatureName) + ); // 2. If no exact match, look for VERY close (<=20m) and matching type if (!matchedPlace) { - const topCandidate = pois[0]; - if (topCandidate._distance <= 20) { - // Check type compatibility if available - // (visual tile 'class' is often 'cafe', osm tag is 'amenity'='cafe') - const pType = topCandidate.tags.amenity || topCandidate.tags.shop || topCandidate.tags.tourism; - if (selectedFeatureType && pType && (selectedFeatureType === pType || pType.includes(selectedFeatureType))) { - console.log("Heuristic match found (distance + type):", topCandidate); - matchedPlace = topCandidate; - } else if (topCandidate._distance <= 10) { - // Even without type match, if it's super close (<=10m), it's likely the one. - console.log("Heuristic match found (proximity):", topCandidate); - matchedPlace = topCandidate; - } - } + const topCandidate = pois[0]; + if (topCandidate._distance <= 20) { + // Check type compatibility if available + // (visual tile 'class' is often 'cafe', osm tag is 'amenity'='cafe') + const pType = + topCandidate.tags.amenity || + topCandidate.tags.shop || + topCandidate.tags.tourism; + if ( + selectedFeatureType && + pType && + (selectedFeatureType === pType || + pType.includes(selectedFeatureType)) + ) { + console.log( + 'Heuristic match found (distance + type):', + topCandidate + ); + matchedPlace = topCandidate; + } else if (topCandidate._distance <= 10) { + // Even without type match, if it's super close (<=10m), it's likely the one. + console.log('Heuristic match found (proximity):', topCandidate); + matchedPlace = topCandidate; + } + } } } if (this.args.onPlacesFound) { this.args.onPlacesFound(pois, matchedPlace); } - } catch (error) { console.error('Failed to fetch POIs:', error); } @@ -218,7 +241,7 @@ export default class MapComponent extends Component { diff --git a/app/components/places-sidebar.gjs b/app/components/places-sidebar.gjs index d1aa567..3bcedd4 100644 --- a/app/components/places-sidebar.gjs +++ b/app/components/places-sidebar.gjs @@ -1,7 +1,6 @@ import Component from '@glimmer/component'; import { service } from '@ember/service'; import { action } from '@ember/object'; -import { tracked } from '@glimmer/tracking'; import { on } from '@ember/modifier'; import { fn } from '@ember/helper'; import or from 'ember-truth-helpers/helpers/or'; @@ -22,7 +21,7 @@ export default class PlacesSidebar extends Component { if (this.args.onSelect) { this.args.onSelect(null); } - + // Fallback logic: if no list available, close sidebar if (!this.args.places || this.args.places.length === 0) { if (this.args.onClose) { @@ -47,13 +46,35 @@ export default class PlacesSidebar extends Component { if (this.args.onBookmarkChange) { this.args.onBookmarkChange(); } - + + // Update selection to the new saved place object + // This updates the local UI state immediately without a route refresh + if (this.args.onUpdate) { + // When deleting, we revert to a "fresh" object or just close. + // Since we close the sidebar below, we might not strictly need to update local state, + // but it's good practice. + // Reconstruct the "original" place without ID/Geohash/CreatedAt + const freshPlace = { + ...place, + id: undefined, + geohash: undefined, + createdAt: undefined + }; + this.args.onUpdate(freshPlace); + } + + // Also fire onSelect if it exists (for list view) + if (this.args.onSelect) { + // Similar logic for select if needed, but we usually close. + this.args.onSelect(null); + } + // Close sidebar after delete if (this.args.onClose) { this.args.onClose(); } } else { - alert('Cannot delete: Missing ID or Geohash'); + alert('Cannot delete: Missing ID or Geohash'); } } catch (e) { console.error('Failed to delete:', e); @@ -66,18 +87,23 @@ export default class PlacesSidebar extends Component { title: place.tags.name || place.tags['name:en'] || 'Untitled Place', lat: place.lat, lon: place.lon, - tags: [], + tags: [], url: place.tags.website, - osmId: String(place.id), + osmId: String(place.osmId || place.id), // Ensure we grab osmId if available, or fallback to id }; try { const savedPlace = await this.storage.places.store(placeData); console.log('Place saved:', placeData.title); - + // Notify parent to refresh map bookmarks if (this.args.onBookmarkChange) { - this.args.onBookmarkChange(); + this.args.onBookmarkChange(); + } + + // Update selection to the new saved place object + if (this.args.onUpdate) { + this.args.onUpdate(savedPlace); } // Update selection to the new saved place object @@ -95,44 +121,86 @@ export default class PlacesSidebar extends Component {