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 mapUi;
|
||||||
@service router;
|
@service router;
|
||||||
@service settings;
|
@service settings;
|
||||||
|
@service nostrData;
|
||||||
|
|
||||||
mapInstance;
|
mapInstance;
|
||||||
bookmarkSource;
|
bookmarkSource;
|
||||||
@@ -1078,6 +1079,7 @@ export default class MapComponent extends Component {
|
|||||||
const bbox = { minLat, minLon, maxLat, maxLon };
|
const bbox = { minLat, minLon, maxLat, maxLon };
|
||||||
this.mapUi.updateBounds(bbox);
|
this.mapUi.updateBounds(bbox);
|
||||||
await this.storage.loadPlacesInBounds(bbox);
|
await this.storage.loadPlacesInBounds(bbox);
|
||||||
|
this.nostrData.loadPlacesInBounds(bbox);
|
||||||
this.loadBookmarks(this.storage.placesInView);
|
this.loadBookmarks(this.storage.placesInView);
|
||||||
|
|
||||||
// Persist view to localStorage
|
// Persist view to localStorage
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ export default class PlacePhotoUpload extends Component {
|
|||||||
|
|
||||||
const event = await factory.sign(template);
|
const event = await factory.sign(template);
|
||||||
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
|
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
|
||||||
|
this.nostrData.store.add(event);
|
||||||
|
|
||||||
this.toast.show('Photos published successfully');
|
this.toast.show('Photos published successfully');
|
||||||
this.status = '';
|
this.status = '';
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import Service from '@ember/service';
|
import Service, { service } from '@ember/service';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
export default class MapUiService extends Service {
|
export default class MapUiService extends Service {
|
||||||
|
@service nostrData;
|
||||||
|
|
||||||
@tracked selectedPlace = null;
|
@tracked selectedPlace = null;
|
||||||
@tracked isSearching = false;
|
@tracked isSearching = false;
|
||||||
@tracked isCreating = false;
|
@tracked isCreating = false;
|
||||||
@@ -19,12 +21,14 @@ export default class MapUiService extends Service {
|
|||||||
selectPlace(place, options = {}) {
|
selectPlace(place, options = {}) {
|
||||||
this.selectedPlace = place;
|
this.selectedPlace = place;
|
||||||
this.selectionOptions = options;
|
this.selectionOptions = options;
|
||||||
|
this.nostrData.loadPhotosForPlace(place);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
this.selectedPlace = null;
|
this.selectedPlace = null;
|
||||||
this.selectionOptions = {};
|
this.selectionOptions = {};
|
||||||
this.preventNextZoom = false;
|
this.preventNextZoom = false;
|
||||||
|
this.nostrData.loadPhotosForPlace(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSearchResults(results) {
|
setSearchResults(results) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { npubEncode } from 'applesauce-core/helpers/pointers';
|
|||||||
import { persistEventsToCache } from 'applesauce-core/helpers/event-cache';
|
import { persistEventsToCache } from 'applesauce-core/helpers/event-cache';
|
||||||
import { NostrIDB, openDB } from 'nostr-idb';
|
import { NostrIDB, openDB } from 'nostr-idb';
|
||||||
import { normalizeRelayUrl } from '../utils/nostr';
|
import { normalizeRelayUrl } from '../utils/nostr';
|
||||||
|
import { getGeohashPrefixesInBbox } from '../utils/geohash-coverage';
|
||||||
|
|
||||||
const DIRECTORY_RELAYS = [
|
const DIRECTORY_RELAYS = [
|
||||||
'wss://purplepag.es',
|
'wss://purplepag.es',
|
||||||
@@ -27,13 +28,16 @@ export default class NostrDataService extends Service {
|
|||||||
@tracked profile = null;
|
@tracked profile = null;
|
||||||
@tracked mailboxes = null;
|
@tracked mailboxes = null;
|
||||||
@tracked blossomServers = [];
|
@tracked blossomServers = [];
|
||||||
|
@tracked placePhotos = [];
|
||||||
|
|
||||||
_profileSub = null;
|
_profileSub = null;
|
||||||
_mailboxesSub = null;
|
_mailboxesSub = null;
|
||||||
_blossomSub = null;
|
_blossomSub = null;
|
||||||
|
_photosSub = null;
|
||||||
|
|
||||||
_requestSub = null;
|
_requestSub = null;
|
||||||
_cachePromise = null;
|
_cachePromise = null;
|
||||||
|
loadedGeohashPrefixes = new Set();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
@@ -51,9 +55,13 @@ export default class NostrDataService extends Service {
|
|||||||
this._stopPersisting = persistEventsToCache(
|
this._stopPersisting = persistEventsToCache(
|
||||||
this.store,
|
this.store,
|
||||||
async (events) => {
|
async (events) => {
|
||||||
// Only cache profiles, mailboxes, and blossom servers
|
// Only cache profiles, mailboxes, blossom servers, and place photos
|
||||||
const toCache = events.filter(
|
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) {
|
if (toCache.length > 0) {
|
||||||
@@ -113,6 +121,138 @@ export default class NostrDataService extends Service {
|
|||||||
return this.defaultWriteRelays;
|
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) {
|
async loadProfile(pubkey) {
|
||||||
if (!pubkey) return;
|
if (!pubkey) return;
|
||||||
|
|
||||||
@@ -233,6 +373,10 @@ export default class NostrDataService extends Service {
|
|||||||
this._blossomSub.unsubscribe();
|
this._blossomSub.unsubscribe();
|
||||||
this._blossomSub = null;
|
this._blossomSub = null;
|
||||||
}
|
}
|
||||||
|
if (this._photosSub) {
|
||||||
|
this._photosSub.unsubscribe();
|
||||||
|
this._photosSub = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
willDestroy() {
|
willDestroy() {
|
||||||
|
|||||||
Reference in New Issue
Block a user