chore: automated housekeeping...

This commit is contained in:
Pieter Vander Vennet 2024-10-19 14:44:55 +02:00
parent c9ce29f206
commit 40e894df8b
294 changed files with 14209 additions and 4192 deletions

View file

@ -33,7 +33,7 @@ export default class ChangeLocationAction extends OsmChangeAction {
meta: {
theme: string
reason: string
},
}
) {
super(id, true)
this.state = state
@ -66,12 +66,10 @@ export default class ChangeLocationAction extends OsmChangeAction {
return [d]
}
const insertIntoWay = new InsertPointIntoWayAction(
lat, lon, this._id, snapToWay, {
allowReuseOfPreviouslyCreatedPoints: false,
reusePointWithinMeters: 0.25,
},
).prepareChangeDescription()
const insertIntoWay = new InsertPointIntoWayAction(lat, lon, this._id, snapToWay, {
allowReuseOfPreviouslyCreatedPoints: false,
reusePointWithinMeters: 0.25,
}).prepareChangeDescription()
return [d, { ...insertIntoWay, meta: d.meta }]
}

View file

@ -38,7 +38,7 @@ export default class CreateNewNodeAction extends OsmCreateAction {
theme: string
changeType: "create" | "import" | null
specialMotivation?: string
},
}
) {
super(null, basicTags !== undefined && basicTags.length > 0)
this._basicTags = basicTags
@ -102,21 +102,12 @@ export default class CreateNewNodeAction extends OsmCreateAction {
return [newPointChange]
}
const change = new InsertPointIntoWayAction(
this._lat,
this._lon,
id,
this._snapOnto,
{
reusePointWithinMeters: this._reusePointDistance,
allowReuseOfPreviouslyCreatedPoints: this._reusePreviouslyCreatedPoint,
},
).prepareChangeDescription()
const change = new InsertPointIntoWayAction(this._lat, this._lon, id, this._snapOnto, {
reusePointWithinMeters: this._reusePointDistance,
allowReuseOfPreviouslyCreatedPoints: this._reusePreviouslyCreatedPoint,
}).prepareChangeDescription()
return [
newPointChange,
{ ...change, meta: this.meta },
]
return [newPointChange, { ...change, meta: this.meta }]
}
private setElementId(id: number) {

View file

@ -2,7 +2,7 @@ import { ChangeDescription } from "./ChangeDescription"
import { GeoOperations } from "../../GeoOperations"
import { OsmWay } from "../OsmObject"
export default class InsertPointIntoWayAction {
export default class InsertPointIntoWayAction {
private readonly _lat: number
private readonly _lon: number
private readonly _idToInsert: number
@ -21,22 +21,19 @@ export default class InsertPointIntoWayAction {
allowReuseOfPreviouslyCreatedPoints?: boolean
reusePointWithinMeters?: number
}
){
) {
this._lat = lat
this._lon = lon
this._idToInsert = idToInsert
this._snapOnto = snapOnto
this._options = options
}
/**
* Tries to create the changedescription of the way where the point is inserted
* Returns `undefined` if inserting failed
*/
public prepareChangeDescription(): Omit<ChangeDescription, "meta"> | undefined {
public prepareChangeDescription(): Omit<ChangeDescription, "meta"> | undefined {
// Project the point onto the way
console.log("Snapping a node onto an existing way...")
const geojson = this._snapOnto.asGeoJson()
@ -59,13 +56,19 @@ export default class InsertPointIntoWayAction {
}
const prev = outerring[index]
if (GeoOperations.distanceBetween(prev, projectedCoor) < this._options.reusePointWithinMeters) {
if (
GeoOperations.distanceBetween(prev, projectedCoor) <
this._options.reusePointWithinMeters
) {
// We reuse this point instead!
reusedPointId = this._snapOnto.nodes[index]
reusedPointCoordinates = this._snapOnto.coordinates[index]
}
const next = outerring[index + 1]
if (GeoOperations.distanceBetween(next, projectedCoor) < this._options.reusePointWithinMeters) {
if (
GeoOperations.distanceBetween(next, projectedCoor) <
this._options.reusePointWithinMeters
) {
// We reuse this point instead!
reusedPointId = this._snapOnto.nodes[index + 1]
reusedPointCoordinates = this._snapOnto.coordinates[index + 1]
@ -82,15 +85,13 @@ export default class InsertPointIntoWayAction {
locations.splice(index + 1, 0, [this._lon, this._lat])
ids.splice(index + 1, 0, this._idToInsert)
return {
return {
type: "way",
id: this._snapOnto.id,
changes: {
coordinates: locations,
nodes: ids,
}
},
}
}
}

View file

@ -217,7 +217,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
const url = `${
this.state.osmConnection?._oauth_config?.url ?? "https://api.openstreetmap.org"
}/api/0.6/${this.wayToReplaceId}/full`
const rawData = await Utils.downloadJsonCached<{elements: any[]}>(url, 1000)
const rawData = await Utils.downloadJsonCached<{ elements: any[] }>(url, 1000)
parsed = OsmObject.ParseObjects(rawData.elements)
}
const allNodes = parsed.filter((o) => o.type === "node")

View file

@ -49,11 +49,11 @@ export class Changes {
featureSwitches: {
featureSwitchMorePrivacy?: Store<boolean>
featureSwitchIsTesting?: Store<boolean>
},
osmConnection: OsmConnection,
reportError?: (error: string) => void,
featureProperties?: FeaturePropertiesStore,
historicalUserLocations?: FeatureSource,
}
osmConnection: OsmConnection
reportError?: (error: string) => void
featureProperties?: FeaturePropertiesStore
historicalUserLocations?: FeatureSource
allElements?: IndexedFeatureSource
},
leftRightSensitive: boolean = false,
@ -64,8 +64,11 @@ export class Changes {
this.allChanges.setData([...this.pendingChanges.data])
// If a pending change contains a negative ID, we save that
this._nextId = Math.min(-1, ...(this.pendingChanges.data?.map((pch) => pch.id ?? 0) ?? []))
if(isNaN(this._nextId) && state.reportError !== undefined){
state.reportError("Got a NaN as nextID. Pending changes IDs are:" +this.pendingChanges.data?.map(pch => pch?.id).join("."))
if (isNaN(this._nextId) && state.reportError !== undefined) {
state.reportError(
"Got a NaN as nextID. Pending changes IDs are:" +
this.pendingChanges.data?.map((pch) => pch?.id).join(".")
)
this._nextId = -100
}
this.state = state
@ -84,12 +87,12 @@ export class Changes {
// This doesn't matter however, as the '-1' is per piecewise upload, not global per changeset
}
public static createTestObject(): Changes{
public static createTestObject(): Changes {
return new Changes({
osmConnection: new OsmConnection(),
featureSwitches:{
featureSwitchIsTesting: new ImmutableStore(true)
}
featureSwitches: {
featureSwitchIsTesting: new ImmutableStore(true),
},
})
}
@ -837,12 +840,16 @@ export class Changes {
)
// We keep all the refused changes to try them again
this.pendingChanges.setData(refusedChanges.flatMap((c) => c).filter(c => {
if(c.id === null || c.id === undefined){
return false
}
return true
}))
this.pendingChanges.setData(
refusedChanges
.flatMap((c) => c)
.filter((c) => {
if (c.id === null || c.id === undefined) {
return false
}
return true
})
)
} catch (e) {
console.error(
"Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those",

View file

@ -205,12 +205,12 @@ export class ChangesetHandler {
try {
return await this.UploadWithNew(generateChangeXML, openChangeset, extraMetaTags)
} catch (e) {
const req = (<XMLHttpRequest>e)
const req = <XMLHttpRequest>e
if (req.status === 403) {
// Someone got the banhammer
// This is the message that OSM returned, will be something like "you have an important message, go to osm.org"
const msg = req.responseText
alert(msg+"\n\nWe'll take you to openstreetmap.org now")
const msg = req.responseText
alert(msg + "\n\nWe'll take you to openstreetmap.org now")
window.location.replace(this.osmConnection.Backend())
return
}

View file

@ -45,14 +45,14 @@ export class OsmConnection {
public userDetails: UIEventSource<UserDetails>
public isLoggedIn: Store<boolean>
public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
"unknown",
"unknown"
)
public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
"unknown",
"unknown"
)
public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">(
"not-attempted",
"not-attempted"
)
public preferencesHandler: OsmPreferences
public readonly _oauth_config: AuthConfig
@ -96,7 +96,7 @@ export class OsmConnection {
this.userDetails = new UIEventSource<UserDetails>(
new UserDetails(this._oauth_config.url),
"userDetails",
"userDetails"
)
if (options.fakeUser) {
const ud = this.userDetails.data
@ -117,7 +117,7 @@ export class OsmConnection {
(user) =>
user.loggedIn &&
(this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"),
[this.apiIsOnline],
[this.apiIsOnline]
)
this.isLoggedIn.addCallback((isLoggedIn) => {
if (this.userDetails.data.loggedIn == false && isLoggedIn == true) {
@ -160,17 +160,16 @@ export class OsmConnection {
defaultValue: string = undefined,
options?: {
prefix?: string
},
}
): UIEventSource<T | undefined> {
const prefix = options?.prefix ?? "mapcomplete-"
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
}
public getPreference<T extends string = string>(
key: string,
defaultValue: string = undefined,
prefix: string = "mapcomplete-",
prefix: string = "mapcomplete-"
): UIEventSource<T | undefined> {
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
}
@ -214,7 +213,7 @@ export class OsmConnection {
this.updateAuthObject()
LocalStorageSource.get("location_before_login").setData(
Utils.runningFromConsole ? undefined : window.location.href,
Utils.runningFromConsole ? undefined : window.location.href
)
this.auth.xhr(
{
@ -252,13 +251,13 @@ export class OsmConnection {
data.account_created = userInfo.getAttribute("account_created")
data.uid = Number(userInfo.getAttribute("id"))
data.languages = Array.from(
userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang"),
userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang")
).map((l) => l.textContent)
data.csCount = Number.parseInt(
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0",
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0"
)
data.tracesCount = Number.parseInt(
userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0",
userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0"
)
data.img = undefined
@ -290,7 +289,7 @@ export class OsmConnection {
action(this.userDetails.data)
}
this._onLoggedIn = []
},
}
)
}
@ -308,7 +307,7 @@ export class OsmConnection {
method: "GET" | "POST" | "PUT" | "DELETE",
header?: Record<string, string>,
content?: string,
allowAnonymous: boolean = false,
allowAnonymous: boolean = false
): Promise<string> {
const connection: osmAuth = this.auth
if (allowAnonymous && !this.auth.authenticated()) {
@ -316,7 +315,7 @@ export class OsmConnection {
`${this.Backend()}/api/0.6/${path}`,
header,
method,
content,
content
)
if (possibleResult["content"]) {
return possibleResult["content"]
@ -333,13 +332,13 @@ export class OsmConnection {
content,
path: `/api/0.6/${path}`,
},
function(err, response) {
function (err, response) {
if (err !== null) {
error(err)
} else {
ok(response)
}
},
}
)
})
}
@ -348,7 +347,7 @@ export class OsmConnection {
path: string,
content?: string,
header?: Record<string, string>,
allowAnonymous: boolean = false,
allowAnonymous: boolean = false
): Promise<T> {
return <T>await this.interact(path, "POST", header, content, allowAnonymous)
}
@ -356,7 +355,7 @@ export class OsmConnection {
public async put<T extends string>(
path: string,
content?: string,
header?: Record<string, string>,
header?: Record<string, string>
): Promise<T> {
return <T>await this.interact(path, "PUT", header, content)
}
@ -364,7 +363,7 @@ export class OsmConnection {
public async get(
path: string,
header?: Record<string, string>,
allowAnonymous: boolean = false,
allowAnonymous: boolean = false
): Promise<string> {
return await this.interact(path, "GET", header, undefined, allowAnonymous)
}
@ -403,7 +402,7 @@ export class OsmConnection {
return new Promise<{ id: number }>((ok) => {
window.setTimeout(
() => ok({ id: Math.floor(Math.random() * 1000) }),
Math.random() * 5000,
Math.random() * 5000
)
})
}
@ -415,7 +414,7 @@ export class OsmConnection {
{
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
},
true,
true
)
const parsed = JSON.parse(response)
console.log("Got result:", parsed)
@ -438,14 +437,14 @@ export class OsmConnection {
* Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words.
*/
labels: string[]
},
}
): Promise<{ id: number }> {
if (this._dryRun.data) {
console.warn("Dryrun enabled - not actually uploading GPX ", gpx)
return new Promise<{ id: number }>((ok) => {
window.setTimeout(
() => ok({ id: Math.floor(Math.random() * 1000) }),
Math.random() * 5000,
Math.random() * 5000
)
})
}
@ -462,9 +461,9 @@ export class OsmConnection {
}
const extras = {
file:
"; filename=\"" +
'; filename="' +
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
"\"\r\nContent-Type: application/gpx+xml",
'"\r\nContent-Type: application/gpx+xml',
}
const boundary = "987654"
@ -472,7 +471,7 @@ export class OsmConnection {
let body = ""
for (const key in contents) {
body += "--" + boundary + "\r\n"
body += "Content-Disposition: form-data; name=\"" + key + "\""
body += 'Content-Disposition: form-data; name="' + key + '"'
if (extras[key] !== undefined) {
body += extras[key]
}
@ -506,13 +505,13 @@ export class OsmConnection {
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`,
},
function(err) {
function (err) {
if (err !== null) {
error(err)
} else {
ok()
}
},
}
)
})
}
@ -521,7 +520,7 @@ export class OsmConnection {
* To be called by land.html
*/
public finishLogin(callback: (previousURL: string) => void) {
this.auth.authenticate(function() {
this.auth.authenticate(function () {
// Fully authed at this point
console.log("Authentication successful!")
const previousLocation = LocalStorageSource.get("location_before_login")
@ -538,8 +537,8 @@ export class OsmConnection {
? "https://mapcomplete.org/land.html"
: window.location.protocol + "//" + window.location.host + "/land.html",
/* We use 'singlePage' as much as possible, it is the most stable - including in PWA.
* However, this breaks in iframes so we open a popup in that case
*/
* However, this breaks in iframes so we open a popup in that case
*/
singlepage: !this._iframeMode,
auto: true,
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url,

View file

@ -5,7 +5,6 @@ import OSMAuthInstance = OSMAuth.osmAuth
import { Utils } from "../../Utils"
export class OsmPreferences {
/**
* A 'cache' of all the preference stores
* @private
@ -39,7 +38,6 @@ export class OsmPreferences {
})
}
private setPreferencesAll(key: string, value: string) {
if (this._allPreferences.data[key] !== value) {
this._allPreferences.data[key] = value
@ -54,11 +52,11 @@ export class OsmPreferences {
}
return this.preferences[key]
}
const pref = this.preferences[key] = new UIEventSource(value, "preference: " + key)
const pref = (this.preferences[key] = new UIEventSource(value, "preference: " + key))
if (value) {
this.setPreferencesAll(key, value)
}
pref.addCallback(v => {
pref.addCallback((v) => {
console.log("Got an update:", key, "--->", v)
this.uploadKvSplit(key, v)
this.setPreferencesAll(key, v)
@ -71,7 +69,7 @@ export class OsmPreferences {
this.seenKeys = Object.keys(prefs)
const legacy = OsmPreferences.getLegacyCombinedItems(prefs)
const merged = OsmPreferences.mergeDict(prefs)
if(Object.keys(legacy).length > 0){
if (Object.keys(legacy).length > 0) {
await this.removeLegacy(legacy)
}
for (const key in merged) {
@ -82,12 +80,7 @@ export class OsmPreferences {
}
}
public getPreference(
key: string,
defaultValue: string = undefined,
prefix?: string,
) {
public getPreference(key: string, defaultValue: string = undefined, prefix?: string) {
return this.getPreferenceSeedFromlocal(key, defaultValue, { prefix })
}
@ -100,27 +93,29 @@ export class OsmPreferences {
key: string,
defaultValue: string = undefined,
options?: {
prefix?: string,
prefix?: string
saveToLocalStorage?: true | boolean
},
}
): UIEventSource<string> {
if (options?.prefix) {
key = options.prefix + key
}
key = key.replace(/[:/"' {}.%\\]/g, "")
const localStorage = LocalStorageSource.get(key) // cached
if (localStorage.data === "null" || localStorage.data === "undefined") {
localStorage.set(undefined)
}
const pref: UIEventSource<string> = this.initPreference(key, localStorage.data ?? defaultValue) // cached
const pref: UIEventSource<string> = this.initPreference(
key,
localStorage.data ?? defaultValue
) // cached
if (this.localStorageInited.has(key)) {
return pref
}
if (options?.saveToLocalStorage ?? true) {
pref.addCallback(v => localStorage.set(v)) // Keep a local copy
pref.addCallback((v) => localStorage.set(v)) // Keep a local copy
}
this.localStorageInited.add(key)
return pref
@ -134,7 +129,7 @@ export class OsmPreferences {
public async removeLegacy(legacyDict: Record<string, string>) {
for (const k in legacyDict) {
const v = legacyDict[k]
console.log("Upgrading legacy preference",k )
console.log("Upgrading legacy preference", k)
await this.removeAllWithPrefix(k)
this.osmConnection.getPreference(k).set(v)
}
@ -148,20 +143,19 @@ export class OsmPreferences {
const newDict = {}
const allKeys: string[] = Object.keys(dict)
const normalKeys = allKeys.filter(k => !k.match(/[a-z-_0-9A-Z]*:[0-9]+/))
const normalKeys = allKeys.filter((k) => !k.match(/[a-z-_0-9A-Z]*:[0-9]+/))
for (const normalKey of normalKeys) {
if (normalKey.match(/-combined-[0-9]*$/) || normalKey.match(/-combined-length$/)) {
// Ignore legacy keys
continue
}
const partKeys = OsmPreferences.keysStartingWith(allKeys, normalKey)
const parts = partKeys.map(k => dict[k])
const parts = partKeys.map((k) => dict[k])
newDict[normalKey] = parts.join("")
}
return newDict
}
/**
* Gets all items which have a 'combined'-string, the legacy long preferences
*
@ -180,7 +174,9 @@ export class OsmPreferences {
public static getLegacyCombinedItems(dict: Record<string, string>): Record<string, string> {
const merged: Record<string, string> = {}
const keys = Object.keys(dict)
const toCheck = Utils.NoNullInplace(Utils.Dedup(keys.map(k => k.match(/(.*)-combined-[0-9]+$/)?.[1])))
const toCheck = Utils.NoNullInplace(
Utils.Dedup(keys.map((k) => k.match(/(.*)-combined-[0-9]+$/)?.[1]))
)
for (const key of toCheck) {
let i = 0
let str = ""
@ -195,7 +191,6 @@ export class OsmPreferences {
return merged
}
/**
* Bulk-downloads all preferences
* @private
@ -221,10 +216,9 @@ export class OsmPreferences {
dict[k] = pref.getAttribute("v")
}
resolve(dict)
},
}
)
})
}
/**
@ -238,7 +232,7 @@ export class OsmPreferences {
*
*/
private static keysStartingWith(allKeys: string[], key: string): string[] {
const keys = allKeys.filter(k => k === key || k.match(new RegExp(key + ":[0-9]+")))
const keys = allKeys.filter((k) => k === key || k.match(new RegExp(key + ":[0-9]+")))
keys.sort()
return keys
}
@ -249,14 +243,12 @@ export class OsmPreferences {
*
*/
private async uploadKvSplit(k: string, v: string) {
if (v === null || v === undefined || v === "" || v === "undefined" || v === "null") {
const keysToDelete = OsmPreferences.keysStartingWith(this.seenKeys, k)
await Promise.all(keysToDelete.map(k => this.deleteKeyDirectly(k)))
await Promise.all(keysToDelete.map((k) => this.deleteKeyDirectly(k)))
return
}
await this.uploadKeyDirectly(k, v.slice(0, 255))
v = v.slice(255)
let i = 0
@ -265,7 +257,6 @@ export class OsmPreferences {
v = v.slice(255)
i++
}
}
/**
@ -283,25 +274,23 @@ export class OsmPreferences {
return
}
return new Promise<void>((resolve, reject) => {
this.auth.xhr(
{
method: "DELETE",
path: "/api/0.6/user/preferences/" + encodeURIComponent(k),
headers: { "Content-Type": "text/plain" },
},
(error) => {
if (error) {
console.warn("Could not remove preference", error)
reject(error)
return
}
console.debug("Preference ", k, "removed!")
resolve()
},
)
},
)
this.auth.xhr(
{
method: "DELETE",
path: "/api/0.6/user/preferences/" + encodeURIComponent(k),
headers: { "Content-Type": "text/plain" },
},
(error) => {
if (error) {
console.warn("Could not remove preference", error)
reject(error)
return
}
console.debug("Preference ", k, "removed!")
resolve()
}
)
})
}
/**
@ -328,7 +317,6 @@ export class OsmPreferences {
}
return new Promise<void>((resolve, reject) => {
this.auth.xhr(
{
method: "PUT",
@ -343,7 +331,7 @@ export class OsmPreferences {
return
}
resolve()
},
}
)
})
}
@ -351,12 +339,10 @@ export class OsmPreferences {
async removeAllWithPrefix(prefix: string) {
const keys = this.seenKeys
for (const key of keys) {
if(!key.startsWith(prefix)){
if (!key.startsWith(prefix)) {
continue
}
await this.deleteKeyDirectly(key)
}
}
}