Load all saved place into memory
Fixes launching the app with a place URL directly, and will be useful for search etc. later.
This commit is contained in:
@@ -407,8 +407,8 @@ export default class MapComponent extends Component {
|
|||||||
|
|
||||||
// Re-fetch bookmarks when the version changes (triggered by parent action or service)
|
// Re-fetch bookmarks when the version changes (triggered by parent action or service)
|
||||||
updateBookmarks = modifier(() => {
|
updateBookmarks = modifier(() => {
|
||||||
// Depend on the tracked storage.savedPlaces to automatically update when they change
|
// Depend on the tracked storage.placesInView to automatically update when they change
|
||||||
const places = this.storage.savedPlaces;
|
const places = this.storage.placesInView;
|
||||||
this.loadBookmarks(places);
|
this.loadBookmarks(places);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -418,13 +418,13 @@ export default class MapComponent extends Component {
|
|||||||
|
|
||||||
if (!places || places.length === 0) {
|
if (!places || places.length === 0) {
|
||||||
// Fallback or explicit check if we have tracked property usage?
|
// Fallback or explicit check if we have tracked property usage?
|
||||||
// The service updates 'savedPlaces'. We should probably use that if we want reactiveness.
|
// The service updates 'placesInView'. We should probably use that if we want reactiveness.
|
||||||
places = this.storage.savedPlaces;
|
places = this.storage.placesInView;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Previously: const places = await this.storage.places.getPlaces();
|
// Previously: const places = await this.storage.places.getPlaces();
|
||||||
// We no longer want to fetch everything blindly.
|
// We no longer want to fetch everything blindly.
|
||||||
// We rely on 'savedPlaces' being updated by handleMapMove calling storage.loadPlacesInBounds.
|
// We rely on 'placesInView' being updated by handleMapMove calling storage.loadPlacesInBounds.
|
||||||
|
|
||||||
this.bookmarkSource.clear();
|
this.bookmarkSource.clear();
|
||||||
|
|
||||||
@@ -457,7 +457,7 @@ export default class MapComponent extends Component {
|
|||||||
|
|
||||||
const bbox = { minLat, minLon, maxLat, maxLon };
|
const bbox = { minLat, minLon, maxLat, maxLon };
|
||||||
await this.storage.loadPlacesInBounds(bbox);
|
await this.storage.loadPlacesInBounds(bbox);
|
||||||
this.loadBookmarks(this.storage.savedPlaces);
|
this.loadBookmarks(this.storage.placesInView);
|
||||||
|
|
||||||
// Persist view to localStorage
|
// Persist view to localStorage
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export default class PlaceRoute extends Route {
|
|||||||
return this.loadOsmPlace(osmId, type);
|
return this.loadOsmPlace(osmId, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for storage sync before checking bookmarks
|
||||||
|
await this.waitForSync();
|
||||||
|
|
||||||
// 1. Try to find in local bookmarks
|
// 1. Try to find in local bookmarks
|
||||||
let bookmark = this.storage.findPlaceById(id);
|
let bookmark = this.storage.findPlaceById(id);
|
||||||
|
|
||||||
@@ -29,6 +32,22 @@ export default class PlaceRoute extends Route {
|
|||||||
return this.loadOsmPlace(id);
|
return this.loadOsmPlace(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async waitForSync() {
|
||||||
|
if (this.storage.initialSyncDone) return;
|
||||||
|
|
||||||
|
console.log('Waiting for initial storage sync...');
|
||||||
|
const timeout = 5000;
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
while (!this.storage.initialSyncDone) {
|
||||||
|
if (Date.now() - start > timeout) {
|
||||||
|
console.warn('Timed out waiting for initial sync');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
afterModel(model) {
|
afterModel(model) {
|
||||||
// Notify the Map UI to show the pin
|
// Notify the Map UI to show the pin
|
||||||
if (model) {
|
if (model) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import Geohash from 'latlon-geohash';
|
|||||||
|
|
||||||
export default class StorageService extends Service {
|
export default class StorageService extends Service {
|
||||||
rs;
|
rs;
|
||||||
|
@tracked placesInView = [];
|
||||||
@tracked savedPlaces = [];
|
@tracked savedPlaces = [];
|
||||||
@tracked loadedPrefixes = [];
|
@tracked loadedPrefixes = [];
|
||||||
@tracked currentBbox = null;
|
@tracked currentBbox = null;
|
||||||
@@ -35,17 +36,60 @@ export default class StorageService extends Service {
|
|||||||
// console.debug('[rs] client ready');
|
// console.debug('[rs] client ready');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rs.on('sync-done', result => {
|
this.rs.on('sync-done', (result) => {
|
||||||
// console.debug('[rs] sync done:', result);
|
// console.debug('[rs] sync done:', result);
|
||||||
if (!this.initialSyncDone) { this.initialSyncDone = true; }
|
if (!this.initialSyncDone) {
|
||||||
|
this.initialSyncDone = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rs.scope('/places/').on('change', (event) => {
|
this.rs.scope('/places/').on('change', (event) => {
|
||||||
console.debug(event);
|
// console.debug(event);
|
||||||
|
this.handlePlaceChange(event);
|
||||||
debounce(this, this.reloadCurrentView, 200);
|
debounce(this, this.reloadCurrentView, 200);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePlaceChange(event) {
|
||||||
|
const { newValue, relativePath } = event;
|
||||||
|
|
||||||
|
// Remove old entry if exists
|
||||||
|
// The relativePath is like "geohash/geohash/ULID" or just "ULID" depending on structure.
|
||||||
|
// Our structure is <2-char>/<2-char>/<id>.
|
||||||
|
// But let's rely on the ID inside the object if possible, or extract from path.
|
||||||
|
|
||||||
|
// We can't easily identify the ID from just relativePath without parsing logic if it's nested.
|
||||||
|
// However, for deletions (newValue is undefined), we might need the ID.
|
||||||
|
// Fortunately, our objects (newValue) contain the ID.
|
||||||
|
|
||||||
|
// If it's a deletion, we need to find the object in our array to remove it.
|
||||||
|
// Since we don't have the ID in newValue (it's null), we rely on `relativePath`.
|
||||||
|
// Let's assume the filename is the ID.
|
||||||
|
const pathParts = relativePath.split('/');
|
||||||
|
const id = pathParts[pathParts.length - 1];
|
||||||
|
|
||||||
|
if (!newValue) {
|
||||||
|
// Deletion
|
||||||
|
this.savedPlaces = this.savedPlaces.filter((p) => p.id !== id);
|
||||||
|
} else {
|
||||||
|
// Add or Update
|
||||||
|
// Ensure the object has the ID (it should)
|
||||||
|
const place = { ...newValue, id };
|
||||||
|
|
||||||
|
// Update existing or add new
|
||||||
|
const index = this.savedPlaces.findIndex((p) => p.id === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
// Replace
|
||||||
|
const newPlaces = [...this.savedPlaces];
|
||||||
|
newPlaces[index] = place;
|
||||||
|
this.savedPlaces = newPlaces;
|
||||||
|
} else {
|
||||||
|
// Add
|
||||||
|
this.savedPlaces = [...this.savedPlaces, place];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get places() {
|
get places() {
|
||||||
return this.rs.places;
|
return this.rs.places;
|
||||||
}
|
}
|
||||||
@@ -105,7 +149,7 @@ export default class StorageService extends Service {
|
|||||||
// Identify existing places that belong to the reloaded prefixes and remove them
|
// Identify existing places that belong to the reloaded prefixes and remove them
|
||||||
const prefixSet = new Set(prefixes);
|
const prefixSet = new Set(prefixes);
|
||||||
|
|
||||||
const keptPlaces = this.savedPlaces.filter((place) => {
|
const keptPlaces = this.placesInView.filter((place) => {
|
||||||
if (!place.lat || !place.lon) return false;
|
if (!place.lat || !place.lon) return false;
|
||||||
try {
|
try {
|
||||||
// Calculate 4-char geohash for the existing place
|
// Calculate 4-char geohash for the existing place
|
||||||
@@ -119,15 +163,15 @@ export default class StorageService extends Service {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Merge the kept places (from other areas) with the fresh places (from these areas)
|
// Merge the kept places (from other areas) with the fresh places (from these areas)
|
||||||
this.savedPlaces = [...keptPlaces, ...places];
|
this.placesInView = [...keptPlaces, ...places];
|
||||||
} else {
|
} else {
|
||||||
// Full reload
|
// Full reload
|
||||||
this.savedPlaces = places;
|
this.placesInView = places;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!prefixes) this.savedPlaces = [];
|
if (!prefixes) this.placesInView = [];
|
||||||
}
|
}
|
||||||
console.log('Loaded saved places:', this.savedPlaces.length);
|
console.log('Loaded saved places:', this.placesInView.length);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load places:', e);
|
console.error('Failed to load places:', e);
|
||||||
}
|
}
|
||||||
|
|||||||
11
tests/unit/routes/place-test.js
Normal file
11
tests/unit/routes/place-test.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupTest } from 'marco/tests/helpers';
|
||||||
|
|
||||||
|
module('Unit | Route | place', function (hooks) {
|
||||||
|
setupTest(hooks);
|
||||||
|
|
||||||
|
test('it exists', function (assert) {
|
||||||
|
let route = this.owner.lookup('route:place');
|
||||||
|
assert.ok(route);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user