From f2375f4877316193132f4ae4189ef5b65e7ef710 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 5 Oct 2023 15:55:18 +0200 Subject: [PATCH] Fix: MC would crash if a pending change was present; remember preferences in order to reuse the changeset --- .../NewGeometryFromChangesFeatureSource.ts | 16 +- src/Logic/Osm/Changes.ts | 15 +- src/Logic/Osm/OsmPreferences.ts | 7 +- src/Logic/State/FeatureSwitchState.ts | 4 +- src/Logic/UIEventSource.ts | 493 ++++++++++-------- src/test.ts | 17 +- 6 files changed, 291 insertions(+), 261 deletions(-) diff --git a/src/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts b/src/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts index a62070e17..c22f82433 100644 --- a/src/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts @@ -55,10 +55,14 @@ export class NewGeometryFromChangesFeatureSource implements WritableFeatureSourc * @private */ private handleChange(change: ChangeDescription): boolean { - const backend = this._backend - const allElementStorage = this._allElementStorage + if (change.changes === undefined) { + // The geometry is not described - not a new point or geometry change, but probably a tagchange to a newly created point + // Not something that should be handled here + return false + } - console.log("Handling pending change") + const allElementStorage = this._allElementStorage + console.log("Handling pending change", change) if (change.id > 0) { // This is an already existing object // In _most_ of the cases, this means that this _isn't_ a new object @@ -86,10 +90,6 @@ export class NewGeometryFromChangesFeatureSource implements WritableFeatureSourc this._featureProperties.trackFeature(feature) this.addNewFeature(feature) return true - } else if (change.changes === undefined) { - // The geometry is not described - not a new point or geometry change, but probably a tagchange to a newly created point - // Not something that should be handled here - return false } try { @@ -151,7 +151,7 @@ export class NewGeometryFromChangesFeatureSource implements WritableFeatureSourc continue } - somethingChanged ||= this.handleChange(change) + somethingChanged = this.handleChange(change) || somethingChanged // important: _first_ evaluate the method, to avoid shortcutting } if (somethingChanged) { this.features.ping() diff --git a/src/Logic/Osm/Changes.ts b/src/Logic/Osm/Changes.ts index 208184848..d6e9c642f 100644 --- a/src/Logic/Osm/Changes.ts +++ b/src/Logic/Osm/Changes.ts @@ -558,19 +558,8 @@ export class Changes { const successes = await Promise.all( Array.from(pendingPerTheme, async ([theme, pendingChanges]) => { try { - const openChangeset = this.state.osmConnection - .GetPreference("current-open-changeset-" + theme) - .sync( - (str) => { - const n = Number(str) - if (isNaN(n)) { - return undefined - } - return n - }, - [], - (n) => "" + n - ) + const openChangeset = UIEventSource.asInt(this.state.osmConnection + .GetPreference("current-open-changeset-" + theme)) console.log( "Using current-open-changeset-" + theme + diff --git a/src/Logic/Osm/OsmPreferences.ts b/src/Logic/Osm/OsmPreferences.ts index cd10b414f..e859e95c8 100644 --- a/src/Logic/Osm/OsmPreferences.ts +++ b/src/Logic/Osm/OsmPreferences.ts @@ -1,9 +1,14 @@ import { UIEventSource } from "../UIEventSource" import UserDetails, { OsmConnection } from "./OsmConnection" import { Utils } from "../../Utils" +import { LocalStorageSource } from "../Web/LocalStorageSource"; export class OsmPreferences { - public preferences = new UIEventSource>({}, "all-osm-preferences") + /** + * A dictionary containing all the preferences. The 'preferenceSources' will be initialized from this + * We keep a local copy of them, to init mapcomplete with the previous choices and to be able to get the open changesets right away + */ + public preferences = LocalStorageSource.GetParsed>( "all-osm-preferences", {}) private readonly preferenceSources = new Map>() private auth: any private userDetails: UIEventSource diff --git a/src/Logic/State/FeatureSwitchState.ts b/src/Logic/State/FeatureSwitchState.ts index a776ae6af..fbf80cc59 100644 --- a/src/Logic/State/FeatureSwitchState.ts +++ b/src/Logic/State/FeatureSwitchState.ts @@ -172,7 +172,7 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { (urls) => urls?.join(",") ) - this.overpassTimeout = UIEventSource.asFloat( + this.overpassTimeout = UIEventSource.asInt( QueryParameters.GetQueryParameter( "overpassTimeout", "" + layoutToUse?.overpassTimeout, @@ -188,7 +188,7 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { ) ) - this.osmApiTileSize = UIEventSource.asFloat( + this.osmApiTileSize = UIEventSource.asInt( QueryParameters.GetQueryParameter( "osmApiTileSize", "" + layoutToUse?.osmApiTileSize, diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index bb22df11c..16e9eeaf7 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -1,28 +1,28 @@ -import { Utils } from "../Utils" -import { Readable, Subscriber, Unsubscriber, Updater, Writable } from "svelte/store" +import { Utils } from "../Utils"; +import { Readable, Subscriber, Unsubscriber, Updater, Writable } from "svelte/store"; /** * Various static utils */ export class Stores { public static Chronic(millis: number, asLong: () => boolean = undefined): Store { - const source = new UIEventSource(undefined) + const source = new UIEventSource(undefined); function run() { - source.setData(new Date()) + source.setData(new Date()); if (asLong === undefined || asLong()) { - window.setTimeout(run, millis) + window.setTimeout(run, millis); } } - run() - return source + run(); + return source; } public static FromPromiseWithErr( promise: Promise ): Store<{ success: T } | { error: any }> { - return UIEventSource.FromPromiseWithErr(promise) + return UIEventSource.FromPromiseWithErr(promise); } /** @@ -32,14 +32,14 @@ export class Stores { * @constructor */ public static FromPromise(promise: Promise): Store { - const src = new UIEventSource(undefined) - promise?.then((d) => src.setData(d)) - promise?.catch((err) => console.warn("Promise failed:", err)) - return src + const src = new UIEventSource(undefined); + promise?.then((d) => src.setData(d)); + promise?.catch((err) => console.warn("Promise failed:", err)); + return src; } public static flatten(source: Store>, possibleSources?: Store[]): Store { - return UIEventSource.flatten(source, possibleSources) + return UIEventSource.flatten(source, possibleSources); } /** @@ -57,39 +57,39 @@ export class Stores { * @constructor */ public static ListStabilized(src: Store): Store { - const stable = new UIEventSource(undefined) + const stable = new UIEventSource(undefined); src.addCallbackAndRun((list) => { if (list === undefined) { - stable.setData(undefined) - return + stable.setData(undefined); + return; } if (Utils.sameList(stable.data, list)) { - return + return; } - stable.setData(list) - }) - return stable + stable.setData(list); + }); + return stable; } } export abstract class Store implements Readable { - abstract readonly data: T + abstract readonly data: T; /** * Optional value giving a title to the UIEventSource, mainly used for debugging */ - public readonly tag: string | undefined + public readonly tag: string | undefined; constructor(tag: string = undefined) { - this.tag = tag + this.tag = tag; if (tag === undefined || tag === "") { - let createStack = Utils.runningFromConsole + let createStack = Utils.runningFromConsole; if (!Utils.runningFromConsole) { - createStack = window.location.hostname === "127.0.0.1" + createStack = window.location.hostname === "127.0.0.1"; } if (createStack) { - const callstack = new Error().stack.split("\n") - this.tag = callstack[1] + const callstack = new Error().stack.split("\n"); + this.tag = callstack[1]; } } } @@ -100,13 +100,13 @@ export abstract class Store implements Readable { public mapD(f: (t: T) => J, extraStoresToWatch?: Store[]): Store { return this.map((t) => { if (t === undefined) { - return undefined + return undefined; } if (t === null) { - return null + return null; } - return f(t) - }, extraStoresToWatch) + return f(t); + }, extraStoresToWatch); } /** @@ -135,17 +135,17 @@ export abstract class Store implements Readable { public withEqualityStabilized( comparator: (t: T | undefined, t1: T | undefined) => boolean ): Store { - let oldValue = undefined + let oldValue = undefined; return this.map((v) => { if (v == oldValue) { - return oldValue + return oldValue; } if (comparator(oldValue, v)) { - return oldValue + return oldValue; } - oldValue = v - return v - }) + oldValue = v; + return v; + }); } /** @@ -195,49 +195,49 @@ export abstract class Store implements Readable { * lastValue // => "def" */ public bind(f: (t: T) => Store): Store { - const mapped = this.map(f) - const sink = new UIEventSource(undefined) - const seenEventSources = new Set>() + const mapped = this.map(f); + const sink = new UIEventSource(undefined); + const seenEventSources = new Set>(); mapped.addCallbackAndRun((newEventSource) => { if (newEventSource === null) { - sink.setData(null) + sink.setData(null); } else if (newEventSource === undefined) { - sink.setData(undefined) + sink.setData(undefined); } else if (!seenEventSources.has(newEventSource)) { - seenEventSources.add(newEventSource) + seenEventSources.add(newEventSource); newEventSource.addCallbackAndRun((resultData) => { if (mapped.data === newEventSource) { - sink.setData(resultData) + sink.setData(resultData); } - }) + }); } else { // Already seen, so we don't have to add a callback, just update the value - sink.setData(newEventSource.data) + sink.setData(newEventSource.data); } - }) + }); - return sink + return sink; } public stabilized(millisToStabilize): Store { if (Utils.runningFromConsole) { - return this + return this; } - const newSource = new UIEventSource(this.data) + const newSource = new UIEventSource(this.data); - const self = this + const self = this; this.addCallback((latestData) => { window.setTimeout(() => { 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 - newSource.setData(latestData) + newSource.setData(latestData); } - }, millisToStabilize) - }) + }, millisToStabilize); + }); - return newSource + return newSource; } /** @@ -247,23 +247,23 @@ export abstract class Store implements Readable { * @constructor */ public AsPromise(condition?: (t: T) => boolean): Promise { - const self = this - condition = condition ?? ((t) => t !== undefined) + const self = this; + condition = condition ?? ((t) => t !== undefined); return new Promise((resolve) => { - const data = self.data + const data = self.data; if (condition(data)) { - resolve(data) + resolve(data); } else { self.addCallbackD((data) => { if (condition(data)) { - resolve(data) - return true // return true to unregister as we only need to be called once + 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 + return false; // We didn't resolve yet, wait for the next ping } - }) + }); } - }) + }); } /** @@ -275,50 +275,51 @@ export abstract class Store implements Readable { // 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) - }) + run(v); + }); } } export class ImmutableStore extends Store { - public readonly data: T + public readonly data: T; constructor(data: T) { - super() - this.data = data + super(); + this.data = data; } - private static readonly pass: () => void = () => {} + private static readonly pass: () => void = () => { + }; addCallback(_: (data: T) => void): () => void { // pass: data will never change - return ImmutableStore.pass + return ImmutableStore.pass; } addCallbackAndRun(callback: (data: T) => void): () => void { - callback(this.data) + callback(this.data); // no callback registry: data will never change - return ImmutableStore.pass + return ImmutableStore.pass; } addCallbackAndRunD(callback: (data: T) => void): () => void { if (this.data !== undefined) { - callback(this.data) + callback(this.data); } // no callback registry: data will never change - return ImmutableStore.pass + return ImmutableStore.pass; } addCallbackD(_: (data: T) => void): () => void { // pass: data will never change - return ImmutableStore.pass + return ImmutableStore.pass; } map(f: (t: T) => J, extraStores: Store[] = undefined): ImmutableStore { if (extraStores?.length > 0) { - return new MappedStore(this, f, extraStores, undefined, f(this.data)) + return new MappedStore(this, f, extraStores, undefined, f(this.data)); } - return new ImmutableStore(f(this.data)) + return new ImmutableStore(f(this.data)); } } @@ -326,8 +327,8 @@ export class ImmutableStore extends Store { * Keeps track of the callback functions */ class ListenerTracker { - public pingCount = 0 - private readonly _callbacks: ((t: T) => boolean | void | any)[] = [] + public pingCount = 0; + private readonly _callbacks: ((t: T) => boolean | void | any)[] = []; /** * Adds a callback which can be called; a function to unregister is returned @@ -335,17 +336,17 @@ class ListenerTracker { 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." + 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) + this._callbacks.push(callback); // Give back an unregister-function! return () => { - const index = this._callbacks.indexOf(callback) + const index = this._callbacks.indexOf(callback); if (index >= 0) { - this._callbacks.splice(index, 1) + this._callbacks.splice(index, 1); } - } + }; } /** @@ -353,40 +354,40 @@ class ListenerTracker { * Returns the number of registered callbacks */ public ping(data: T): number { - this.pingCount++ - let toDelete = undefined - let startTime = new Date().getTime() / 1000 + this.pingCount++; + let toDelete = undefined; + let startTime = new Date().getTime() / 1000; for (const callback of this._callbacks) { 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] + toDelete = [callback]; } else { - toDelete.push(callback) + toDelete.push(callback); } } } catch (e) { - console.error("Got an error while running a callback:", e) + console.error("Got an error while running a callback:", e); } } - let endTime = new Date().getTime() / 1000 + let endTime = new Date().getTime() / 1000; if (endTime - startTime > 500) { console.trace( "Warning: a ping took more then 500ms; this is probably a performance issue" - ) + ); } if (toDelete !== undefined) { for (const toDeleteElement of toDelete) { - this._callbacks.splice(this._callbacks.indexOf(toDeleteElement), 1) + this._callbacks.splice(this._callbacks.indexOf(toDeleteElement), 1); } } - return this._callbacks.length + return this._callbacks.length; } length() { - return this._callbacks.length + return this._callbacks.length; } } @@ -394,16 +395,16 @@ class ListenerTracker { * The mapped store is a helper type which does the mapping of a function. */ class MappedStore extends Store { - private static readonly pass: () => {} - private readonly _upstream: Store - private readonly _upstreamCallbackHandler: ListenerTracker | undefined - private _upstreamPingCount: number = -1 - private _unregisterFromUpstream: () => void - private readonly _f: (t: TIn) => T - private readonly _extraStores: Store[] | undefined - private _unregisterFromExtraStores: (() => void)[] | undefined - private _callbacks: ListenerTracker = new ListenerTracker() - private _callbacksAreRegistered = false + private static readonly pass: () => {}; + private readonly _upstream: Store; + private readonly _upstreamCallbackHandler: ListenerTracker | undefined; + private _upstreamPingCount: number = -1; + private _unregisterFromUpstream: () => void; + private readonly _f: (t: TIn) => T; + private readonly _extraStores: Store[] | undefined; + private _unregisterFromExtraStores: (() => void)[] | undefined; + private _callbacks: ListenerTracker = new ListenerTracker(); + private _callbacksAreRegistered = false; constructor( upstream: Store, @@ -413,20 +414,20 @@ class MappedStore extends Store { initialState: T, onDestroy?: (f: () => void) => void ) { - super() - this._upstream = upstream - this._upstreamCallbackHandler = upstreamListenerHandler - this._f = f - this._data = initialState - this._upstreamPingCount = upstreamListenerHandler?.pingCount - this._extraStores = extraStores - this.registerCallbacksToUpstream() + super(); + this._upstream = upstream; + this._upstreamCallbackHandler = upstreamListenerHandler; + this._f = f; + this._data = initialState; + this._upstreamPingCount = upstreamListenerHandler?.pingCount; + this._extraStores = extraStores; + this.registerCallbacksToUpstream(); if (onDestroy !== undefined) { - onDestroy(() => this.unregisterFromUpstream()) + onDestroy(() => this.unregisterFromUpstream()); } } - private _data: T + private _data: T; /** * Gets the current data from the store @@ -442,27 +443,27 @@ class MappedStore extends Store { // Callbacks are not registered, so we haven't been listening for updates from the upstream which might have changed if (this._upstreamCallbackHandler?.pingCount != this._upstreamPingCount) { // Upstream has pinged - let's update our data first - this._data = this._f(this._upstream.data) + this._data = this._f(this._upstream.data); } - return this._data + return this._data; } - return this._data + return this._data; } map(f: (t: T) => J, extraStores: Store[] = undefined): Store { - let stores: Store[] = undefined + let stores: Store[] = undefined; if (extraStores?.length > 0 || this._extraStores?.length > 0) { - stores = [] + stores = []; } if (extraStores?.length > 0) { - stores.push(...extraStores) + stores.push(...extraStores); } if (this._extraStores?.length > 0) { this._extraStores?.forEach((store) => { if (stores.indexOf(store) < 0) { - stores.push(store) + stores.push(store); } - }) + }); } return new MappedStore( this, @@ -470,111 +471,111 @@ class MappedStore extends Store { stores, this._callbacks, f(this.data) - ) + ); } 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 - this.registerCallbacksToUpstream() + this.registerCallbacksToUpstream(); } - const unregister = this._callbacks.addCallback(callback) + const unregister = this._callbacks.addCallback(callback); return () => { - unregister() + unregister(); if (this._callbacks.length() == 0) { - this.unregisterFromUpstream() + this.unregisterFromUpstream(); } - } + }; } addCallbackAndRun(callback: (data: T) => any | boolean | void): () => void { - const unregister = this.addCallback(callback) - const doRemove = callback(this.data) + const unregister = this.addCallback(callback); + const doRemove = callback(this.data); if (doRemove === true) { - unregister() - return MappedStore.pass + unregister(); + return MappedStore.pass; } - return unregister + return unregister; } addCallbackAndRunD(callback: (data: T) => any | boolean | void): () => void { return this.addCallbackAndRun((data) => { if (data !== undefined) { - return callback(data) + return callback(data); } - }) + }); } addCallbackD(callback: (data: T) => any | boolean | void): () => void { return this.addCallback((data) => { if (data !== undefined) { - return callback(data) + return callback(data); } - }) + }); } private unregisterFromUpstream() { - console.debug("Unregistering callbacks for", this.tag) - this._callbacksAreRegistered = false - this._unregisterFromUpstream() - this._unregisterFromExtraStores?.forEach((unr) => unr()) + console.debug("Unregistering callbacks for", this.tag); + this._callbacksAreRegistered = false; + this._unregisterFromUpstream(); + this._unregisterFromExtraStores?.forEach((unr) => unr()); } private registerCallbacksToUpstream() { - const self = this + const self = this; - this._unregisterFromUpstream = this._upstream.addCallback((_) => self.update()) + this._unregisterFromUpstream = this._upstream.addCallback((_) => self.update()); this._unregisterFromExtraStores = this._extraStores?.map((store) => store?.addCallback((_) => self.update()) - ) - this._callbacksAreRegistered = true + ); + this._callbacksAreRegistered = true; } private update(): void { - const newData = this._f(this._upstream.data) - this._upstreamPingCount = this._upstreamCallbackHandler?.pingCount + const newData = this._f(this._upstream.data); + this._upstreamPingCount = this._upstreamCallbackHandler?.pingCount; if (this._data == newData) { - return + return; } - this._data = newData - this._callbacks.ping(this._data) + this._data = newData; + this._callbacks.ping(this._data); } } export class UIEventSource extends Store implements Writable { - private static readonly pass: () => {} - public data: T - _callbacks: ListenerTracker = new ListenerTracker() + private static readonly pass: () => {}; + public data: T; + _callbacks: ListenerTracker = new ListenerTracker(); constructor(data: T, tag: string = "") { - super(tag) - this.data = data + super(tag); + this.data = data; } public static flatten( source: Store>, possibleSources?: Store[] ): UIEventSource { - const sink = new UIEventSource(source.data?.data) + const sink = new UIEventSource(source.data?.data); source.addCallback((latestData) => { - sink.setData(latestData?.data) + sink.setData(latestData?.data); latestData.addCallback((data) => { if (source.data !== latestData) { - return true + return true; } - sink.setData(data) - }) - }) + sink.setData(data); + }); + }); for (const possibleSource of possibleSources ?? []) { possibleSource?.addCallback(() => { - sink.setData(source.data?.data) - }) + sink.setData(source.data?.data); + }); } - return sink + return sink; } /** @@ -585,16 +586,16 @@ export class UIEventSource extends Store implements Writable { promise: Promise, onError: (e: any) => void = undefined ): UIEventSource { - const src = new UIEventSource(undefined) - promise?.then((d) => src.setData(d)) + const src = new UIEventSource(undefined); + promise?.then((d) => src.setData(d)); promise?.catch((err) => { if (onError !== undefined) { - onError(err) + onError(err); } else { - console.warn("Promise failed:", err) + console.warn("Promise failed:", err); } - }) - return src + }); + return src; } /** @@ -606,26 +607,68 @@ export class UIEventSource extends Store implements Writable { public static FromPromiseWithErr( promise: Promise ): UIEventSource<{ success: T } | { error: any }> { - const src = new UIEventSource<{ success: T } | { error: any }>(undefined) - promise?.then((d) => src.setData({ success: d })) - promise?.catch((err) => src.setData({ error: err })) - return src + const src = new UIEventSource<{ success: T } | { error: any }>(undefined); + promise?.then((d) => src.setData({ success: d })); + promise?.catch((err) => src.setData({ error: err })); + return src; } - public static asFloat(source: UIEventSource): UIEventSource { + /** + * + * @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): UIEventSource { return source.sync( (str) => { - let parsed = parseFloat(str) - return isNaN(parsed) ? undefined : parsed + let parsed = parseInt(str); + return isNaN(parsed) ? undefined : parsed; }, [], (fl) => { if (fl === undefined || isNaN(fl)) { - return undefined + return undefined; } - return ("" + fl).substr(0, 8) + return ("" + fl); } - ) + ); + } + + /** + * 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 + */ + + public static asFloat(source: UIEventSource): UIEventSource { + return source.sync( + (str) => { + let parsed = parseFloat(str); + return isNaN(parsed) ? undefined : parsed; + }, + [], + (fl) => { + if (fl === undefined || isNaN(fl)) { + return undefined; + } + return ("" + fl); + } + ); } static asBoolean(stringUIEventSource: UIEventSource) { @@ -633,7 +676,7 @@ export class UIEventSource extends Store implements Writable { (str) => str === "true", [], (b) => "" + b - ) + ); } /** @@ -641,9 +684,9 @@ export class UIEventSource extends Store implements Writable { * However, this value can be overriden without affecting source */ static feedFrom(store: Store): UIEventSource { - const src = new UIEventSource(store.data) - store.addCallback((t) => src.setData(t)) - return src + const src = new UIEventSource(store.data); + store.addCallback((t) => src.setData(t)); + return src; } /** @@ -653,46 +696,46 @@ export class UIEventSource extends Store implements Writable { * @param callback */ public addCallback(callback: (latestData: T) => boolean | void | any): () => void { - return this._callbacks.addCallback(callback) + return this._callbacks.addCallback(callback); } public addCallbackAndRun(callback: (latestData: T) => boolean | void | any): () => void { - const doDeleteCallback = callback(this.data) + const doDeleteCallback = callback(this.data); if (doDeleteCallback !== true) { - return this.addCallback(callback) + return this.addCallback(callback); } else { - return UIEventSource.pass + return UIEventSource.pass; } } public addCallbackAndRunD(callback: (data: T) => void): () => void { return this.addCallbackAndRun((data) => { if (data !== undefined && data !== null) { - return callback(data) + return callback(data); } - }) + }); } public addCallbackD(callback: (data: T) => void): () => void { return this.addCallback((data) => { if (data !== undefined && data !== null) { - return callback(data) + return callback(data); } - }) + }); } public setData(t: T): UIEventSource { if (this.data == t) { // MUST COMPARE BY REFERENCE! - return + return; } - this.data = t - this._callbacks.ping(t) - return this + this.data = t; + this._callbacks.ping(t); + return this; } public ping(): void { - this._callbacks.ping(this.data) + this._callbacks.ping(this.data); } /** @@ -724,7 +767,7 @@ export class UIEventSource extends Store implements Writable { extraSources: Store[] = [], onDestroy?: (f: () => void) => void ): Store { - return new MappedStore(this, f, extraSources, this._callbacks, f(this.data), onDestroy) + return new MappedStore(this, f, extraSources, this._callbacks, f(this.data), onDestroy); } /** @@ -736,14 +779,14 @@ export class UIEventSource extends Store implements Writable { this, (t) => { if (t === undefined) { - return undefined + return undefined; } - return f(t) + return f(t); }, extraSources, this._callbacks, this.data === undefined ? undefined : f(this.data) - ) + ); } /** @@ -760,53 +803,53 @@ export class UIEventSource extends Store implements Writable { g: (j: J, t: T) => T, allowUnregister = false ): UIEventSource { - const self = this + const self = this; - const stack = new Error().stack.split("\n") - const callee = stack[1] + const stack = new Error().stack.split("\n"); + const callee = stack[1]; - const newSource = new UIEventSource(f(this.data), "map(" + this.tag + ")@" + callee) + const newSource = new UIEventSource(f(this.data), "map(" + this.tag + ")@" + callee); - const update = function () { - newSource.setData(f(self.data)) - return allowUnregister && newSource._callbacks.length() === 0 - } + const update = function() { + newSource.setData(f(self.data)); + return allowUnregister && newSource._callbacks.length() === 0; + }; - this.addCallback(update) + this.addCallback(update); for (const extraSource of extraSources) { - extraSource?.addCallback(update) + extraSource?.addCallback(update); } if (g !== undefined) { newSource.addCallback((latest) => { - self.setData(g(latest, self.data)) - }) + self.setData(g(latest, self.data)); + }); } - return newSource + return newSource; } public syncWith(otherSource: UIEventSource, reverseOverride = false): UIEventSource { - this.addCallback((latest) => otherSource.setData(latest)) - const self = this - otherSource.addCallback((latest) => self.setData(latest)) + this.addCallback((latest) => otherSource.setData(latest)); + const self = this; + otherSource.addCallback((latest) => self.setData(latest)); if (reverseOverride) { if (otherSource.data !== undefined) { - this.setData(otherSource.data) + this.setData(otherSource.data); } } else if (this.data === undefined) { - this.setData(otherSource.data) + this.setData(otherSource.data); } else { - otherSource.setData(this.data) + otherSource.setData(this.data); } - return this + return this; } set(value: T): void { - this.setData(value) + this.setData(value); } update(f: Updater & ((value: T) => T)): void { - this.setData(f(this.data)) + this.setData(f(this.data)); } } diff --git a/src/test.ts b/src/test.ts index 36ce24a74..1734faedb 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,15 +1,8 @@ -import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" -import * as theme from "./assets/generated/themes/bookcases.json" -import ThemeViewState from "./Models/ThemeViewState" -import Combine from "./UI/Base/Combine" -import SpecialVisualizations from "./UI/SpecialVisualizations" -import ValidatedInput from "./UI/InputElement/ValidatedInput.svelte" -import SvelteUIElement from "./UI/Base/SvelteUIElement" -import { UIEventSource } from "./Logic/UIEventSource" -import { Unit } from "./Models/Unit" -import { Denomination } from "./Models/Denomination" -import { VariableUiElement } from "./UI/Base/VariableUIElement" -import { FixedUiElement } from "./UI/Base/FixedUiElement" +import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"; +import * as theme from "./assets/generated/themes/bookcases.json"; +import ThemeViewState from "./Models/ThemeViewState"; +import Combine from "./UI/Base/Combine"; +import SpecialVisualizations from "./UI/SpecialVisualizations"; function testspecial() { const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)