| 
									
										
										
										
											2021-01-27 03:08:46 +01:00
										 |  |  | import { Utils } from "../Utils" | 
					
						
							| 
									
										
										
										
											2023-03-11 02:35:45 +01:00
										 |  |  | import { Readable, Subscriber, Unsubscriber, Updater, Writable } from "svelte/store" | 
					
						
							| 
									
										
										
										
											2021-01-27 03:08:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Various static utils | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export class Stores { | 
					
						
							|  |  |  |     public static Chronic(millis: number, asLong: () => boolean = undefined): Store<Date> { | 
					
						
							| 
									
										
										
										
											2021-01-25 03:12:09 +01:00
										 |  |  |         const source = new UIEventSource<Date>(undefined) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         function run() { | 
					
						
							| 
									
										
										
										
											2024-11-03 19:02:16 +01:00
										 |  |  |             if(asLong !== undefined && !asLong()){ | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-01-25 03:12:09 +01:00
										 |  |  |             source.setData(new Date()) | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |             if (Utils.runningFromConsole) { | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-11-03 19:02:16 +01:00
										 |  |  |             window.setTimeout(run, millis) | 
					
						
							| 
									
										
										
										
											2021-01-25 03:12:09 +01:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         run() | 
					
						
							|  |  |  |         return source | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-02 17:57:54 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     public static FromPromiseWithErr<T>( | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         promise: Promise<T> | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     ): Store<{ success: T } | { error: any }> { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         return UIEventSource.FromPromiseWithErr(promise) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-02 17:57:54 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Converts a promise into a UIVentsource, sets the UIEVentSource when the result is calculated. | 
					
						
							|  |  |  |      * If the promise fails, the value will stay undefined | 
					
						
							|  |  |  |      * @param promise | 
					
						
							|  |  |  |      * @constructor | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-12-03 03:51:18 +01:00
										 |  |  |     public static FromPromise<T>(promise: Promise<T>): Store<T | undefined> { | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  |         const src = new UIEventSource<T>(undefined) | 
					
						
							| 
									
										
										
										
											2021-09-29 23:56:59 +02:00
										 |  |  |         promise?.then((d) => src.setData(d)) | 
					
						
							| 
									
										
										
										
											2021-10-02 17:57:54 +02:00
										 |  |  |         promise?.catch((err) => console.warn("Promise failed:", err)) | 
					
						
							|  |  |  |         return src | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-03 01:14:08 +02:00
										 |  |  |     public static concat<T>(stores: Store<T[] | undefined>[]): Store<(T[] | undefined)[]> { | 
					
						
							|  |  |  |         const newStore = new UIEventSource<(T[] | undefined)[]>([]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         function update() { | 
					
						
							|  |  |  |             if (newStore._callbacks.isDestroyed) { | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  |                 return true // unregister
 | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-09-03 01:14:08 +02:00
										 |  |  |             const results: (T[] | undefined)[] = [] | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  |             for (const store of stores) { | 
					
						
							| 
									
										
										
										
											2024-09-03 01:14:08 +02:00
										 |  |  |                 results.push(store.data) | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             newStore.setData(results) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const store of stores) { | 
					
						
							|  |  |  |             store.addCallback(() => update()) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         update() | 
					
						
							|  |  |  |         return newStore | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Given a UIEVentSource with a list, returns a new UIEventSource which is only updated if the _contents_ of the list are different. | 
					
						
							|  |  |  |      * E.g. | 
					
						
							|  |  |  |      * const src = new UIEventSource([1,2,3]) | 
					
						
							|  |  |  |      * const stable = UIEventSource.ListStabilized(src) | 
					
						
							|  |  |  |      * src.addCallback(_ => console.log("src pinged")) | 
					
						
							|  |  |  |      * stable.addCallback(_ => console.log("stable pinged)) | 
					
						
							|  |  |  |      * src.setDate([...src.data]) | 
					
						
							| 
									
										
										
										
											2021-10-03 20:50:18 +02:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |      * This will only trigger 'src pinged' | 
					
						
							| 
									
										
										
										
											2021-10-03 20:50:18 +02:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |      * @param src | 
					
						
							|  |  |  |      * @constructor | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public static ListStabilized<T>(src: Store<T[]>): Store<T[]> { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         const stable = new UIEventSource<T[]>(undefined) | 
					
						
							|  |  |  |         src.addCallbackAndRun((list) => { | 
					
						
							| 
									
										
										
										
											2021-10-03 20:50:18 +02:00
										 |  |  |             if (list === undefined) { | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |                 stable.setData(undefined) | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |             if (Utils.sameList(stable.data, list)) { | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |             stable.setData(list) | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |         }) | 
					
						
							|  |  |  |         return stable | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-08-23 02:16:24 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Constructs a new store, but tries to keep the value 'defined' | 
					
						
							|  |  |  |      * If a defined value was in the stream once, a defined value will be returned | 
					
						
							|  |  |  |      * @param store | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     static holdDefined<T>(store: Store<T | undefined>): Store<T | undefined> { | 
					
						
							|  |  |  |         const newStore = new UIEventSource(store.data) | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         store.addCallbackD((t) => { | 
					
						
							| 
									
										
										
										
											2024-08-23 02:16:24 +02:00
										 |  |  |             newStore.setData(t) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         return newStore | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-10-03 20:50:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-29 13:10:57 +01:00
										 |  |  | export abstract class Store<T> implements Readable<T> { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     abstract readonly data: T | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |      * Optional value giving a title to the UIEventSource, mainly used for debugging | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |      */ | 
					
						
							|  |  |  |     public readonly tag: string | undefined | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     constructor(tag: string = undefined) { | 
					
						
							|  |  |  |         this.tag = tag | 
					
						
							|  |  |  |         if (tag === undefined || tag === "") { | 
					
						
							|  |  |  |             let createStack = Utils.runningFromConsole | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             if (!Utils.runningFromConsole) { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 createStack = window.location.hostname === "127.0.0.1" | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             if (createStack) { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 const callstack = new Error().stack.split("\n") | 
					
						
							|  |  |  |                 this.tag = callstack[1] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     abstract map<J>(f: (t: T) => J): Store<J> | 
					
						
							|  |  |  |     abstract map<J>(f: (t: T) => J, extraStoresToWatch: Store<any>[]): Store<J> | 
					
						
							| 
									
										
										
										
											2023-12-12 19:18:50 +01:00
										 |  |  |     abstract map<J>( | 
					
						
							|  |  |  |         f: (t: T) => J, | 
					
						
							|  |  |  |         extraStoresToWatch: Store<any>[], | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         callbackDestroyFunction: (f: () => void) => void | 
					
						
							| 
									
										
										
										
											2023-12-12 19:18:50 +01:00
										 |  |  |     ): Store<J> | 
					
						
							| 
									
										
										
										
											2024-08-22 02:54:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |     public mapD<J>( | 
					
						
							|  |  |  |         f: (t: Exclude<T, undefined | null>) => J, | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |         extraStoresToWatch?: Store<any>[], | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         callbackDestroyFunction?: (f: () => void) => void | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |     ): Store<J> { | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         return this.map( | 
					
						
							|  |  |  |             (t) => { | 
					
						
							|  |  |  |                 if (t === undefined) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (t === null) { | 
					
						
							|  |  |  |                     return null | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return f(<Exclude<T, undefined | null>>t) | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             extraStoresToWatch, | 
					
						
							|  |  |  |             callbackDestroyFunction | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-01-29 13:10:57 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Add a callback function which will run on future data changes | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     abstract addCallback(callback: (data: T) => void): () => void | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Adds a callback function, which will be run immediately. | 
					
						
							|  |  |  |      * Only triggers if the current data is defined | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     abstract addCallbackAndRunD(callback: (data: T) => void): () => void | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Add a callback function which will run on future data changes | 
					
						
							|  |  |  |      * Only triggers if the data is defined | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     abstract addCallbackD(callback: (data: T) => void): () => void | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Adds a callback function, which will be run immediately. | 
					
						
							|  |  |  |      * Only triggers if the current data is defined | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     abstract addCallbackAndRun(callback: (data: T) => void): () => void | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     public withEqualityStabilized( | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         comparator: (t: T | undefined, t1: T | undefined) => boolean | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     ): Store<T> { | 
					
						
							|  |  |  |         let oldValue = undefined | 
					
						
							|  |  |  |         return this.map((v) => { | 
					
						
							|  |  |  |             if (v == oldValue) { | 
					
						
							|  |  |  |                 return oldValue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (comparator(oldValue, v)) { | 
					
						
							|  |  |  |                 return oldValue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             oldValue = v | 
					
						
							|  |  |  |             return v | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Monadic bind function | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |      * | 
					
						
							|  |  |  |      * // simple test with bound and immutablestores
 | 
					
						
							|  |  |  |      * const src = new UIEventSource<number>(3) | 
					
						
							|  |  |  |      * const bound = src.bind(i => new ImmutableStore(i * 2)) | 
					
						
							|  |  |  |      * let lastValue = undefined; | 
					
						
							|  |  |  |      * bound.addCallbackAndRun(v => lastValue = v); | 
					
						
							|  |  |  |      * lastValue // => 6
 | 
					
						
							|  |  |  |      * src.setData(21) | 
					
						
							|  |  |  |      * lastValue // => 42
 | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * // simple test with bind over a mapped value
 | 
					
						
							|  |  |  |      * const src = new UIEventSource<number>(0) | 
					
						
							|  |  |  |      * const srcs : UIEventSource<string>[] = [new UIEventSource<string>("a"), new UIEventSource<string>("b")] | 
					
						
							|  |  |  |      * const bound = src.map(i => -i).bind(i => srcs[i]) | 
					
						
							|  |  |  |      * let lastValue : string = undefined; | 
					
						
							|  |  |  |      * bound.addCallbackAndRun(v => lastValue = v); | 
					
						
							|  |  |  |      * lastValue // => "a"
 | 
					
						
							|  |  |  |      * src.setData(-1) | 
					
						
							|  |  |  |      * lastValue // => "b"
 | 
					
						
							|  |  |  |      * srcs[1].setData("xyz") | 
					
						
							|  |  |  |      * lastValue // => "xyz"
 | 
					
						
							|  |  |  |      * srcs[0].setData("def") | 
					
						
							|  |  |  |      * lastValue // => "xyz"
 | 
					
						
							|  |  |  |      * src.setData(0) | 
					
						
							|  |  |  |      * lastValue // => "def"
 | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * // advanced test with bound
 | 
					
						
							|  |  |  |      * const src = new UIEventSource<number>(0) | 
					
						
							|  |  |  |      * const srcs : UIEventSource<string>[] = [new UIEventSource<string>("a"), new UIEventSource<string>("b")] | 
					
						
							|  |  |  |      * const bound = src.bind(i => srcs[i]) | 
					
						
							|  |  |  |      * let lastValue : string = undefined; | 
					
						
							|  |  |  |      * bound.addCallbackAndRun(v => lastValue = v); | 
					
						
							|  |  |  |      * lastValue // => "a"
 | 
					
						
							|  |  |  |      * src.setData(1) | 
					
						
							|  |  |  |      * lastValue // => "b"
 | 
					
						
							|  |  |  |      * srcs[1].setData("xyz") | 
					
						
							|  |  |  |      * lastValue // => "xyz"
 | 
					
						
							|  |  |  |      * srcs[0].setData("def") | 
					
						
							|  |  |  |      * lastValue // => "xyz"
 | 
					
						
							|  |  |  |      * src.setData(0) | 
					
						
							|  |  |  |      * lastValue // => "def"
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-08-30 02:18:29 +02:00
										 |  |  |     public bind<X>(f: (t: T) => Store<X>, extraSources: Store<object>[] = []): Store<X> { | 
					
						
							|  |  |  |         const mapped = this.map(f, extraSources) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         const sink = new UIEventSource<X>(undefined) | 
					
						
							|  |  |  |         const seenEventSources = new Set<Store<X>>() | 
					
						
							|  |  |  |         mapped.addCallbackAndRun((newEventSource) => { | 
					
						
							|  |  |  |             if (newEventSource === null) { | 
					
						
							|  |  |  |                 sink.setData(null) | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (newEventSource === undefined) { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 sink.setData(undefined) | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (seenEventSources.has(newEventSource)) { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 // Already seen, so we don't have to add a callback, just update the value
 | 
					
						
							|  |  |  |                 sink.setData(newEventSource.data) | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |                 return | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |             seenEventSources.add(newEventSource) | 
					
						
							|  |  |  |             newEventSource.addCallbackAndRun((resultData) => { | 
					
						
							|  |  |  |                 if (mapped.data === newEventSource) { | 
					
						
							|  |  |  |                     sink.setData(resultData) | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-09-03 01:14:08 +02:00
										 |  |  |                 if (sink._callbacks.isDestroyed) { | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  |                     return true // unregister
 | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return sink | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |     public bindD<X>( | 
					
						
							|  |  |  |         f: (t: Exclude<T, undefined | null>) => Store<X>, | 
					
						
							|  |  |  |         extraSources: UIEventSource<object>[] = [] | 
					
						
							|  |  |  |     ): Store<X> { | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |         return this.bind((t) => { | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |             if (t === null) { | 
					
						
							| 
									
										
										
										
											2024-02-26 02:24:46 +01:00
										 |  |  |                 return null | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |             if (t === undefined) { | 
					
						
							| 
									
										
										
										
											2024-02-26 02:24:46 +01:00
										 |  |  |                 return undefined | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |             return f(<Exclude<T, undefined | null>>t) | 
					
						
							| 
									
										
										
										
											2024-08-30 02:18:29 +02:00
										 |  |  |         }, extraSources) | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-08-22 02:54:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public stabilized(millisToStabilize): Store<T> { | 
					
						
							|  |  |  |         if (Utils.runningFromConsole) { | 
					
						
							|  |  |  |             return this | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const newSource = new UIEventSource<T>(this.data) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-03 02:24:03 +01:00
										 |  |  |         const self = this | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         this.addCallback((latestData) => { | 
					
						
							|  |  |  |             window.setTimeout(() => { | 
					
						
							| 
									
										
										
										
											2023-01-03 02:24:03 +01:00
										 |  |  |                 if (self.data == latestData) { | 
					
						
							|  |  |  |                     // compare by reference.
 | 
					
						
							|  |  |  |                     // Note that 'latestData' and 'self.data' are both from the same UIEVentSource, but both are dereferenced at a different time
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                     newSource.setData(latestData) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }, millisToStabilize) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return newSource | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-21 20:50:38 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Converts the uiEventSource into a promise. | 
					
						
							|  |  |  |      * The promise will return the value of the store if the given condition evaluates to true | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |      * @param condition an optional condition, default to 'store.value !== undefined' | 
					
						
							| 
									
										
										
										
											2023-04-21 20:50:38 +02:00
										 |  |  |      * @constructor | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public AsPromise(condition?: (t: T) => boolean): Promise<T> { | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |         const self = this | 
					
						
							| 
									
										
										
										
											2022-03-23 19:48:06 +01:00
										 |  |  |         condition = condition ?? ((t) => t !== undefined) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         return new Promise((resolve) => { | 
					
						
							| 
									
										
										
										
											2023-04-20 17:42:07 +02:00
										 |  |  |             const data = self.data | 
					
						
							|  |  |  |             if (condition(data)) { | 
					
						
							|  |  |  |                 resolve(data) | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |             } else { | 
					
						
							|  |  |  |                 self.addCallbackD((data) => { | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  |                     if (condition(data)) { | 
					
						
							|  |  |  |                         resolve(data) | 
					
						
							|  |  |  |                         return true // return true to unregister as we only need to be called once
 | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         return false // We didn't resolve yet, wait for the next ping
 | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |                 }) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-01-29 13:10:57 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Same as 'addCallbackAndRun', added to be compatible with Svelte | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-06-02 08:42:08 +02:00
										 |  |  |     public subscribe(run: Subscriber<T> & ((value: T) => void), _?): Unsubscriber { | 
					
						
							| 
									
										
										
										
											2023-01-29 13:10:57 +01:00
										 |  |  |         // We don't need to do anything with 'invalidate', see
 | 
					
						
							|  |  |  |         // https://github.com/sveltejs/svelte/issues/3859
 | 
					
						
							| 
									
										
										
										
											2023-03-30 04:51:56 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Note: run is wrapped in an anonymous function. 'Run' returns the value. If this value happens to be true, it would unsubscribe
 | 
					
						
							|  |  |  |         return this.addCallbackAndRun((v) => { | 
					
						
							|  |  |  |             run(v) | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-01-29 13:10:57 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     public abstract destroy() | 
					
						
							| 
									
										
										
										
											2024-10-17 02:10:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |     when(callback: () => void, condition?: (v: T) => boolean) { | 
					
						
							|  |  |  |         condition ??= (v) => v === true | 
					
						
							|  |  |  |         this.addCallbackAndRunD((v) => { | 
					
						
							|  |  |  |             if (condition(v)) { | 
					
						
							| 
									
										
										
										
											2024-10-17 02:10:25 +02:00
										 |  |  |                 callback() | 
					
						
							|  |  |  |                 return true | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | export class ImmutableStore<T> extends Store<T> { | 
					
						
							|  |  |  |     public readonly data: T | 
					
						
							| 
									
										
										
										
											2024-02-15 03:11:10 +01:00
										 |  |  |     static FALSE = new ImmutableStore<boolean>(false) | 
					
						
							|  |  |  |     static TRUE = new ImmutableStore<boolean>(true) | 
					
						
							| 
									
										
										
										
											2024-08-22 02:54:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     constructor(data: T) { | 
					
						
							|  |  |  |         super() | 
					
						
							|  |  |  |         this.data = data | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |     private static readonly pass: () => void = () => {} | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-02 08:42:08 +02:00
										 |  |  |     addCallback(_: (data: T) => void): () => void { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         // pass: data will never change
 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         return ImmutableStore.pass | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     addCallbackAndRun(callback: (data: T) => void): () => void { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         callback(this.data) | 
					
						
							|  |  |  |         // no callback registry: data will never change
 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         return ImmutableStore.pass | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     addCallbackAndRunD(callback: (data: T) => void): () => void { | 
					
						
							|  |  |  |         if (this.data !== undefined) { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |             callback(this.data) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         // no callback registry: data will never change
 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         return ImmutableStore.pass | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-02 08:42:08 +02:00
										 |  |  |     addCallbackD(_: (data: T) => void): () => void { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         // pass: data will never change
 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         return ImmutableStore.pass | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 19:18:50 +01:00
										 |  |  |     map<J>( | 
					
						
							|  |  |  |         f: (t: T) => J, | 
					
						
							|  |  |  |         extraStores: Store<any>[] = undefined, | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         ondestroyCallback?: (f: () => void) => void | 
					
						
							| 
									
										
										
										
											2023-12-12 19:18:50 +01:00
										 |  |  |     ): ImmutableStore<J> { | 
					
						
							| 
									
										
										
										
											2022-06-13 20:16:39 +02:00
										 |  |  |         if (extraStores?.length > 0) { | 
					
						
							| 
									
										
										
										
											2023-12-12 19:18:50 +01:00
										 |  |  |             return new MappedStore(this, f, extraStores, undefined, f(this.data), ondestroyCallback) | 
					
						
							| 
									
										
										
										
											2022-06-13 20:16:39 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         return new ImmutableStore<J>(f(this.data)) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-08-09 10:53:37 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     bind<X>(f: (t: T) => Store<X>): Store<X> { | 
					
						
							|  |  |  |         return f(this.data) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     destroy() { | 
					
						
							|  |  |  |         // pass
 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Keeps track of the callback functions | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class ListenerTracker<T> { | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  |     public pingCount = 0 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     private readonly _callbacks: ((t: T) => boolean | void | any)[] = [] | 
					
						
							| 
									
										
										
										
											2024-09-03 01:14:08 +02:00
										 |  |  |     public isDestroyed = false | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Adds a callback which can be called; a function to unregister is returned | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public addCallback(callback: (t: T) => boolean | void | any): () => void { | 
					
						
							|  |  |  |         if (callback === console.log) { | 
					
						
							|  |  |  |             // This ^^^ actually works!
 | 
					
						
							|  |  |  |             throw "Don't add console.log directly as a callback - you'll won't be able to find it afterwards. Wrap it in a lambda instead." | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this._callbacks.push(callback) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Give back an unregister-function!
 | 
					
						
							|  |  |  |         return () => { | 
					
						
							|  |  |  |             const index = this._callbacks.indexOf(callback) | 
					
						
							|  |  |  |             if (index >= 0) { | 
					
						
							|  |  |  |                 this._callbacks.splice(index, 1) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Call all the callbacks. | 
					
						
							|  |  |  |      * Returns the number of registered callbacks | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public ping(data: T): number { | 
					
						
							| 
									
										
										
										
											2022-06-08 01:20:37 +02:00
										 |  |  |         this.pingCount++ | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         let toDelete = undefined | 
					
						
							|  |  |  |         let startTime = new Date().getTime() / 1000 | 
					
						
							|  |  |  |         for (const callback of this._callbacks) { | 
					
						
							| 
									
										
										
										
											2023-09-24 01:25:00 +02:00
										 |  |  |             try { | 
					
						
							|  |  |  |                 if (callback(data) === true) { | 
					
						
							|  |  |  |                     // This callback wants to be deleted
 | 
					
						
							|  |  |  |                     // Note: it has to return precisely true in order to avoid accidental deletions
 | 
					
						
							|  |  |  |                     if (toDelete === undefined) { | 
					
						
							|  |  |  |                         toDelete = [callback] | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         toDelete.push(callback) | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-09-24 01:25:00 +02:00
										 |  |  |             } catch (e) { | 
					
						
							|  |  |  |                 console.error("Got an error while running a callback:", e) | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         let endTime = new Date().getTime() / 1000 | 
					
						
							|  |  |  |         if (endTime - startTime > 500) { | 
					
						
							|  |  |  |             console.trace( | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |                 "Warning: a ping took more then 500ms; this is probably a performance issue" | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             ) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (toDelete !== undefined) { | 
					
						
							|  |  |  |             for (const toDeleteElement of toDelete) { | 
					
						
							|  |  |  |                 this._callbacks.splice(this._callbacks.indexOf(toDeleteElement), 1) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return this._callbacks.length | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     length() { | 
					
						
							|  |  |  |         return this._callbacks.length | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-03 01:14:08 +02:00
										 |  |  |     public destroy() { | 
					
						
							|  |  |  |         this.isDestroyed = true | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  |         this._callbacks.splice(0, this._callbacks.length) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * The mapped store is a helper type which does the mapping of a function. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class MappedStore<TIn, T> extends Store<T> { | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  |     private static readonly pass: () => {} | 
					
						
							| 
									
										
										
										
											2023-01-21 23:58:14 +01:00
										 |  |  |     private readonly _upstream: Store<TIn> | 
					
						
							|  |  |  |     private readonly _upstreamCallbackHandler: ListenerTracker<TIn> | undefined | 
					
						
							| 
									
										
										
										
											2022-06-08 01:20:37 +02:00
										 |  |  |     private _upstreamPingCount: number = -1 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     private _unregisterFromUpstream: () => void | 
					
						
							| 
									
										
										
										
											2023-01-21 23:58:14 +01:00
										 |  |  |     private readonly _f: (t: TIn) => T | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     private readonly _extraStores: Store<any>[] | undefined | 
					
						
							|  |  |  |     private _unregisterFromExtraStores: (() => void)[] | undefined | 
					
						
							|  |  |  |     private _callbacks: ListenerTracker<T> = new ListenerTracker<T>() | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  |     private _callbacksAreRegistered = false | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-09 02:55:14 +02:00
										 |  |  |     constructor( | 
					
						
							|  |  |  |         upstream: Store<TIn>, | 
					
						
							|  |  |  |         f: (t: TIn) => T, | 
					
						
							|  |  |  |         extraStores: Store<any>[], | 
					
						
							| 
									
										
										
										
											2022-06-13 20:16:39 +02:00
										 |  |  |         upstreamListenerHandler: ListenerTracker<TIn> | undefined, | 
					
						
							| 
									
										
										
										
											2023-05-05 00:58:26 +02:00
										 |  |  |         initialState: T, | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         onDestroy?: (f: () => void) => void | 
					
						
							| 
									
										
										
										
											2022-06-13 20:16:39 +02:00
										 |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         super() | 
					
						
							|  |  |  |         this._upstream = upstream | 
					
						
							| 
									
										
										
										
											2022-06-08 01:20:37 +02:00
										 |  |  |         this._upstreamCallbackHandler = upstreamListenerHandler | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         this._f = f | 
					
						
							| 
									
										
										
										
											2022-06-09 02:55:14 +02:00
										 |  |  |         this._data = initialState | 
					
						
							| 
									
										
										
										
											2022-06-13 20:16:39 +02:00
										 |  |  |         this._upstreamPingCount = upstreamListenerHandler?.pingCount | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         this._extraStores = extraStores | 
					
						
							| 
									
										
										
										
											2022-06-08 01:39:58 +02:00
										 |  |  |         this.registerCallbacksToUpstream() | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         if (onDestroy !== undefined) { | 
					
						
							| 
									
										
										
										
											2023-05-05 00:58:26 +02:00
										 |  |  |             onDestroy(() => this.unregisterFromUpstream()) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private _data: T | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-08 01:20:37 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Gets the current data from the store | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * const src = new UIEventSource(21) | 
					
						
							|  |  |  |      * const mapped = src.map(i => i * 2) | 
					
						
							|  |  |  |      * src.setData(3) | 
					
						
							|  |  |  |      * mapped.data // => 6
 | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     get data(): T { | 
					
						
							|  |  |  |         if (!this._callbacksAreRegistered) { | 
					
						
							|  |  |  |             // Callbacks are not registered, so we haven't been listening for updates from the upstream which might have changed
 | 
					
						
							| 
									
										
										
										
											2022-06-13 20:16:39 +02:00
										 |  |  |             if (this._upstreamCallbackHandler?.pingCount != this._upstreamPingCount) { | 
					
						
							| 
									
										
										
										
											2022-06-08 01:20:37 +02:00
										 |  |  |                 // Upstream has pinged - let's update our data first
 | 
					
						
							|  |  |  |                 this._data = this._f(this._upstream.data) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return this._data | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         return this._data | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-12 19:18:50 +01:00
										 |  |  |     map<J>( | 
					
						
							|  |  |  |         f: (t: T) => J, | 
					
						
							|  |  |  |         extraStores: Store<any>[] = undefined, | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         ondestroyCallback?: (f: () => void) => void | 
					
						
							| 
									
										
										
										
											2023-12-12 19:18:50 +01:00
										 |  |  |     ): Store<J> { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         let stores: Store<any>[] = undefined | 
					
						
							|  |  |  |         if (extraStores?.length > 0 || this._extraStores?.length > 0) { | 
					
						
							|  |  |  |             stores = [] | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (extraStores?.length > 0) { | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |             stores?.push(...extraStores) | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         if (this._extraStores?.length > 0) { | 
					
						
							|  |  |  |             this._extraStores?.forEach((store) => { | 
					
						
							|  |  |  |                 if (stores.indexOf(store) < 0) { | 
					
						
							|  |  |  |                     stores.push(store) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return new MappedStore( | 
					
						
							| 
									
										
										
										
											2022-06-09 02:55:14 +02:00
										 |  |  |             this, | 
					
						
							|  |  |  |             f, // we could fuse the functions here (e.g. data => f(this._f(data), but this might result in _f being calculated multiple times, breaking things
 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             stores, | 
					
						
							| 
									
										
										
										
											2022-06-09 02:55:14 +02:00
										 |  |  |             this._callbacks, | 
					
						
							| 
									
										
										
										
											2023-12-12 19:18:50 +01:00
										 |  |  |             f(this.data), | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |             ondestroyCallback | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     addCallback(callback: (data: T) => any | boolean | void): () => void { | 
					
						
							|  |  |  |         if (!this._callbacksAreRegistered) { | 
					
						
							|  |  |  |             // This is the first callback that is added
 | 
					
						
							|  |  |  |             // We register this 'map' to the upstream object and all the streams
 | 
					
						
							| 
									
										
										
										
											2022-06-08 01:39:58 +02:00
										 |  |  |             this.registerCallbacksToUpstream() | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         const unregister = this._callbacks.addCallback(callback) | 
					
						
							|  |  |  |         return () => { | 
					
						
							|  |  |  |             unregister() | 
					
						
							|  |  |  |             if (this._callbacks.length() == 0) { | 
					
						
							|  |  |  |                 this.unregisterFromUpstream() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     addCallbackAndRun(callback: (data: T) => any | boolean | void): () => void { | 
					
						
							|  |  |  |         const unregister = this.addCallback(callback) | 
					
						
							|  |  |  |         const doRemove = callback(this.data) | 
					
						
							|  |  |  |         if (doRemove === true) { | 
					
						
							|  |  |  |             unregister() | 
					
						
							|  |  |  |             return MappedStore.pass | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return unregister | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     addCallbackAndRunD(callback: (data: T) => any | boolean | void): () => void { | 
					
						
							|  |  |  |         return this.addCallbackAndRun((data) => { | 
					
						
							|  |  |  |             if (data !== undefined) { | 
					
						
							|  |  |  |                 return callback(data) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     addCallbackD(callback: (data: T) => any | boolean | void): () => void { | 
					
						
							|  |  |  |         return this.addCallback((data) => { | 
					
						
							|  |  |  |             if (data !== undefined) { | 
					
						
							|  |  |  |                 return callback(data) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     private unregisterFromUpstream() { | 
					
						
							|  |  |  |         this._callbacksAreRegistered = false | 
					
						
							|  |  |  |         this._unregisterFromUpstream() | 
					
						
							|  |  |  |         this._unregisterFromExtraStores?.forEach((unr) => unr()) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private registerCallbacksToUpstream() { | 
					
						
							|  |  |  |         const self = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this._unregisterFromUpstream = this._upstream.addCallback((_) => self.update()) | 
					
						
							|  |  |  |         this._unregisterFromExtraStores = this._extraStores?.map((store) => | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |             store?.addCallback((_) => self.update()) | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  |         this._callbacksAreRegistered = true | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private update(): void { | 
					
						
							|  |  |  |         const newData = this._f(this._upstream.data) | 
					
						
							|  |  |  |         this._upstreamPingCount = this._upstreamCallbackHandler?.pingCount | 
					
						
							| 
									
										
										
										
											2023-10-07 03:07:32 +02:00
										 |  |  |         if (this._data === newData) { | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this._data = newData | 
					
						
							|  |  |  |         this._callbacks.ping(this._data) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     destroy() { | 
					
						
							|  |  |  |         this.unregisterFromUpstream() | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-11 02:35:45 +01:00
										 |  |  | export class UIEventSource<T> extends Store<T> implements Writable<T> { | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |     private static readonly pass: () => void = () => {} | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public data: T | 
					
						
							| 
									
										
										
										
											2022-06-08 01:20:37 +02:00
										 |  |  |     _callbacks: ListenerTracker<T> = new ListenerTracker<T>() | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     constructor(data: T, tag: string = "") { | 
					
						
							|  |  |  |         super(tag) | 
					
						
							|  |  |  |         this.data = data | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-03 01:14:08 +02:00
										 |  |  |     public destroy() { | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  |         this._callbacks.destroy() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public static flatten<X>( | 
					
						
							|  |  |  |         source: Store<Store<X>>, | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         possibleSources?: Store<object>[] | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     ): UIEventSource<X> { | 
					
						
							|  |  |  |         const sink = new UIEventSource<X>(source.data?.data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         source.addCallback((latestData) => { | 
					
						
							|  |  |  |             sink.setData(latestData?.data) | 
					
						
							|  |  |  |             latestData.addCallback((data) => { | 
					
						
							|  |  |  |                 if (source.data !== latestData) { | 
					
						
							|  |  |  |                     return true | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 sink.setData(data) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const possibleSource of possibleSources ?? []) { | 
					
						
							|  |  |  |             possibleSource?.addCallback(() => { | 
					
						
							|  |  |  |                 sink.setData(source.data?.data) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return sink | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2024-08-21 14:06:42 +02:00
										 |  |  |      * Converts a promise into a UIventsource, sets the UIeventSource when the result is calculated. | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |      * If the promise fails, the value will stay undefined, but 'onError' will be called | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     public static FromPromise<T>( | 
					
						
							|  |  |  |         promise: Promise<T>, | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         onError: (e) => void = undefined | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     ): UIEventSource<T> { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         const src = new UIEventSource<T>(undefined) | 
					
						
							|  |  |  |         promise?.then((d) => src.setData(d)) | 
					
						
							|  |  |  |         promise?.catch((err) => { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             if (onError !== undefined) { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 onError(err) | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 console.warn("Promise failed:", err) | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         return src | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Converts a promise into a UIVentsource, sets the UIEVentSource when the result is calculated. | 
					
						
							|  |  |  |      * If the promise fails, the value will stay undefined | 
					
						
							|  |  |  |      * @param promise | 
					
						
							|  |  |  |      * @constructor | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static FromPromiseWithErr<T>( | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         promise: Promise<T> | 
					
						
							| 
									
										
										
										
											2023-12-03 03:51:18 +01:00
										 |  |  |     ): UIEventSource<{ success: T } | { error: any } | undefined> { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         const src = new UIEventSource<{ success: T } | { error: any }>(undefined) | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |         promise | 
					
						
							|  |  |  |             ?.then((d) => src.setData({ success: d })) | 
					
						
							|  |  |  |             ?.catch((err) => src.setData({ error: err })) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         return src | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-05 15:55:18 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param source | 
					
						
							|  |  |  |      * UIEventSource.asInt(new UIEventSource("123")).data // => 123
 | 
					
						
							|  |  |  |      * UIEventSource.asInt(new UIEventSource("123456789")).data // => 123456789
 | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * const srcStr = new UIEventSource("123456789")) | 
					
						
							|  |  |  |      * const srcInt = UIEventSource.asInt(srcStr) | 
					
						
							|  |  |  |      * srcInt.setData(987654321) | 
					
						
							|  |  |  |      * srcStr.data // => "987654321"
 | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static asInt(source: UIEventSource<string>): UIEventSource<number> { | 
					
						
							|  |  |  |         return source.sync( | 
					
						
							|  |  |  |             (str) => { | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  |                 const parsed = parseInt(str) | 
					
						
							| 
									
										
										
										
											2023-10-06 03:34:26 +02:00
										 |  |  |                 return isNaN(parsed) ? undefined : parsed | 
					
						
							| 
									
										
										
										
											2023-10-05 15:55:18 +02:00
										 |  |  |             }, | 
					
						
							|  |  |  |             [], | 
					
						
							|  |  |  |             (fl) => { | 
					
						
							|  |  |  |                 if (fl === undefined || isNaN(fl)) { | 
					
						
							| 
									
										
										
										
											2023-10-06 03:34:26 +02:00
										 |  |  |                     return undefined | 
					
						
							| 
									
										
										
										
											2023-10-05 15:55:18 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-10-06 03:34:26 +02:00
										 |  |  |                 return "" + fl | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-10-06 03:34:26 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-05 15:55:18 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * UIEventSource.asFloat(new UIEventSource("123")).data // => 123
 | 
					
						
							|  |  |  |      * UIEventSource.asFloat(new UIEventSource("123456789")).data // => 123456789
 | 
					
						
							|  |  |  |      * UIEventSource.asFloat(new UIEventSource("0.5")).data // => 0.5
 | 
					
						
							|  |  |  |      * UIEventSource.asFloat(new UIEventSource("0.125")).data // => 0.125
 | 
					
						
							|  |  |  |      * UIEventSource.asFloat(new UIEventSource("0.0000000001")).data // => 0.0000000001
 | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * const srcStr = new UIEventSource("123456789")) | 
					
						
							|  |  |  |      * const srcInt = UIEventSource.asFloat(srcStr) | 
					
						
							|  |  |  |      * srcInt.setData(987654321) | 
					
						
							|  |  |  |      * srcStr.data // => "987654321"
 | 
					
						
							|  |  |  |      * @param source | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public static asFloat(source: UIEventSource<string>): UIEventSource<number> { | 
					
						
							|  |  |  |         return source.sync( | 
					
						
							|  |  |  |             (str) => { | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  |                 const parsed = parseFloat(str) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 return isNaN(parsed) ? undefined : parsed | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             [], | 
					
						
							|  |  |  |             (fl) => { | 
					
						
							|  |  |  |                 if (fl === undefined || isNaN(fl)) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-10-06 03:34:26 +02:00
										 |  |  |                 return "" + fl | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-24 21:40:34 +02:00
										 |  |  |     static asBoolean(stringUIEventSource: UIEventSource<string>): UIEventSource<boolean> { | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  |         return stringUIEventSource.sync( | 
					
						
							|  |  |  |             (str) => str === "true", | 
					
						
							|  |  |  |             [], | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |             (b) => "" + b | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |     static asObject<T extends object>( | 
					
						
							|  |  |  |         stringUIEventSource: UIEventSource<string>, | 
					
						
							|  |  |  |         defaultV: T | 
					
						
							|  |  |  |     ): UIEventSource<T> { | 
					
						
							| 
									
										
										
										
											2024-08-22 02:54:46 +02:00
										 |  |  |         return stringUIEventSource.sync( | 
					
						
							|  |  |  |             (str) => { | 
					
						
							|  |  |  |                 if (str === undefined || str === null || str === "") { | 
					
						
							|  |  |  |                     return defaultV | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 try { | 
					
						
							| 
									
										
										
										
											2024-09-03 01:14:08 +02:00
										 |  |  |                     return <T>JSON.parse(str) | 
					
						
							| 
									
										
										
										
											2024-08-22 02:54:46 +02:00
										 |  |  |                 } catch (e) { | 
					
						
							| 
									
										
										
										
											2024-09-03 01:14:08 +02:00
										 |  |  |                     console.error("Could not parse value", str, "due to", e) | 
					
						
							| 
									
										
										
										
											2024-08-22 02:54:46 +02:00
										 |  |  |                     return defaultV | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             [], | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |             (b) => JSON.stringify(b) ?? "" | 
					
						
							| 
									
										
										
										
											2024-08-22 02:54:46 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Create a new UIEVentSource. Whenever 'source' changes, the returned UIEventSource will get this value as well. | 
					
						
							|  |  |  |      * However, this value can be overriden without affecting source | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     static feedFrom<T>(store: Store<T>): UIEventSource<T> { | 
					
						
							|  |  |  |         const src = new UIEventSource(store.data) | 
					
						
							|  |  |  |         store.addCallback((t) => src.setData(t)) | 
					
						
							|  |  |  |         return src | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Adds a callback | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * If the result of the callback is 'true', the callback is considered finished and will be removed again | 
					
						
							|  |  |  |      * @param callback | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     public addCallback(callback: (latestData: T) => boolean | void | any): () => void { | 
					
						
							|  |  |  |         return this._callbacks.addCallback(callback) | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     public addCallbackAndRun(callback: (latestData: T) => boolean | void | any): () => void { | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |         const doDeleteCallback = callback(this.data) | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |         if (doDeleteCallback !== true) { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             return this.addCallback(callback) | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             return UIEventSource.pass | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public addCallbackAndRunD(callback: (data: T) => void): () => void { | 
					
						
							|  |  |  |         return this.addCallbackAndRun((data) => { | 
					
						
							|  |  |  |             if (data !== undefined && data !== null) { | 
					
						
							|  |  |  |                 return callback(data) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public addCallbackD(callback: (data: T) => void): () => void { | 
					
						
							|  |  |  |         return this.addCallback((data) => { | 
					
						
							|  |  |  |             if (data !== undefined && data !== null) { | 
					
						
							|  |  |  |                 return callback(data) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2020-09-03 03:16:43 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-31 02:59:47 +02:00
										 |  |  |     public setData(t: T): UIEventSource<T> { | 
					
						
							| 
									
										
										
										
											2021-02-14 19:45:02 +01:00
										 |  |  |         if (this.data == t) { | 
					
						
							|  |  |  |             // MUST COMPARE BY REFERENCE!
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.data = t | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         this._callbacks.ping(t) | 
					
						
							| 
									
										
										
										
											2020-08-31 02:59:47 +02:00
										 |  |  |         return this | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public ping(): void { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         this._callbacks.ping(this.data) | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-03 01:38:57 +02:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |      * Monoidal map which results in a read-only store | 
					
						
							|  |  |  |      * Given a function 'f', will construct a new UIEventSource where the contents will always be "f(this.data)' | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |      * @param f The transforming function | 
					
						
							|  |  |  |      * @param extraSources also trigger the update if one of these sources change | 
					
						
							|  |  |  |      * @param onDestroy a callback that can trigger the destroy function | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |      * | 
					
						
							|  |  |  |      * const src = new UIEventSource<number>(10) | 
					
						
							|  |  |  |      * const store = src.map(i => i * 2) | 
					
						
							|  |  |  |      * store.data // => 20
 | 
					
						
							|  |  |  |      * let srcSeen = undefined; | 
					
						
							|  |  |  |      * src.addCallback(v => { | 
					
						
							|  |  |  |      *     console.log("Triggered") | 
					
						
							|  |  |  |      *     srcSeen = v | 
					
						
							|  |  |  |      * }) | 
					
						
							|  |  |  |      * let lastSeen = undefined | 
					
						
							|  |  |  |      * store.addCallback(v => { | 
					
						
							|  |  |  |      *     console.log("Triggered!") | 
					
						
							|  |  |  |      *     lastSeen = v | 
					
						
							|  |  |  |      * }) | 
					
						
							|  |  |  |      * src.setData(21) | 
					
						
							|  |  |  |      * srcSeen // => 21
 | 
					
						
							|  |  |  |      * lastSeen // => 42
 | 
					
						
							| 
									
										
										
										
											2021-10-03 01:38:57 +02:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     public map<J>( | 
					
						
							|  |  |  |         f: (t: T) => J, | 
					
						
							|  |  |  |         extraSources: Store<any>[] = [], | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         onDestroy?: (f: () => void) => void | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     ): Store<J> { | 
					
						
							| 
									
										
										
										
											2023-05-05 00:58:26 +02:00
										 |  |  |         return new MappedStore(this, f, extraSources, this._callbacks, f(this.data), onDestroy) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-07-18 12:38:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-17 03:24:01 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * 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)' | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |     public mapD<J>( | 
					
						
							|  |  |  |         f: (t: Exclude<T, undefined | null>) => J, | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |         extraSources: Store<any>[] = [], | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         callbackDestroyFunction?: (f: () => void) => void | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |     ): Store<J | undefined> { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         return new MappedStore( | 
					
						
							|  |  |  |             this, | 
					
						
							|  |  |  |             (t) => { | 
					
						
							|  |  |  |                 if (t === undefined) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-12-03 03:51:18 +01:00
										 |  |  |                 if (t === null) { | 
					
						
							|  |  |  |                     return null | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |                 return f(<Exclude<T, undefined | null>>t) | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             }, | 
					
						
							|  |  |  |             extraSources, | 
					
						
							|  |  |  |             this._callbacks, | 
					
						
							| 
									
										
										
										
											2023-12-06 17:27:30 +01:00
										 |  |  |             this.data === undefined || this.data === null | 
					
						
							|  |  |  |                 ? <undefined | null>this.data | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |                 : f(<any>this.data), | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |             callbackDestroyFunction | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-09-17 03:24:01 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-23 15:35:18 +02:00
										 |  |  |     public mapAsyncD<J>(f: (t: T) => Promise<J>): Store<J> { | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |         return this.bindD((t) => UIEventSource.FromPromise(f(t))) | 
					
						
							| 
									
										
										
										
											2024-04-23 15:35:18 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-19 19:16:20 +02:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |      * Two way sync with functions in both directions | 
					
						
							| 
									
										
										
										
											2021-06-19 19:16:20 +02:00
										 |  |  |      * Given a function 'f', will construct a new UIEventSource where the contents will always be "f(this.data)' | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |      * @param f The transforming function | 
					
						
							|  |  |  |      * @param extraSources also trigger the update if one of these sources change | 
					
						
							|  |  |  |      * @param g a 'backfunction to let the sync run in two directions. (data of the new UIEVEntSource, currentData) => newData | 
					
						
							|  |  |  |      * @param allowUnregister if set, the update will be halted if no listeners are registered | 
					
						
							| 
									
										
										
										
											2021-06-19 19:16:20 +02:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public sync<J>( | 
					
						
							|  |  |  |         f: (t: T) => J, | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         extraSources: Store<any>[], | 
					
						
							|  |  |  |         g: (j: J, t: T) => T, | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         allowUnregister = false | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     ): UIEventSource<J> { | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         const self = this | 
					
						
							| 
									
										
										
										
											2021-01-21 05:52:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-30 18:10:46 +01:00
										 |  |  |         const stack = new Error().stack.split("\n") | 
					
						
							|  |  |  |         const callee = stack[1] | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-08 02:16:42 +02:00
										 |  |  |         const newSource = new UIEventSource<J>(f(this.data), "map(" + this.tag + ")@" + callee) | 
					
						
							| 
									
										
										
										
											2021-01-21 05:52:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         const update = function () { | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |             newSource.setData(f(self.data)) | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             return allowUnregister && newSource._callbacks.length() === 0 | 
					
						
							| 
									
										
										
										
											2020-07-08 11:23:36 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-13 02:05:34 +01:00
										 |  |  |         this.addCallback(update) | 
					
						
							| 
									
										
										
										
											2020-07-08 11:23:36 +02:00
										 |  |  |         for (const extraSource of extraSources) { | 
					
						
							| 
									
										
										
										
											2020-08-31 13:25:13 +02:00
										 |  |  |             extraSource?.addCallback(update) | 
					
						
							| 
									
										
										
										
											2020-07-08 11:23:36 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-01-21 05:52:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-25 03:12:09 +01:00
										 |  |  |         if (g !== undefined) { | 
					
						
							| 
									
										
										
										
											2020-08-08 02:16:42 +02:00
										 |  |  |             newSource.addCallback((latest) => { | 
					
						
							| 
									
										
										
										
											2021-06-19 19:16:20 +02:00
										 |  |  |                 self.setData(g(latest, self.data)) | 
					
						
							| 
									
										
										
										
											2020-08-08 02:16:42 +02:00
										 |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-01-21 05:52:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         return newSource | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-18 12:00:38 +02:00
										 |  |  |     public syncWith(otherSource: UIEventSource<T>, reverseOverride = false): UIEventSource<T> { | 
					
						
							| 
									
										
										
										
											2020-07-21 00:07:04 +02:00
										 |  |  |         this.addCallback((latest) => otherSource.setData(latest)) | 
					
						
							|  |  |  |         const self = this | 
					
						
							|  |  |  |         otherSource.addCallback((latest) => self.setData(latest)) | 
					
						
							| 
									
										
										
										
											2022-02-14 18:18:05 +01:00
										 |  |  |         if (reverseOverride) { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |             if (otherSource.data !== undefined) { | 
					
						
							| 
									
										
										
										
											2022-02-14 18:18:05 +01:00
										 |  |  |                 this.setData(otherSource.data) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-09-18 12:00:38 +02:00
										 |  |  |         } else if (this.data === undefined) { | 
					
						
							|  |  |  |             this.setData(otherSource.data) | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2020-07-21 00:07:04 +02:00
										 |  |  |             otherSource.setData(this.data) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-07-24 17:53:09 +02:00
										 |  |  |         return this | 
					
						
							| 
									
										
										
										
											2020-07-21 00:07:04 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-08 01:36:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-11 02:35:45 +01:00
										 |  |  |     set(value: T): void { | 
					
						
							|  |  |  |         this.setData(value) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     update(f: Updater<T> & ((value: T) => T)): void { | 
					
						
							|  |  |  |         this.setData(f(this.data)) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-05 13:56:12 +02:00
										 |  |  | } |