Chore: housekeeping, fix tests

This commit is contained in:
Pieter Vander Vennet 2023-10-06 03:34:26 +02:00
parent 1f67ab2aca
commit 36a73f3a17
50 changed files with 1124 additions and 1022 deletions

View file

@ -1,17 +1,16 @@
import Constants from "../Models/Constants";
import Constants from "../Models/Constants"
export default class Maproulette {
public static readonly defaultEndpoint = "https://maproulette.org/api/v2"
public static readonly defaultEndpoint = "https://maproulette.org/api/v2";
public static readonly STATUS_OPEN = 0;
public static readonly STATUS_FIXED = 1;
public static readonly STATUS_FALSE_POSITIVE = 2;
public static readonly STATUS_SKIPPED = 3;
public static readonly STATUS_DELETED = 4;
public static readonly STATUS_ALREADY_FIXED = 5;
public static readonly STATUS_TOO_HARD = 6;
public static readonly STATUS_DISABLED = 9;
public static readonly STATUS_OPEN = 0
public static readonly STATUS_FIXED = 1
public static readonly STATUS_FALSE_POSITIVE = 2
public static readonly STATUS_SKIPPED = 3
public static readonly STATUS_DELETED = 4
public static readonly STATUS_ALREADY_FIXED = 5
public static readonly STATUS_TOO_HARD = 6
public static readonly STATUS_DISABLED = 9
public static readonly STATUS_MEANING = {
0: "Open",
@ -21,28 +20,28 @@ export default class Maproulette {
4: "Deleted",
5: "Already fixed",
6: "Too hard",
9: "Disabled"
};
public static singleton = new Maproulette();
9: "Disabled",
}
public static singleton = new Maproulette()
/*
* The API endpoint to use
*/
endpoint: string;
endpoint: string
/**
* The API key to use for all requests
*/
private readonly apiKey: string;
private readonly apiKey: string
/**
* Creates a new Maproulette instance
* @param endpoint The API endpoint to use
*/
constructor(endpoint?: string) {
this.endpoint = endpoint ?? Maproulette.defaultEndpoint;
if(!this.endpoint ){
this.endpoint = endpoint ?? Maproulette.defaultEndpoint
if (!this.endpoint) {
throw "MapRoulette endpoint is undefined. Make sure that `Maproulette.defaultEndpoint` is defined on top of the class"
}
this.apiKey = Constants.MaprouletteApiKey;
this.apiKey = Constants.MaprouletteApiKey
}
/**
@ -54,14 +53,14 @@ export default class Maproulette {
*/
public static codeToIndex(code: string): number | undefined {
if (code === "Created") {
return Maproulette.STATUS_OPEN;
return Maproulette.STATUS_OPEN
}
for (let i = 0; i < 9; i++) {
if (Maproulette.STATUS_MEANING["" + i] === code) {
return i;
return i
}
}
return undefined;
return undefined
}
/**
@ -82,18 +81,18 @@ export default class Maproulette {
completionResponses?: Record<string, string>
}
): Promise<void> {
console.log("Maproulette: setting", `${this.endpoint}/task/${taskId}/${status}`, options);
console.log("Maproulette: setting", `${this.endpoint}/task/${taskId}/${status}`, options)
const response = await fetch(`${this.endpoint}/task/${taskId}/${status}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
apiKey: this.apiKey
apiKey: this.apiKey,
},
body: options !== undefined ? JSON.stringify(options) : undefined
});
body: options !== undefined ? JSON.stringify(options) : undefined,
})
if (response.status !== 204) {
console.log(`Failed to close task: ${response.status}`);
throw `Failed to close task: ${response.status}`;
console.log(`Failed to close task: ${response.status}`)
throw `Failed to close task: ${response.status}`
}
}
}

View file

@ -532,7 +532,6 @@ export class Changes {
deletedObjects: OsmObject[]
} = self.CreateChangesetObjects(pending, objects)
return Changes.createChangesetFor("" + csId, changes)
},
metatags,
@ -561,8 +560,11 @@ export class Changes {
const successes = await Promise.all(
Array.from(pendingPerTheme, async ([theme, pendingChanges]) => {
try {
const openChangeset = UIEventSource.asInt(this.state.osmConnection
.GetPreference("current-open-changeset-" + theme))
const openChangeset = UIEventSource.asInt(
this.state.osmConnection.GetPreference(
"current-open-changeset-" + theme
)
)
console.log(
"Using current-open-changeset-" +
theme +

View file

@ -1,14 +1,17 @@
import { UIEventSource } from "../UIEventSource"
import UserDetails, { OsmConnection } from "./OsmConnection"
import { Utils } from "../../Utils"
import { LocalStorageSource } from "../Web/LocalStorageSource";
import { LocalStorageSource } from "../Web/LocalStorageSource"
export class OsmPreferences {
/**
* 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<Record<string, string>>( "all-osm-preferences", {})
public preferences = LocalStorageSource.GetParsed<Record<string, string>>(
"all-osm-preferences",
{}
)
private readonly preferenceSources = new Map<string, UIEventSource<string>>()
private auth: any
private userDetails: UIEventSource<UserDetails>

View file

@ -30,7 +30,6 @@ export class OsmConnectionFeatureSwitches {
public readonly featureSwitchFakeUser: UIEventSource<boolean>
constructor() {
this.featureSwitchFakeUser = QueryParameters.GetBooleanQueryParameter(
"fake-user",
false,

View file

@ -1,14 +1,42 @@
import { Utils } from "../../Utils"
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
export class ThemeMetaTagging {
public static readonly themeName = "usersettings"
public static readonly themeName = "usersettings"
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/&lt;/g,'<')?.replace(/&gt;/g,'>') ?? '' )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
feat.properties['__current_backgroun'] = 'initial_value'
}
}
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
feat.properties._description
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
?.at(1)
)
Utils.AddLazyProperty(
feat.properties,
"_d",
() => feat.properties._description?.replace(/&lt;/g, "<")?.replace(/&gt;/g, ">") ?? ""
)
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.href.match(/mastodon|en.osm.town/) !== null
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(
feat.properties,
"_mastodon_candidate",
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
)
feat.properties["__current_backgroun"] = "initial_value"
}
}

View file

@ -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<Date> {
const source = new UIEventSource<Date>(undefined);
const source = new UIEventSource<Date>(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<T>(
promise: Promise<T>
): Store<{ success: T } | { error: any }> {
return UIEventSource.FromPromiseWithErr(promise);
return UIEventSource.FromPromiseWithErr(promise)
}
/**
@ -32,14 +32,14 @@ export class Stores {
* @constructor
*/
public static FromPromise<T>(promise: Promise<T>): Store<T> {
const src = new UIEventSource<T>(undefined);
promise?.then((d) => src.setData(d));
promise?.catch((err) => console.warn("Promise failed:", err));
return src;
const src = new UIEventSource<T>(undefined)
promise?.then((d) => src.setData(d))
promise?.catch((err) => console.warn("Promise failed:", err))
return src
}
public static flatten<X>(source: Store<Store<X>>, possibleSources?: Store<any>[]): Store<X> {
return UIEventSource.flatten(source, possibleSources);
return UIEventSource.flatten(source, possibleSources)
}
/**
@ -57,39 +57,39 @@ export class Stores {
* @constructor
*/
public static ListStabilized<T>(src: Store<T[]>): Store<T[]> {
const stable = new UIEventSource<T[]>(undefined);
const stable = new UIEventSource<T[]>(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<T> implements Readable<T> {
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<T> implements Readable<T> {
public mapD<J>(f: (t: T) => J, extraStoresToWatch?: Store<any>[]): Store<J> {
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<T> implements Readable<T> {
public withEqualityStabilized(
comparator: (t: T | undefined, t1: T | undefined) => boolean
): Store<T> {
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<T> implements Readable<T> {
* lastValue // => "def"
*/
public bind<X>(f: (t: T) => Store<X>): Store<X> {
const mapped = this.map(f);
const sink = new UIEventSource<X>(undefined);
const seenEventSources = new Set<Store<X>>();
const mapped = this.map(f)
const sink = new UIEventSource<X>(undefined)
const seenEventSources = new Set<Store<X>>()
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<T> {
if (Utils.runningFromConsole) {
return this;
return this
}
const newSource = new UIEventSource<T>(this.data);
const newSource = new UIEventSource<T>(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<T> implements Readable<T> {
* @constructor
*/
public AsPromise(condition?: (t: T) => boolean): Promise<T> {
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,51 +275,50 @@ export abstract class Store<T> implements Readable<T> {
// 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<T> extends Store<T> {
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<J>(f: (t: T) => J, extraStores: Store<any>[] = undefined): ImmutableStore<J> {
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<J>(f(this.data));
return new ImmutableStore<J>(f(this.data))
}
}
@ -327,8 +326,8 @@ export class ImmutableStore<T> extends Store<T> {
* Keeps track of the callback functions
*/
class ListenerTracker<T> {
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
@ -336,17 +335,17 @@ class ListenerTracker<T> {
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)
}
};
}
}
/**
@ -354,40 +353,40 @@ class ListenerTracker<T> {
* 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
}
}
@ -395,16 +394,16 @@ class ListenerTracker<T> {
* The mapped store is a helper type which does the mapping of a function.
*/
class MappedStore<TIn, T> extends Store<T> {
private static readonly pass: () => {};
private readonly _upstream: Store<TIn>;
private readonly _upstreamCallbackHandler: ListenerTracker<TIn> | undefined;
private _upstreamPingCount: number = -1;
private _unregisterFromUpstream: () => void;
private readonly _f: (t: TIn) => T;
private readonly _extraStores: Store<any>[] | undefined;
private _unregisterFromExtraStores: (() => void)[] | undefined;
private _callbacks: ListenerTracker<T> = new ListenerTracker<T>();
private _callbacksAreRegistered = false;
private static readonly pass: () => {}
private readonly _upstream: Store<TIn>
private readonly _upstreamCallbackHandler: ListenerTracker<TIn> | undefined
private _upstreamPingCount: number = -1
private _unregisterFromUpstream: () => void
private readonly _f: (t: TIn) => T
private readonly _extraStores: Store<any>[] | undefined
private _unregisterFromExtraStores: (() => void)[] | undefined
private _callbacks: ListenerTracker<T> = new ListenerTracker<T>()
private _callbacksAreRegistered = false
constructor(
upstream: Store<TIn>,
@ -414,20 +413,20 @@ class MappedStore<TIn, T> extends Store<T> {
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
@ -443,27 +442,27 @@ class MappedStore<TIn, T> extends Store<T> {
// 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<J>(f: (t: T) => J, extraStores: Store<any>[] = undefined): Store<J> {
let stores: Store<any>[] = undefined;
let stores: Store<any>[] = 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,
@ -471,111 +470,111 @@ class MappedStore<TIn, T> extends Store<T> {
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<T> extends Store<T> implements Writable<T> {
private static readonly pass: () => {};
public data: T;
_callbacks: ListenerTracker<T> = new ListenerTracker<T>();
private static readonly pass: () => {}
public data: T
_callbacks: ListenerTracker<T> = new ListenerTracker<T>()
constructor(data: T, tag: string = "") {
super(tag);
this.data = data;
super(tag)
this.data = data
}
public static flatten<X>(
source: Store<Store<X>>,
possibleSources?: Store<any>[]
): UIEventSource<X> {
const sink = new UIEventSource<X>(source.data?.data);
const sink = new UIEventSource<X>(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
}
/**
@ -586,16 +585,16 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
promise: Promise<T>,
onError: (e: any) => void = undefined
): UIEventSource<T> {
const src = new UIEventSource<T>(undefined);
promise?.then((d) => src.setData(d));
const src = new UIEventSource<T>(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
}
/**
@ -607,10 +606,10 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
public static FromPromiseWithErr<T>(
promise: Promise<T>
): 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
}
/**
@ -627,17 +626,17 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
public static asInt(source: UIEventSource<string>): UIEventSource<number> {
return source.sync(
(str) => {
let parsed = parseInt(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);
return "" + fl
}
);
)
}
/**
@ -658,17 +657,17 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
public static asFloat(source: UIEventSource<string>): UIEventSource<number> {
return source.sync(
(str) => {
let parsed = parseFloat(str);
return isNaN(parsed) ? undefined : parsed;
let parsed = parseFloat(str)
return isNaN(parsed) ? undefined : parsed
},
[],
(fl) => {
if (fl === undefined || isNaN(fl)) {
return undefined;
return undefined
}
return ("" + fl);
return "" + fl
}
);
)
}
static asBoolean(stringUIEventSource: UIEventSource<string>) {
@ -676,7 +675,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
(str) => str === "true",
[],
(b) => "" + b
);
)
}
/**
@ -684,9 +683,9 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
* However, this value can be overriden without affecting source
*/
static feedFrom<T>(store: Store<T>): UIEventSource<T> {
const src = new UIEventSource(store.data);
store.addCallback((t) => src.setData(t));
return src;
const src = new UIEventSource(store.data)
store.addCallback((t) => src.setData(t))
return src
}
/**
@ -696,46 +695,46 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
* @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<T> {
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)
}
/**
@ -767,7 +766,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
extraSources: Store<any>[] = [],
onDestroy?: (f: () => void) => void
): Store<J> {
return new MappedStore(this, f, extraSources, this._callbacks, f(this.data), onDestroy);
return new MappedStore(this, f, extraSources, this._callbacks, f(this.data), onDestroy)
}
/**
@ -779,14 +778,14 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
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)
);
)
}
/**
@ -803,53 +802,53 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
g: (j: J, t: T) => T,
allowUnregister = false
): UIEventSource<J> {
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<J>(f(this.data), "map(" + this.tag + ")@" + callee);
const newSource = new UIEventSource<J>(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<T>, reverseOverride = false): UIEventSource<T> {
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<T> & ((value: T) => T)): void {
this.setData(f(this.data));
this.setData(f(this.data))
}
}

View file

@ -1,58 +1,62 @@
import LayoutConfig from "./ThemeConfig/LayoutConfig";
import { SpecialVisualizationState } from "../UI/SpecialVisualization";
import { Changes } from "../Logic/Osm/Changes";
import { Store, UIEventSource } from "../Logic/UIEventSource";
import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource";
import { OsmConnection } from "../Logic/Osm/OsmConnection";
import { ExportableMap, MapProperties } from "./MapProperties";
import LayerState from "../Logic/State/LayerState";
import { Feature, Point, Polygon } from "geojson";
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
import { Map as MlMap } from "maplibre-gl";
import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning";
import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor";
import { GeoLocationState } from "../Logic/State/GeoLocationState";
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
import { QueryParameters } from "../Logic/Web/QueryParameters";
import UserRelatedState from "../Logic/State/UserRelatedState";
import LayerConfig from "./ThemeConfig/LayerConfig";
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler";
import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers";
import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource";
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource";
import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore";
import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter";
import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource";
import ShowDataLayer from "../UI/Map/ShowDataLayer";
import TitleHandler from "../Logic/Actors/TitleHandler";
import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor";
import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader";
import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater";
import { BBox } from "../Logic/BBox";
import Constants from "./Constants";
import Hotkeys from "../UI/Base/Hotkeys";
import Translations from "../UI/i18n/Translations";
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore";
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource";
import { MenuState } from "./MenuState";
import MetaTagging from "../Logic/MetaTagging";
import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator";
import LayoutConfig from "./ThemeConfig/LayoutConfig"
import { SpecialVisualizationState } from "../UI/SpecialVisualization"
import { Changes } from "../Logic/Osm/Changes"
import { Store, UIEventSource } from "../Logic/UIEventSource"
import {
NewGeometryFromChangesFeatureSource
} from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource";
import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader";
import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer";
import { Utils } from "../Utils";
import { EliCategory } from "./RasterLayerProperties";
import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter";
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage";
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource";
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor";
import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector";
import FilteredLayer from "./FilteredLayer";
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector";
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager";
import { Imgur } from "../Logic/ImageProviders/Imgur";
FeatureSource,
IndexedFeatureSource,
WritableFeatureSource,
} from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { ExportableMap, MapProperties } from "./MapProperties"
import LayerState from "../Logic/State/LayerState"
import { Feature, Point, Polygon } from "geojson"
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import { Map as MlMap } from "maplibre-gl"
import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning"
import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor"
import { GeoLocationState } from "../Logic/State/GeoLocationState"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import { QueryParameters } from "../Logic/Web/QueryParameters"
import UserRelatedState from "../Logic/State/UserRelatedState"
import LayerConfig from "./ThemeConfig/LayerConfig"
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers"
import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource"
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"
import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"
import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter"
import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource"
import ShowDataLayer from "../UI/Map/ShowDataLayer"
import TitleHandler from "../Logic/Actors/TitleHandler"
import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor"
import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader"
import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"
import { BBox } from "../Logic/BBox"
import Constants from "./Constants"
import Hotkeys from "../UI/Base/Hotkeys"
import Translations from "../UI/i18n/Translations"
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
import { MenuState } from "./MenuState"
import MetaTagging from "../Logic/MetaTagging"
import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator"
import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource"
import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"
import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer"
import { Utils } from "../Utils"
import { EliCategory } from "./RasterLayerProperties"
import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
import NoElementsInViewDetector, {
FeatureViewState,
} from "../Logic/Actors/NoElementsInViewDetector"
import FilteredLayer from "./FilteredLayer"
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
import { Imgur } from "../Logic/ImageProviders/Imgur"
/**
*
@ -63,71 +67,71 @@ import { Imgur } from "../Logic/ImageProviders/Imgur";
* It ties up all the needed elements and starts some actors.
*/
export default class ThemeViewState implements SpecialVisualizationState {
readonly layout: LayoutConfig;
readonly map: UIEventSource<MlMap>;
readonly changes: Changes;
readonly featureSwitches: FeatureSwitchState;
readonly featureSwitchIsTesting: Store<boolean>;
readonly featureSwitchUserbadge: Store<boolean>;
readonly layout: LayoutConfig
readonly map: UIEventSource<MlMap>
readonly changes: Changes
readonly featureSwitches: FeatureSwitchState
readonly featureSwitchIsTesting: Store<boolean>
readonly featureSwitchUserbadge: Store<boolean>
readonly featureProperties: FeaturePropertiesStore;
readonly featureProperties: FeaturePropertiesStore
readonly osmConnection: OsmConnection;
readonly selectedElement: UIEventSource<Feature>;
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>;
readonly mapProperties: MapProperties & ExportableMap;
readonly osmObjectDownloader: OsmObjectDownloader;
readonly osmConnection: OsmConnection
readonly selectedElement: UIEventSource<Feature>
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>
readonly mapProperties: MapProperties & ExportableMap
readonly osmObjectDownloader: OsmObjectDownloader
readonly dataIsLoading: Store<boolean>;
readonly dataIsLoading: Store<boolean>
/**
* Indicates if there is _some_ data in view, even if it is not shown due to the filters
*/
readonly hasDataInView: Store<FeatureViewState>;
readonly hasDataInView: Store<FeatureViewState>
readonly guistate: MenuState;
readonly fullNodeDatabase?: FullNodeDatabaseSource;
readonly guistate: MenuState
readonly fullNodeDatabase?: FullNodeDatabaseSource
readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>;
readonly indexedFeatures: IndexedFeatureSource & LayoutSource;
readonly currentView: FeatureSource<Feature<Polygon>>;
readonly featuresInView: FeatureSource;
readonly newFeatures: WritableFeatureSource;
readonly layerState: LayerState;
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>;
readonly perLayerFiltered: ReadonlyMap<string, FilteringFeatureSource>;
readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>
readonly indexedFeatures: IndexedFeatureSource & LayoutSource
readonly currentView: FeatureSource<Feature<Polygon>>
readonly featuresInView: FeatureSource
readonly newFeatures: WritableFeatureSource
readonly layerState: LayerState
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
readonly perLayerFiltered: ReadonlyMap<string, FilteringFeatureSource>
readonly availableLayers: Store<RasterLayerPolygon[]>;
readonly selectedLayer: UIEventSource<LayerConfig>;
readonly userRelatedState: UserRelatedState;
readonly geolocation: GeoLocationHandler;
readonly availableLayers: Store<RasterLayerPolygon[]>
readonly selectedLayer: UIEventSource<LayerConfig>
readonly userRelatedState: UserRelatedState
readonly geolocation: GeoLocationHandler
readonly imageUploadManager: ImageUploadManager;
readonly imageUploadManager: ImageUploadManager
readonly lastClickObject: WritableFeatureSource;
readonly lastClickObject: WritableFeatureSource
readonly overlayLayerStates: ReadonlyMap<
string,
{ readonly isDisplayed: UIEventSource<boolean> }
>;
>
/**
* All 'level'-tags that are available with the current features
*/
readonly floors: Store<string[]>;
readonly floors: Store<string[]>
constructor(layout: LayoutConfig) {
Utils.initDomPurify();
this.layout = layout;
this.featureSwitches = new FeatureSwitchState(layout);
Utils.initDomPurify()
this.layout = layout
this.featureSwitches = new FeatureSwitchState(layout)
this.guistate = new MenuState(
this.featureSwitches.featureSwitchWelcomeMessage.data,
layout.id
);
this.map = new UIEventSource<MlMap>(undefined);
const initial = new InitialMapPositioning(layout);
this.mapProperties = new MapLibreAdaptor(this.map, initial);
const geolocationState = new GeoLocationState();
)
this.map = new UIEventSource<MlMap>(undefined)
const initial = new InitialMapPositioning(layout)
this.mapProperties = new MapLibreAdaptor(this.map, initial)
const geolocationState = new GeoLocationState()
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting;
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin;
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin
this.osmConnection = new OsmConnection({
dryRun: this.featureSwitches.featureSwitchIsTesting,
@ -136,57 +140,57 @@ export default class ThemeViewState implements SpecialVisualizationState {
"oauth_token",
undefined,
"Used to complete the login"
)
});
),
})
this.userRelatedState = new UserRelatedState(
this.osmConnection,
layout?.language,
layout,
this.featureSwitches,
this.mapProperties
);
)
this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => {
this.mapProperties.allowRotating.setData(fixated !== "yes");
});
this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element");
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer");
this.mapProperties.allowRotating.setData(fixated !== "yes")
})
this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element")
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer")
this.selectedElementAndLayer = this.selectedElement.mapD(
(feature) => {
const layer = this.selectedLayer.data;
const layer = this.selectedLayer.data
if (!layer) {
return undefined;
return undefined
}
return { layer, feature };
return { layer, feature }
},
[this.selectedLayer]
);
)
this.geolocation = new GeoLocationHandler(
geolocationState,
this.selectedElement,
this.mapProperties,
this.userRelatedState.gpsLocationHistoryRetentionTime
);
)
this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location);
this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location)
const self = this;
this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id);
const self = this
this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id)
{
const overlayLayerStates = new Map<string, { isDisplayed: UIEventSource<boolean> }>();
const overlayLayerStates = new Map<string, { isDisplayed: UIEventSource<boolean> }>()
for (const rasterInfo of this.layout.tileLayerSources) {
const isDisplayed = QueryParameters.GetBooleanQueryParameter(
"overlay-" + rasterInfo.id,
rasterInfo.defaultState ?? true,
"Wether or not overlayer layer " + rasterInfo.id + " is shown"
);
const state = { isDisplayed };
overlayLayerStates.set(rasterInfo.id, state);
new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state);
)
const state = { isDisplayed }
overlayLayerStates.set(rasterInfo.id, state)
new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state)
}
this.overlayLayerStates = overlayLayerStates;
this.overlayLayerStates = overlayLayerStates
}
{
@ -195,7 +199,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
*/
if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) {
this.fullNodeDatabase = new FullNodeDatabaseSource();
this.fullNodeDatabase = new FullNodeDatabaseSource()
}
const layoutSource = new LayoutSource(
@ -205,49 +209,49 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.osmConnection.Backend(),
(id) => self.layerState.filteredLayers.get(id).isDisplayed,
this.fullNodeDatabase
);
)
this.indexedFeatures = layoutSource;
this.indexedFeatures = layoutSource
const empty = [];
let currentViewIndex = 0;
const empty = []
let currentViewIndex = 0
this.currentView = new StaticFeatureSource(
this.mapProperties.bounds.map((bbox) => {
if (!bbox) {
return empty;
return empty
}
currentViewIndex++;
currentViewIndex++
return <Feature[]>[
bbox.asGeoJson({
zoom: this.mapProperties.zoom.data,
...this.mapProperties.location.data,
id: "current_view"
})
];
id: "current_view",
}),
]
})
);
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds);
this.dataIsLoading = layoutSource.isLoading;
)
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
this.dataIsLoading = layoutSource.isLoading
const indexedElements = this.indexedFeatures;
this.featureProperties = new FeaturePropertiesStore(indexedElements);
const indexedElements = this.indexedFeatures
this.featureProperties = new FeaturePropertiesStore(indexedElements)
this.changes = new Changes(
{
dryRun: this.featureSwitches.featureSwitchIsTesting,
allElements: indexedElements,
featurePropertiesStore: this.featureProperties,
osmConnection: this.osmConnection,
historicalUserLocations: this.geolocation.historicalUserLocations
historicalUserLocations: this.geolocation.historicalUserLocations,
},
layout?.isLeftRightSensitive() ?? false
);
this.historicalUserLocations = this.geolocation.historicalUserLocations;
)
this.historicalUserLocations = this.geolocation.historicalUserLocations
this.newFeatures = new NewGeometryFromChangesFeatureSource(
this.changes,
indexedElements,
this.featureProperties
);
layoutSource.addSource(this.newFeatures);
)
layoutSource.addSource(this.newFeatures)
const perLayer = new PerLayerFeatureSourceSplitter(
Array.from(this.layerState.filteredLayers.values()).filter(
@ -263,11 +267,11 @@ export default class ThemeViewState implements SpecialVisualizationState {
features.length,
"leftover features, such as",
features[0].properties
);
}
)
},
}
);
this.perLayer = perLayer.perLayer;
)
this.perLayer = perLayer.perLayer
}
this.perLayer.forEach((fs) => {
new SaveFeatureSourceToLocalStorage(
@ -277,80 +281,80 @@ export default class ThemeViewState implements SpecialVisualizationState {
fs,
this.featureProperties,
fs.layer.layerDef.maxAgeOfCache
);
});
)
})
this.floors = this.featuresInView.features.stabilized(500).map((features) => {
if (!features) {
return [];
return []
}
const floors = new Set<string>();
const floors = new Set<string>()
for (const feature of features) {
let level = feature.properties["_level"];
let level = feature.properties["_level"]
if (level) {
const levels = level.split(";");
const levels = level.split(";")
for (const l of levels) {
floors.add(l);
floors.add(l)
}
} else {
floors.add("0"); // '0' is the default and is thus _always_ present
floors.add("0") // '0' is the default and is thus _always_ present
}
}
const sorted = Array.from(floors);
const sorted = Array.from(floors)
// Sort alphabetically first, to deal with floor "A", "B" and "C"
sorted.sort();
sorted.sort()
sorted.sort((a, b) => {
// We use the laxer 'parseInt' to deal with floor '1A'
const na = parseInt(a);
const nb = parseInt(b);
const na = parseInt(a)
const nb = parseInt(b)
if (isNaN(na) || isNaN(nb)) {
return 0;
return 0
}
return na - nb;
});
sorted.reverse(/* new list, no side-effects */);
return sorted;
});
return na - nb
})
sorted.reverse(/* new list, no side-effects */)
return sorted
})
const lastClick = (this.lastClickObject = new LastClickFeatureSource(
this.mapProperties.lastClickLocation,
this.layout
));
))
this.osmObjectDownloader = new OsmObjectDownloader(
this.osmConnection.Backend(),
this.changes
);
)
this.perLayerFiltered = this.showNormalDataOn(this.map);
this.perLayerFiltered = this.showNormalDataOn(this.map)
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView;
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView
this.imageUploadManager = new ImageUploadManager(
layout,
Imgur.singleton,
this.featureProperties,
this.osmConnection,
this.changes
);
)
this.initActors();
this.addLastClick(lastClick);
this.drawSpecialLayers();
this.initHotkeys();
this.miscSetup();
this.initActors()
this.addLastClick(lastClick)
this.drawSpecialLayers()
this.initHotkeys()
this.miscSetup()
if (!Utils.runningFromConsole) {
console.log("State setup completed", this);
console.log("State setup completed", this)
}
}
public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> {
const filteringFeatureSource = new Map<string, FilteringFeatureSource>();
const filteringFeatureSource = new Map<string, FilteringFeatureSource>()
this.perLayer.forEach((fs, layerName) => {
const doShowLayer = this.mapProperties.zoom.map(
(z) =>
(fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0),
[fs.layer.isDisplayed]
);
)
if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) {
/* This layer is hidden and there is no way to enable it (filterview is disabled or this layer doesn't show up in the filter view as the name is not defined)
@ -360,15 +364,15 @@ export default class ThemeViewState implements SpecialVisualizationState {
* Note: it is tempting to also permanently disable the layer if it is not visible _and_ the layer name is hidden.
* However, this is _not_ correct: the layer might be hidden because zoom is not enough. Zooming in more _will_ reveal the layer!
* */
return;
return
}
const filtered = new FilteringFeatureSource(
fs.layer,
fs,
(id) => this.featureProperties.getStore(id),
this.layerState.globalFilters
);
filteringFeatureSource.set(layerName, filtered);
)
filteringFeatureSource.set(layerName, filtered)
new ShowDataLayer(map, {
layer: fs.layer.layerDef,
@ -376,30 +380,30 @@ export default class ThemeViewState implements SpecialVisualizationState {
doShowLayer,
selectedElement: this.selectedElement,
selectedLayer: this.selectedLayer,
fetchStore: (id) => this.featureProperties.getStore(id)
});
});
return filteringFeatureSource;
fetchStore: (id) => this.featureProperties.getStore(id),
})
})
return filteringFeatureSource
}
/**
* Various small methods that need to be called
*/
private miscSetup() {
this.userRelatedState.markLayoutAsVisited(this.layout);
this.userRelatedState.markLayoutAsVisited(this.layout)
this.selectedElement.addCallbackAndRunD((feature) => {
// As soon as we have a selected element, we clear the selected element
// This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature
// The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear
if (feature.properties.id === "last_click") {
return;
return
}
this.lastClickObject.features.setData([]);
});
this.lastClickObject.features.setData([])
})
if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) {
Utils.LoadCustomCss(this.layout.customCss);
Utils.LoadCustomCss(this.layout.customCss)
}
}
@ -408,74 +412,74 @@ export default class ThemeViewState implements SpecialVisualizationState {
{ nomod: "Escape", onUp: true },
Translations.t.hotkeyDocumentation.closeSidebar,
() => {
this.selectedElement.setData(undefined);
this.guistate.closeAll();
this.selectedElement.setData(undefined)
this.guistate.closeAll()
}
);
)
Hotkeys.RegisterHotkey(
{
nomod: "b"
nomod: "b",
},
Translations.t.hotkeyDocumentation.openLayersPanel,
() => {
if (this.featureSwitches.featureSwitchFilter.data) {
this.guistate.openFilterView();
this.guistate.openFilterView()
}
}
);
)
Hotkeys.RegisterHotkey(
{ shift: "O" },
Translations.t.hotkeyDocumentation.selectMapnik,
() => {
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto);
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto)
}
);
)
const setLayerCategory = (category: EliCategory) => {
const available = this.availableLayers.data;
const current = this.mapProperties.rasterLayer;
const available = this.availableLayers.data
const current = this.mapProperties.rasterLayer
const best = RasterLayerUtils.SelectBestLayerAccordingTo(
available,
category,
current.data
);
console.log("Best layer for category", category, "is", best.properties.id);
current.setData(best);
};
)
console.log("Best layer for category", category, "is", best.properties.id)
current.setData(best)
}
Hotkeys.RegisterHotkey(
{ nomod: "O" },
Translations.t.hotkeyDocumentation.selectOsmbasedmap,
() => setLayerCategory("osmbasedmap")
);
)
Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () =>
setLayerCategory("map")
);
)
Hotkeys.RegisterHotkey(
{ nomod: "P" },
Translations.t.hotkeyDocumentation.selectAerial,
() => setLayerCategory("photo")
);
)
}
private addLastClick(last_click: LastClickFeatureSource) {
// The last_click gets a _very_ special treatment as it interacts with various parts
const last_click_layer = this.layerState.filteredLayers.get("last_click");
this.featureProperties.trackFeatureSource(last_click);
this.indexedFeatures.addSource(last_click);
const last_click_layer = this.layerState.filteredLayers.get("last_click")
this.featureProperties.trackFeatureSource(last_click)
this.indexedFeatures.addSource(last_click)
last_click.features.addCallbackAndRunD((features) => {
if (this.selectedLayer.data?.id === "last_click") {
// The last-click location moved, but we have selected the last click of the previous location
// So, we update _after_ clearing the selection to make sure no stray data is sticking around
this.selectedElement.setData(undefined);
this.selectedElement.setData(features[0]);
this.selectedElement.setData(undefined)
this.selectedElement.setData(features[0])
}
});
})
new ShowDataLayer(this.map, {
features: new FilteringFeatureSource(last_click_layer, last_click),
@ -487,18 +491,18 @@ export default class ThemeViewState implements SpecialVisualizationState {
if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) {
this.map.data.flyTo({
zoom: Constants.minZoomLevelToAddNewPoint,
center: this.mapProperties.lastClickLocation.data
});
return;
center: this.mapProperties.lastClickLocation.data,
})
return
}
// We first clear the selection to make sure no weird state is around
this.selectedLayer.setData(undefined);
this.selectedElement.setData(undefined);
this.selectedLayer.setData(undefined)
this.selectedElement.setData(undefined)
this.selectedElement.setData(feature);
this.selectedLayer.setData(last_click_layer.layerDef);
}
});
this.selectedElement.setData(feature)
this.selectedLayer.setData(last_click_layer.layerDef)
},
})
}
/**
@ -506,7 +510,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
*/
private drawSpecialLayers() {
type AddedByDefaultTypes = (typeof Constants.added_by_default)[number]
const empty = [];
const empty = []
/**
* A listing which maps the layerId onto the featureSource
*/
@ -526,21 +530,21 @@ export default class ThemeViewState implements SpecialVisualizationState {
bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })]
)
),
current_view: this.currentView
};
current_view: this.currentView,
}
if (this.layout?.lockLocation) {
const bbox = new BBox(this.layout.lockLocation);
this.mapProperties.maxbounds.setData(bbox);
const bbox = new BBox(this.layout.lockLocation)
this.mapProperties.maxbounds.setData(bbox)
ShowDataLayer.showRange(
this.map,
new StaticFeatureSource([bbox.asGeoJson({id: "range"})]),
new StaticFeatureSource([bbox.asGeoJson({ id: "range" })]),
this.featureSwitches.featureSwitchIsTesting
);
)
}
const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view");
const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view")
if (currentViewLayer?.tagRenderings?.length > 0) {
const params = MetaTagging.createExtraFuncParams(this);
this.featureProperties.trackFeatureSource(specialLayers.current_view);
const params = MetaTagging.createExtraFuncParams(this)
this.featureProperties.trackFeatureSource(specialLayers.current_view)
specialLayers.current_view.features.addCallbackAndRunD((features) => {
MetaTagging.addMetatags(
features,
@ -549,36 +553,36 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.layout,
this.osmObjectDownloader,
this.featureProperties
);
});
)
})
}
const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range");
const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range")
const rangeIsDisplayed = rangeFLayer?.isDisplayed;
const rangeIsDisplayed = rangeFLayer?.isDisplayed
if (
!QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef))
) {
rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true);
rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true)
}
this.layerState.filteredLayers.forEach((flayer) => {
const id = flayer.layerDef.id;
const features: FeatureSource = specialLayers[id];
const id = flayer.layerDef.id
const features: FeatureSource = specialLayers[id]
if (features === undefined) {
return;
return
}
this.featureProperties.trackFeatureSource(features);
this.featureProperties.trackFeatureSource(features)
new ShowDataLayer(this.map, {
features,
doShowLayer: flayer.isDisplayed,
layer: flayer.layerDef,
selectedElement: this.selectedElement,
selectedLayer: this.selectedLayer
});
});
selectedLayer: this.selectedLayer,
})
})
}
/**
@ -587,35 +591,35 @@ export default class ThemeViewState implements SpecialVisualizationState {
private initActors() {
// Unselect the selected element if it is panned out of view
this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => {
const selected = this.selectedElement.data;
const selected = this.selectedElement.data
if (selected === undefined) {
return;
return
}
const bbox = BBox.get(selected);
const bbox = BBox.get(selected)
if (!bbox.overlapsWith(bounds)) {
this.selectedElement.setData(undefined);
this.selectedElement.setData(undefined)
}
});
})
this.selectedElement.addCallback((selected) => {
if (selected === undefined) {
// We did _unselect_ an item - we always remove the lastclick-object
this.lastClickObject.features.setData([]);
this.selectedLayer.setData(undefined);
this.lastClickObject.features.setData([])
this.selectedLayer.setData(undefined)
}
});
new ThemeViewStateHashActor(this);
new MetaTagging(this);
new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this);
new ChangeToElementsActor(this.changes, this.featureProperties);
new PendingChangesUploader(this.changes, this.selectedElement);
new SelectedElementTagsUpdater(this);
new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers);
})
new ThemeViewStateHashActor(this)
new MetaTagging(this)
new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this)
new ChangeToElementsActor(this.changes, this.featureProperties)
new PendingChangesUploader(this.changes, this.selectedElement)
new SelectedElementTagsUpdater(this)
new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers)
new PreferredRasterLayerSelector(
this.mapProperties.rasterLayer,
this.availableLayers,
this.featureSwitches.backgroundLayerId,
this.userRelatedState.preferredBackgroundLayer
);
)
}
}

View file

@ -22,7 +22,7 @@ export default class AllThemesGui {
"oauth_token",
undefined,
"Used to complete the login"
)
),
})
const state = new UserRelatedState(osmConnection)
const intro = new Combine([

View file

@ -14,7 +14,6 @@
export let tags: UIEventSource<Record<string, string>>
export let highlightedRendering: UIEventSource<string> = undefined
let _metatags: Record<string, string>
onDestroy(
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
@ -31,7 +30,7 @@
{:else}
<div class="flex flex-col gap-y-2 overflow-y-auto p-1 px-2">
{#each layer.tagRenderings as config (config.id)}
{#if (config.condition?.matchesProperties($tags) ?? true) && (config.metacondition?.matchesProperties( { ...$tags, ..._metatags } ?? true))}
{#if (config.condition?.matchesProperties($tags) ?? true) && config.metacondition?.matchesProperties({ ...$tags, ..._metatags } ?? true)}
{#if config.IsKnown($tags)}
<TagRenderingEditable
{tags}

View file

@ -422,7 +422,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background))
}
if (!map.getLayer(background.id)) {
console.log("Adding background layer", background.id, "beforeId", addLayerBeforeId,"; all layers are", map.getStyle().layers.map(l => l.id))
console.log(
"Adding background layer",
background.id,
"beforeId",
addLayerBeforeId,
"; all layers are",
map.getStyle().layers.map((l) => l.id)
)
map.addLayer(
{
id: background.id,

View file

@ -282,7 +282,11 @@ class LineRenderingLayer {
// As such, we only now read the features from the featureSource and compare with the previously set data
const features = featureSource.data
const src = <GeoJSONSource>map.getSource(this._layername)
if (src !== undefined && this.currentSourceData === features && src._data === <any> features) {
if (
src !== undefined &&
this.currentSourceData === features &&
src._data === <any>features
) {
// Already up to date
return
}
@ -315,7 +319,7 @@ class LineRenderingLayer {
})
for (const feature of features) {
if(!feature.properties.id){
if (!feature.properties.id) {
console.warn("Feature without id:", feature)
continue
}
@ -403,8 +407,8 @@ class LineRenderingLayer {
const tags = this._fetchStore(id)
this._listenerInstalledOn.add(id)
map.setFeatureState(
{ source: this._layername, id },
this.calculatePropsFor(feature.properties)
{ source: this._layername, id },
this.calculatePropsFor(feature.properties)
)
tags.addCallbackD((properties) => {
if (!map.getLayer(this._layername)) {
@ -418,7 +422,6 @@ class LineRenderingLayer {
}
}
}
}
export default class ShowDataLayer {
@ -439,7 +442,7 @@ export default class ShowDataLayer {
options: ShowDataLayerOptions & {
layer: LayerConfig
drawMarkers?: true | boolean
drawLines?: true | boolean,
drawLines?: true | boolean
}
) {
this._options = options
@ -462,7 +465,7 @@ export default class ShowDataLayer {
}
)
perLayer.forEach((fs) => {
new ShowDataLayer(mlmap,{
new ShowDataLayer(mlmap, {
layer: fs.layer.layerDef,
features: fs,
...(options ?? {}),
@ -475,7 +478,7 @@ export default class ShowDataLayer {
features: FeatureSource,
doShowLayer?: Store<boolean>
): ShowDataLayer {
return new ShowDataLayer(map,{
return new ShowDataLayer(map, {
layer: ShowDataLayer.rangeLayer,
features,
doShowLayer,

View file

@ -116,7 +116,7 @@ class ApplyButton extends UIElement {
this.state.indexedFeatures.featuresById.data.get(id)
)
new ShowDataLayer(mlmap,{
new ShowDataLayer(mlmap, {
features: StaticFeatureSource.fromGeojson(features),
zoomToFeatures: true,
layer: this.layer.layerDef,

View file

@ -50,7 +50,7 @@
$: isHardDelete = changedProperties[DeleteConfig.deleteReasonKey] !== undefined
async function onDelete() {
if(selectedTags === undefined){
if (selectedTags === undefined) {
return
}
currentState = "applying"

View file

@ -81,7 +81,7 @@ export class MinimapViz implements SpecialVisualization {
const mlmap = new UIEventSource(undefined)
const mla = new MapLibreAdaptor(mlmap, {
rasterLayer: state.mapProperties.rasterLayer
rasterLayer: state.mapProperties.rasterLayer,
})
mla.maxzoom.setData(17)

View file

@ -1,53 +0,0 @@
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { Feature } from "geojson"
import BaseUIElement from "../BaseUIElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import SvelteUIElement from "../Base/SvelteUIElement"
import Questionbox from "./TagRendering/Questionbox.svelte"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
/**
* Thin wrapper around QuestionBox.svelte to include it into the special Visualisations
*/
export default class QuestionViz implements SpecialVisualization {
funcName = "questions"
needsUrls = []
docs =
"The special element which shows the questions which are unkown. Added by default if not yet there"
args = [
{
name: "labels",
doc: "One or more ';'-separated labels. If these are given, only questions with these labels will be given. Use `unlabeled` for all questions that don't have an explicit label. If none given, all questions will be shown",
},
{
name: "blacklisted-labels",
doc: "One or more ';'-separated labels of questions which should _not_ be included",
},
]
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const labels = args[0]
?.split(";")
?.map((s) => s.trim())
?.filter((s) => s !== "")
const blacklist = args[1]
?.split(";")
?.map((s) => s.trim())
?.filter((s) => s !== "")
return new SvelteUIElement(Questionbox, {
layer,
tags,
selectedElement: feature,
state,
onlyForLabels: labels,
notForLabels: blacklist,
})
}
}

View file

@ -111,7 +111,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
while (spec.length > 0) {
const [part] = spec.match(/((\\;)|[^;])*/)
console.log(("Spec is"), part, spec)
console.log("Spec is", part, spec)
spec = spec.substring(part.length + 1) // +1 to remove the pending ';' as well
const kv = part.split("=").map((s) => s.trim().replace("\\;", ";"))
if (kv.length == 2) {

View file

@ -1,4 +1,3 @@
import QuestionViz from "./Popup/QuestionViz"
import Combine from "./Base/Combine"
import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement"
@ -75,6 +74,7 @@ import NearbyImagesSearch from "../Logic/Web/NearbyImagesSearch"
import AllReviews from "./Reviews/AllReviews.svelte"
import StarsBarIcon from "./Reviews/StarsBarIcon.svelte"
import ReviewForm from "./Reviews/ReviewForm.svelte"
import Questionbox from "./Popup/TagRendering/Questionbox.svelte";
class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -181,6 +181,52 @@ class StealViz implements SpecialVisualization {
}
}
/**
* Thin wrapper around QuestionBox.svelte to include it into the special Visualisations
*/
export class QuestionViz implements SpecialVisualization {
funcName = "questions"
needsUrls = []
docs =
"The special element which shows the questions which are unkown. Added by default if not yet there"
args = [
{
name: "labels",
doc: "One or more ';'-separated labels. If these are given, only questions with these labels will be given. Use `unlabeled` for all questions that don't have an explicit label. If none given, all questions will be shown",
},
{
name: "blacklisted-labels",
doc: "One or more ';'-separated labels of questions which should _not_ be included",
},
]
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const labels = args[0]
?.split(";")
?.map((s) => s.trim())
?.filter((s) => s !== "")
const blacklist = args[1]
?.split(";")
?.map((s) => s.trim())
?.filter((s) => s !== "")
return new SvelteUIElement(Questionbox, {
layer,
tags,
selectedElement: feature,
state,
onlyForLabels: labels,
notForLabels: blacklist,
})
}
}
export default class SpecialVisualizations {
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList()

View file

@ -1,11 +1,11 @@
{
"contributors": [
{
"commits": 6055,
"commits": 6085,
"contributor": "Pieter Vander Vennet"
},
{
"commits": 414,
"commits": 417,
"contributor": "Robin van der Linde"
},
{

View file

@ -506,12 +506,6 @@
"ur",
"en"
],
"PL": [
"pl",
"be",
"pl",
"be"
],
"PS": [
"ar"
],

View file

@ -12,7 +12,7 @@
"gl": "lingua galega",
"he": "עברית",
"hu": "magyar",
"id": "bahasa Indonesia",
"id": "Indonesia",
"it": "italiano",
"ja": "日本語",
"nb_NO": "bokmål",
@ -23,6 +23,5 @@
"ru": "русский язык",
"sl": "slovenščina",
"sv": "svenska",
"zh_Hans": "简体中文",
"zh_Hant": "繁體中文"
"zh_Hant": "簡體中文"
}

View file

@ -146,7 +146,7 @@
"gl": "Lingua adigue",
"he": "אדיגית",
"hu": "adigei",
"id": "bahasa Adyghe",
"id": "Adyghe",
"it": "adighè",
"ja": "アディゲ語",
"nb_NO": "adygeisk",
@ -603,7 +603,7 @@
"gl": "árabe",
"he": "ערבית",
"hu": "arab",
"id": "bahasa Arab",
"id": "Arab",
"it": "arabo",
"ja": "アラビア語",
"nb_NO": "arabisk",
@ -929,7 +929,7 @@
"fi": "Awadhin kieli",
"fr": "awadhi",
"gl": "Lingua awadhi",
"he": "אוודהית",
"he": "אוודית",
"id": "Bahasa Awadhi",
"it": "awadhi",
"ja": "アワディー語",
@ -1603,7 +1603,7 @@
"gl": "lingua bretoa",
"he": "ברטונית",
"hu": "breton",
"id": "Bahasa Breton",
"id": "Breton",
"it": "bretone",
"ja": "ブルトン語",
"nb_NO": "bretonsk",
@ -1772,7 +1772,7 @@
"gl": "Lingua buriata",
"he": "בוריאטית",
"hu": "burját",
"id": "bahasa Buryat",
"id": "Buryat",
"it": "buriato",
"ja": "ブリヤート語",
"nb_NO": "burjatisk",
@ -2316,7 +2316,7 @@
"gl": "Lingua tártara de Crimea",
"he": "טטרית של קרים",
"hu": "krími tatár",
"id": "Bahasa Tatar Krimea",
"id": "Tatar Krimea",
"it": "tataro di Crimea",
"ja": "クリミア・タタール語",
"nb_NO": "krimtatarisk",
@ -2442,6 +2442,7 @@
"id": "Bahasa Chittagonia",
"it": "lingua chittagonian",
"ja": "チッタゴン語",
"nb_NO": "Chittagong",
"pl": "Język chatgaya",
"pt": "Língua chittagong",
"pt_BR": "Língua chittagong",
@ -2532,7 +2533,7 @@
"gl": "lingua dinamarquesa",
"he": "דנית",
"hu": "dán",
"id": "bahasa Denmark",
"id": "Denmark",
"it": "danese",
"ja": "デンマーク語",
"nb_NO": "dansk",
@ -2595,7 +2596,7 @@
"gl": "lingua alemá",
"he": "גרמנית",
"hu": "német",
"id": "bahasa Jerman",
"id": "Jerman",
"it": "tedesco",
"ja": "ドイツ語",
"nb_NO": "tysk",
@ -2966,8 +2967,8 @@
"ru": "новогреческий язык",
"sl": "novogrščina",
"sv": "nygrekiska",
"zh_Hans": "希腊语",
"zh_Hant": "希臘語",
"zh_Hans": "现代希腊语",
"zh_Hant": "現代希臘語",
"_meta": {
"countries": [
"CY",
@ -3584,7 +3585,7 @@
"gl": "lingua feroesa",
"he": "פארואזית",
"hu": "feröeri",
"id": "bahasa Faroe",
"id": "Faroe",
"it": "faroese",
"ja": "フェロー語",
"nb_NO": "færøysk",
@ -4872,7 +4873,7 @@
"gl": "lingua indonesia",
"he": "אינדונזית",
"hu": "indonéz",
"id": "bahasa Indonesia",
"id": "Indonesia",
"it": "indonesiano",
"ja": "インドネシア語",
"nb_NO": "indonesisk",
@ -5019,7 +5020,7 @@
"gl": "lingua islandesa",
"he": "איסלנדית",
"hu": "izlandi",
"id": "bahasa Islandia",
"id": "Islandia",
"it": "islandese",
"ja": "アイスランド語",
"nb_NO": "islandsk",
@ -5055,7 +5056,7 @@
"gl": "lingua italiana",
"he": "איטלקית",
"hu": "olasz",
"id": "bahasa Italia",
"id": "Italia",
"it": "italiano",
"ja": "イタリア語",
"nb_NO": "italiensk",
@ -5127,7 +5128,7 @@
"gl": "lingua xaponesa",
"he": "יפנית",
"hu": "japán",
"id": "bahasa Jepang",
"id": "Jepang",
"it": "giapponese",
"ja": "日本語",
"nb_NO": "japansk",
@ -5210,7 +5211,7 @@
"gl": "Lingua xavanesa",
"he": "ג'אווה",
"hu": "jávai",
"id": "bahasa Jawa",
"id": "Jawa",
"it": "giavanese",
"ja": "ジャワ語",
"nb_NO": "javanesisk",
@ -5247,7 +5248,7 @@
"gl": "lingua xeorxiana",
"he": "גאורגית",
"hu": "grúz",
"id": "Bahasa Georgia",
"id": "Georgia",
"it": "georgiano",
"ja": "ジョージア語",
"nb_NO": "georgisk",
@ -5282,7 +5283,7 @@
"gl": "Lingua karakalpak",
"he": "קראקלפקית",
"hu": "karakalpak",
"id": "Bahasa Karakalpak",
"id": "Karakalpak",
"it": "karakalpako",
"ja": "カラカルパク語",
"nl": "Karakalpaks",
@ -5466,6 +5467,7 @@
"ja": "カインガング語",
"nb_NO": "Kaingang",
"nl": "Kaingang",
"pl": "Języki caingang",
"pt": "Língua caingangue",
"pt_BR": "Língua kaingáng",
"ru": "Каинганг",
@ -5636,7 +5638,7 @@
"gl": "Lingua casaca",
"he": "קזחית",
"hu": "kazak",
"id": "bahasa Kazakh",
"id": "Kazakh",
"it": "kazako",
"ja": "カザフ語",
"nb_NO": "kasakhisk",
@ -5673,7 +5675,7 @@
"gl": "Lingua grenlandesa",
"he": "גרינלנדית",
"hu": "grönlandi",
"id": "bahasa Greenland",
"id": "Greenland",
"it": "groenlandese",
"ja": "グリーンランド語",
"nb_NO": "grønlandsk",
@ -5705,7 +5707,7 @@
"gl": "Lingua khmer",
"he": "קמרית",
"hu": "khmer",
"id": "bahasa Khmer",
"id": "Khmer",
"it": "khmer",
"ja": "クメール語",
"nb_NO": "khmer",
@ -5816,6 +5818,7 @@
"pl": "język komi-permiacki",
"pt": "Língua komi-permyak",
"ru": "коми-пермяцкий язык",
"sl": "permjaščina",
"sv": "komi-permjakiska",
"zh_Hans": "彼尔姆科米语",
"zh_Hant": "彼爾姆科米語",
@ -6025,32 +6028,32 @@
}
},
"ku": {
"ca": "kurd del nord",
"cs": "kurmándží",
"da": "Kurmanji",
"de": "Kurmandschi",
"en": "Kurmanji",
"eo": "kurmanĝa lingvo",
"es": "kurmanji",
"eu": "Kurmanji",
"fi": "Kurmandži",
"fr": "kurmandji",
"ca": "kurd",
"cs": "kurdština",
"da": "kurdisk",
"de": "Kurdisch",
"en": "Kurdish",
"eo": "kurda lingvo",
"es": "kurdo",
"eu": "kurduera",
"fi": "kurdi",
"fr": "kurde",
"gl": "lingua kurda",
"he": "כורמנג'ית",
"hu": "kurmandzsi",
"id": "Kurmanji",
"it": "kurmanji",
"ja": "クルマンジー",
"he": "כורדית",
"hu": "kurd",
"id": "Bahasa Kurdi",
"it": "curdo",
"ja": "クルド語",
"nb_NO": "kurdisk",
"nl": "Kurmançi",
"pl": "język kurmandżi",
"pt": "curmânji",
"pt_BR": "Curmânji",
"ru": "курманджи",
"sl": "kurmandži",
"sv": "nordkurdiska",
"nl": "Koerdisch",
"pl": "język kurdyjski",
"pt": "língua curda",
"pt_BR": "língua curda",
"ru": "курдские языки",
"sl": "kurdščina",
"sv": "kurdiska",
"zh_Hans": "库尔德语",
"zh_Hant": "庫德語",
"zh_Hant": "庫德語",
"_meta": {
"countries": [
"IQ"
@ -6127,7 +6130,7 @@
"gl": "lingua komi",
"he": "קומי",
"hu": "komi",
"id": "Bahasa Komi",
"id": "Komi",
"it": "comi",
"ja": "コミ語",
"nb_NO": "syrjensk",
@ -6135,6 +6138,7 @@
"pl": "język komi",
"pt": "língua komi",
"ru": "коми язык",
"sl": "komijščina",
"sv": "komi",
"_meta": {
"dir": [
@ -6217,7 +6221,7 @@
"gl": "kirguiz",
"he": "קירגיזית",
"hu": "kirgiz",
"id": "bahasa Kirgiz",
"id": "Kirgiz",
"it": "kirghiso",
"ja": "キルギス語",
"nb_NO": "kirgisisk",
@ -6303,7 +6307,7 @@
"gl": "Lingua luxemburguesa",
"he": "לוקסמבורגית",
"hu": "luxemburgi",
"id": "bahasa Luksemburg",
"id": "Luksemburg",
"it": "lussemburghese",
"ja": "ルクセンブルク語",
"nb_NO": "luxembourgsk",
@ -6543,7 +6547,7 @@
"gl": "Lingua lombarda",
"he": "לומברד (שפה)",
"hu": "lombard",
"id": "bahasa Lombard",
"id": "Lombard",
"it": "lingua lombarda",
"ja": "ロンバルド語",
"nb_NO": "lombardisk",
@ -6603,7 +6607,7 @@
"gl": "Lingua laosiana",
"he": "לאית",
"hu": "lao",
"id": "bahasa Lao",
"id": "Lao",
"it": "lao",
"ja": "ラーオ語",
"nb_NO": "laotisk",
@ -6973,7 +6977,7 @@
"gl": "Lingua malgaxe",
"he": "מלגשית",
"hu": "malgas",
"id": "Bahasa Malagasi",
"id": "Malagasi",
"it": "malgascio",
"ja": "マダガスカル語",
"nb_NO": "gassisk",
@ -7159,7 +7163,7 @@
"gl": "Lingua macedonia",
"he": "מקדונית",
"hu": "macedón",
"id": "bahasa Makedonia",
"id": "Makedonia",
"it": "macedone",
"ja": "マケドニア語",
"nb_NO": "makedonsk",
@ -7227,7 +7231,7 @@
"gl": "Lingua mongol",
"he": "מונגולית",
"hu": "mongol",
"id": "bahasa Mongol",
"id": "Mongol",
"it": "mongolo",
"ja": "モンゴル語",
"nb_NO": "mongolsk",
@ -7468,7 +7472,7 @@
"gl": "lingua malaia",
"he": "מלאית",
"hu": "maláj",
"id": "bahasa Melayu",
"id": "Melayu",
"it": "malese",
"ja": "マレー語",
"nb_NO": "malayisk",
@ -7646,7 +7650,7 @@
"gl": "birmano",
"he": "בורמזית",
"hu": "burmai",
"id": "bahasa Burma",
"id": "Burma",
"it": "birmano",
"ja": "ビルマ語",
"nb_NO": "burmesisk",
@ -8108,7 +8112,7 @@
"gl": "lingua norueguesa",
"he": "נורווגית",
"hu": "norvég",
"id": "bahasa Norwegia",
"id": "Norwegia",
"it": "norvegese",
"ja": "ノルウェー語",
"nb_NO": "norsk",
@ -8435,12 +8439,12 @@
"eo": "olonec-karela lingvo",
"fi": "livvinkarjala",
"fr": "olonetsien",
"gl": "Lingua livvi",
"gl": "lingua livvi",
"it": "lingua livvi",
"ja": "リッヴィ語",
"nb_NO": "livvisk",
"nl": "Olonetsisch",
"pl": "Dialekt ołoniecki",
"pl": "dialekt ołoniecki",
"ru": "ливвиковское наречие",
"sv": "livvi",
"zh_Hant": "利維卡累利阿語",
@ -8545,7 +8549,7 @@
"gl": "Lingua oseta",
"he": "אוסטית",
"hu": "oszét",
"id": "bahasa Ossetia",
"id": "Ossetia",
"it": "osseto",
"ja": "オセット語",
"nb_NO": "ossetisk",
@ -8621,7 +8625,7 @@
"gl": "lingua punjabi (Shahmukhi)",
"he": "פנג'אבי (אלפבית שאהמוקי)",
"hu": "pandzsábi (Shahmukhi)",
"id": "Bahasa Punjab (Abjad Shahmukhi)",
"id": "Punjab (Abjad Shahmukhi)",
"it": "punjabi (Shahmukhī)",
"ja": "パンジャーブ語 (シャームキー文字)",
"nb_NO": "panjabi (Shahmukhi)",
@ -8846,6 +8850,7 @@
"pl": "Język neosalomoński",
"pt": "Língua pijin",
"ru": "Пиджин Соломоновых Островов",
"sl": "salomonski pidžin",
"sv": "pijin",
"_meta": {
"dir": [
@ -8900,9 +8905,6 @@
"zh_Hans": "波兰语",
"zh_Hant": "波蘭語",
"_meta": {
"countries": [
"PL"
],
"dir": [
"left-to-right"
]
@ -9043,7 +9045,7 @@
"gl": "lingua portuguesa",
"he": "פורטוגזית",
"hu": "portugál",
"id": "bahasa Portugis",
"id": "Portugis",
"it": "portoghese",
"ja": "ポルトガル語",
"nb_NO": "portugisisk",
@ -9253,7 +9255,7 @@
"en": "Rakhine",
"fr": "arakanais",
"gl": "Lingua arakanesa",
"id": "bahasa Rakhine",
"id": "Rakhine",
"ja": "ラカイン語",
"nl": "Arakanees",
"pl": "Język arakański",
@ -9501,7 +9503,7 @@
"gl": "Lingua arromanesa",
"he": "ארומנית",
"hu": "aromán",
"id": "Bahasa Arumania",
"id": "Arumania",
"it": "arumeno",
"ja": "アルーマニア語",
"nb_NO": "arumensk",
@ -9896,7 +9898,7 @@
"ca": "taixelhit",
"cs": "tašelhit",
"de": "Taschelhit",
"en": "Shilha",
"en": "Tachelhit",
"eo": "ŝelha lingvo",
"es": "chilha",
"fi": "Tašelhit",
@ -9996,7 +9998,7 @@
"pt": "Língua cingalesa",
"pt_BR": "Língua cingalesa",
"ru": "сингальский язык",
"sl": "sinhalščina",
"sl": "singalščina",
"sv": "singalesiska",
"zh_Hant": "僧伽羅語",
"_meta": {
@ -10456,7 +10458,7 @@
"gl": "Lingua albanesa",
"he": "אלבנית",
"hu": "albán",
"id": "Bahasa Albania",
"id": "Albania",
"it": "albanese",
"ja": "アルバニア語",
"nb_NO": "albansk",
@ -10699,7 +10701,7 @@
"gl": "lingua sueca",
"he": "שוודית",
"hu": "svéd",
"id": "bahasa Swedia",
"id": "Swedia",
"it": "svedese",
"ja": "スウェーデン語",
"nb_NO": "svensk",
@ -10798,7 +10800,7 @@
"gl": "Lingua silesiana",
"he": "שלזית",
"hu": "sziléziai",
"id": "bahasa Silesia",
"id": "Silesia",
"it": "slesiano",
"ja": "シレジア語",
"nb_NO": "schlesisk",
@ -10847,7 +10849,7 @@
"gl": "Lingua támil",
"he": "טמילית",
"hu": "tamil",
"id": "Bahasa Tamil",
"id": "Tamil",
"it": "tamil",
"ja": "タミル語",
"nb_NO": "tamilsk",
@ -11034,7 +11036,7 @@
"gl": "lingua tailandesa",
"he": "תאית",
"hu": "thai",
"id": "bahasa Thai",
"id": "Thai",
"it": "thailandese",
"ja": "タイ語",
"nb_NO": "thai",
@ -11104,7 +11106,7 @@
"gl": "Lingua turcomá",
"he": "טורקמנית",
"hu": "türkmén",
"id": "bahasa Turkmen",
"id": "Turkmen",
"it": "Turkmeno",
"ja": "トルクメン語",
"nb_NO": "turkmensk",
@ -11632,7 +11634,7 @@
"gl": "Lingua uigur",
"he": "אויגורית",
"hu": "ujgur",
"id": "bahasa Uyghur",
"id": "Uighur",
"it": "uiguro",
"ja": "ウイグル語",
"nb_NO": "uigurisk",
@ -11702,7 +11704,7 @@
"gl": "Lingua usbeka",
"he": "אוזבקית",
"hu": "üzbég",
"id": "bahasa Uzbek",
"id": "Uzbek",
"it": "uzbeco",
"ja": "ウズベク語",
"nb_NO": "usbekisk",
@ -12591,7 +12593,7 @@
"gl": "lingua chinesa",
"he": "סינית",
"hu": "kínai",
"id": "bahasa Tionghoa",
"id": "Tionghoa",
"it": "cinese",
"ja": "中国語",
"nb_NO": "kinesisk",
@ -12647,7 +12649,7 @@
]
}
},
"zh_Hans": {
"zh_Hant": {
"ca": "xinès simplificat",
"cs": "zjednodušená čínština",
"da": "forenklet kinesisk",
@ -12656,6 +12658,7 @@
"eo": "simpligita ĉina skribsistemo",
"es": "chino simplificado",
"eu": "Txinera sinplifikatua",
"fi": "perinteinen kiina",
"fr": "chinois simplifié",
"gl": "chinés simplificado",
"he": "סינית מפושטת",
@ -12678,36 +12681,6 @@
]
}
},
"zh_Hant": {
"ca": "xinès tradicional",
"cs": "čínština (tradiční)",
"da": "traditionel kinesisk",
"de": "traditionelles Chinesisch",
"en": "Traditional Chinese",
"eo": "ĉina lingvo de tradicia ortografio",
"es": "chino tradicional",
"eu": "Txinera tradizional",
"fi": "perinteinen kiina",
"fr": "chinois traditionnel",
"gl": "chinés tradicional",
"he": "סינית מסורתית",
"it": "cinese tradizionale",
"ja": "繁体字中国語",
"nb_NO": "tradisjonell kinesisk",
"nl": "traditioneel Chinees",
"pl": "język chiński tradycyjny",
"pt": "chinês tradicional",
"ru": "традиционный китайский",
"sl": "tradicionalna kitajščina",
"sv": "traditionell kinesiska",
"zh_Hans": "繁体中文",
"zh_Hant": "繁體中文",
"_meta": {
"dir": [
"left-to-right"
]
}
},
"zu": {
"ca": "zulu",
"cs": "zuluština",

View file

@ -1,15 +1,15 @@
{
"contributors": [
{
"commits": 308,
"commits": 310,
"contributor": "kjon"
},
{
"commits": 287,
"commits": 288,
"contributor": "Pieter Vander Vennet"
},
{
"commits": 176,
"commits": 178,
"contributor": "paunofu"
},
{
@ -32,6 +32,10 @@
"commits": 36,
"contributor": "Iago"
},
{
"commits": 32,
"contributor": "Jiří Podhorecký"
},
{
"commits": 32,
"contributor": "Lucas"
@ -40,10 +44,6 @@
"commits": 32,
"contributor": "Babos Gábor"
},
{
"commits": 31,
"contributor": "Jiří Podhorecký"
},
{
"commits": 31,
"contributor": "Supaplex"
@ -124,6 +124,10 @@
"commits": 10,
"contributor": "Irina"
},
{
"commits": 9,
"contributor": "Ettore Atalan"
},
{
"commits": 9,
"contributor": "deep map"
@ -144,10 +148,6 @@
"commits": 9,
"contributor": "Jacque Fresco"
},
{
"commits": 8,
"contributor": "Ettore Atalan"
},
{
"commits": 8,
"contributor": "Vinicius"
@ -444,6 +444,14 @@
"commits": 2,
"contributor": "Leo Alcaraz"
},
{
"commits": 1,
"contributor": "Kelson Vibber"
},
{
"commits": 1,
"contributor": "Juan"
},
{
"commits": 1,
"contributor": "Traladarer"

View file

@ -1,8 +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 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(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)