forked from MapComplete/MapComplete
Performance: sprinkle 'onDestroy' everywhere to cleanup old stores; cleanup 'Stores' utility class
This commit is contained in:
parent
66f093afd8
commit
81be4db044
79 changed files with 332 additions and 325 deletions
|
@ -47,7 +47,7 @@ export default class TileLocalStorage<T> {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
const src = <UIEventSource<T> & { flush: () => void }>(
|
const src = <UIEventSource<T> & { flush: () => void }>(
|
||||||
UIEventSource.FromPromise(this.GetIdb(tileIndex))
|
UIEventSource.fromPromise(this.GetIdb(tileIndex))
|
||||||
)
|
)
|
||||||
src.flush = () => this.SetIdb(tileIndex, src.data)
|
src.flush = () => this.SetIdb(tileIndex, src.data)
|
||||||
src.addCallbackD((data) => this.SetIdb(tileIndex, data))
|
src.addCallbackD((data) => this.SetIdb(tileIndex, data))
|
||||||
|
|
|
@ -21,7 +21,7 @@ export default class ChangeGeometryApplicator implements FeatureSource {
|
||||||
|
|
||||||
source.features.addCallbackAndRunD(() => this.update())
|
source.features.addCallbackAndRunD(() => this.update())
|
||||||
|
|
||||||
Stores.ListStabilized(changes.allChanges).addCallbackAndRunD(() => this.update())
|
Stores.listStabilized(changes.allChanges).addCallbackAndRunD(() => this.update())
|
||||||
}
|
}
|
||||||
|
|
||||||
private update() {
|
private update() {
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
|
||||||
public readonly allFavourites: Store<Feature[]>
|
public readonly allFavourites: Store<Feature[]>
|
||||||
|
|
||||||
constructor(state: WithChangesState) {
|
constructor(state: WithChangesState) {
|
||||||
const features: Store<Feature[]> = Stores.ListStabilized(
|
const features: Store<Feature[]> = Stores.listStabilized(
|
||||||
state.osmConnection.preferencesHandler.allPreferences.map((prefs) => {
|
state.osmConnection.preferencesHandler.allPreferences.map((prefs) => {
|
||||||
const feats: Feature[] = []
|
const feats: Feature[] = []
|
||||||
const allIds = new Set<string>()
|
const allIds = new Set<string>()
|
||||||
|
@ -60,7 +60,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
|
||||||
this.allFavourites = features
|
this.allFavourites = features
|
||||||
|
|
||||||
this._osmConnection = state.osmConnection
|
this._osmConnection = state.osmConnection
|
||||||
this._detectedIds = Stores.ListStabilized(
|
this._detectedIds = Stores.listStabilized(
|
||||||
features.map((feats) => feats.map((f) => f.properties.id))
|
features.map((feats) => feats.map((f) => f.properties.id))
|
||||||
)
|
)
|
||||||
const allFeatures = state.indexedFeatures
|
const allFeatures = state.indexedFeatures
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default class NearbyFeatureSource implements FeatureSource {
|
||||||
this._currentZoom = options?.currentZoom.stabilized(500)
|
this._currentZoom = options?.currentZoom.stabilized(500)
|
||||||
this._bounds = options?.bounds
|
this._bounds = options?.bounds
|
||||||
|
|
||||||
this.features = Stores.ListStabilized(this._result)
|
this.features = Stores.listStabilized(this._result)
|
||||||
|
|
||||||
sources.forEach((source, layer) => {
|
sources.forEach((source, layer) => {
|
||||||
this.registerSource(source, layer)
|
this.registerSource(source, layer)
|
||||||
|
|
|
@ -44,7 +44,7 @@ export default class DynamicTileSource<
|
||||||
this.zDiff = options?.zDiff ?? 0
|
this.zDiff = options?.zDiff ?? 0
|
||||||
this.bounds = mapProperties.bounds
|
this.bounds = mapProperties.bounds
|
||||||
|
|
||||||
const neededTiles: Store<number[]> = Stores.ListStabilized(
|
const neededTiles: Store<number[]> = Stores.listStabilized(
|
||||||
mapProperties.bounds
|
mapProperties.bounds
|
||||||
.mapD(() => {
|
.mapD(() => {
|
||||||
if (options?.isActive && !options?.isActive.data) {
|
if (options?.isActive && !options?.isActive.data) {
|
||||||
|
|
|
@ -123,7 +123,7 @@ export class SummaryTileSource extends DynamicTileSource {
|
||||||
const [z, x, y] = Tiles.tile_from_index(tileIndex)
|
const [z, x, y] = Tiles.tile_from_index(tileIndex)
|
||||||
let coordinates = Tiles.centerPointOf(z, x, y)
|
let coordinates = Tiles.centerPointOf(z, x, y)
|
||||||
const url = `${cacheserver}/summary/${layersSummed}/${z}/${x}/${y}.json`
|
const url = `${cacheserver}/summary/${layersSummed}/${z}/${x}/${y}.json`
|
||||||
const count = UIEventSource.FromPromiseWithErr(Utils.downloadJson(url))
|
const count = UIEventSource.fromPromiseWithErr(Utils.downloadJson(url))
|
||||||
return count.mapD((count) => {
|
return count.mapD((count) => {
|
||||||
if (count["error"] !== undefined) {
|
if (count["error"] !== undefined) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|
|
@ -148,7 +148,7 @@ export default class AllImageProviders {
|
||||||
const singleSource = tags.bindD((tags) => imageProvider.getRelevantUrls(tags, prefixes))
|
const singleSource = tags.bindD((tags) => imageProvider.getRelevantUrls(tags, prefixes))
|
||||||
allSources.push(singleSource)
|
allSources.push(singleSource)
|
||||||
}
|
}
|
||||||
const source = Stores.fromStoresArray(allSources).map((result) => {
|
const source = Stores.concat(allSources).map((result) => {
|
||||||
const all = Utils.concat(result)
|
const all = Utils.concat(result)
|
||||||
return Utils.DedupOnId(all, (i) => [i?.id, i?.url, i?.alt_id])
|
return Utils.DedupOnId(all, (i) => [i?.id, i?.url, i?.alt_id])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Store, Stores } from "../UIEventSource"
|
import { Store, UIEventSource } from "../UIEventSource"
|
||||||
import { LicenseInfo } from "./LicenseInfo"
|
import { LicenseInfo } from "./LicenseInfo"
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import { Feature, Point } from "geojson"
|
import { Feature, Point } from "geojson"
|
||||||
|
@ -114,7 +114,7 @@ export default abstract class ImageProvider {
|
||||||
tags: Record<string, string>,
|
tags: Record<string, string>,
|
||||||
prefixes: string[]
|
prefixes: string[]
|
||||||
): Store<ProvidedImage[]> {
|
): Store<ProvidedImage[]> {
|
||||||
return Stores.FromPromise(this.getRelevantUrlsFor(tags, prefixes))
|
return UIEventSource.fromPromise(this.getRelevantUrlsFor(tags, prefixes))
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract ExtractUrls(
|
public abstract ExtractUrls(
|
||||||
|
|
|
@ -81,7 +81,7 @@ export class ImageUploadManager {
|
||||||
this._changes = changes
|
this._changes = changes
|
||||||
this._gps = gpsLocation
|
this._gps = gpsLocation
|
||||||
this._reportError = reportError
|
this._reportError = reportError
|
||||||
Stores.Chronic(5 * 60000).addCallback(() => {
|
Stores.chronic(5 * 60000).addCallback(() => {
|
||||||
// If images failed to upload: attempt to reupload
|
// If images failed to upload: attempt to reupload
|
||||||
this.uploadQueue()
|
this.uploadQueue()
|
||||||
})
|
})
|
||||||
|
|
|
@ -290,7 +290,7 @@ export class Mapillary extends ImageProvider {
|
||||||
*/
|
*/
|
||||||
public static isInStrictMode(): Store<boolean> {
|
public static isInStrictMode(): Store<boolean> {
|
||||||
if (this._isInStrictMode === undefined) {
|
if (this._isInStrictMode === undefined) {
|
||||||
this._isInStrictMode = UIEventSource.FromPromise(this.checkStrictMode())
|
this._isInStrictMode = UIEventSource.fromPromise(this.checkStrictMode())
|
||||||
}
|
}
|
||||||
return this._isInStrictMode
|
return this._isInStrictMode
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ export default class PanoramaxImageProvider extends ImageProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
getRelevantUrls(tags: Record<string, string>, prefixes: string[]): Store<ProvidedImage[]> {
|
getRelevantUrls(tags: Record<string, string>, prefixes: string[]): Store<ProvidedImage[]> {
|
||||||
const source = UIEventSource.FromPromise(super.getRelevantUrlsFor(tags, prefixes))
|
const source = UIEventSource.fromPromise(super.getRelevantUrlsFor(tags, prefixes))
|
||||||
function hasLoading(data: ProvidedImage[]) {
|
function hasLoading(data: ProvidedImage[]) {
|
||||||
if (data === undefined) {
|
if (data === undefined) {
|
||||||
return true
|
return true
|
||||||
|
@ -182,14 +182,14 @@ export default class PanoramaxImageProvider extends ImageProvider {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Stores.Chronic(5000, () => hasLoading(source.data)).addCallback(() => {
|
Stores.chronic(5000, () => hasLoading(source.data)).addCallback(() => {
|
||||||
super.getRelevantUrlsFor(tags, prefixes).then((data) => {
|
super.getRelevantUrlsFor(tags, prefixes).then((data) => {
|
||||||
source.set(data)
|
source.set(data)
|
||||||
return !hasLoading(data)
|
return !hasLoading(data)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return Stores.ListStabilized(source)
|
return Stores.listStabilized(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async DownloadAttribution(providedImage: { id: string }): Promise<LicenseInfo> {
|
public async DownloadAttribution(providedImage: { id: string }): Promise<LicenseInfo> {
|
||||||
|
|
|
@ -682,7 +682,7 @@ export class OsmConnection {
|
||||||
if (this.isChecking) {
|
if (this.isChecking) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Stores.Chronic(3 * 1000).addCallback(() => {
|
Stores.chronic(3 * 1000).addCallback(() => {
|
||||||
if (!(this.apiIsOnline.data === "unreachable" || this.apiIsOnline.data === "offline")) {
|
if (!(this.apiIsOnline.data === "unreachable" || this.apiIsOnline.data === "offline")) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -699,7 +699,7 @@ export class OsmConnection {
|
||||||
if (!this._doCheckRegularly) {
|
if (!this._doCheckRegularly) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Stores.Chronic(60 * 5 * 1000).addCallback(() => {
|
Stores.chronic(60 * 5 * 1000).addCallback(() => {
|
||||||
// Check for new messages every 5 minutes
|
// Check for new messages every 5 minutes
|
||||||
if (this.isLoggedIn.data) {
|
if (this.isLoggedIn.data) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -40,6 +40,6 @@ export class NominatimGeocoding implements GeocodingProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
suggest(query: string, options?: GeocodingOptions): Store<{ success: GeocodeResult[] } | { error: any }> {
|
suggest(query: string, options?: GeocodingOptions): Store<{ success: GeocodeResult[] } | { error: any }> {
|
||||||
return UIEventSource.FromPromiseWithErr(this.search(query, options))
|
return UIEventSource.fromPromiseWithErr(this.search(query, options))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,6 @@ export default class OpenStreetMapIdSearch implements GeocodingProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
suggest(query: string, options?: GeocodingOptions): Store<{success: GeocodeResult[]} | {error: any}> {
|
suggest(query: string, options?: GeocodingOptions): Store<{success: GeocodeResult[]} | {error: any}> {
|
||||||
return UIEventSource.FromPromiseWithErr(this.search(query, options))
|
return UIEventSource.fromPromiseWithErr(this.search(query, options))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Utils } from "../../Utils"
|
||||||
import { Feature, FeatureCollection } from "geojson"
|
import { Feature, FeatureCollection } from "geojson"
|
||||||
import Locale from "../../UI/i18n/Locale"
|
import Locale from "../../UI/i18n/Locale"
|
||||||
import { GeoOperations } from "../GeoOperations"
|
import { GeoOperations } from "../GeoOperations"
|
||||||
import { Store, Stores } from "../UIEventSource"
|
import { Store, UIEventSource } from "../UIEventSource"
|
||||||
|
|
||||||
export default class PhotonSearch implements GeocodingProvider, ReverseGeocodingProvider {
|
export default class PhotonSearch implements GeocodingProvider, ReverseGeocodingProvider {
|
||||||
private readonly _endpoint: string
|
private readonly _endpoint: string
|
||||||
|
@ -74,7 +74,7 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding
|
||||||
}
|
}
|
||||||
|
|
||||||
suggest(query: string, options?: GeocodingOptions): Store<{success: GeocodeResult[]} | {error: any}> {
|
suggest(query: string, options?: GeocodingOptions): Store<{success: GeocodeResult[]} | {error: any}> {
|
||||||
return Stores.FromPromiseWithErr(this.search(query, options))
|
return UIEventSource.fromPromiseWithErr(this.search(query, options))
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildDescription(entry: Feature) {
|
private buildDescription(entry: Feature) {
|
||||||
|
|
|
@ -97,7 +97,7 @@ export class ThemeSearchIndex {
|
||||||
theme: ThemeConfig
|
theme: ThemeConfig
|
||||||
}): Store<ThemeSearchIndex> {
|
}): Store<ThemeSearchIndex> {
|
||||||
const layersToIgnore = state.theme.layers.filter((l) => l.isNormal()).map((l) => l.id)
|
const layersToIgnore = state.theme.layers.filter((l) => l.isNormal()).map((l) => l.id)
|
||||||
const knownHidden: Store<string[]> = Stores.ListStabilized(
|
const knownHidden: Store<string[]> = Stores.listStabilized(
|
||||||
UserRelatedState.initDiscoveredHiddenThemes(state.osmConnection)
|
UserRelatedState.initDiscoveredHiddenThemes(state.osmConnection)
|
||||||
.stabilized(1000)
|
.stabilized(1000)
|
||||||
.map((list) => Utils.Dedup(list))
|
.map((list) => Utils.Dedup(list))
|
||||||
|
|
|
@ -53,8 +53,13 @@ class RoundRobinStore<T> {
|
||||||
* @param t
|
* @param t
|
||||||
*/
|
*/
|
||||||
public add(t: T) {
|
public add(t: T) {
|
||||||
const i = this._index.data
|
const i = this._index.data ?? 0
|
||||||
this._index.set((i + 1) % this._maxCount)
|
if(isNaN(Number(i))){
|
||||||
|
this._index.set(0)
|
||||||
|
this.add(t)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._index.set((Math.max(i,0) + 1) % this._maxCount)
|
||||||
this._store.data[i] = t
|
this._store.data[i] = t
|
||||||
this._store.ping()
|
this._store.ping()
|
||||||
}
|
}
|
||||||
|
@ -84,19 +89,17 @@ export class OptionallySyncedHistory<T extends object | string> {
|
||||||
defaultValue: "sync",
|
defaultValue: "sync",
|
||||||
})
|
})
|
||||||
|
|
||||||
this.syncedBackingStore = Stores.fromArray(
|
this.syncedBackingStore = UIEventSource.concat(Utils.TimesT(maxHistory, (i) => {
|
||||||
Utils.TimesT(maxHistory, (i) => {
|
const pref = osmconnection.getPreference(key + "-hist-" + i + "-")
|
||||||
const pref = osmconnection.getPreference(key + "-hist-" + i + "-")
|
return UIEventSource.asObject<T>(pref, undefined)
|
||||||
return UIEventSource.asObject<T>(pref, undefined)
|
}))
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const ringIndex = UIEventSource.asInt(
|
const ringIndex = UIEventSource.asInt(
|
||||||
osmconnection.getPreference(key + "-hist-round-robin", {
|
osmconnection.getPreference(key + "-hist-round-robin", {
|
||||||
defaultValue: "0",
|
defaultValue: "0",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
this.syncedOrdered = new RoundRobinStore<T>(this.syncedBackingStore, ringIndex, 10)
|
this.syncedOrdered = new RoundRobinStore<T>(this.syncedBackingStore, ringIndex, maxHistory)
|
||||||
const local = (this.local = LocalStorageSource.getParsed<T[]>(key + "-history", []))
|
const local = (this.local = LocalStorageSource.getParsed<T[]>(key + "-history", []))
|
||||||
const thisSession = (this.thisSession = new UIEventSource<T[]>(
|
const thisSession = (this.thisSession = new UIEventSource<T[]>(
|
||||||
[],
|
[],
|
||||||
|
@ -170,10 +173,10 @@ export class OptionallySyncedHistory<T extends object | string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the value when the user is actually logged in
|
* Sets the value when the user is actually logged in
|
||||||
* @param t
|
* @param t
|
||||||
*/
|
*/
|
||||||
public addDefferred(t: T) {
|
public addDeferred(t: T) {
|
||||||
if (t === undefined) {
|
if (t === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -217,7 +220,7 @@ export default class UserRelatedState {
|
||||||
|
|
||||||
public readonly fixateNorth: UIEventSource<undefined | "yes">
|
public readonly fixateNorth: UIEventSource<undefined | "yes">
|
||||||
public readonly a11y: UIEventSource<undefined | "always" | "never" | "default">
|
public readonly a11y: UIEventSource<undefined | "always" | "never" | "default">
|
||||||
public readonly homeLocation: FeatureSource
|
public readonly homeLocation: FeatureSource<Feature>
|
||||||
public readonly morePrivacy: UIEventSource<undefined | "yes" | "no">
|
public readonly morePrivacy: UIEventSource<undefined | "yes" | "no">
|
||||||
/**
|
/**
|
||||||
* The language as saved into the preferences of the user, if logged in.
|
* The language as saved into the preferences of the user, if logged in.
|
||||||
|
@ -335,7 +338,7 @@ export default class UserRelatedState {
|
||||||
(a, b) => a.osm_id === b.osm_id && a.osm_type === b.osm_type
|
(a, b) => a.osm_id === b.osm_id && a.osm_type === b.osm_type
|
||||||
)
|
)
|
||||||
this.syncLanguage()
|
this.syncLanguage()
|
||||||
this.recentlyVisitedThemes.addDefferred(layout?.id)
|
this.recentlyVisitedThemes.addDeferred(layout?.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private syncLanguage() {
|
private syncLanguage() {
|
||||||
|
@ -461,9 +464,9 @@ export default class UserRelatedState {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private initHomeLocation(): FeatureSource {
|
private initHomeLocation(): FeatureSource<Feature> {
|
||||||
const empty = []
|
const empty = []
|
||||||
const feature: Store<Feature[]> = Stores.ListStabilized(
|
const feature: Store<Feature[]> = Stores.listStabilized(
|
||||||
this.osmConnection.userDetails.map((userDetails) => {
|
this.osmConnection.userDetails.map((userDetails) => {
|
||||||
if (userDetails === undefined) {
|
if (userDetails === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Readable, Subscriber, Unsubscriber, Updater, Writable } from "svelte/st
|
||||||
* Various static utils
|
* Various static utils
|
||||||
*/
|
*/
|
||||||
export class Stores {
|
export class Stores {
|
||||||
public static Chronic(millis: number, asLong: () => boolean = undefined): Store<Date> {
|
public static chronic(millis: number, asLong: () => boolean = undefined): Store<Date> {
|
||||||
const source = new UIEventSource<Date>(undefined)
|
const source = new UIEventSource<Date>(undefined)
|
||||||
|
|
||||||
function run() {
|
function run() {
|
||||||
|
@ -23,32 +23,12 @@ export class Stores {
|
||||||
return source
|
return source
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FromPromiseWithErr<T>(
|
public static concat<T>(stores: ReadonlyArray<Store<T | undefined>>): Store<(T | undefined)[]> ;
|
||||||
promise: Promise<T>
|
public static concat<T>(stores: ReadonlyArray<Store<T>>): Store<T[]> ;
|
||||||
): Store<{ success: T } | { error: any }> {
|
public static concat<T>(stores: ReadonlyArray<Store<T | undefined>>): Store<(T | undefined)[]> {
|
||||||
return UIEventSource.FromPromiseWithErr(promise)
|
const newStore = new UIEventSource<(T | undefined)[]>(
|
||||||
}
|
stores.map(store => store?.data),
|
||||||
|
)
|
||||||
/**
|
|
||||||
* Converts a promise into a UIVentsource, sets the UIEVentSource when the result is calculated.
|
|
||||||
* If the promise fails, the value will stay undefined
|
|
||||||
* @param promise
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
public static FromPromise<T>(promise: Promise<T>): Store<T | undefined> {
|
|
||||||
const src = new UIEventSource<T>(undefined)
|
|
||||||
promise
|
|
||||||
?.catch((err): undefined => {
|
|
||||||
console.warn("Promise failed:", err)
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
?.then((d) => src.setData(d))
|
|
||||||
return src
|
|
||||||
}
|
|
||||||
public static concat<T>(stores: Store<T | undefined>[]): Store<(T | undefined)[]> ;
|
|
||||||
public static concat<T>(stores: Store<T>[]): Store<T[]> ;
|
|
||||||
public static concat<T>(stores: Store<T | undefined>[]): Store<(T | undefined)[]> {
|
|
||||||
const newStore = new UIEventSource<(T | undefined)[]>([])
|
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
if (newStore._callbacks.isDestroyed) {
|
if (newStore._callbacks.isDestroyed) {
|
||||||
|
@ -64,7 +44,6 @@ export class Stores {
|
||||||
for (const store of stores) {
|
for (const store of stores) {
|
||||||
store.addCallback(() => update())
|
store.addCallback(() => update())
|
||||||
}
|
}
|
||||||
update()
|
|
||||||
return newStore
|
return newStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +61,7 @@ export class Stores {
|
||||||
* @param src
|
* @param src
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
public static ListStabilized<T>(src: Store<T[]>): Store<T[]> {
|
public static listStabilized<T>(src: Store<T[]>): Store<T[]> {
|
||||||
const stable = new UIEventSource<T[]>(undefined)
|
const stable = new UIEventSource<T[]>(undefined)
|
||||||
src.addCallbackAndRun((list) => {
|
src.addCallbackAndRun((list) => {
|
||||||
if (list === undefined) {
|
if (list === undefined) {
|
||||||
|
@ -110,33 +89,6 @@ export class Stores {
|
||||||
})
|
})
|
||||||
return newStore
|
return newStore
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromArray<T>(sources: ReadonlyArray<UIEventSource<T>>): UIEventSource<T[]> {
|
|
||||||
const src = new UIEventSource<T[]>(sources.map((s) => s.data))
|
|
||||||
for (let i = 0; i < sources.length; i++) {
|
|
||||||
sources[i].addCallback((content) => {
|
|
||||||
src.data[i] = content
|
|
||||||
src.ping()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
src.addCallbackD((contents) => {
|
|
||||||
for (let i = 0; i < contents.length; i++) {
|
|
||||||
sources[i]?.setData(contents[i])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return src
|
|
||||||
}
|
|
||||||
|
|
||||||
public static fromStoresArray<T>(sources: ReadonlyArray<Store<T>>): Store<T[]> {
|
|
||||||
const src = new UIEventSource<T[]>(sources.map((s) => s.data))
|
|
||||||
for (let i = 0; i < sources.length; i++) {
|
|
||||||
sources[i].addCallback((content) => {
|
|
||||||
src.data[i] = content
|
|
||||||
src.ping()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return src
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Store<T> implements Readable<T> {
|
export abstract class Store<T> implements Readable<T> {
|
||||||
|
@ -162,17 +114,26 @@ export abstract class Store<T> implements Readable<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract map<J>(f: (t: T) => J): Store<J>
|
abstract map<J>(f: (t: T) => J): Store<J>
|
||||||
abstract map<J>(f: (t: T) => J, extraStoresToWatch: Store<unknown>[]): Store<J>
|
abstract map<J>(f: (t: T) => J, callbackDestroyFunction: (f: () => void) => void): Store<J>
|
||||||
abstract map<J>(
|
abstract map<J>(
|
||||||
f: (t: T) => J,
|
f: (t: T) => J,
|
||||||
extraStoresToWatch: Store<unknown>[],
|
extraStoresToWatch: Store<unknown>[],
|
||||||
callbackDestroyFunction: (f: () => void) => void
|
callbackDestroyFunction?: (f: () => void) => void,
|
||||||
): Store<J>
|
): Store<J>
|
||||||
|
|
||||||
public mapD<J>(
|
public mapD<J>(
|
||||||
f: (t: Exclude<T, undefined | null>) => J,
|
f: (t: Exclude<T, undefined | null>) => J,
|
||||||
extraStoresToWatch?: Store<unknown>[],
|
extraStoresToWatch?: Store<unknown>[],
|
||||||
callbackDestroyFunction?: (f: () => void) => void
|
callbackDestroyFunction?: (f: () => void) => void,
|
||||||
|
): Store<J>
|
||||||
|
public mapD<J>(
|
||||||
|
f: (t: Exclude<T, undefined | null>) => J,
|
||||||
|
callbackDestroyFunction?: (f: () => void) => void,
|
||||||
|
): Store<J>
|
||||||
|
public mapD<J>(
|
||||||
|
f: (t: Exclude<T, undefined | null>) => J,
|
||||||
|
extraStoresToWatch?: Store<unknown>[] | ((f: () => void) => void),
|
||||||
|
callbackDestroyFunction?: (f: () => void) => void,
|
||||||
): Store<J> {
|
): Store<J> {
|
||||||
return this.map(
|
return this.map(
|
||||||
(t) => {
|
(t) => {
|
||||||
|
@ -184,8 +145,8 @@ export abstract class Store<T> implements Readable<T> {
|
||||||
}
|
}
|
||||||
return f(<Exclude<T, undefined | null>>t)
|
return f(<Exclude<T, undefined | null>>t)
|
||||||
},
|
},
|
||||||
extraStoresToWatch,
|
typeof extraStoresToWatch === "function" ? [] : extraStoresToWatch,
|
||||||
callbackDestroyFunction
|
callbackDestroyFunction ?? (typeof extraStoresToWatch === "function" ? extraStoresToWatch : undefined),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,22 +173,6 @@ export abstract class Store<T> implements Readable<T> {
|
||||||
*/
|
*/
|
||||||
abstract addCallbackAndRun(callback: (data: T) => void): () => void
|
abstract addCallbackAndRun(callback: (data: T) => void): () => void
|
||||||
|
|
||||||
public withEqualityStabilized(
|
|
||||||
comparator: (t: T | undefined, t1: T | undefined) => boolean
|
|
||||||
): Store<T> {
|
|
||||||
let oldValue = undefined
|
|
||||||
return this.map((v) => {
|
|
||||||
if (v == oldValue) {
|
|
||||||
return oldValue
|
|
||||||
}
|
|
||||||
if (comparator(oldValue, v)) {
|
|
||||||
return oldValue
|
|
||||||
}
|
|
||||||
oldValue = v
|
|
||||||
return v
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monadic bind function
|
* Monadic bind function
|
||||||
*
|
*
|
||||||
|
@ -274,8 +219,9 @@ export abstract class Store<T> implements Readable<T> {
|
||||||
* src.setData(0)
|
* src.setData(0)
|
||||||
* lastValue // => "def"
|
* lastValue // => "def"
|
||||||
*/
|
*/
|
||||||
public bind<X>(f: (t: T) => Store<X>, extraSources: Store<unknown>[] = []): Store<X> {
|
public bind<X>(f: (t: T) => Store<X>, extraSources?: Store<unknown>[] | ((f: () => void) => void), onDestroy?: (f : () => void) => void): Store<X> {
|
||||||
const mapped = this.map(f, extraSources)
|
const mapped = this.map(f, typeof extraSources === "function" ? undefined : extraSources,
|
||||||
|
onDestroy ?? (typeof extraSources === "function" ? extraSources : undefined))
|
||||||
const sink = new UIEventSource<X>(undefined)
|
const sink = new UIEventSource<X>(undefined)
|
||||||
const seenEventSources = new Set<Store<X>>()
|
const seenEventSources = new Set<Store<X>>()
|
||||||
mapped.addCallbackAndRun((newEventSource) => {
|
mapped.addCallbackAndRun((newEventSource) => {
|
||||||
|
@ -308,7 +254,8 @@ export abstract class Store<T> implements Readable<T> {
|
||||||
|
|
||||||
public bindD<X>(
|
public bindD<X>(
|
||||||
f: (t: Exclude<T, undefined | null>) => Store<X>,
|
f: (t: Exclude<T, undefined | null>) => Store<X>,
|
||||||
extraSources: Store<unknown>[] = []
|
extraSources?: Store<unknown>[],
|
||||||
|
onDestroy?: ((f: () => void) => void)
|
||||||
): Store<X> {
|
): Store<X> {
|
||||||
return this.bind((t) => {
|
return this.bind((t) => {
|
||||||
if (t === null) {
|
if (t === null) {
|
||||||
|
@ -318,7 +265,7 @@ export abstract class Store<T> implements Readable<T> {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return f(<Exclude<T, undefined | null>>t)
|
return f(<Exclude<T, undefined | null>>t)
|
||||||
}, extraSources)
|
}, extraSources, onDestroy)
|
||||||
}
|
}
|
||||||
|
|
||||||
public stabilized(millisToStabilize): Store<T> {
|
public stabilized(millisToStabilize): Store<T> {
|
||||||
|
@ -402,7 +349,8 @@ export class ImmutableStore<T> extends Store<T> {
|
||||||
this.data = data
|
this.data = data
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly pass: () => void = () => {}
|
private static readonly pass: () => void = () => {
|
||||||
|
}
|
||||||
|
|
||||||
addCallback(_: (data: T) => void): () => void {
|
addCallback(_: (data: T) => void): () => void {
|
||||||
// pass: data will never change
|
// pass: data will never change
|
||||||
|
@ -430,8 +378,8 @@ export class ImmutableStore<T> extends Store<T> {
|
||||||
|
|
||||||
map<J>(
|
map<J>(
|
||||||
f: (t: T) => J,
|
f: (t: T) => J,
|
||||||
extraStores: Store<any>[] = undefined,
|
extraStores: Store<any>[] | ((f: () => void) => void)= undefined,
|
||||||
ondestroyCallback?: (f: () => void) => void
|
ondestroyCallback?: (f: () => void) => void,
|
||||||
): ImmutableStore<J> {
|
): ImmutableStore<J> {
|
||||||
if (extraStores?.length > 0) {
|
if (extraStores?.length > 0) {
|
||||||
return new MappedStore(this, f, extraStores, undefined, f(this.data), ondestroyCallback)
|
return new MappedStore(this, f, extraStores, undefined, f(this.data), ondestroyCallback)
|
||||||
|
@ -482,7 +430,6 @@ class ListenerTracker<T> {
|
||||||
public ping(data: T): number {
|
public ping(data: T): number {
|
||||||
this.pingCount++
|
this.pingCount++
|
||||||
let toDelete = undefined
|
let toDelete = undefined
|
||||||
const startTime = new Date().getTime() / 1000
|
|
||||||
for (const callback of this._callbacks) {
|
for (const callback of this._callbacks) {
|
||||||
try {
|
try {
|
||||||
if (callback(data) === true) {
|
if (callback(data) === true) {
|
||||||
|
@ -498,12 +445,6 @@ class ListenerTracker<T> {
|
||||||
console.error("Got an error while running a callback:", e)
|
console.error("Got an error while running a callback:", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const 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) {
|
if (toDelete !== undefined) {
|
||||||
for (const toDeleteElement of toDelete) {
|
for (const toDeleteElement of toDelete) {
|
||||||
this._callbacks.splice(this._callbacks.indexOf(toDeleteElement), 1)
|
this._callbacks.splice(this._callbacks.indexOf(toDeleteElement), 1)
|
||||||
|
@ -529,6 +470,11 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
private static readonly pass: () => void
|
private static readonly pass: () => void
|
||||||
private readonly _upstream: Store<TIn>
|
private readonly _upstream: Store<TIn>
|
||||||
private readonly _upstreamCallbackHandler: ListenerTracker<TIn> | undefined
|
private readonly _upstreamCallbackHandler: ListenerTracker<TIn> | undefined
|
||||||
|
/**
|
||||||
|
* THe last 'pingcount' (aka 'tick') that the upstream did.
|
||||||
|
* If we get a request for the data, we check if it is still up to date with this
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
private _upstreamPingCount: number = -1
|
private _upstreamPingCount: number = -1
|
||||||
private _unregisterFromUpstream: () => void
|
private _unregisterFromUpstream: () => void
|
||||||
private readonly _f: (t: TIn) => T
|
private readonly _f: (t: TIn) => T
|
||||||
|
@ -540,10 +486,10 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
constructor(
|
constructor(
|
||||||
upstream: Store<TIn>,
|
upstream: Store<TIn>,
|
||||||
f: (t: TIn) => T,
|
f: (t: TIn) => T,
|
||||||
extraStores: Store<unknown>[],
|
extraStores: Store<unknown>[] | ((t: () => void) => void),
|
||||||
upstreamListenerHandler: ListenerTracker<TIn> | undefined,
|
upstreamListenerHandler: ListenerTracker<TIn> | undefined,
|
||||||
initialState: T,
|
initialState: T,
|
||||||
onDestroy?: (f: () => void) => void
|
onDestroy?: (f: () => void) => void,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this._upstream = upstream
|
this._upstream = upstream
|
||||||
|
@ -551,7 +497,11 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
this._f = f
|
this._f = f
|
||||||
this._data = initialState
|
this._data = initialState
|
||||||
this._upstreamPingCount = upstreamListenerHandler?.pingCount
|
this._upstreamPingCount = upstreamListenerHandler?.pingCount
|
||||||
this._extraStores = extraStores
|
if (typeof extraStores === "function") {
|
||||||
|
onDestroy ??= extraStores
|
||||||
|
} else {
|
||||||
|
this._extraStores = extraStores
|
||||||
|
}
|
||||||
this.registerCallbacksToUpstream()
|
this.registerCallbacksToUpstream()
|
||||||
if (onDestroy !== undefined) {
|
if (onDestroy !== undefined) {
|
||||||
onDestroy(() => this.unregisterFromUpstream())
|
onDestroy(() => this.unregisterFromUpstream())
|
||||||
|
@ -572,7 +522,7 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
if (!this._callbacksAreRegistered) {
|
if (!this._callbacksAreRegistered) {
|
||||||
// Callbacks are not registered, so we haven't been listening for updates from the upstream which might have changed
|
// 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) {
|
if (this._upstreamCallbackHandler?.pingCount != this._upstreamPingCount) {
|
||||||
// Upstream has pinged - let's update our data first
|
// Upstream has pinged at least once - 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
|
||||||
|
@ -582,30 +532,34 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
|
|
||||||
map<J>(
|
map<J>(
|
||||||
f: (t: T) => J,
|
f: (t: T) => J,
|
||||||
extraStores: Store<unknown>[] = undefined,
|
extraStores: Store<unknown>[] | ((f: () => void) => void) = undefined,
|
||||||
ondestroyCallback?: (f: () => void) => void
|
ondestroyCallback?: (f: () => void) => void,
|
||||||
): Store<J> {
|
): Store<J> {
|
||||||
let stores: Store<unknown>[] = undefined
|
let stores: Store<unknown>[] = undefined
|
||||||
if (extraStores?.length > 0 || this._extraStores?.length > 0) {
|
if (typeof extraStores !== "function") {
|
||||||
stores = []
|
|
||||||
}
|
if (extraStores?.length > 0 || this._extraStores?.length > 0) {
|
||||||
if (extraStores?.length > 0) {
|
stores = []
|
||||||
stores?.push(...extraStores)
|
}
|
||||||
}
|
if (extraStores?.length > 0) {
|
||||||
if (this._extraStores?.length > 0) {
|
stores?.push(...extraStores)
|
||||||
this._extraStores?.forEach((store) => {
|
}
|
||||||
if (stores.indexOf(store) < 0) {
|
if (this._extraStores?.length > 0) {
|
||||||
stores.push(store)
|
this._extraStores?.forEach((store) => {
|
||||||
}
|
if (stores.indexOf(store) < 0) {
|
||||||
})
|
stores.push(store)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MappedStore(
|
return new MappedStore(
|
||||||
this,
|
this,
|
||||||
f, // we could fuse the functions here (e.g. data => f(this._f(data), but this might result in _f being calculated multiple times, breaking things
|
f, // we could fuse the functions here (e.g. data => f(this._f(data), but this might result in _f being calculated multiple times, breaking things
|
||||||
stores,
|
stores,
|
||||||
this._callbacks,
|
this._callbacks,
|
||||||
f(this.data),
|
f(this.data),
|
||||||
ondestroyCallback
|
ondestroyCallback,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,7 +605,12 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private unregisterFromUpstream() {
|
private unregisterFromUpstream() {
|
||||||
|
if (!this._callbacksAreRegistered) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._upstreamPingCount = this._upstreamCallbackHandler.pingCount
|
||||||
this._callbacksAreRegistered = false
|
this._callbacksAreRegistered = false
|
||||||
|
console.log("Unregistering from upstream", this._upstream)
|
||||||
this._unregisterFromUpstream()
|
this._unregisterFromUpstream()
|
||||||
this._unregisterFromExtraStores?.forEach((unr) => unr())
|
this._unregisterFromExtraStores?.forEach((unr) => unr())
|
||||||
}
|
}
|
||||||
|
@ -659,7 +618,7 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
private registerCallbacksToUpstream() {
|
private registerCallbacksToUpstream() {
|
||||||
this._unregisterFromUpstream = this._upstream.addCallback(() => this.update())
|
this._unregisterFromUpstream = this._upstream.addCallback(() => this.update())
|
||||||
this._unregisterFromExtraStores = this._extraStores?.map((store) =>
|
this._unregisterFromExtraStores = this._extraStores?.map((store) =>
|
||||||
store?.addCallback(() => this.update())
|
store?.addCallback(() => this.update()),
|
||||||
)
|
)
|
||||||
this._callbacksAreRegistered = true
|
this._callbacksAreRegistered = true
|
||||||
}
|
}
|
||||||
|
@ -680,7 +639,8 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
private static readonly pass: () => void = () => {}
|
private static readonly pass: () => void = () => {
|
||||||
|
}
|
||||||
public data: T
|
public data: T
|
||||||
_callbacks: ListenerTracker<T> = new ListenerTracker<T>()
|
_callbacks: ListenerTracker<T> = new ListenerTracker<T>()
|
||||||
|
|
||||||
|
@ -695,7 +655,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
|
|
||||||
public static flatten<X>(
|
public static flatten<X>(
|
||||||
source: Store<Store<X>>,
|
source: Store<Store<X>>,
|
||||||
possibleSources?: Store<object>[]
|
possibleSources?: Store<object>[],
|
||||||
): UIEventSource<X> {
|
): UIEventSource<X> {
|
||||||
const sink = new UIEventSource<X>(source.data?.data)
|
const sink = new UIEventSource<X>(source.data?.data)
|
||||||
|
|
||||||
|
@ -722,9 +682,9 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
* Converts a promise into a UIventsource, sets the UIeventSource when the result is calculated.
|
* Converts a promise into a UIventsource, sets the UIeventSource when the result is calculated.
|
||||||
* If the promise fails, the value will stay undefined, but 'onError' will be called
|
* If the promise fails, the value will stay undefined, but 'onError' will be called
|
||||||
*/
|
*/
|
||||||
public static FromPromise<T>(
|
public static fromPromise<T>(
|
||||||
promise: Promise<T>,
|
promise: Promise<T>,
|
||||||
onError: (e) => void = undefined
|
onError: (e) => void = undefined,
|
||||||
): UIEventSource<T> {
|
): UIEventSource<T> {
|
||||||
const src = new UIEventSource<T>(undefined)
|
const src = new UIEventSource<T>(undefined)
|
||||||
promise?.then((d) => src.setData(d))
|
promise?.then((d) => src.setData(d))
|
||||||
|
@ -744,8 +704,8 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
* @param promise
|
* @param promise
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
public static FromPromiseWithErr<T>(
|
public static fromPromiseWithErr<T>(
|
||||||
promise: Promise<T>
|
promise: Promise<T>,
|
||||||
): UIEventSource<{ success: T } | { error: any } | undefined> {
|
): UIEventSource<{ success: T } | { error: any } | undefined> {
|
||||||
const src = new UIEventSource<{ success: T } | { error: any }>(undefined)
|
const src = new UIEventSource<{ success: T } | { error: any }>(undefined)
|
||||||
promise
|
promise
|
||||||
|
@ -778,7 +738,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return "" + fl
|
return "" + fl
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -809,7 +769,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return "" + fl
|
return "" + fl
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -817,13 +777,13 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
return stringUIEventSource.sync(
|
return stringUIEventSource.sync(
|
||||||
(str) => str === "true",
|
(str) => str === "true",
|
||||||
[],
|
[],
|
||||||
(b) => "" + b
|
(b) => "" + b,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static asObject<T extends object | string>(
|
static asObject<T extends object | string>(
|
||||||
stringUIEventSource: UIEventSource<string>,
|
stringUIEventSource: UIEventSource<string>,
|
||||||
defaultV: T
|
defaultV: T,
|
||||||
): UIEventSource<T> {
|
): UIEventSource<T> {
|
||||||
return stringUIEventSource.sync(
|
return stringUIEventSource.sync(
|
||||||
(str) => {
|
(str) => {
|
||||||
|
@ -839,13 +799,13 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
"due to",
|
"due to",
|
||||||
e,
|
e,
|
||||||
"; the underlying data store has tag",
|
"; the underlying data store has tag",
|
||||||
stringUIEventSource.tag
|
stringUIEventSource.tag,
|
||||||
)
|
)
|
||||||
return defaultV
|
return defaultV
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
(b) => JSON.stringify(b) ?? ""
|
(b) => JSON.stringify(b) ?? "",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -934,8 +894,17 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
*/
|
*/
|
||||||
public map<J>(
|
public map<J>(
|
||||||
f: (t: T) => J,
|
f: (t: T) => J,
|
||||||
extraSources: Store<unknown>[] = [],
|
extraSources?: Store<unknown>[],
|
||||||
onDestroy?: (f: () => void) => void
|
onDestroy?: (f: () => void) => void,
|
||||||
|
)
|
||||||
|
public map<J>(
|
||||||
|
f: (t: T) => J,
|
||||||
|
onDestroy: (f: () => void) => void,
|
||||||
|
)
|
||||||
|
public map<J>(
|
||||||
|
f: (t: T) => J,
|
||||||
|
extraSources: Store<unknown>[] | ((f: () => void) => void),
|
||||||
|
onDestroy?: (f: () => void) => void,
|
||||||
): Store<J> {
|
): 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)
|
||||||
}
|
}
|
||||||
|
@ -946,8 +915,8 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
*/
|
*/
|
||||||
public mapD<J>(
|
public mapD<J>(
|
||||||
f: (t: Exclude<T, undefined | null>) => J,
|
f: (t: Exclude<T, undefined | null>) => J,
|
||||||
extraSources: Store<unknown>[] = [],
|
extraSources?: Store<unknown>[] | ((f: () => void) => void),
|
||||||
callbackDestroyFunction?: (f: () => void) => void
|
callbackDestroyFunction?: (f: () => void) => void,
|
||||||
): Store<J | undefined> {
|
): Store<J | undefined> {
|
||||||
return new MappedStore(
|
return new MappedStore(
|
||||||
this,
|
this,
|
||||||
|
@ -965,12 +934,12 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
this.data === undefined || this.data === null
|
this.data === undefined || this.data === null
|
||||||
? <undefined | null>this.data
|
? <undefined | null>this.data
|
||||||
: f(<Exclude<T, undefined | null>>this.data),
|
: f(<Exclude<T, undefined | null>>this.data),
|
||||||
callbackDestroyFunction
|
callbackDestroyFunction,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public mapAsyncD<J>(f: (t: T) => Promise<J>): Store<J> {
|
public mapAsyncD<J>(f: (t: T) => Promise<J>): Store<J> {
|
||||||
return this.bindD((t) => UIEventSource.FromPromise(f(t)))
|
return this.bindD((t) => UIEventSource.fromPromise(f(t)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -985,7 +954,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
f: (t: T) => J,
|
f: (t: T) => J,
|
||||||
extraSources: Store<unknown>[],
|
extraSources: Store<unknown>[],
|
||||||
g: (j: J, t: T) => T,
|
g: (j: J, t: T) => T,
|
||||||
allowUnregister = false
|
allowUnregister = false,
|
||||||
): UIEventSource<J> {
|
): UIEventSource<J> {
|
||||||
const stack = new Error().stack.split("\n")
|
const stack = new Error().stack.split("\n")
|
||||||
const callee = stack[1]
|
const callee = stack[1]
|
||||||
|
@ -1033,4 +1002,16 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
update(f: Updater<T> & ((value: T) => T)): void {
|
update(f: Updater<T> & ((value: T) => T)): void {
|
||||||
this.setData(f(this.data))
|
this.setData(f(this.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static concat<T>(stores: ReadonlyArray<UIEventSource<T | undefined>>): UIEventSource<(T | undefined)[]> ;
|
||||||
|
public static concat<T>(stores: ReadonlyArray<UIEventSource<T>>): UIEventSource<T[]> ;
|
||||||
|
public static concat<T>(stores: ReadonlyArray<UIEventSource<T | undefined>>): UIEventSource<(T | undefined)[]> {
|
||||||
|
const newStore = <UIEventSource<T[]>>Stores.concat(stores)
|
||||||
|
newStore.addCallbackD(list => {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
stores[i]?.setData(list[i])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return newStore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ export class AndroidPolyfill {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private static backfillGeolocation(databridgePlugin: DatabridgePlugin) {
|
private static backfillGeolocation(databridgePlugin: DatabridgePlugin) {
|
||||||
const src = UIEventSource.FromPromise(
|
const src = UIEventSource.fromPromise(
|
||||||
databridgePlugin.request({ key: "location:has-permission" })
|
databridgePlugin.request({ key: "location:has-permission" })
|
||||||
)
|
)
|
||||||
src.addCallbackAndRunD((permission) => {
|
src.addCallbackAndRunD((permission) => {
|
||||||
|
|
|
@ -173,7 +173,7 @@ export default class Wikidata {
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
const src = UIEventSource.FromPromiseWithErr(Wikidata.LoadWikidataEntryAsync(key))
|
const src = UIEventSource.fromPromiseWithErr(Wikidata.LoadWikidataEntryAsync(key))
|
||||||
Wikidata._storeCache.set(key, src)
|
Wikidata._storeCache.set(key, src)
|
||||||
return src
|
return src
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,14 +90,14 @@ export class AvailableRasterLayers {
|
||||||
location: Store<{ lon: number; lat: number }>,
|
location: Store<{ lon: number; lat: number }>,
|
||||||
enableBing?: Store<boolean>
|
enableBing?: Store<boolean>
|
||||||
): Store<RasterLayerPolygon[]> {
|
): Store<RasterLayerPolygon[]> {
|
||||||
const availableLayersBboxes = Stores.ListStabilized(
|
const availableLayersBboxes = Stores.listStabilized(
|
||||||
location.mapD((loc) => {
|
location.mapD((loc) => {
|
||||||
const eli = AvailableRasterLayers._editorLayerIndex
|
const eli = AvailableRasterLayers._editorLayerIndex
|
||||||
const lonlat: [number, number] = [loc.lon, loc.lat]
|
const lonlat: [number, number] = [loc.lon, loc.lat]
|
||||||
return eli.filter((eliPolygon) => BBox.get(eliPolygon).contains(lonlat))
|
return eli.filter((eliPolygon) => BBox.get(eliPolygon).contains(lonlat))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
return Stores.ListStabilized(
|
return Stores.listStabilized(
|
||||||
availableLayersBboxes.mapD(
|
availableLayersBboxes.mapD(
|
||||||
(eliPolygons) => {
|
(eliPolygons) => {
|
||||||
const loc = location.data
|
const loc = location.data
|
||||||
|
|
|
@ -1167,7 +1167,7 @@ export class TagRenderingConfigUtils {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const center = GeoOperations.centerpointCoordinates(feature)
|
const center = GeoOperations.centerpointCoordinates(feature)
|
||||||
return UIEventSource.FromPromise(
|
return UIEventSource.fromPromise(
|
||||||
NameSuggestionIndex.generateMappings(
|
NameSuggestionIndex.generateMappings(
|
||||||
config.freeform.key,
|
config.freeform.key,
|
||||||
tags,
|
tags,
|
||||||
|
|
|
@ -48,7 +48,7 @@ export class Orientation {
|
||||||
|
|
||||||
if (rotateAlpha) {
|
if (rotateAlpha) {
|
||||||
this._animateFakeMeasurements = true
|
this._animateFakeMeasurements = true
|
||||||
Stores.Chronic(25).addCallback((date) => {
|
Stores.chronic(25).addCallback((date) => {
|
||||||
this.alpha.setData((date.getTime() / 50) % 360)
|
this.alpha.setData((date.getTime() / 50) % 360)
|
||||||
if (!this._animateFakeMeasurements) {
|
if (!this._animateFakeMeasurements) {
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -78,7 +78,7 @@
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const customThemes: Store<MinimalThemeInformation[]> = Stores.ListStabilized<string>(
|
const customThemes: Store<MinimalThemeInformation[]> = Stores.listStabilized<string>(
|
||||||
state.installedUserThemes.stabilized(1000)
|
state.installedUserThemes.stabilized(1000)
|
||||||
).mapD((stableIds) => Utils.NoNullInplace(stableIds.map((id) => state.getUnofficialTheme(id))))
|
).mapD((stableIds) => Utils.NoNullInplace(stableIds.map((id) => state.getUnofficialTheme(id))))
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
import { ariaLabelStore } from "../../Utils/ariaLabel"
|
import { ariaLabelStore } from "../../Utils/ariaLabel"
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
import Center from "../../assets/svg/Center.svelte"
|
import Center from "../../assets/svg/Center.svelte"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
export let feature: Feature
|
export let feature: Feature
|
||||||
|
@ -31,9 +32,7 @@
|
||||||
return { bearing, dist }
|
return { bearing, dist }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
let bearingFromGps = state.geolocation.geolocationState.currentGPSLocation.mapD((coordinate) => {
|
let bearingFromGps = state.geolocation.geolocationState.currentGPSLocation.mapD((coordinate) => GeoOperations.bearing([coordinate.longitude, coordinate.latitude], fcenter),onDestroy)
|
||||||
return GeoOperations.bearing([coordinate.longitude, coordinate.latitude], fcenter)
|
|
||||||
})
|
|
||||||
let compass = Orientation.singleton.alpha
|
let compass = Orientation.singleton.alpha
|
||||||
|
|
||||||
let relativeDirections = Translations.t.general.visualFeedback.directionsRelative
|
let relativeDirections = Translations.t.general.visualFeedback.directionsRelative
|
||||||
|
@ -80,11 +79,11 @@
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
> = state.geolocation.geolocationState.currentGPSLocation.mapD(({ longitude, latitude }) => {
|
> = state.geolocation.geolocationState.currentGPSLocation.mapD(({ longitude, latitude }) => {
|
||||||
let gps = [longitude, latitude]
|
const gps = [longitude, latitude]
|
||||||
let bearing = Math.round(GeoOperations.bearing(gps, fcenter))
|
const bearing = Math.round(GeoOperations.bearing(gps, fcenter))
|
||||||
let dist = round10(Math.round(GeoOperations.distanceBetween(fcenter, gps)))
|
const dist = round10(Math.round(GeoOperations.distanceBetween(fcenter, gps)))
|
||||||
return { bearing, dist }
|
return { bearing, dist }
|
||||||
})
|
}, onDestroy)
|
||||||
let labelFromGps: Store<string | undefined> = bearingAndDistGps.mapD(
|
let labelFromGps: Store<string | undefined> = bearingAndDistGps.mapD(
|
||||||
({ bearing, dist }) => {
|
({ bearing, dist }) => {
|
||||||
const distHuman = GeoOperations.distanceToHuman(dist)
|
const distHuman = GeoOperations.distanceToHuman(dist)
|
||||||
|
@ -103,7 +102,7 @@
|
||||||
})
|
})
|
||||||
return mainTr.textFor(lang)
|
return mainTr.textFor(lang)
|
||||||
},
|
},
|
||||||
[compass, Locale.language]
|
[compass, Locale.language], onDestroy
|
||||||
)
|
)
|
||||||
|
|
||||||
let label = labelFromCenter.map(
|
let label = labelFromCenter.map(
|
||||||
|
@ -116,7 +115,7 @@
|
||||||
}
|
}
|
||||||
return labelFromCenter
|
return labelFromCenter
|
||||||
},
|
},
|
||||||
[labelFromGps]
|
[labelFromGps], onDestroy
|
||||||
)
|
)
|
||||||
|
|
||||||
function focusMap() {
|
function focusMap() {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
let layer = state.getMatchingLayer(selected.properties)
|
let layer = state.getMatchingLayer(selected.properties)
|
||||||
|
|
||||||
let stillMatches = tags.map(
|
let stillMatches = tags.map(
|
||||||
(tags) => !layer?.source?.osmTags || layer?.source?.osmTags?.matchesProperties(tags)
|
(tags) => !layer?.source?.osmTags || layer?.source?.osmTags?.matchesProperties(tags), onDestroy
|
||||||
)
|
)
|
||||||
onDestroy(
|
onDestroy(
|
||||||
stillMatches.addCallbackAndRunD((matches) => {
|
stillMatches.addCallbackAndRunD((matches) => {
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
const globalResources: UIEventSource<Record<string, CommunityResource>> =
|
const globalResources: UIEventSource<Record<string, CommunityResource>> =
|
||||||
UIEventSource.FromPromise(
|
UIEventSource.fromPromise(
|
||||||
Utils.downloadJsonCached<Record<string, CommunityResource>>(
|
Utils.downloadJsonCached<Record<string, CommunityResource>>(
|
||||||
Constants.communityIndexHost + "/global.json",
|
Constants.communityIndexHost + "/global.json",
|
||||||
24 * 60 * 60 * 1000
|
24 * 60 * 60 * 1000
|
||||||
|
|
|
@ -61,6 +61,7 @@
|
||||||
import QueuedImagesView from "../Image/QueuedImagesView.svelte"
|
import QueuedImagesView from "../Image/QueuedImagesView.svelte"
|
||||||
import InsetSpacer from "../Base/InsetSpacer.svelte"
|
import InsetSpacer from "../Base/InsetSpacer.svelte"
|
||||||
import UserCircle from "@rgossiaux/svelte-heroicons/solid/UserCircle"
|
import UserCircle from "@rgossiaux/svelte-heroicons/solid/UserCircle"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let state: {
|
export let state: {
|
||||||
favourites: FavouritesFeatureSource
|
favourites: FavouritesFeatureSource
|
||||||
|
@ -212,7 +213,7 @@
|
||||||
</LoginToggle>
|
</LoginToggle>
|
||||||
<LanguagePicker
|
<LanguagePicker
|
||||||
preferredLanguages={state.userRelatedState.osmConnection.userDetails.mapD(
|
preferredLanguages={state.userRelatedState.osmConnection.userDetails.mapD(
|
||||||
(ud) => ud.languages
|
(ud) => ud.languages, onDestroy
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</SidebarUnit>
|
</SidebarUnit>
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
let isAddNew = tags.mapD(
|
let isAddNew = tags.mapD(
|
||||||
(t) => t?.id?.startsWith(LastClickFeatureSource.newPointElementId) ?? false
|
(t) => t?.id?.startsWith(LastClickFeatureSource.newPointElementId) ?? false, onDestroy
|
||||||
)
|
)
|
||||||
|
|
||||||
export let layer: LayerConfig
|
export let layer: LayerConfig
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
(config.condition?.matchesProperties(tgs) ?? true) &&
|
(config.condition?.matchesProperties(tgs) ?? true) &&
|
||||||
(config.metacondition?.matchesProperties({ ...tgs, ..._metatags }) ?? true)
|
(config.metacondition?.matchesProperties({ ...tgs, ..._metatags }) ?? true)
|
||||||
)
|
)
|
||||||
})
|
}, onDestroy)
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
import { Popover } from "flowbite-svelte"
|
import { Popover } from "flowbite-svelte"
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
let open = false
|
let open = false
|
||||||
export let state: { osmConnection: OsmConnection }
|
export let state: { osmConnection: OsmConnection }
|
||||||
let userdetails = state.osmConnection.userDetails
|
let userdetails = state.osmConnection.userDetails
|
||||||
let username = userdetails.mapD((ud) => ud.name)
|
let username = userdetails.mapD((ud) => ud.name, onDestroy)
|
||||||
username.addCallbackAndRunD((ud) => {
|
username.addCallbackAndRunD((ud) => {
|
||||||
if (ud) {
|
if (ud) {
|
||||||
open = true
|
open = true
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
export let features: Feature[]
|
export let features: Feature[]
|
||||||
|
|
||||||
const downloader = new OsmObjectDownloader()
|
const downloader = new OsmObjectDownloader()
|
||||||
let allHistories: UIEventSource<OsmObject[][]> = UIEventSource.FromPromise(
|
let allHistories: UIEventSource<OsmObject[][]> = UIEventSource.fromPromise(
|
||||||
Promise.all(features.map((f) => downloader.downloadHistory(f.properties.id)))
|
Promise.all(features.map((f) => downloader.downloadHistory(f.properties.id)))
|
||||||
)
|
)
|
||||||
let imageKeys = new Set(
|
let imageKeys = new Set(
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
let usernames = new Set(onlyShowUsername)
|
let usernames = new Set(onlyShowUsername)
|
||||||
|
|
||||||
const downloader = new OsmObjectDownloader()
|
const downloader = new OsmObjectDownloader()
|
||||||
let allHistories: UIEventSource<OsmObject[][]> = UIEventSource.FromPromise(
|
let allHistories: UIEventSource<OsmObject[][]> = UIEventSource.fromPromise(
|
||||||
Promise.all(features.map((f) => downloader.downloadHistory(f.properties.id)))
|
Promise.all(features.map((f) => downloader.downloadHistory(f.properties.id)))
|
||||||
)
|
)
|
||||||
let allDiffs: Store<
|
let allDiffs: Store<
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||||
|
|
||||||
export let hash: string
|
export let hash: string
|
||||||
let image: UIEventSource<ProvidedImage> = UIEventSource.FromPromise(
|
let image: UIEventSource<ProvidedImage> = UIEventSource.fromPromise(
|
||||||
PanoramaxImageProvider.singleton.getInfo(hash)
|
PanoramaxImageProvider.singleton.getInfo(hash)
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
export let id: OsmId
|
export let id: OsmId
|
||||||
|
|
||||||
let usernames = new Set(onlyShowChangesBy)
|
let usernames = new Set(onlyShowChangesBy)
|
||||||
let fullHistory = UIEventSource.FromPromise(new OsmObjectDownloader().downloadHistory(id))
|
let fullHistory = UIEventSource.fromPromise(new OsmObjectDownloader().downloadHistory(id))
|
||||||
|
|
||||||
let partOfLayer = fullHistory.mapD((history) =>
|
let partOfLayer = fullHistory.mapD((history) =>
|
||||||
history.map((step) => ({
|
history.map((step) => ({
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
console.log("Initial license:", image.license, image.provider)
|
console.log("Initial license:", image.license, image.provider)
|
||||||
let license: Store<LicenseInfo> = image.license
|
let license: Store<LicenseInfo> = image.license
|
||||||
? new ImmutableStore(image.license)
|
? new ImmutableStore(image.license)
|
||||||
: UIEventSource.FromPromise(image.provider?.DownloadAttribution(image))
|
: UIEventSource.fromPromise(image.provider?.DownloadAttribution(image))
|
||||||
let icon = image.provider?.sourceIcon()
|
let icon = image.provider?.sourceIcon()
|
||||||
let openOriginal = image.provider?.visitUrl(image)
|
let openOriginal = image.provider?.visitUrl(image)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -75,8 +75,8 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
let selected = new UIEventSource<P4CPicture>(undefined)
|
let selected = new UIEventSource<P4CPicture>(undefined)
|
||||||
let selectedAsFeature = selected.mapD((s) => {
|
let selectedAsFeature = selected.mapD((s) =>
|
||||||
return [
|
[
|
||||||
<Feature<Point>>{
|
<Feature<Point>>{
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
geometry: {
|
geometry: {
|
||||||
|
@ -89,14 +89,13 @@
|
||||||
rotation: s.direction,
|
rotation: s.direction,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
], onDestroy)
|
||||||
})
|
|
||||||
|
|
||||||
let someLoading = imageState.state.mapD((stateRecord) =>
|
let someLoading = imageState.state.mapD((stateRecord) =>
|
||||||
Object.values(stateRecord).some((v) => v === "loading")
|
Object.values(stateRecord).some((v) => v === "loading"), onDestroy
|
||||||
)
|
)
|
||||||
let errors = imageState.state.mapD((stateRecord) =>
|
let errors = imageState.state.mapD((stateRecord) =>
|
||||||
Object.keys(stateRecord).filter((k) => stateRecord[k] === "error")
|
Object.keys(stateRecord).filter((k) => stateRecord[k] === "error"), onDestroy
|
||||||
)
|
)
|
||||||
let highlighted = new UIEventSource<string>(undefined)
|
let highlighted = new UIEventSource<string>(undefined)
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||||
import Translations from "../../i18n/Translations"
|
import Translations from "../../i18n/Translations"
|
||||||
import Tr from "../../Base/Tr.svelte"
|
import Tr from "../../Base/Tr.svelte"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let value: UIEventSource<number>
|
export let value: UIEventSource<number>
|
||||||
export let feature: Feature
|
export let feature: Feature
|
||||||
|
@ -84,7 +85,7 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
[mapLocation]
|
[mapLocation], onDestroy
|
||||||
)
|
)
|
||||||
|
|
||||||
new ShowDataLayer(map, {
|
new ShowDataLayer(map, {
|
||||||
|
|
|
@ -79,7 +79,7 @@
|
||||||
forceIndex = floors.data.indexOf(level)
|
forceIndex = floors.data.indexOf(level)
|
||||||
})
|
})
|
||||||
|
|
||||||
Stores.Chronic(50).addCallback(() => stabilize())
|
Stores.chronic(50).addCallback(() => stabilize())
|
||||||
floors.addCallback((floors) => {
|
floors.addCallback((floors) => {
|
||||||
forceIndex = floors.findIndex((s) => s === value.data)
|
forceIndex = floors.findIndex((s) => s === value.data)
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import { GeoOperations } from "../../../Logic/GeoOperations"
|
import { GeoOperations } from "../../../Logic/GeoOperations"
|
||||||
import If from "../../Base/If.svelte"
|
import If from "../../Base/If.svelte"
|
||||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value exported to outside, saved only when the button is pressed
|
* The value exported to outside, saved only when the button is pressed
|
||||||
|
@ -61,7 +62,7 @@
|
||||||
} else {
|
} else {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
})
|
}, onDestroy)
|
||||||
|
|
||||||
beta.map(
|
beta.map(
|
||||||
(beta) => {
|
(beta) => {
|
||||||
|
@ -77,7 +78,7 @@
|
||||||
previewDegrees.setData(beta + "°")
|
previewDegrees.setData(beta + "°")
|
||||||
previewPercentage.setData(degreesToPercentage(beta))
|
previewPercentage.setData(degreesToPercentage(beta))
|
||||||
},
|
},
|
||||||
[valuesign, beta]
|
[valuesign, beta], onDestroy
|
||||||
)
|
)
|
||||||
|
|
||||||
function onSave() {
|
function onSave() {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import Translations from "../../i18n/Translations"
|
import Translations from "../../i18n/Translations"
|
||||||
import Tr from "../../Base/Tr.svelte"
|
import Tr from "../../Base/Tr.svelte"
|
||||||
import { ImmutableStore, Store, Stores, UIEventSource } from "../../../Logic/UIEventSource"
|
import { ImmutableStore, Store, UIEventSource } from "../../../Logic/UIEventSource"
|
||||||
import Wikidata, { WikidataResponse } from "../../../Logic/Web/Wikidata"
|
import Wikidata, { WikidataResponse } from "../../../Logic/Web/Wikidata"
|
||||||
import Locale from "../../i18n/Locale"
|
import Locale from "../../i18n/Locale"
|
||||||
import Loading from "../../Base/Loading.svelte"
|
import Loading from "../../Base/Loading.svelte"
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
})
|
})
|
||||||
WikidataValidator._searchCache.set(key, promise)
|
WikidataValidator._searchCache.set(key, promise)
|
||||||
}
|
}
|
||||||
return Stores.FromPromiseWithErr(promise)
|
return UIEventSource.fromPromiseWithErr(promise)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -173,7 +173,7 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValid = unvalidatedText.map((v) => validator?.isValid(v, getCountry) ?? true)
|
const isValid = unvalidatedText.map((v) => validator?.isValid(v, getCountry) ?? true, onDestroy)
|
||||||
|
|
||||||
let htmlElem: HTMLInputElement | HTMLTextAreaElement
|
let htmlElem: HTMLInputElement | HTMLTextAreaElement
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
let searchSuggestions = searchvalue.bindD((search) => {
|
let searchSuggestions = searchvalue.bindD((search) => {
|
||||||
searchIsRunning.set(true)
|
searchIsRunning.set(true)
|
||||||
try {
|
try {
|
||||||
return UIEventSource.FromPromise(geocoder.search(search))
|
return UIEventSource.fromPromise(geocoder.search(search))
|
||||||
} finally {
|
} finally {
|
||||||
searchIsRunning.set(false)
|
searchIsRunning.set(false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import DynamicIcon from "./DynamicIcon.svelte"
|
import DynamicIcon from "./DynamicIcon.svelte"
|
||||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||||
import { Orientation } from "../../Sensors/Orientation"
|
import { Orientation } from "../../Sensors/Orientation"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a 'marker', which consists of multiple 'icons'
|
* Renders a 'marker', which consists of multiple 'icons'
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
? tags.map((tags) => rotation.GetRenderValue(tags).Subs(tags).txt)
|
? tags.map((tags) => rotation.GetRenderValue(tags).Subs(tags).txt)
|
||||||
: new ImmutableStore("0deg")
|
: new ImmutableStore("0deg")
|
||||||
if (rotation?.render?.txt === "{alpha}deg") {
|
if (rotation?.render?.txt === "{alpha}deg") {
|
||||||
_rotation = Orientation.singleton.alpha.map((alpha) => (alpha ? alpha + "deg" : "0deg "))
|
_rotation = Orientation.singleton.alpha.map((alpha) => (alpha ? alpha + "deg" : "0deg "), onDestroy)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -179,7 +179,7 @@ class SingleBackgroundHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fadeOut() {
|
private fadeOut() {
|
||||||
Stores.Chronic(
|
Stores.chronic(
|
||||||
8,
|
8,
|
||||||
() => this.opacity.data > 0 && this._deactivationTime !== undefined
|
() => this.opacity.data > 0 && this._deactivationTime !== undefined
|
||||||
).addCallback(() => {
|
).addCallback(() => {
|
||||||
|
@ -193,7 +193,7 @@ class SingleBackgroundHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fadeIn() {
|
private fadeIn() {
|
||||||
Stores.Chronic(
|
Stores.chronic(
|
||||||
8,
|
8,
|
||||||
() => this.opacity.data < 1.0 && this._deactivationTime === undefined
|
() => this.opacity.data < 1.0 && this._deactivationTime === undefined
|
||||||
).addCallback(() => {
|
).addCallback(() => {
|
||||||
|
|
|
@ -4,10 +4,11 @@
|
||||||
import MapControlButton from "../Base/MapControlButton.svelte"
|
import MapControlButton from "../Base/MapControlButton.svelte"
|
||||||
import Plus from "../../assets/svg/Plus.svelte"
|
import Plus from "../../assets/svg/Plus.svelte"
|
||||||
import type { MapProperties } from "../../Models/MapProperties"
|
import type { MapProperties } from "../../Models/MapProperties"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let adaptor: MapProperties
|
export let adaptor: MapProperties
|
||||||
let canZoomIn = adaptor.maxzoom.map((mz) => adaptor.zoom.data < mz, [adaptor.zoom])
|
let canZoomIn = adaptor.maxzoom.map((mz) => adaptor.zoom.data < mz, [adaptor.zoom], onDestroy)
|
||||||
let canZoomOut = adaptor.minzoom.map((mz) => adaptor.zoom.data > mz, [adaptor.zoom])
|
let canZoomOut = adaptor.minzoom.map((mz) => adaptor.zoom.data > mz, [adaptor.zoom], onDestroy)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="pointer-events-none absolute bottom-0 right-0 flex flex-col">
|
<div class="pointer-events-none absolute bottom-0 right-0 flex flex-col">
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
onDestroy(
|
onDestroy(
|
||||||
Stores.Chronic(250).addCallback(() => {
|
Stores.chronic(250).addCallback(() => {
|
||||||
const mapIsLoading = !map.data?.isStyleLoaded()
|
const mapIsLoading = !map.data?.isStyleLoaded()
|
||||||
isLoading = mapIsLoading && (didChange || rasterLayer === undefined)
|
isLoading = mapIsLoading && (didChange || rasterLayer === undefined)
|
||||||
if (didChange && !mapIsLoading) {
|
if (didChange && !mapIsLoading) {
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[Stores.Chronic(5 * 60 * 1000)]
|
[Stores.chronic(5 * 60 * 1000)]
|
||||||
)
|
)
|
||||||
.mapD((date) => Utils.TwoDigits(date.getHours()) + ":" + Utils.TwoDigits(date.getMinutes()))
|
.mapD((date) => Utils.TwoDigits(date.getHours()) + ":" + Utils.TwoDigits(date.getMinutes()))
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import { OH } from "./OpeningHours"
|
import { OH } from "./OpeningHours"
|
||||||
import TimeInput from "../InputElement/Helpers/TimeInput.svelte"
|
import TimeInput from "../InputElement/Helpers/TimeInput.svelte"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let value: UIEventSource<string>
|
export let value: UIEventSource<string>
|
||||||
let startValue: UIEventSource<string> = new UIEventSource<string>(undefined)
|
let startValue: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||||
|
@ -13,8 +14,9 @@
|
||||||
const t = Translations.t.general.opening_hours
|
const t = Translations.t.general.opening_hours
|
||||||
let mode = new UIEventSource("")
|
let mode = new UIEventSource("")
|
||||||
|
|
||||||
|
onDestroy(
|
||||||
value
|
value
|
||||||
.map((ph) => OH.ParsePHRule(ph))
|
.map((ph) => OH.ParsePHRule(ph), onDestroy)
|
||||||
.addCallbackAndRunD((parsed) => {
|
.addCallbackAndRunD((parsed) => {
|
||||||
if (parsed === null) {
|
if (parsed === null) {
|
||||||
return
|
return
|
||||||
|
@ -23,7 +25,7 @@
|
||||||
startValue.setData(parsed.start)
|
startValue.setData(parsed.start)
|
||||||
endValue.setData(parsed.end)
|
endValue.setData(parsed.end)
|
||||||
})
|
})
|
||||||
|
)
|
||||||
function updateValue() {
|
function updateValue() {
|
||||||
if (mode.data === undefined || mode.data === "") {
|
if (mode.data === undefined || mode.data === "") {
|
||||||
// not known
|
// not known
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
import WikidatapreviewWithLoading from "../Wikipedia/WikidatapreviewWithLoading.svelte"
|
import WikidatapreviewWithLoading from "../Wikipedia/WikidatapreviewWithLoading.svelte"
|
||||||
|
|
||||||
export let species: PlantNetSpeciesMatch
|
export let species: PlantNetSpeciesMatch
|
||||||
let wikidata = UIEventSource.FromPromise(
|
let wikidata = UIEventSource.fromPromise(
|
||||||
Wikidata.Sparql<{ species }>(
|
Wikidata.Sparql<{ species }>(
|
||||||
["?species", "?speciesLabel"],
|
["?species", "?speciesLabel"],
|
||||||
['?species wdt:P846 "' + species.gbif.id + '"']
|
['?species wdt:P846 "' + species.gbif.id + '"']
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
* PlantNet give us a GBIF-id, but we want the Wikidata-id instead.
|
* PlantNet give us a GBIF-id, but we want the Wikidata-id instead.
|
||||||
* We look this up in wikidata
|
* We look this up in wikidata
|
||||||
*/
|
*/
|
||||||
const wikidataId: Store<string> = UIEventSource.FromPromise(
|
const wikidataId: Store<string> = UIEventSource.fromPromise(
|
||||||
Wikidata.Sparql<{ species }>(
|
Wikidata.Sparql<{ species }>(
|
||||||
["?species", "?speciesLabel"],
|
["?species", "?speciesLabel"],
|
||||||
['?species wdt:P846 "' + species.gbif.id + '"']
|
['?species wdt:P846 "' + species.gbif.id + '"']
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
import type { GlobalFilter } from "../../../Models/GlobalFilter"
|
import type { GlobalFilter } from "../../../Models/GlobalFilter"
|
||||||
import { EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import { EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||||
import Layers from "../../../assets/svg/Layers.svelte"
|
import Layers from "../../../assets/svg/Layers.svelte"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let state: ThemeViewState
|
export let state: ThemeViewState
|
||||||
export let layer: LayerConfig
|
export let layer: LayerConfig
|
||||||
|
@ -44,7 +45,7 @@
|
||||||
let asTags = tags.map((tgs) =>
|
let asTags = tags.map((tgs) =>
|
||||||
Object.keys(tgs)
|
Object.keys(tgs)
|
||||||
.filter((k) => !k.startsWith("_") && !forbiddenKeys.has(k))
|
.filter((k) => !k.startsWith("_") && !forbiddenKeys.has(k))
|
||||||
.map((k) => new Tag(k, tgs[k]))
|
.map((k) => new Tag(k, tgs[k])), onDestroy
|
||||||
)
|
)
|
||||||
let showPopup: UIEventSource<boolean> = new UIEventSource(false)
|
let showPopup: UIEventSource<boolean> = new UIEventSource(false)
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
import { Utils } from "../../../Utils"
|
import { Utils } from "../../../Utils"
|
||||||
import TagLink from "./TagLink.svelte"
|
import TagLink from "./TagLink.svelte"
|
||||||
import Tag from "@rgossiaux/svelte-heroicons/solid/Tag"
|
import Tag from "@rgossiaux/svelte-heroicons/solid/Tag"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let tags: UIEventSource<Record<string, any>>
|
export let tags: UIEventSource<Record<string, any>>
|
||||||
export let tagKeys = tags.map((tgs) => (tgs === undefined ? [] : Object.keys(tgs)))
|
export let tagKeys = tags.map((tgs) => (tgs === undefined ? [] : Object.keys(tgs)), onDestroy)
|
||||||
|
|
||||||
export let layer: LayerConfig | undefined = undefined
|
export let layer: LayerConfig | undefined = undefined
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
|
||||||
}
|
}
|
||||||
|
|
||||||
const to_parse: UIEventSource<string[]> = new UIEventSource<string[]>(undefined)
|
const to_parse: UIEventSource<string[]> = new UIEventSource<string[]>(undefined)
|
||||||
Stores.Chronic(500, () => to_parse.data === undefined)
|
Stores.chronic(500, () => to_parse.data === undefined)
|
||||||
.map(() => {
|
.map(() => {
|
||||||
const applicable = <string | string[]>tagSource.data[argument[1]]
|
const applicable = <string | string[]>tagSource.data[argument[1]]
|
||||||
if (typeof applicable === "string") {
|
if (typeof applicable === "string") {
|
||||||
|
@ -116,7 +116,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
|
||||||
to_parse.set(data)
|
to_parse.set(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
const stableIds: Store<string[]> = Stores.ListStabilized(to_parse).map((ids) => {
|
const stableIds: Store<string[]> = Stores.listStabilized(to_parse).map((ids) => {
|
||||||
if (typeof ids === "string") {
|
if (typeof ids === "string") {
|
||||||
ids = JSON.parse(ids)
|
ids = JSON.parse(ids)
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ export class DeleteFlowState {
|
||||||
|
|
||||||
if (allByMyself.data === null && useTheInternet) {
|
if (allByMyself.data === null && useTheInternet) {
|
||||||
// We kickoff the download here as it hasn't yet been downloaded. Note that this is mapped onto 'all by myself' above
|
// We kickoff the download here as it hasn't yet been downloaded. Note that this is mapped onto 'all by myself' above
|
||||||
UIEventSource.FromPromise(this.objectDownloader.downloadHistory(id))
|
UIEventSource.fromPromise(this.objectDownloader.downloadHistory(id))
|
||||||
.mapD((versions) =>
|
.mapD((versions) =>
|
||||||
versions.map((version) =>
|
versions.map((version) =>
|
||||||
Number(version.tags["_last_edit:contributor:uid"])
|
Number(version.tags["_last_edit:contributor:uid"])
|
||||||
|
|
|
@ -4,19 +4,18 @@
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte"
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let key: string
|
export let key: string
|
||||||
export let tags: Store<Record<string, string>>
|
export let tags: Store<Record<string, string>>
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
const validator = new FediverseValidator()
|
const validator = new FediverseValidator()
|
||||||
const userinfo = tags
|
let userinfo = tags
|
||||||
.mapD((t) => t[key])
|
.mapD((t) => t[key], onDestroy)
|
||||||
.mapD((fediAccount) => {
|
.mapD((fediAccount) => FediverseValidator.extractServer(validator.reformat(fediAccount)), onDestroy)
|
||||||
return FediverseValidator.extractServer(validator.reformat(fediAccount))
|
let homeLocation: Store<string> = state.userRelatedState?.preferencesAsTags
|
||||||
})
|
.mapD((prefs) => prefs["_mastodon_link"], onDestroy)
|
||||||
const homeLocation: Store<string> = state.userRelatedState?.preferencesAsTags
|
.mapD((userhandle) => FediverseValidator.extractServer(validator.reformat(userhandle))?.server, onDestroy)
|
||||||
.mapD((prefs) => prefs["_mastodon_link"])
|
|
||||||
.mapD((userhandle) => FediverseValidator.extractServer(validator.reformat(userhandle))?.server)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex w-full flex-col">
|
<div class="flex w-full flex-col">
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import type { Feature } from "geojson"
|
import type { Feature } from "geojson"
|
||||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||||
import EditButton from "../TagRendering/EditButton.svelte"
|
import EditButton from "../TagRendering/EditButton.svelte"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let key: string
|
export let key: string
|
||||||
export let tags: UIEventSource<Record<string, string>>
|
export let tags: UIEventSource<Record<string, string>>
|
||||||
|
@ -34,7 +35,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return foundLanguages
|
return foundLanguages
|
||||||
})
|
}, onDestroy)
|
||||||
|
|
||||||
const forceInputMode = new UIEventSource(false)
|
const forceInputMode = new UIEventSource(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
import { ariaLabel } from "../../Utils/ariaLabel"
|
import { ariaLabel } from "../../Utils/ariaLabel"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A small 'mark as favourite'-button to serve as title-icon
|
* A small 'mark as favourite'-button to serve as title-icon
|
||||||
|
@ -16,7 +17,7 @@
|
||||||
export let feature: Feature
|
export let feature: Feature
|
||||||
export let tags: UIEventSource<Record<string, string>>
|
export let tags: UIEventSource<Record<string, string>>
|
||||||
export let layer: LayerConfig
|
export let layer: LayerConfig
|
||||||
let isFavourite = tags?.map((tags) => tags._favourite === "yes")
|
let isFavourite = tags?.map((tags) => tags._favourite === "yes", onDestroy)
|
||||||
const t = Translations.t.favouritePoi
|
const t = Translations.t.favouritePoi
|
||||||
|
|
||||||
function markFavourite(isFavourite: boolean) {
|
function markFavourite(isFavourite: boolean) {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
|
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
|
||||||
import MaplibreMap from "../Map/MaplibreMap.svelte"
|
import MaplibreMap from "../Map/MaplibreMap.svelte"
|
||||||
import DelayedComponent from "../Base/DelayedComponent.svelte"
|
import DelayedComponent from "../Base/DelayedComponent.svelte"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
export let tagSource: UIEventSource<Record<string, string>>
|
export let tagSource: UIEventSource<Record<string, string>>
|
||||||
|
@ -48,7 +49,7 @@
|
||||||
}
|
}
|
||||||
return features
|
return features
|
||||||
},
|
},
|
||||||
[tagSource]
|
[tagSource], onDestroy
|
||||||
)
|
)
|
||||||
|
|
||||||
let mlmap = new UIEventSource(undefined)
|
let mlmap = new UIEventSource(undefined)
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
import { Map as MlMap } from "maplibre-gl"
|
import { Map as MlMap } from "maplibre-gl"
|
||||||
import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider"
|
import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let state: ThemeViewState
|
export let state: ThemeViewState
|
||||||
|
|
||||||
|
@ -62,12 +63,15 @@
|
||||||
}
|
}
|
||||||
let notAllowed = moveWizardState.moveDisallowedReason
|
let notAllowed = moveWizardState.moveDisallowedReason
|
||||||
let currentMapProperties: Store<Partial<MapProperties> & { location }> = reason.mapD((r) =>
|
let currentMapProperties: Store<Partial<MapProperties> & { location }> = reason.mapD((r) =>
|
||||||
initMapProperties(r)
|
initMapProperties(r), onDestroy
|
||||||
)
|
)
|
||||||
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||||
|
|
||||||
let searchValue = new UIEventSource<string>("")
|
let searchValue = new UIEventSource<string>("")
|
||||||
let isSearching = new UIEventSource<boolean>(false)
|
let isSearching = new UIEventSource<boolean>(false)
|
||||||
|
let zoomedInEnough = currentMapProperties.bindD(properties => properties.zoom, onDestroy).mapD(
|
||||||
|
(zoom) => zoom >= Constants.minZoomLevelToAddNewPoint, onDestroy
|
||||||
|
)
|
||||||
const searcher = new NominatimGeocoding(1)
|
const searcher = new NominatimGeocoding(1)
|
||||||
|
|
||||||
async function searchPressed() {
|
async function searchPressed() {
|
||||||
|
@ -160,9 +164,7 @@
|
||||||
|
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<If
|
<If
|
||||||
condition={$currentMapProperties.zoom.mapD(
|
condition={zoomedInEnough}
|
||||||
(zoom) => zoom >= Constants.minZoomLevelToAddNewPoint
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="primary w-full"
|
class="primary w-full"
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
import LoginToggle from "../../Base/LoginToggle.svelte"
|
import LoginToggle from "../../Base/LoginToggle.svelte"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import Loading from "../../Base/Loading.svelte"
|
import Loading from "../../Base/Loading.svelte"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
export let tags: UIEventSource<Record<string, string>>
|
export let tags: UIEventSource<Record<string, string>>
|
||||||
|
@ -29,7 +30,7 @@
|
||||||
}
|
}
|
||||||
const t = Translations.t.notes
|
const t = Translations.t.notes
|
||||||
|
|
||||||
let isClosed: Store<boolean> = tags.map((tags) => (tags?.["closed_at"] ?? "") !== "")
|
let isClosed: Store<boolean> = tags.map((tags) => (tags?.["closed_at"] ?? "") !== "", onDestroy)
|
||||||
|
|
||||||
let isProcessing = writable(false)
|
let isProcessing = writable(false)
|
||||||
async function addComment() {
|
async function addComment() {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import Icon from "../../Map/Icon.svelte"
|
import Icon from "../../Map/Icon.svelte"
|
||||||
import NoteCommentElement from "./NoteCommentElement"
|
import NoteCommentElement from "./NoteCommentElement"
|
||||||
import { Translation } from "../../i18n/Translation"
|
import { Translation } from "../../i18n/Translation"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
const t = Translations.t.notes
|
const t = Translations.t.notes
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
export let zoomMoreMessage: string
|
export let zoomMoreMessage: string
|
||||||
|
|
||||||
let curZoom = state.mapProperties.zoom
|
let curZoom = state.mapProperties.zoom
|
||||||
const isClosed = tags.map((tags) => (tags["closed_at"] ?? "") !== "")
|
const isClosed = tags.map((tags) => (tags["closed_at"] ?? "") !== "", onDestroy)
|
||||||
|
|
||||||
async function closeNote() {
|
async function closeNote() {
|
||||||
const id = tags.data[idkey]
|
const id = tags.data[idkey]
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import Note from "../../../assets/svg/Note.svelte"
|
import Note from "../../../assets/svg/Note.svelte"
|
||||||
import Resolved from "../../../assets/svg/Resolved.svelte"
|
import Resolved from "../../../assets/svg/Resolved.svelte"
|
||||||
import Speech_bubble from "../../../assets/svg/Speech_bubble.svelte"
|
import Speech_bubble from "../../../assets/svg/Speech_bubble.svelte"
|
||||||
import { Stores } from "../../../Logic/UIEventSource"
|
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||||
import { Utils } from "../../../Utils"
|
import { Utils } from "../../../Utils"
|
||||||
import Tr from "../../Base/Tr.svelte"
|
import Tr from "../../Base/Tr.svelte"
|
||||||
import AllImageProviders from "../../../Logic/ImageProviders/AllImageProviders"
|
import AllImageProviders from "../../../Logic/ImageProviders/AllImageProviders"
|
||||||
|
@ -25,12 +25,10 @@
|
||||||
const t = Translations.t.notes
|
const t = Translations.t.notes
|
||||||
|
|
||||||
// Info about the user who made the comment
|
// Info about the user who made the comment
|
||||||
let userinfo = Stores.FromPromise(
|
let userinfo = UIEventSource.fromPromise(Utils.downloadJsonCached<{ user: { img: { href: string } } }>(
|
||||||
Utils.downloadJsonCached<{ user: { img: { href: string } } }>(
|
"https://api.openstreetmap.org/api/0.6/user/" + comment.uid,
|
||||||
"https://api.openstreetmap.org/api/0.6/user/" + comment.uid,
|
24 * 60 * 60 * 1000
|
||||||
24 * 60 * 60 * 1000
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
const htmlElement = document.createElement("div")
|
const htmlElement = document.createElement("div")
|
||||||
htmlElement.innerHTML = Utils.purify(comment.html)
|
htmlElement.innerHTML = Utils.purify(comment.html)
|
||||||
|
|
|
@ -79,7 +79,8 @@
|
||||||
}
|
}
|
||||||
return questionsToAsk
|
return questionsToAsk
|
||||||
},
|
},
|
||||||
[skippedQuestions]
|
[skippedQuestions],
|
||||||
|
onDestroy
|
||||||
)
|
)
|
||||||
let firstQuestion: UIEventSource<TagRenderingConfig> = new UIEventSource<TagRenderingConfig>(
|
let firstQuestion: UIEventSource<TagRenderingConfig> = new UIEventSource<TagRenderingConfig>(
|
||||||
undefined
|
undefined
|
||||||
|
@ -87,6 +88,8 @@
|
||||||
let allQuestionsToAsk: UIEventSource<TagRenderingConfig[]> = new UIEventSource<
|
let allQuestionsToAsk: UIEventSource<TagRenderingConfig[]> = new UIEventSource<
|
||||||
TagRenderingConfig[]
|
TagRenderingConfig[]
|
||||||
>([])
|
>([])
|
||||||
|
onDestroy(() => firstQuestion.destroy())
|
||||||
|
onDestroy(() => allQuestionsToAsk.destroy())
|
||||||
|
|
||||||
async function calculateQuestions() {
|
async function calculateQuestions() {
|
||||||
const qta = questionsToAsk.data
|
const qta = questionsToAsk.data
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
|
||||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let tags: UIEventSource<Record<string, string> | undefined>
|
export let tags: UIEventSource<Record<string, string> | undefined>
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@
|
||||||
throw "Config is undefined in tagRenderingAnswer"
|
throw "Config is undefined in tagRenderingAnswer"
|
||||||
}
|
}
|
||||||
let trs: Store<{ then: Translation; icon?: string; iconClass?: string }[]> = tags.mapD((tags) =>
|
let trs: Store<{ then: Translation; icon?: string; iconClass?: string }[]> = tags.mapD((tags) =>
|
||||||
Utils.NoNull(config?.GetRenderValues(tags))
|
Utils.NoNull(config?.GetRenderValues(tags)),onDestroy
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
||||||
|
|
||||||
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||||
let isKnown = tags.mapD((tags) => config.GetRenderValue(tags) !== undefined)
|
let isKnown = tags.mapD((tags) => config.GetRenderValue(tags) !== undefined, onDestroy)
|
||||||
let matchesEmpty = config.GetRenderValue({}) !== undefined
|
let matchesEmpty = config.GetRenderValue({}) !== undefined
|
||||||
|
|
||||||
// Will be bound if a freeform is available
|
// Will be bound if a freeform is available
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
* The tags to apply to mark this answer as "unknown"
|
* The tags to apply to mark this answer as "unknown"
|
||||||
*/
|
*/
|
||||||
let onMarkUnknown: Store<UploadableTag[] | undefined> = tags.mapD((tags) =>
|
let onMarkUnknown: Store<UploadableTag[] | undefined> = tags.mapD((tags) =>
|
||||||
config.markUnknown(layer, tags)
|
config.markUnknown(layer, tags), onDestroy
|
||||||
)
|
)
|
||||||
let unknownModal = new UIEventSource(false)
|
let unknownModal = new UIEventSource(false)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
import { ExclamationTriangle } from "@babeard/svelte-heroicons/solid/ExclamationTriangle"
|
import { ExclamationTriangle } from "@babeard/svelte-heroicons/solid/ExclamationTriangle"
|
||||||
import ReviewPrivacyShield from "./ReviewPrivacyShield.svelte"
|
import ReviewPrivacyShield from "./ReviewPrivacyShield.svelte"
|
||||||
import ThemeViewState from "../../Models/ThemeViewState"
|
import ThemeViewState from "../../Models/ThemeViewState"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let state: ThemeViewState
|
export let state: ThemeViewState
|
||||||
export let tags: UIEventSource<Record<string, string>>
|
export let tags: UIEventSource<Record<string, string>>
|
||||||
|
@ -46,7 +47,7 @@
|
||||||
return "too_long"
|
return "too_long"
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
})
|
}, onDestroy)
|
||||||
|
|
||||||
let uploadFailed: string = undefined
|
let uploadFailed: string = undefined
|
||||||
let isTesting = state?.featureSwitchIsTesting
|
let isTesting = state?.featureSwitchIsTesting
|
||||||
|
|
|
@ -4,10 +4,11 @@
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
import FilterToggle from "./FilterToggle.svelte"
|
import FilterToggle from "./FilterToggle.svelte"
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let activeFilter: ActiveFilter[]
|
export let activeFilter: ActiveFilter[]
|
||||||
let { control, filter } = activeFilter[0]
|
let { control, filter } = activeFilter[0]
|
||||||
let option = control.map((c) => filter.options[c] ?? filter.options[0])
|
let option = control.map((c) => filter.options[c] ?? filter.options[0], onDestroy)
|
||||||
let loading = false
|
let loading = false
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
import Locale from "../i18n/Locale"
|
import Locale from "../i18n/Locale"
|
||||||
import { Store } from "../../Logic/UIEventSource"
|
import { Store } from "../../Logic/UIEventSource"
|
||||||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
let searchTerm = state.searchState.searchTerm
|
let searchTerm = state.searchState.searchTerm
|
||||||
|
@ -20,7 +21,8 @@
|
||||||
|
|
||||||
let filtersMerged = filterResults.map(
|
let filtersMerged = filterResults.map(
|
||||||
(filters) => FilterSearch.mergeSemiIdenticalLayers(filters, Locale.language.data),
|
(filters) => FilterSearch.mergeSemiIdenticalLayers(filters, Locale.language.data),
|
||||||
[Locale.language]
|
[Locale.language],
|
||||||
|
onDestroy
|
||||||
)
|
)
|
||||||
|
|
||||||
let layerResults = state.searchState.layerSuggestions.map(
|
let layerResults = state.searchState.layerSuggestions.map(
|
||||||
|
@ -32,7 +34,7 @@
|
||||||
}
|
}
|
||||||
return layers
|
return layers
|
||||||
},
|
},
|
||||||
[activeLayers]
|
[activeLayers], onDestroy
|
||||||
)
|
)
|
||||||
let filterResultsClipped: Store<{
|
let filterResultsClipped: Store<{
|
||||||
clipped: (FilterSearchResult[] | LayerConfig)[]
|
clipped: (FilterSearchResult[] | LayerConfig)[]
|
||||||
|
@ -46,7 +48,7 @@
|
||||||
}
|
}
|
||||||
return { clipped: ls.slice(0, 4), rest: ls.slice(4) }
|
return { clipped: ls.slice(0, 4), rest: ls.slice(4) }
|
||||||
},
|
},
|
||||||
[layerResults, activeLayers, Locale.language]
|
[layerResults, activeLayers, Locale.language], onDestroy
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
import FeaturePropertiesStore from "../../Logic/FeatureSource/Actors/FeaturePropertiesStore"
|
import FeaturePropertiesStore from "../../Logic/FeatureSource/Actors/FeaturePropertiesStore"
|
||||||
import SearchState from "../../Logic/State/SearchState"
|
import SearchState from "../../Logic/State/SearchState"
|
||||||
import ArrowUp from "@babeard/svelte-heroicons/mini/ArrowUp"
|
import ArrowUp from "@babeard/svelte-heroicons/mini/ArrowUp"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher, onDestroy } from "svelte"
|
||||||
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
|
|
||||||
export let entry: GeocodeResult
|
export let entry: GeocodeResult
|
||||||
export let state: {
|
export let state: {
|
||||||
|
@ -21,7 +22,7 @@
|
||||||
theme?: ThemeConfig
|
theme?: ThemeConfig
|
||||||
featureProperties?: FeaturePropertiesStore
|
featureProperties?: FeaturePropertiesStore
|
||||||
searchState: Partial<SearchState>
|
searchState: Partial<SearchState>
|
||||||
}
|
} & SpecialVisualizationState
|
||||||
|
|
||||||
let layer: LayerConfig
|
let layer: LayerConfig
|
||||||
let tags: UIEventSource<Record<string, string>>
|
let tags: UIEventSource<Record<string, string>>
|
||||||
|
@ -33,13 +34,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let distance = state.mapProperties.location.mapD((l) =>
|
let distance = state.mapProperties.location.mapD((l) =>
|
||||||
GeoOperations.distanceBetween([l.lon, l.lat], [entry.lon, entry.lat])
|
GeoOperations.distanceBetween([l.lon, l.lat], [entry.lon, entry.lat]), onDestroy
|
||||||
)
|
)
|
||||||
let bearing = state.mapProperties.location.mapD((l) =>
|
let bearing = state.mapProperties.location.mapD((l) =>
|
||||||
GeoOperations.bearing([l.lon, l.lat], [entry.lon, entry.lat])
|
GeoOperations.bearing([l.lon, l.lat], [entry.lon, entry.lat]), onDestroy)
|
||||||
)
|
|
||||||
let mapRotation = state.mapProperties.rotation
|
let mapRotation = state.mapProperties.rotation
|
||||||
let inView = state.mapProperties.bounds.mapD((bounds) => bounds.contains([entry.lon, entry.lat]))
|
let inView = state.mapProperties.bounds.mapD((bounds) => bounds.contains([entry.lon, entry.lat]), onDestroy)
|
||||||
|
|
||||||
let dispatch = createEventDispatcher<{ select: GeocodeResult }>()
|
let dispatch = createEventDispatcher<{ select: GeocodeResult }>()
|
||||||
function select() {
|
function select() {
|
||||||
|
|
|
@ -12,25 +12,22 @@
|
||||||
import { CogIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import { CogIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte"
|
||||||
import { MinimalThemeInformation } from "../../Models/ThemeConfig/ThemeConfig"
|
import { MinimalThemeInformation } from "../../Models/ThemeConfig/ThemeConfig"
|
||||||
|
import ThemeViewState from "../../Models/ThemeViewState"
|
||||||
|
import { WithSearchState } from "../../Models/ThemeViewState/WithSearchState"
|
||||||
|
import { Store } from "../../Logic/UIEventSource"
|
||||||
|
import { Utils } from "../../Utils"
|
||||||
|
|
||||||
export let state: SpecialVisualizationState
|
export let state: WithSearchState
|
||||||
let searchTerm = state.searchState.searchTerm
|
let searchTerm = state.searchState.searchTerm
|
||||||
let recentThemes = state.userRelatedState.recentlyVisitedThemes.value.map((themes) => {
|
let visitedThemes = state.userRelatedState.recentlyVisitedThemes.value
|
||||||
const recent = themes.filter((th) => th !== state.theme.id).slice(0, 6)
|
let recentThemes: Store<string[]> = visitedThemes.map((themes) =>
|
||||||
const deduped: MinimalThemeInformation[] = []
|
Utils.Dedup(themes.filter((th) => th !== state.theme.id)).slice(0, 6))
|
||||||
for (const theme of recent) {
|
|
||||||
if (deduped.some((th) => th.id === theme.id)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
deduped.push(theme)
|
|
||||||
}
|
|
||||||
return deduped
|
|
||||||
})
|
|
||||||
let themeResults = state.searchState.themeSuggestions
|
let themeResults = state.searchState.themeSuggestions
|
||||||
|
|
||||||
const t = Translations.t.general.search
|
const t = Translations.t.general.search
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{JSON.stringify($visitedThemes)}
|
||||||
{#if $themeResults.length > 0}
|
{#if $themeResults.length > 0}
|
||||||
<SidebarUnit>
|
<SidebarUnit>
|
||||||
<h3>
|
<h3>
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
AndroidPolyfill.init()
|
AndroidPolyfill.init()
|
||||||
let webgl_supported = webgl_support()
|
let webgl_supported = webgl_support()
|
||||||
|
|
||||||
let availableLayers = UIEventSource.FromPromise(getAvailableLayers())
|
let availableLayers = UIEventSource.fromPromise(getAvailableLayers())
|
||||||
const state = new WithSearchState(theme, availableLayers)
|
const state = new WithSearchState(theme, availableLayers)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import WayImportButtonViz from "../Popup/ImportButtons/WayImportButtonViz"
|
||||||
import ConflateImportButtonViz from "../Popup/ImportButtons/ConflateImportButtonViz"
|
import ConflateImportButtonViz from "../Popup/ImportButtons/ConflateImportButtonViz"
|
||||||
import { PlantNetDetectionViz } from "../Popup/PlantNetDetectionViz"
|
import { PlantNetDetectionViz } from "../Popup/PlantNetDetectionViz"
|
||||||
import Constants from "../../Models/Constants"
|
import Constants from "../../Models/Constants"
|
||||||
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import { Feature, GeoJsonProperties } from "geojson"
|
import { Feature, GeoJsonProperties } from "geojson"
|
||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
import BaseUIElement from "../BaseUIElement"
|
import BaseUIElement from "../BaseUIElement"
|
||||||
|
@ -170,50 +170,46 @@ class LinkedDataFromWebsite extends SpecialVisualization {
|
||||||
}
|
}
|
||||||
const country = countryStore.data
|
const country = countryStore.data
|
||||||
if (url.startsWith("https://data.velopark.be/")) {
|
if (url.startsWith("https://data.velopark.be/")) {
|
||||||
return Stores.FromPromiseWithErr(
|
return UIEventSource.fromPromiseWithErr((async () => {
|
||||||
(async () => {
|
try {
|
||||||
try {
|
const loadAll = layer.id.toLowerCase().indexOf("maproulette") >= 0 // Dirty hack
|
||||||
const loadAll = layer.id.toLowerCase().indexOf("maproulette") >= 0 // Dirty hack
|
const features = await LinkedDataLoader.fetchVeloparkEntry(
|
||||||
const features = await LinkedDataLoader.fetchVeloparkEntry(
|
url,
|
||||||
url,
|
loadAll
|
||||||
loadAll
|
)
|
||||||
)
|
const feature =
|
||||||
const feature =
|
features.find((f) => f.properties["ref:velopark"] === url) ??
|
||||||
features.find((f) => f.properties["ref:velopark"] === url) ??
|
features[0]
|
||||||
features[0]
|
const properties = feature.properties
|
||||||
const properties = feature.properties
|
properties["ref:velopark"] = url
|
||||||
properties["ref:velopark"] = url
|
console.log("Got properties from velopark:", properties)
|
||||||
console.log("Got properties from velopark:", properties)
|
return properties
|
||||||
return properties
|
} catch (e) {
|
||||||
} catch (e) {
|
console.error(e)
|
||||||
console.error(e)
|
throw e
|
||||||
throw e
|
}
|
||||||
}
|
})())
|
||||||
})()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (country === undefined) {
|
if (country === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return Stores.FromPromiseWithErr(
|
return UIEventSource.fromPromiseWithErr((async () => {
|
||||||
(async () => {
|
try {
|
||||||
try {
|
return await LinkedDataLoader.fetchJsonLd(
|
||||||
return await LinkedDataLoader.fetchJsonLd(
|
url,
|
||||||
url,
|
{ country },
|
||||||
{ country },
|
useProxy ? "proxy" : "fetch-lod"
|
||||||
useProxy ? "proxy" : "fetch-lod"
|
)
|
||||||
)
|
} catch (e) {
|
||||||
} catch (e) {
|
console.log(
|
||||||
console.log(
|
"Could not get with proxy/download LOD, attempting to download directly. Error for ",
|
||||||
"Could not get with proxy/download LOD, attempting to download directly. Error for ",
|
url,
|
||||||
url,
|
"is",
|
||||||
"is",
|
e
|
||||||
e
|
)
|
||||||
)
|
return await LinkedDataLoader.fetchJsonLd(url, { country }, "fetch-raw")
|
||||||
return await LinkedDataLoader.fetchJsonLd(url, { country }, "fetch-raw")
|
}
|
||||||
}
|
})())
|
||||||
})()
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
[countryStore, downloadInformation]
|
[countryStore, downloadInformation]
|
||||||
)
|
)
|
||||||
|
@ -271,7 +267,7 @@ class CompareData extends SpecialVisualization {
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const url = args[0]
|
const url = args[0]
|
||||||
const readonly = args[3] === "yes"
|
const readonly = args[3] === "yes"
|
||||||
const externalData = Stores.FromPromiseWithErr(Utils.downloadJson(url))
|
const externalData = UIEventSource.fromPromiseWithErr(Utils.downloadJson(url))
|
||||||
return new SvelteUIElement(ComparisonTool, {
|
return new SvelteUIElement(ComparisonTool, {
|
||||||
url,
|
url,
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let allData = <UIEventSource<(ChangeSetData & OsmFeature)[]>>(
|
let allData = <UIEventSource<(ChangeSetData & OsmFeature)[]>>(
|
||||||
UIEventSource.FromPromise(downloadData())
|
UIEventSource.fromPromise(downloadData())
|
||||||
)
|
)
|
||||||
|
|
||||||
let overview: Store<ChangesetsOverview | undefined> = allData.mapD(
|
let overview: Store<ChangesetsOverview | undefined> = allData.mapD(
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
let homeUrl = "https://data.mapcomplete.org/changeset-metadata/"
|
let homeUrl = "https://data.mapcomplete.org/changeset-metadata/"
|
||||||
let stats_files = "file-overview.json"
|
let stats_files = "file-overview.json"
|
||||||
|
|
||||||
let indexFile = UIEventSource.FromPromise(Utils.downloadJson<string[]>(homeUrl + stats_files))
|
let indexFile = UIEventSource.fromPromise(Utils.downloadJson<string[]>(homeUrl + stats_files))
|
||||||
let prefix = /^stats.202[45]/
|
let prefix = /^stats.202[45]/
|
||||||
let filteredIndex = indexFile.mapD((index) => index.filter((path) => path.match(prefix)))
|
let filteredIndex = indexFile.mapD((index) => index.filter((path) => path.match(prefix)))
|
||||||
filteredIndex.addCallbackAndRunD((filtered) =>
|
filteredIndex.addCallbackAndRunD((filtered) =>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
let services: MCService[] = []
|
let services: MCService[] = []
|
||||||
|
|
||||||
let recheckSignal: UIEventSource<any> = new UIEventSource<any>(undefined)
|
let recheckSignal: UIEventSource<any> = new UIEventSource<any>(undefined)
|
||||||
let checkSignal = Stores.Chronic(10000)
|
let checkSignal = Stores.chronic(10000)
|
||||||
let autoCheckAgain = new UIEventSource<boolean>(false)
|
let autoCheckAgain = new UIEventSource<boolean>(false)
|
||||||
|
|
||||||
function testDownload(url: string, raw: boolean = false): Store<{ success } | { error }> {
|
function testDownload(url: string, raw: boolean = false): Store<{ success } | { error }> {
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
export let osmConnection: OsmConnection
|
export let osmConnection: OsmConnection
|
||||||
const dispatch = createEventDispatcher<{ layerSelected: string }>()
|
const dispatch = createEventDispatcher<{ layerSelected: string }>()
|
||||||
|
|
||||||
let displayName = UIEventSource.FromPromise(
|
let displayName = UIEventSource.fromPromise(
|
||||||
osmConnection.getInformationAboutUser(info.owner)
|
osmConnection.getInformationAboutUser(info.owner)
|
||||||
).mapD((response) => response.display_name)
|
).mapD((response) => response.display_name)
|
||||||
let selfId = osmConnection.userDetails.mapD((ud) => ud.uid)
|
let selfId = osmConnection.userDetails.mapD((ud) => ud.uid)
|
||||||
|
|
|
@ -23,7 +23,7 @@ export default class StudioServer {
|
||||||
constructor(url: string, userId: Store<number | undefined>) {
|
constructor(url: string, userId: Store<number | undefined>) {
|
||||||
this.url = url
|
this.url = url
|
||||||
this._userId = userId
|
this._userId = userId
|
||||||
this.overview = UIEventSource.FromPromiseWithErr(this.fetchOverviewRaw())
|
this.overview = UIEventSource.fromPromiseWithErr(this.fetchOverviewRaw())
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchOverview(): Store<
|
public fetchOverview(): Store<
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
if (!k) {
|
if (!k) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return UIEventSource.FromPromise(TagInfo.global.getStats(k, v))
|
return UIEventSource.fromPromise(TagInfo.global.getStats(k, v))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,14 @@
|
||||||
import Wikipedia from "../../assets/svg/Wikipedia.svelte"
|
import Wikipedia from "../../assets/svg/Wikipedia.svelte"
|
||||||
import Wikidatapreview from "./Wikidatapreview.svelte"
|
import Wikidatapreview from "./Wikidatapreview.svelte"
|
||||||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a wikipedia-article + wikidata preview for the given item
|
* Shows a wikipedia-article + wikidata preview for the given item
|
||||||
*/
|
*/
|
||||||
export let wikipediaDetails: Store<FullWikipediaDetails>
|
export let wikipediaDetails: Store<FullWikipediaDetails>
|
||||||
let titleOnly = wikipediaDetails.mapD(
|
let titleOnly = wikipediaDetails.mapD(
|
||||||
(details) => Object.keys(details).length === 1 && details.title !== undefined
|
(details) => Object.keys(details).length === 1 && details.title !== undefined, [], onDestroy
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,8 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
}
|
}
|
||||||
>()
|
>()
|
||||||
|
|
||||||
|
public static readonly empty: ReadonlyArray<any>
|
||||||
|
|
||||||
public static readonly isIframe = !Utils.runningFromConsole && window !== window.top
|
public static readonly isIframe = !Utils.runningFromConsole && window !== window.top
|
||||||
|
|
||||||
public static initDomPurify() {
|
public static initDomPurify() {
|
||||||
|
@ -1902,4 +1904,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
}
|
}
|
||||||
return <T>copy
|
return <T>copy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static pass(){
|
||||||
|
// Does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue