diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index 7e16d96e6b..b80c08f384 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -31,7 +31,7 @@ export class Stores { * @param promise * @constructor */ - public static FromPromise(promise: Promise): Store { + public static FromPromise(promise: Promise): Store { const src = new UIEventSource(undefined) promise?.then((d) => src.setData(d)) promise?.catch((err) => console.warn("Promise failed:", err)) @@ -97,7 +97,7 @@ export abstract class Store implements Readable { abstract map(f: (t: T) => J): Store abstract map(f: (t: T) => J, extraStoresToWatch: Store[]): Store - public mapD(f: (t: T) => J, extraStoresToWatch?: Store[]): Store { + public mapD(f: (t: Exclude) => J, extraStoresToWatch?: Store[]): Store { return this.map((t) => { if (t === undefined) { return undefined @@ -105,7 +105,7 @@ export abstract class Store implements Readable { if (t === null) { return null } - return f(t) + return f(> t) }, extraStoresToWatch) } @@ -603,7 +603,7 @@ export class UIEventSource extends Store implements Writable { */ public static FromPromiseWithErr( promise: Promise - ): UIEventSource<{ success: T } | { error: any }> { + ): UIEventSource<{ success: T } | { error: any } | undefined> { const src = new UIEventSource<{ success: T } | { error: any }>(undefined) promise?.then((d) => src.setData({ success: d })) promise?.catch((err) => src.setData({ error: err })) @@ -771,18 +771,21 @@ export class UIEventSource extends Store implements Writable { * Monoidal map which results in a read-only store. 'undefined' is passed 'as is' * Given a function 'f', will construct a new UIEventSource where the contents will always be "f(this.data)' */ - public mapD(f: (t: T) => J, extraSources: Store[] = []): Store { + public mapD(f: (t: Exclude) => J, extraSources: Store[] = []): Store { return new MappedStore( this, (t) => { if (t === undefined) { return undefined } - return f(t) + if (t === null) { + return null + } + return f(> t) }, extraSources, this._callbacks, - this.data === undefined ? undefined : f(this.data) + (this.data === undefined || this.data === null) ?( this.data) : f( this.data) ) } diff --git a/src/Logic/Web/NearbyImagesSearch.ts b/src/Logic/Web/NearbyImagesSearch.ts index 2c81f19e06..c442b59c02 100644 --- a/src/Logic/Web/NearbyImagesSearch.ts +++ b/src/Logic/Web/NearbyImagesSearch.ts @@ -40,15 +40,26 @@ export interface P4CPicture { export default class NearbyImagesSearch { public static readonly services = ["mapillary", "flickr", "kartaview", "wikicommons"] as const public static readonly apiUrls = ["https://api.flickr.com"] - private readonly individualStores: Store<{ images: P4CPicture[]; beforeFilter: number }>[] + private readonly individualStores: Store<{ images: P4CPicture[]; beforeFilter: number } | undefined>[] private readonly _store: UIEventSource = new UIEventSource([]) public readonly store: Store = this._store + public readonly allDone: Store private readonly _options: NearbyImageOptions constructor(options: NearbyImageOptions, features: IndexedFeatureSource) { this.individualStores = NearbyImagesSearch.services.map((s) => NearbyImagesSearch.buildPictureFetcher(options, s) ) + + const allDone = new UIEventSource(false) + this.allDone = allDone + const self = this + function updateAllDone(){ + const stillRunning = self.individualStores.some(store => store.data === undefined) + allDone.setData(!stillRunning) + } + self.individualStores.forEach(s => s.addCallback(_ => updateAllDone())) + this._options = options if (features !== undefined) { const osmImages = new ImagesInLoadedDataFetcher(features).fetchAround({ @@ -93,13 +104,17 @@ export default class NearbyImagesSearch { private static buildPictureFetcher( options: NearbyImageOptions, fetcher: P4CService - ): Store<{ images: P4CPicture[]; beforeFilter: number }> { - const p4cStore = Stores.FromPromise( + ): Store<{ images: P4CPicture[]; beforeFilter: number } | null | undefined> { + const p4cStore = Stores.FromPromiseWithErr( NearbyImagesSearch.fetchImages(options, fetcher) ) const searchRadius = options.searchRadius ?? 100 - return p4cStore.map( - (images) => { + return p4cStore.mapD( + (imagesState) => { + if(imagesState["error"]){ + return null + } + let images = imagesState["success"] if (images === undefined) { return undefined }