Fetch and cache photo events while browsing map and when opening place details

This commit is contained in:
2026-04-21 21:28:57 +04:00
parent 8d40b3bb35
commit 99cfd96ca1
4 changed files with 154 additions and 3 deletions

View File

@@ -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() {