Fetch and cache photo events while browsing map and when opening place details
This commit is contained in:
@@ -27,6 +27,7 @@ export default class MapComponent extends Component {
|
||||
@service mapUi;
|
||||
@service router;
|
||||
@service settings;
|
||||
@service nostrData;
|
||||
|
||||
mapInstance;
|
||||
bookmarkSource;
|
||||
@@ -1078,6 +1079,7 @@ export default class MapComponent extends Component {
|
||||
const bbox = { minLat, minLon, maxLat, maxLon };
|
||||
this.mapUi.updateBounds(bbox);
|
||||
await this.storage.loadPlacesInBounds(bbox);
|
||||
this.nostrData.loadPlacesInBounds(bbox);
|
||||
this.loadBookmarks(this.storage.placesInView);
|
||||
|
||||
// Persist view to localStorage
|
||||
|
||||
@@ -183,6 +183,7 @@ export default class PlacePhotoUpload extends Component {
|
||||
|
||||
const event = await factory.sign(template);
|
||||
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
|
||||
this.nostrData.store.add(event);
|
||||
|
||||
this.toast.show('Photos published successfully');
|
||||
this.status = '';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import Service from '@ember/service';
|
||||
import Service, { service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class MapUiService extends Service {
|
||||
@service nostrData;
|
||||
|
||||
@tracked selectedPlace = null;
|
||||
@tracked isSearching = false;
|
||||
@tracked isCreating = false;
|
||||
@@ -19,12 +21,14 @@ export default class MapUiService extends Service {
|
||||
selectPlace(place, options = {}) {
|
||||
this.selectedPlace = place;
|
||||
this.selectionOptions = options;
|
||||
this.nostrData.loadPhotosForPlace(place);
|
||||
}
|
||||
|
||||
clearSelection() {
|
||||
this.selectedPlace = null;
|
||||
this.selectionOptions = {};
|
||||
this.preventNextZoom = false;
|
||||
this.nostrData.loadPhotosForPlace(null);
|
||||
}
|
||||
|
||||
setSearchResults(results) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { npubEncode } from 'applesauce-core/helpers/pointers';
|
||||
import { persistEventsToCache } from 'applesauce-core/helpers/event-cache';
|
||||
import { NostrIDB, openDB } from 'nostr-idb';
|
||||
import { normalizeRelayUrl } from '../utils/nostr';
|
||||
import { getGeohashPrefixesInBbox } from '../utils/geohash-coverage';
|
||||
|
||||
const DIRECTORY_RELAYS = [
|
||||
'wss://purplepag.es',
|
||||
@@ -27,13 +28,16 @@ export default class NostrDataService extends Service {
|
||||
@tracked profile = null;
|
||||
@tracked mailboxes = null;
|
||||
@tracked blossomServers = [];
|
||||
@tracked placePhotos = [];
|
||||
|
||||
_profileSub = null;
|
||||
_mailboxesSub = null;
|
||||
_blossomSub = null;
|
||||
_photosSub = null;
|
||||
|
||||
_requestSub = null;
|
||||
_cachePromise = null;
|
||||
loadedGeohashPrefixes = new Set();
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
@@ -51,9 +55,13 @@ export default class NostrDataService extends Service {
|
||||
this._stopPersisting = persistEventsToCache(
|
||||
this.store,
|
||||
async (events) => {
|
||||
// Only cache profiles, mailboxes, and blossom servers
|
||||
// Only cache profiles, mailboxes, blossom servers, and place photos
|
||||
const toCache = events.filter(
|
||||
(e) => e.kind === 0 || e.kind === 10002 || e.kind === 10063
|
||||
(e) =>
|
||||
e.kind === 0 ||
|
||||
e.kind === 10002 ||
|
||||
e.kind === 10063 ||
|
||||
e.kind === 360
|
||||
);
|
||||
|
||||
if (toCache.length > 0) {
|
||||
@@ -113,6 +121,138 @@ export default class NostrDataService extends Service {
|
||||
return this.defaultWriteRelays;
|
||||
}
|
||||
|
||||
async loadPlacesInBounds(bbox) {
|
||||
const requiredPrefixes = getGeohashPrefixesInBbox(bbox);
|
||||
|
||||
const missingPrefixes = requiredPrefixes.filter(
|
||||
(p) => !this.loadedGeohashPrefixes.has(p)
|
||||
);
|
||||
|
||||
if (missingPrefixes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug(
|
||||
'[nostr-data] Loading place photos for prefixes:',
|
||||
missingPrefixes
|
||||
);
|
||||
|
||||
try {
|
||||
await this._cachePromise;
|
||||
|
||||
const cachedEvents = await this.cache.query([
|
||||
{
|
||||
kinds: [360],
|
||||
'#g': missingPrefixes,
|
||||
},
|
||||
]);
|
||||
|
||||
if (cachedEvents && cachedEvents.length > 0) {
|
||||
for (const event of cachedEvents) {
|
||||
this.store.add(event);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
'[nostr-data] Failed to read photos from local Nostr IDB cache',
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Fire network request for new prefixes
|
||||
this.nostrRelay.pool
|
||||
.request(this.activeReadRelays, [
|
||||
{
|
||||
kinds: [360],
|
||||
'#g': missingPrefixes,
|
||||
},
|
||||
])
|
||||
.subscribe({
|
||||
next: (event) => {
|
||||
this.store.add(event);
|
||||
},
|
||||
error: (err) => {
|
||||
console.error(
|
||||
'[nostr-data] Error fetching place photos by geohash:',
|
||||
err
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
for (const p of missingPrefixes) {
|
||||
this.loadedGeohashPrefixes.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
async loadPhotosForPlace(place) {
|
||||
if (this._photosSub) {
|
||||
this._photosSub.unsubscribe();
|
||||
this._photosSub = null;
|
||||
}
|
||||
|
||||
this.placePhotos = [];
|
||||
|
||||
if (!place || !place.osmId || !place.osmType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entityId = `osm:${place.osmType}:${place.osmId}`;
|
||||
|
||||
// Setup reactive store query
|
||||
this._photosSub = this.store
|
||||
.timeline([
|
||||
{
|
||||
kinds: [360],
|
||||
'#i': [entityId],
|
||||
},
|
||||
])
|
||||
.subscribe((events) => {
|
||||
this.placePhotos = events;
|
||||
});
|
||||
|
||||
try {
|
||||
await this._cachePromise;
|
||||
|
||||
const cachedEvents = await this.cache.query([
|
||||
{
|
||||
kinds: [360],
|
||||
'#i': [entityId],
|
||||
},
|
||||
]);
|
||||
|
||||
if (cachedEvents && cachedEvents.length > 0) {
|
||||
for (const event of cachedEvents) {
|
||||
this.store.add(event);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
'[nostr-data] Failed to read photos for place from local Nostr IDB cache',
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Fire network request specifically for this place
|
||||
this.nostrRelay.pool
|
||||
.request(this.activeReadRelays, [
|
||||
{
|
||||
kinds: [360],
|
||||
'#i': [entityId],
|
||||
},
|
||||
])
|
||||
.subscribe({
|
||||
next: (event) => {
|
||||
this.store.add(event);
|
||||
},
|
||||
error: (err) => {
|
||||
console.error(
|
||||
'[nostr-data] Error fetching place photos for place:',
|
||||
err
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async loadProfile(pubkey) {
|
||||
if (!pubkey) return;
|
||||
|
||||
@@ -233,6 +373,10 @@ export default class NostrDataService extends Service {
|
||||
this._blossomSub.unsubscribe();
|
||||
this._blossomSub = null;
|
||||
}
|
||||
if (this._photosSub) {
|
||||
this._photosSub.unsubscribe();
|
||||
this._photosSub = null;
|
||||
}
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
|
||||
Reference in New Issue
Block a user