150 lines
5.7 KiB
JavaScript
150 lines
5.7 KiB
JavaScript
import Service from '@ember/service';
|
|
import RemoteStorage from 'remotestoragejs';
|
|
import Places from '@remotestorage/module-places';
|
|
import Widget from 'remotestorage-widget';
|
|
import { tracked } from '@glimmer/tracking';
|
|
import { getGeohashPrefixesInBbox } from '../utils/geohash-coverage';
|
|
|
|
export default class StorageService extends Service {
|
|
rs;
|
|
@tracked savedPlaces = [];
|
|
@tracked loadedPrefixes = [];
|
|
@tracked currentBbox = null;
|
|
@tracked version = 0; // Shared version tracker for bookmarks
|
|
|
|
constructor() {
|
|
super(...arguments);
|
|
console.log('ohai');
|
|
|
|
this.rs = new RemoteStorage({
|
|
modules: [Places],
|
|
});
|
|
|
|
this.rs.access.claim('places', 'rw');
|
|
// Caching strategy:
|
|
// With the new nested structure, enabling caching on root '/' might try to sync everything.
|
|
// For now, let's keep it, but we might need to be more selective later if data grows huge.
|
|
// Note: The path structure changed from /places/ to just root access in the module?
|
|
// In places.ts: getPath returns "ab/cd/id".
|
|
// RemoteStorage modules usually implicitly use a base scope based on module name if not defined differently?
|
|
// Wait, the module defines `privateClient`.
|
|
// When we do `privateClient.storeObject`, it stores it under the module's scope.
|
|
// If module name is 'places', then it's stored under /places/.
|
|
// So getPath "ab/cd/id" becomes "/places/ab/cd/id".
|
|
// So enabling caching on '/places/' is correct.
|
|
// However, per instructions, we should set maxAge to false for listings in the module,
|
|
// which we did.
|
|
// We also need to be careful about what we cache here.
|
|
this.rs.caching.enable('/places/');
|
|
|
|
window.remoteStorage = this.rs;
|
|
|
|
// const widget = new Widget(this.rs);
|
|
// widget.attach();
|
|
|
|
this.rs.on('ready', () => {
|
|
// this.loadAllPlaces();
|
|
});
|
|
|
|
this.rs.scope('/places/').on('change', (event) => {
|
|
// When data changes remotely or locally, we should ideally re-fetch the affected area.
|
|
// However, we don't easily know the bbox of the changed event without parsing paths.
|
|
// For now, let's trigger a reload of the *currently loaded* prefixes to ensure consistency.
|
|
// Or simpler: just let the manual user interaction drive it?
|
|
// No, if a sync happens, we want to see it.
|
|
if (this.currentBbox) {
|
|
console.log('Reloading loaded prefixes due to change event');
|
|
// Reset loaded prefixes to force a reload of the current view
|
|
// Ideally we should just invalidate the specific changed one, but tracking that is harder.
|
|
// Or just re-run loadPlacesInBounds which filters? No, because filters exclude "loadedPrefixes".
|
|
|
|
// Strategy:
|
|
// 1. Calculate required prefixes for current view
|
|
const required = getGeohashPrefixesInBbox(this.currentBbox);
|
|
// 2. Force load them
|
|
this.loadAllPlaces(required);
|
|
// Note: we don't update loadedPrefixes here as they are already in the set,
|
|
// but we just want to refresh their data.
|
|
}
|
|
});
|
|
}
|
|
|
|
get places() {
|
|
return this.rs.places;
|
|
}
|
|
|
|
notifyChange() {
|
|
this.version++;
|
|
// this.loadAllPlaces();
|
|
}
|
|
|
|
async loadPlacesInBounds(bbox) {
|
|
// 1. Calculate required prefixes
|
|
const requiredPrefixes = getGeohashPrefixesInBbox(bbox);
|
|
|
|
// 2. Filter out prefixes we've already loaded
|
|
const missingPrefixes = requiredPrefixes.filter(
|
|
(p) => !this.loadedPrefixes.includes(p)
|
|
);
|
|
|
|
if (missingPrefixes.length === 0) {
|
|
// console.log('All prefixes already loaded for this view');
|
|
return;
|
|
}
|
|
|
|
console.log('Loading new prefixes:', missingPrefixes);
|
|
|
|
// 3. Load places for only the new prefixes
|
|
await this.loadAllPlaces(missingPrefixes);
|
|
|
|
// 4. Update our tracked list of loaded prefixes
|
|
// Using assignment to trigger reactivity if needed, though simple push/mutation might suffice
|
|
// depending on usage. Tracked arrays need reassignment or specific Ember array methods
|
|
// if we want to observe the array itself, but here we just check inclusion.
|
|
// Let's do a reassignment to be safe and clean.
|
|
this.loadedPrefixes = [...this.loadedPrefixes, ...missingPrefixes];
|
|
this.currentBbox = bbox;
|
|
}
|
|
|
|
async loadAllPlaces(prefixes = null) {
|
|
try {
|
|
// If prefixes is null, it loads everything (recursive scan).
|
|
// If prefixes is an array ['w1q7'], it loads just that sector.
|
|
const places = await this.rs.places.getPlaces(prefixes);
|
|
|
|
if (places && Array.isArray(places)) {
|
|
if (prefixes) {
|
|
// If partial load, we might want to merge instead of replace?
|
|
// For now, let's keep the simple behavior: replacing the tracked array triggers updates.
|
|
// However, replacing 'all saved places' with 'just these visible places'
|
|
// might hide other bookmarks from the list.
|
|
|
|
// Strategy: maintain a Map of loaded places to avoid duplicates/overwrites?
|
|
// Or just append unique ones?
|
|
const currentIds = new Set(this.savedPlaces.map((p) => p.id));
|
|
const newPlaces = places.filter((p) => !currentIds.has(p.id));
|
|
this.savedPlaces = [...this.savedPlaces, ...newPlaces];
|
|
} else {
|
|
// Full reload
|
|
this.savedPlaces = places;
|
|
}
|
|
} else {
|
|
if (!prefixes) this.savedPlaces = [];
|
|
}
|
|
console.log('Loaded saved places:', this.savedPlaces.length);
|
|
} catch (e) {
|
|
console.error('Failed to load places:', e);
|
|
}
|
|
}
|
|
|
|
findPlaceById(id) {
|
|
// Search by internal ID first
|
|
let place = this.savedPlaces.find((p) => p.id === id);
|
|
if (place) return place;
|
|
|
|
// Then search by OSM ID
|
|
place = this.savedPlaces.find((p) => p.osmId === id);
|
|
return place;
|
|
}
|
|
}
|