Merge develop

This commit is contained in:
Pieter Vander Vennet 2024-08-22 03:01:21 +02:00
commit ee77dd0fc9
288 changed files with 7485 additions and 28619 deletions

View file

@ -1,6 +1,10 @@
import { Store, UIEventSource } from "../UIEventSource"
import { Utils } from "../../Utils"
import { RasterLayerPolygon, RasterLayerUtils } from "../../Models/RasterLayers"
import {
AvailableRasterLayers,
RasterLayerPolygon,
RasterLayerUtils,
} from "../../Models/RasterLayers"
/**
* When a user pans around on the map, they might pan out of the range of the current background raster layer.
@ -9,12 +13,32 @@ import { RasterLayerPolygon, RasterLayerUtils } from "../../Models/RasterLayers"
export default class BackgroundLayerResetter {
constructor(
currentBackgroundLayer: UIEventSource<RasterLayerPolygon | undefined>,
availableLayers: Store<RasterLayerPolygon[]>
availableLayers: { store: Store<RasterLayerPolygon[]> }
) {
if (Utils.runningFromConsole) {
return
}
currentBackgroundLayer.addCallbackAndRunD((l) => {
if (
l.geometry !== undefined &&
AvailableRasterLayers.globalLayers.find(
(global) => global.properties.id !== l.properties.id
)
) {
BackgroundLayerResetter.installHandler(
currentBackgroundLayer,
availableLayers.store
)
return true // unregister
}
})
}
private static installHandler(
currentBackgroundLayer: UIEventSource<RasterLayerPolygon | undefined>,
availableLayers: Store<RasterLayerPolygon[]>
) {
// Change the baseLayer back to OSM if we go out of the current range of the layer
availableLayers.addCallbackAndRunD((availableLayers) => {
// We only check on move/on change of the availableLayers

View file

@ -1,5 +1,5 @@
import { Store, UIEventSource } from "../UIEventSource"
import { RasterLayerPolygon } from "../../Models/RasterLayers"
import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers"
/**
* Selects the appropriate raster layer as background for the given query parameter, theme setting, user preference or default value.
@ -8,7 +8,7 @@ import { RasterLayerPolygon } from "../../Models/RasterLayers"
*/
export class PreferredRasterLayerSelector {
private readonly _rasterLayerSetting: UIEventSource<RasterLayerPolygon>
private readonly _availableLayers: Store<RasterLayerPolygon[]>
private readonly _availableLayers: { store: Store<RasterLayerPolygon[]> }
private readonly _preferredBackgroundLayer: UIEventSource<
string | "photo" | "map" | "osmbasedmap" | undefined
>
@ -16,7 +16,7 @@ export class PreferredRasterLayerSelector {
constructor(
rasterLayerSetting: UIEventSource<RasterLayerPolygon>,
availableLayers: Store<RasterLayerPolygon[]>,
availableLayers: { store: Store<RasterLayerPolygon[]> },
queryParameter: UIEventSource<string>,
preferredBackgroundLayer: UIEventSource<
string | "photo" | "map" | "osmbasedmap" | undefined
@ -47,7 +47,13 @@ export class PreferredRasterLayerSelector {
this._preferredBackgroundLayer.addCallbackD((_) => self.updateLayer())
this._availableLayers.addCallbackD((_) => self.updateLayer())
rasterLayerSetting.addCallbackAndRunD((layer) => {
if (AvailableRasterLayers.globalLayers.find((l) => l.id === layer.properties.id)) {
return
}
this._availableLayers.store.addCallbackD((_) => self.updateLayer())
return true // unregister
})
self.updateLayer()
}
@ -58,9 +64,19 @@ export class PreferredRasterLayerSelector {
private updateLayer() {
// What is the ID of the layer we have to (try to) load?
const targetLayerId = this._queryParameter.data ?? this._preferredBackgroundLayer.data
const available = this._availableLayers.data
if (targetLayerId === undefined || targetLayerId === "default") {
return
}
const global = AvailableRasterLayers.globalLayers.find(
(l) => l.properties.id === targetLayerId
)
if (global) {
this._rasterLayerSetting.setData(global)
return
}
const isCategory =
targetLayerId === "photo" || targetLayerId === "osmbasedmap" || targetLayerId === "map"
const available = this._availableLayers.store.data
const foundLayer = isCategory
? available.find((l) => l.properties.category === targetLayerId)
: available.find((l) => l.properties.id === targetLayerId)

View file

@ -14,12 +14,13 @@ import licenses from "../assets/generated/license_info.json"
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages"
import questions from "../assets/generated/layers/questions.json"
import { DoesImageExist, PrevalidateTheme, ValidateThemeAndLayers } from "../Models/ThemeConfig/Conversion/Validation"
import { DoesImageExist, PrevalidateTheme } from "../Models/ThemeConfig/Conversion/Validation"
import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion"
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
import Hash from "./Web/Hash"
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson"
import { ValidateThemeAndLayers } from "../Models/ThemeConfig/Conversion/ValidateThemeAndLayers"
export default class DetermineLayout {
private static readonly _knownImages = new Set(Array.from(licenses).map((l) => l.path))
@ -107,9 +108,18 @@ export default class DetermineLayout {
).data
const id = layoutId?.toLowerCase()
const layouts = AllKnownLayouts.allKnownLayouts
if (layouts.size() == 0) {
throw "Build failed or running, no layouts are known at all"
}
if (layouts.getConfig(id) === undefined) {
const alternatives = Utils.sortedByLevenshteinDistance(id, Array.from(layouts.keys()), i => i).slice(0, 3)
const msg = (`No builtin map theme with name ${layoutId} exists. Perhaps you meant one of ${alternatives.join(", ")}`)
const alternatives = Utils.sortedByLevenshteinDistance(
id,
Array.from(layouts.keys()),
(i) => i
).slice(0, 3)
const msg = `No builtin map theme with name ${layoutId} exists. Perhaps you meant one of ${alternatives.join(
", "
)}`
throw msg
}
return layouts.get(id)
@ -200,11 +210,11 @@ export default class DetermineLayout {
id: json.id,
description: json.description,
descriptionTail: {
en: "<div class='alert'>Layer only mode.</div> The loaded custom theme actually isn't a custom theme, but only contains a layer."
en: "<div class='alert'>Layer only mode.</div> The loaded custom theme actually isn't a custom theme, but only contains a layer.",
},
icon,
title: json.name,
layers: [json]
layers: [json],
}
}
@ -217,7 +227,7 @@ export default class DetermineLayout {
tagRenderings: DetermineLayout.getSharedTagRenderings(),
tagRenderingOrder: DetermineLayout.getSharedTagRenderingOrder(),
sharedLayers: knownLayersDict,
publicLayers: new Set<string>()
publicLayers: new Set<string>(),
}
json = new FixLegacyTheme().convertStrict(json)
const raw = json
@ -241,7 +251,7 @@ export default class DetermineLayout {
}
return new LayoutConfig(json, false, {
definitionRaw: JSON.stringify(raw, null, " "),
definedAtUrl: sourceUrl
definedAtUrl: sourceUrl,
})
}

View file

@ -55,12 +55,11 @@ export default class LayoutSource extends FeatureSourceMerger {
mapProperties,
{
isActive: isDisplayed(layer.id),
maxAge: layer.maxAgeOfCache
maxAge: layer.maxAgeOfCache,
}
)
fromCache.set(layer.id, src)
}
}
const mvtSources: UpdatableFeatureSource[] = osmLayers
.filter((f) => mvtAvailableLayers.has(f.id))
@ -170,7 +169,7 @@ export default class LayoutSource extends FeatureSourceMerger {
backend,
isActive,
patchRelations: true,
fullNodeDatabase
fullNodeDatabase,
})
}
@ -202,11 +201,11 @@ export default class LayoutSource extends FeatureSourceMerger {
widenFactor: featureSwitches.layoutToUse.widenFactor,
overpassUrl: featureSwitches.overpassUrl,
overpassTimeout: featureSwitches.overpassTimeout,
overpassMaxZoom: featureSwitches.overpassMaxZoom
overpassMaxZoom: featureSwitches.overpassMaxZoom,
},
{
padToTiles: zoom.map((zoom) => Math.min(15, zoom + 1)),
isActive
isActive,
}
)
}

View file

@ -129,7 +129,7 @@ export default class MetaTagging {
state.featureProperties,
{
includeDates: !lightUpdate,
evaluateStrict: !lightUpdate
evaluateStrict: !lightUpdate,
}
)
}
@ -305,7 +305,7 @@ export default class MetaTagging {
return []
}
return [state.perLayer.get(layerId).GetFeaturesWithin(bbox)]
}
},
}
}
@ -350,8 +350,8 @@ export default class MetaTagging {
if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) {
console.warn(
"Could not calculate a " +
(isStrict ? "strict " : "") +
"calculated tag for key",
(isStrict ? "strict " : "") +
"calculated tag for key",
key,
"for feature",
feat.properties.id,
@ -359,9 +359,9 @@ export default class MetaTagging {
code,
"(in layer",
layerId +
") due to \n" +
e +
"\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features",
") due to \n" +
e +
"\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features",
e,
e.stack,
{ feat }

View file

@ -440,7 +440,13 @@ export class Changes {
}
})
if(!(result.newObjects.length === 0 && result.modifiedObjects.length === 0 && result.deletedObjects.length === 0)) {
if (
!(
result.newObjects.length === 0 &&
result.modifiedObjects.length === 0 &&
result.deletedObjects.length === 0
)
) {
console.debug(
"Calculated the pending changes: ",
result.newObjects.length,
@ -589,7 +595,13 @@ export class Changes {
if (matchFound) {
toUpload.push(c)
} else {
console.log("Refusing change about "+c.type+"/"+ c.id+" as not in the objects. No internet?")
console.log(
"Refusing change about " +
c.type +
"/" +
c.id +
" as not in the objects. No internet?"
)
refused.push(c)
}
})
@ -711,7 +723,7 @@ export class Changes {
let { toUpload, refused } = this.fragmentChanges(pending, objects)
if(toUpload.length === 0){
if (toUpload.length === 0) {
return refused
}
await this._changesetHandler.UploadChangeset(

View file

@ -154,7 +154,7 @@ export class ChangesetHandler {
if (this._reportError) {
this._reportError(e)
}
if((<XMLHttpRequest> e).status === 400){
if ((<XMLHttpRequest>e).status === 400) {
// This request is invalid. We simply drop the changes and hope that someone will analyze what went wrong with it in the upload; we pretend everything went fine
return
}

View file

@ -107,7 +107,8 @@ export class OsmConnection {
ud.name = "Fake user"
ud.totalMessages = 42
ud.languages = ["en"]
ud.description = "The 'fake-user' is a URL-parameter which allows to test features without needing an OSM account or even internet connection."
ud.description =
"The 'fake-user' is a URL-parameter which allows to test features without needing an OSM account or even internet connection."
this.loadingStatus.setData("logged-in")
}
this.UpdateCapabilities()

View file

@ -184,7 +184,6 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
"Enable/disable caching from localStorage"
)
let testingDefaultValue = false
if (
!Utils.runningFromConsole &&

View file

@ -57,7 +57,8 @@ export class GeoLocationState {
* If the user denies the geolocation this time, we unset this flag
* @private
*/
private readonly _previousLocationGrant: UIEventSource<boolean> = LocalStorageSource.GetParsed<boolean>("geolocation-permissions", false)
private readonly _previousLocationGrant: UIEventSource<boolean> =
LocalStorageSource.GetParsed<boolean>("geolocation-permissions", false)
/**
* Used to detect a permission retraction
@ -67,8 +68,8 @@ export class GeoLocationState {
/**
* A human explanation of the current gps state, to be shown on the home screen or as tooltip
*/
public readonly gpsStateExplanation : Store<Translation>
constructor() {
public readonly gpsStateExplanation: Store<Translation>
constructor() {
const self = this
this.permission.addCallbackAndRunD(async (state) => {
@ -103,33 +104,33 @@ export class GeoLocationState {
this.requestPermission()
}
this.gpsStateExplanation = this.gpsAvailable.map(
(available) => {
if (this.currentGPSLocation.data !== undefined) {
if (!this.allowMoving.data) {
return Translations.t.general.visualFeedback.islocked
}
this.gpsStateExplanation = this.gpsAvailable.map(available => {
if (!available) {
return Translations.t.general.labels.locationNotAvailable
}
if (this.permission.data === "denied") {
return Translations.t.general.geopermissionDenied
}
if (this.permission.data === "prompt") {
return Translations.t.general.labels.jumpToLocation
}
if (this.permission.data === "requested") {
return Translations.t.general.waitingForGeopermission
}
return Translations.t.general.labels.jumpToLocation
}
if (!this.allowMoving.data) {
return Translations.t.general.visualFeedback.islocked
}
if (this.currentGPSLocation.data !== undefined) {
return Translations.t.general.labels.jumpToLocation
}
return Translations.t.general.waitingForLocation
}, [this.allowMoving, this.permission, this.currentGPSLocation])
}
if (!available) {
return Translations.t.general.labels.locationNotAvailable
}
if (this.permission.data === "denied") {
return Translations.t.general.geopermissionDenied
}
if (this.permission.data === "prompt") {
return Translations.t.general.labels.jumpToLocation
}
if (this.permission.data === "requested") {
return Translations.t.general.waitingForGeopermission
}
return Translations.t.general.waitingForLocation
},
[this.allowMoving, this.permission, this.currentGPSLocation]
)
}
/**
* Requests the user to allow access to their position.
@ -208,12 +209,12 @@ export class GeoLocationState {
self._previousLocationGrant.setData(true)
},
function (e) {
if(e.code === 2 || e.code === 3){
if (e.code === 2 || e.code === 3) {
self._gpsAvailable.set(false)
return
}
self._gpsAvailable.set(true) // We go back to the default assumption that the location is physically available
if(e.code === 1) {
if (e.code === 1) {
self.permission.set("denied")
self._grantedThisSession.setData(false)
return

View file

@ -420,11 +420,13 @@ export default class UserRelatedState {
amendedPrefs.data["_" + k] = "" + userDetails[k]
}
if (userDetails.description) {
amendedPrefs.data["_description_html"] = Utils.purify(new Showdown.Converter()
.makeHtml(userDetails.description)
?.replace(/&gt;/g, ">")
?.replace(/&lt;/g, "<")
?.replace(/\n/g, ""))
amendedPrefs.data["_description_html"] = Utils.purify(
new Showdown.Converter()
.makeHtml(userDetails.description)
?.replace(/&gt;/g, ">")
?.replace(/&lt;/g, "<")
?.replace(/\n/g, "")
)
}
usersettingMetaTagging.metaTaggging_for_usersettings({ properties: amendedPrefs.data })

View file

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

View file

@ -218,7 +218,7 @@ export class TagUtils {
*
* TagUtils.KVtoProperties([new Tag("a","b"), new Tag("c","d")] // => {a: "b", c: "d"}
*/
static KVtoProperties(tags: {key: string, value: string}[]): Record<string, string> {
static KVtoProperties(tags: { key: string; value: string }[]): Record<string, string> {
const properties: Record<string, string> = {}
for (const tag of tags) {
properties[tag.key] = tag.value
@ -226,7 +226,7 @@ export class TagUtils {
return properties
}
static KVObjtoProperties(tags: {k: string, v: string}[]): Record<string, string> {
static KVObjtoProperties(tags: { k: string; v: string }[]): Record<string, string> {
const properties: Record<string, string> = {}
for (const tag of tags) {
properties[tag.k] = tag.v

View file

@ -10,7 +10,7 @@ export class IdbLocalStorage {
public static Get<T>(
key: string,
options?: { defaultValue?: T; whenLoaded?: (t: T | null) => void },
options?: { defaultValue?: T; whenLoaded?: (t: T | null) => void }
): UIEventSource<T> {
if (IdbLocalStorage._sourceCache[key] !== undefined) {
return IdbLocalStorage._sourceCache[key]

View file

@ -143,14 +143,14 @@ export default class NameSuggestionIndex {
tags: Record<string, string>,
country: string[],
location?: [number, number],
options?:{
options?: {
/**
* If set, sort by frequency instead of alphabetically
*/
sortByFrequency: boolean
}
): Promise<Mapping[]> {
const mappings: (Mapping & {frequency: number})[] = []
const mappings: (Mapping & { frequency: number })[] = []
const frequencies = await NameSuggestionIndex.fetchFrequenciesFor(type, country)
for (const key in tags) {
if (key.startsWith("_")) {
@ -196,11 +196,11 @@ export default class NameSuggestionIndex {
// As such, it should be "true" but this is not supported
priorityIf: frequency > 0 ? new RegexTag("id", /.*/) : undefined,
searchTerms: { "*": [nsiItem.displayName, nsiItem.id] },
frequency: frequency ?? -1
frequency: frequency ?? -1,
})
}
}
if(options?.sortByFrequency){
if (options?.sortByFrequency) {
mappings.sort((a, b) => b.frequency - a.frequency)
}

View file

@ -17,7 +17,7 @@ export default class ThemeViewStateHashActor {
"The possible hashes are:",
"",
MenuState._menuviewTabs.map((tab) => "`menu:" + tab + "`").join(","),
MenuState._themeviewTabs.map((tab) => "`theme-menu:" + tab + "`").join(",")
MenuState._themeviewTabs.map((tab) => "`theme-menu:" + tab + "`").join(","),
]
/**