Core: improve typing of mapD

This commit is contained in:
Pieter Vander Vennet 2023-12-03 03:51:18 +01:00
parent 09ea799ad4
commit ab5ec29c02
2 changed files with 30 additions and 12 deletions

View file

@ -31,7 +31,7 @@ export class Stores {
* @param promise * @param promise
* @constructor * @constructor
*/ */
public static FromPromise<T>(promise: Promise<T>): Store<T> { public static FromPromise<T>(promise: Promise<T>): Store<T | undefined> {
const src = new UIEventSource<T>(undefined) const src = new UIEventSource<T>(undefined)
promise?.then((d) => src.setData(d)) promise?.then((d) => src.setData(d))
promise?.catch((err) => console.warn("Promise failed:", err)) promise?.catch((err) => console.warn("Promise failed:", err))
@ -97,7 +97,7 @@ export abstract class Store<T> implements Readable<T> {
abstract map<J>(f: (t: T) => J): Store<J> abstract map<J>(f: (t: T) => J): Store<J>
abstract map<J>(f: (t: T) => J, extraStoresToWatch: Store<any>[]): Store<J> abstract map<J>(f: (t: T) => J, extraStoresToWatch: Store<any>[]): Store<J>
public mapD<J>(f: (t: T) => J, extraStoresToWatch?: Store<any>[]): Store<J> { public mapD<J>(f: (t: Exclude<T, undefined | null>) => J, extraStoresToWatch?: Store<any>[]): Store<J> {
return this.map((t) => { return this.map((t) => {
if (t === undefined) { if (t === undefined) {
return undefined return undefined
@ -105,7 +105,7 @@ export abstract class Store<T> implements Readable<T> {
if (t === null) { if (t === null) {
return null return null
} }
return f(t) return f(<Exclude<T, undefined | null>> t)
}, extraStoresToWatch) }, extraStoresToWatch)
} }
@ -603,7 +603,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
*/ */
public static FromPromiseWithErr<T>( public static FromPromiseWithErr<T>(
promise: Promise<T> promise: Promise<T>
): UIEventSource<{ success: T } | { error: any }> { ): UIEventSource<{ success: T } | { error: any } | undefined> {
const src = new UIEventSource<{ success: T } | { error: any }>(undefined) const src = new UIEventSource<{ success: T } | { error: any }>(undefined)
promise?.then((d) => src.setData({ success: d })) promise?.then((d) => src.setData({ success: d }))
promise?.catch((err) => src.setData({ error: err })) promise?.catch((err) => src.setData({ error: err }))
@ -771,18 +771,21 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
* Monoidal map which results in a read-only store. 'undefined' is passed 'as is' * 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)' * Given a function 'f', will construct a new UIEventSource where the contents will always be "f(this.data)'
*/ */
public mapD<J>(f: (t: T) => J, extraSources: Store<any>[] = []): Store<J | undefined> { public mapD<J>(f: (t: Exclude<T, undefined | null>) => J, extraSources: Store<any>[] = []): Store<J | undefined> {
return new MappedStore( return new MappedStore(
this, this,
(t) => { (t) => {
if (t === undefined) { if (t === undefined) {
return undefined return undefined
} }
return f(t) if (t === null) {
return null
}
return f(<Exclude<T, undefined | null>> t)
}, },
extraSources, extraSources,
this._callbacks, this._callbacks,
this.data === undefined ? undefined : f(this.data) (this.data === undefined || this.data === null) ?(<undefined | null> this.data) : f(<any> this.data)
) )
} }

View file

@ -40,15 +40,26 @@ export interface P4CPicture {
export default class NearbyImagesSearch { export default class NearbyImagesSearch {
public static readonly services = ["mapillary", "flickr", "kartaview", "wikicommons"] as const public static readonly services = ["mapillary", "flickr", "kartaview", "wikicommons"] as const
public static readonly apiUrls = ["https://api.flickr.com"] 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<P4CPicture[]> = new UIEventSource<P4CPicture[]>([]) private readonly _store: UIEventSource<P4CPicture[]> = new UIEventSource<P4CPicture[]>([])
public readonly store: Store<P4CPicture[]> = this._store public readonly store: Store<P4CPicture[]> = this._store
public readonly allDone: Store<boolean>
private readonly _options: NearbyImageOptions private readonly _options: NearbyImageOptions
constructor(options: NearbyImageOptions, features: IndexedFeatureSource) { constructor(options: NearbyImageOptions, features: IndexedFeatureSource) {
this.individualStores = NearbyImagesSearch.services.map((s) => this.individualStores = NearbyImagesSearch.services.map((s) =>
NearbyImagesSearch.buildPictureFetcher(options, 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 this._options = options
if (features !== undefined) { if (features !== undefined) {
const osmImages = new ImagesInLoadedDataFetcher(features).fetchAround({ const osmImages = new ImagesInLoadedDataFetcher(features).fetchAround({
@ -93,13 +104,17 @@ export default class NearbyImagesSearch {
private static buildPictureFetcher( private static buildPictureFetcher(
options: NearbyImageOptions, options: NearbyImageOptions,
fetcher: P4CService fetcher: P4CService
): Store<{ images: P4CPicture[]; beforeFilter: number }> { ): Store<{ images: P4CPicture[]; beforeFilter: number } | null | undefined> {
const p4cStore = Stores.FromPromise<P4CPicture[]>( const p4cStore = Stores.FromPromiseWithErr<P4CPicture[]>(
NearbyImagesSearch.fetchImages(options, fetcher) NearbyImagesSearch.fetchImages(options, fetcher)
) )
const searchRadius = options.searchRadius ?? 100 const searchRadius = options.searchRadius ?? 100
return p4cStore.map( return p4cStore.mapD(
(images) => { (imagesState) => {
if(imagesState["error"]){
return null
}
let images = imagesState["success"]
if (images === undefined) { if (images === undefined) {
return undefined return undefined
} }