| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | import { Utils } from "../Utils" | 
					
						
							| 
									
										
										
										
											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> { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const source = new UIEventSource<Date>(undefined) | 
					
						
							| 
									
										
										
										
											2021-01-25 03:12:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         function run() { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             source.setData(new Date()) | 
					
						
							| 
									
										
										
										
											2021-01-25 03:12:09 +01:00
										 |  |  |             if (asLong === undefined || asLong()) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 window.setTimeout(run, millis) | 
					
						
							| 
									
										
										
										
											2021-01-25 03:12:09 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         run() | 
					
						
							|  |  |  |         return source | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-02 17:57:54 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public static FromPromiseWithErr<T>( | 
					
						
							|  |  |  |         promise: Promise<T> | 
					
						
							|  |  |  |     ): Store<{ success: T } | { error: any }> { | 
					
						
							|  |  |  |         return UIEventSource.FromPromiseWithErr(promise) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public static FromPromise<T>(promise: Promise<T>): Store<T> { | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  |         const src = new UIEventSource<T>(undefined) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         promise?.then((d) => src.setData(d)) | 
					
						
							|  |  |  |         promise?.catch((err) => console.warn("Promise failed:", err)) | 
					
						
							| 
									
										
										
										
											2021-10-02 17:57:54 +02:00
										 |  |  |         return src | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public static flatten<X>(source: Store<Store<X>>, possibleSources?: Store<any>[]): Store<X> { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         return UIEventSource.flatten(source, possibleSources) | 
					
						
							| 
									
										
										
										
											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) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         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) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 return | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             const oldList = stable.data | 
					
						
							| 
									
										
										
										
											2021-10-03 20:50:18 +02:00
										 |  |  |             if (oldList === list) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 return | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             if (oldList == list) { | 
					
						
							|  |  |  |                 return | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-10-15 14:52:11 +02:00
										 |  |  |             if (oldList === undefined || oldList.length !== list.length) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 stable.setData(list) | 
					
						
							|  |  |  |                 return | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for (let i = 0; i < list.length; i++) { | 
					
						
							| 
									
										
										
										
											2021-10-03 20:50:18 +02:00
										 |  |  |                 if (oldList[i] !== list[i]) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     stable.setData(list) | 
					
						
							|  |  |  |                     return | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-10-03 20:50:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |             // No actual changes, so we don't do anything
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             return | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |         }) | 
					
						
							|  |  |  |         return stable | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-10-03 20:50:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | export abstract class Store<T> { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     abstract readonly data: T | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * OPtional value giving a title to the UIEventSource, mainly used for debugging | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public readonly tag: string | undefined | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor(tag: string = undefined) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         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-09-08 21:40:48 +02:00
										 |  |  |     abstract map<J>(f: (t: T) => J): Store<J> | 
					
						
							|  |  |  |     abstract map<J>(f: (t: T) => J, extraStoresToWatch: Store<any>[]): Store<J> | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Add a callback function which will run on future data changes | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +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-09-08 21:40:48 +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-09-08 21:40:48 +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-09-08 21:40:48 +02:00
										 |  |  |     abstract addCallbackAndRun(callback: (data: T) => void): () => void | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public withEqualityStabilized( | 
					
						
							|  |  |  |         comparator: (t: T | undefined, t1: T | undefined) => boolean | 
					
						
							|  |  |  |     ): Store<T> { | 
					
						
							|  |  |  |         let oldValue = undefined | 
					
						
							|  |  |  |         return this.map((v) => { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |             if (v == oldValue) { | 
					
						
							|  |  |  |                 return oldValue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (comparator(oldValue, v)) { | 
					
						
							|  |  |  |                 return oldValue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             oldValue = v | 
					
						
							|  |  |  |             return v | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Monadic bind function | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											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
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |      * // 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"
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |      * | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |      * // 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
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public bind<X>(f: (t: T) => Store<X>): Store<X> { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         const mapped = this.map(f) | 
					
						
							|  |  |  |         const sink = new UIEventSource<X>(undefined) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const seenEventSources = new Set<Store<X>>() | 
					
						
							|  |  |  |         mapped.addCallbackAndRun((newEventSource) => { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |             if (newEventSource === null) { | 
					
						
							|  |  |  |                 sink.setData(null) | 
					
						
							|  |  |  |             } else if (newEventSource === undefined) { | 
					
						
							|  |  |  |                 sink.setData(undefined) | 
					
						
							|  |  |  |             } else if (!seenEventSources.has(newEventSource)) { | 
					
						
							|  |  |  |                 seenEventSources.add(newEventSource) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 newEventSource.addCallbackAndRun((resultData) => { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                     if (mapped.data === newEventSource) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                         sink.setData(resultData) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                     } | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 // Already seen, so we don't have to add a callback, just update the value
 | 
					
						
							|  |  |  |                 sink.setData(newEventSource.data) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         return sink | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public stabilized(millisToStabilize): Store<T> { | 
					
						
							|  |  |  |         if (Utils.runningFromConsole) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             return this | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const newSource = new UIEventSource<T>(this.data) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         this.addCallback((latestData) => { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |             window.setTimeout(() => { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 if (this.data == latestData) { | 
					
						
							|  |  |  |                     // compare by reference
 | 
					
						
							|  |  |  |                     newSource.setData(latestData) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |             }, millisToStabilize) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         return newSource | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public AsPromise(condition?: (t: T) => boolean): Promise<T> { | 
					
						
							|  |  |  |         const self = this | 
					
						
							|  |  |  |         condition = condition ?? ((t) => t !== undefined) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         return new Promise((resolve) => { | 
					
						
							| 
									
										
										
										
											2022-03-23 19:48:06 +01:00
										 |  |  |             if (condition(self.data)) { | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |                 resolve(self.data) | 
					
						
							|  |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 self.addCallbackD((data) => { | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |                     resolve(data) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     return true // return true to unregister as we only need to be called once
 | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |                 }) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											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> { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public readonly data: T | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     private static readonly pass: () => void = () => {} | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     constructor(data: T) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         super() | 
					
						
							|  |  |  |         this.data = data | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     addCallback(callback: (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-09-08 21:40:48 +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-09-08 21:40:48 +02:00
										 |  |  |     addCallbackAndRunD(callback: (data: T) => void): () => void { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         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
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     addCallbackD(callback: (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-13 20:16:39 +02:00
										 |  |  |     map<J>(f: (t: T) => J, extraStores: Store<any>[] = undefined): ImmutableStore<J> { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         if (extraStores?.length > 0) { | 
					
						
							| 
									
										
										
										
											2022-06-13 20:16:39 +02:00
										 |  |  |             return new MappedStore(this, f, extraStores, undefined, f(this.data)) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         return new ImmutableStore<J>(f(this.data)) | 
					
						
							| 
									
										
										
										
											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> { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     private readonly _callbacks: ((t: T) => boolean | void | any)[] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public pingCount = 0 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Adds a callback which can be called; a function to unregister is returned | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public addCallback(callback: (t: T) => boolean | void | any): () => void { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         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." | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         this._callbacks.push(callback) | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // 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-09-08 21:40:48 +02:00
										 |  |  |         this.pingCount++ | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         let toDelete = undefined | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         let startTime = new Date().getTime() / 1000 | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         for (const callback of this._callbacks) { | 
					
						
							|  |  |  |             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) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         let endTime = new Date().getTime() / 1000 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         if (endTime - startTime > 500) { | 
					
						
							|  |  |  |             console.trace( | 
					
						
							|  |  |  |                 "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 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * The mapped store is a helper type which does the mapping of a function. | 
					
						
							|  |  |  |  * It'll fuse | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class MappedStore<TIn, T> extends Store<T> { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     private _upstream: Store<TIn> | 
					
						
							|  |  |  |     private _upstreamCallbackHandler: ListenerTracker<TIn> | undefined | 
					
						
							|  |  |  |     private _upstreamPingCount: number = -1 | 
					
						
							|  |  |  |     private _unregisterFromUpstream: () => void | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     private _f: (t: TIn) => T | 
					
						
							|  |  |  |     private readonly _extraStores: Store<any>[] | undefined | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     private _unregisterFromExtraStores: (() => void)[] | undefined | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private _callbacks: ListenerTracker<T> = new ListenerTracker<T>() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static readonly pass: () => {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     constructor( | 
					
						
							|  |  |  |         upstream: Store<TIn>, | 
					
						
							|  |  |  |         f: (t: TIn) => T, | 
					
						
							|  |  |  |         extraStores: Store<any>[], | 
					
						
							|  |  |  |         upstreamListenerHandler: ListenerTracker<TIn> | undefined, | 
					
						
							|  |  |  |         initialState: T | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |         super() | 
					
						
							|  |  |  |         this._upstream = upstream | 
					
						
							| 
									
										
										
										
											2022-06-08 01:20:37 +02:00
										 |  |  |         this._upstreamCallbackHandler = upstreamListenerHandler | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +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-09-08 21:40:48 +02:00
										 |  |  |         this._extraStores = extraStores | 
					
						
							| 
									
										
										
										
											2022-06-08 01:39:58 +02:00
										 |  |  |         this.registerCallbacksToUpstream() | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     private _data: T | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     private _callbacksAreRegistered = false | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-08 01:20:37 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Gets the current data from the store | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2022-06-08 01:20:37 +02:00
										 |  |  |      * const src = new UIEventSource(21) | 
					
						
							|  |  |  |      * const mapped = src.map(i => i * 2) | 
					
						
							|  |  |  |      * src.setData(3) | 
					
						
							|  |  |  |      * mapped.data // => 6
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2022-06-08 01:20:37 +02:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     get data(): T { | 
					
						
							| 
									
										
										
										
											2022-06-08 01:20:37 +02:00
										 |  |  |         if (!this._callbacksAreRegistered) { | 
					
						
							|  |  |  |             // Callbacks are not registered, so we haven't been listening for updates from the upstream which might have changed
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +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 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     map<J>(f: (t: T) => J, extraStores: Store<any>[] = undefined): 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) { | 
					
						
							|  |  |  |             stores.push(...extraStores) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (this._extraStores?.length > 0) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             this._extraStores?.forEach((store) => { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |                 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, | 
					
						
							|  |  |  |             f(this.data) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private unregisterFromUpstream() { | 
					
						
							|  |  |  |         console.log("Unregistering callbacks for", this.tag) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         this._callbacksAreRegistered = false | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         this._unregisterFromUpstream() | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         this._unregisterFromExtraStores?.forEach((unr) => unr()) | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-08 01:39:58 +02:00
										 |  |  |     private registerCallbacksToUpstream() { | 
					
						
							|  |  |  |         const self = this | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this._unregisterFromUpstream = this._upstream.addCallback((_) => self.update()) | 
					
						
							|  |  |  |         this._unregisterFromExtraStores = this._extraStores?.map((store) => | 
					
						
							|  |  |  |             store?.addCallback((_) => self.update()) | 
					
						
							| 
									
										
										
										
											2022-06-08 01:39:58 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         this._callbacksAreRegistered = true | 
					
						
							| 
									
										
										
										
											2022-06-08 01:39:58 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     private update(): void { | 
					
						
							|  |  |  |         const newData = this._f(this._upstream.data) | 
					
						
							| 
									
										
										
										
											2022-06-13 20:16:39 +02:00
										 |  |  |         this._upstreamPingCount = this._upstreamCallbackHandler?.pingCount | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         if (this._data == newData) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             return | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         this._data = newData | 
					
						
							|  |  |  |         this._callbacks.ping(this._data) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     addCallback(callback: (data: T) => any | boolean | void): () => void { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         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() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     addCallbackAndRun(callback: (data: T) => any | boolean | void): () => void { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         const unregister = this.addCallback(callback) | 
					
						
							|  |  |  |         const doRemove = callback(this.data) | 
					
						
							|  |  |  |         if (doRemove === true) { | 
					
						
							|  |  |  |             unregister() | 
					
						
							|  |  |  |             return MappedStore.pass | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return unregister | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     addCallbackAndRunD(callback: (data: T) => any | boolean | void): () => void { | 
					
						
							|  |  |  |         return this.addCallbackAndRun((data) => { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             if (data !== undefined) { | 
					
						
							|  |  |  |                 return callback(data) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     addCallbackD(callback: (data: T) => any | boolean | void): () => void { | 
					
						
							|  |  |  |         return this.addCallback((data) => { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             if (data !== undefined) { | 
					
						
							|  |  |  |                 return callback(data) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | export class UIEventSource<T> extends Store<T> { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +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
										 |  |  | 
 | 
					
						
							|  |  |  |     private static readonly pass: () => {} | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor(data: T, tag: string = "") { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         super(tag) | 
					
						
							|  |  |  |         this.data = data | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public static flatten<X>( | 
					
						
							|  |  |  |         source: Store<Store<X>>, | 
					
						
							|  |  |  |         possibleSources?: Store<any>[] | 
					
						
							|  |  |  |     ): UIEventSource<X> { | 
					
						
							|  |  |  |         const sink = new UIEventSource<X>(source.data?.data) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         source.addCallback((latestData) => { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             sink.setData(latestData?.data) | 
					
						
							|  |  |  |             latestData.addCallback((data) => { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 if (source.data !== latestData) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     return true | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 sink.setData(data) | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for (const possibleSource of possibleSources ?? []) { | 
					
						
							|  |  |  |             possibleSource?.addCallback(() => { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 sink.setData(source.data?.data) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         return sink | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +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, but 'onError' will be called | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public static FromPromise<T>( | 
					
						
							|  |  |  |         promise: Promise<T>, | 
					
						
							|  |  |  |         onError: (e: any) => void = undefined | 
					
						
							|  |  |  |     ): UIEventSource<T> { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         const src = new UIEventSource<T>(undefined) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         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-09-08 21:40:48 +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 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public static FromPromiseWithErr<T>( | 
					
						
							|  |  |  |         promise: Promise<T> | 
					
						
							|  |  |  |     ): UIEventSource<{ success: T } | { error: any }> { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         const src = new UIEventSource<{ success: T } | { error: any }>(undefined) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         promise?.then((d) => src.setData({ success: d })) | 
					
						
							|  |  |  |         promise?.catch((err) => src.setData({ error: err })) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         return src | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static asFloat(source: UIEventSource<string>): UIEventSource<number> { | 
					
						
							|  |  |  |         return source.sync( | 
					
						
							|  |  |  |             (str) => { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 let parsed = parseFloat(str) | 
					
						
							|  |  |  |                 return isNaN(parsed) ? undefined : parsed | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |             }, | 
					
						
							|  |  |  |             [], | 
					
						
							|  |  |  |             (fl) => { | 
					
						
							|  |  |  |                 if (fl === undefined || isNaN(fl)) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     return undefined | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 return ("" + fl).substr(0, 8) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-09-08 21:40:48 +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-09-08 21:40:48 +02:00
										 |  |  |     public addCallbackAndRun(callback: (latestData: T) => boolean | void | any): () => void { | 
					
						
							|  |  |  |         const doDeleteCallback = callback(this.data) | 
					
						
							| 
									
										
										
										
											2021-10-02 15:16:41 +02:00
										 |  |  |         if (doDeleteCallback !== true) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             return this.addCallback(callback) | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             return UIEventSource.pass | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public addCallbackAndRunD(callback: (data: T) => void): () => void { | 
					
						
							|  |  |  |         return this.addCallbackAndRun((data) => { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             if (data !== undefined && data !== null) { | 
					
						
							|  |  |  |                 return callback(data) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public addCallbackD(callback: (data: T) => void): () => void { | 
					
						
							|  |  |  |         return this.addCallback((data) => { | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |             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> { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         if (this.data == t) { | 
					
						
							|  |  |  |             // MUST COMPARE BY REFERENCE!
 | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         this.data = t | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +02:00
										 |  |  |         this._callbacks.ping(t) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +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)' | 
					
						
							|  |  |  |      * @param f: The transforming function | 
					
						
							|  |  |  |      * @param extraSources: also trigger the update if one of these sources change | 
					
						
							| 
									
										
										
										
											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
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public map<J>(f: (t: T) => J, extraSources: Store<any>[] = []): Store<J> { | 
					
						
							|  |  |  |         return new MappedStore(this, f, extraSources, this._callbacks, f(this.data)) | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +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
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-17 03:24:01 +02:00
										 |  |  |     public mapD<J>(f: (t: T) => J, extraSources: Store<any>[] = []): Store<J | undefined> { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         return new MappedStore( | 
					
						
							|  |  |  |             this, | 
					
						
							|  |  |  |             (t) => { | 
					
						
							|  |  |  |                 if (t === undefined) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return f(t) | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             extraSources, | 
					
						
							|  |  |  |             this._callbacks, | 
					
						
							|  |  |  |             this.data === undefined ? undefined : f(this.data) | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-09-17 03:24:01 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-06 19:37:22 +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)' | 
					
						
							|  |  |  |      * @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 | 
					
						
							| 
									
										
										
										
											2022-01-05 18:08:42 +01:00
										 |  |  |      * @param allowUnregister: if set, the update will be halted if no listeners are registered | 
					
						
							| 
									
										
										
										
											2021-06-19 19:16:20 +02:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public sync<J>( | 
					
						
							|  |  |  |         f: (t: T) => J, | 
					
						
							|  |  |  |         extraSources: Store<any>[], | 
					
						
							|  |  |  |         g: (j: J, t: T) => T, | 
					
						
							|  |  |  |         allowUnregister = false | 
					
						
							|  |  |  |     ): UIEventSource<J> { | 
					
						
							|  |  |  |         const self = this | 
					
						
							| 
									
										
										
										
											2021-01-21 05:52:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const stack = new Error().stack.split("\n") | 
					
						
							| 
									
										
										
										
											2021-11-30 18:10:46 +01:00
										 |  |  |         const callee = stack[1] | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const newSource = new UIEventSource<J>(f(this.data), "map(" + this.tag + ")@" + callee) | 
					
						
							| 
									
										
										
										
											2021-01-21 05:52:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-08 11:23:36 +02:00
										 |  |  |         const update = function () { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         this.addCallback(update) | 
					
						
							| 
									
										
										
										
											2020-07-08 11:23:36 +02:00
										 |  |  |         for (const extraSource of extraSources) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +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) => { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 self.setData(g(latest, self.data)) | 
					
						
							| 
									
										
										
										
											2020-08-08 02:16:42 +02:00
										 |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-01-21 05:52:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         return newSource | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-18 12:00:38 +02:00
										 |  |  |     public syncWith(otherSource: UIEventSource<T>, reverseOverride = false): UIEventSource<T> { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +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-09-08 21:40:48 +02:00
										 |  |  |                 this.setData(otherSource.data) | 
					
						
							| 
									
										
										
										
											2022-02-14 18:18:05 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-09-18 12:00:38 +02:00
										 |  |  |         } else if (this.data === undefined) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             this.setData(otherSource.data) | 
					
						
							| 
									
										
										
										
											2020-09-18 12:00:38 +02:00
										 |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             otherSource.setData(this.data) | 
					
						
							| 
									
										
										
										
											2020-07-21 00:07:04 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         return this | 
					
						
							| 
									
										
										
										
											2020-07-21 00:07:04 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-05 13:56:12 +02:00
										 |  |  | } |