forked from MapComplete/MapComplete
Scripts: create script to import layers from studio into official mapcomplete
This commit is contained in:
parent
c7b905d1fb
commit
db685dc05f
5 changed files with 186 additions and 81 deletions
142
src/Utils.ts
142
src/Utils.ts
|
@ -9,7 +9,7 @@ export class Utils {
|
|||
public static runningFromConsole = typeof window === "undefined"
|
||||
public static externalDownloadFunction: (
|
||||
url: string,
|
||||
headers?: any
|
||||
headers?: any,
|
||||
) => Promise<{ content: string } | { redirect: string }>
|
||||
public static Special_visualizations_tagsToApplyHelpText = `These can either be a tag to add, such as \`amenity=fast_food\` or can use a substitution, e.g. \`addr:housenumber=$number\`.
|
||||
This new point will then have the tags \`amenity=fast_food\` and \`addr:housenumber\` with the value that was saved in \`number\` in the original feature.
|
||||
|
@ -61,7 +61,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
if (Utils.runningFromConsole) {
|
||||
return
|
||||
}
|
||||
DOMPurify.addHook("afterSanitizeAttributes", function (node) {
|
||||
DOMPurify.addHook("afterSanitizeAttributes", function(node) {
|
||||
// set all elements owning target to target=_blank + add noopener noreferrer
|
||||
const target = node.getAttribute("target")
|
||||
if (target) {
|
||||
|
@ -83,7 +83,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
*/
|
||||
public static ParseVisArgs<T = Record<string, string>>(
|
||||
specs: { name: string; defaultValue?: string }[],
|
||||
args: string[]
|
||||
args: string[],
|
||||
): T {
|
||||
const parsed: Record<string, string> = {}
|
||||
if (args.length > specs.length) {
|
||||
|
@ -238,7 +238,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
object: any,
|
||||
name: string,
|
||||
init: () => any,
|
||||
whenDone?: () => void
|
||||
whenDone?: () => void,
|
||||
) {
|
||||
Object.defineProperty(object, name, {
|
||||
enumerable: false,
|
||||
|
@ -266,7 +266,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
object: any,
|
||||
name: string,
|
||||
init: () => Promise<any>,
|
||||
whenDone?: () => void
|
||||
whenDone?: () => void,
|
||||
) {
|
||||
Object.defineProperty(object, name, {
|
||||
enumerable: false,
|
||||
|
@ -334,7 +334,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
*/
|
||||
public static DedupOnId<T = { id: string }>(
|
||||
arr: T[],
|
||||
toKey?: (t: T) => string | string[]
|
||||
toKey?: (t: T) => string | string[],
|
||||
): T[] {
|
||||
const uniq: T[] = []
|
||||
const seen = new Set<string>()
|
||||
|
@ -461,7 +461,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
public static SubstituteKeys(
|
||||
txt: string | undefined,
|
||||
tags: Record<string, any> | undefined,
|
||||
useLang?: string
|
||||
useLang?: string,
|
||||
): string | undefined {
|
||||
if (txt === undefined) {
|
||||
return undefined
|
||||
|
@ -493,7 +493,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
"SubstituteKeys received a BaseUIElement to substitute in - this is probably a bug and will be downcast to a string\nThe key is",
|
||||
key,
|
||||
"\nThe value is",
|
||||
v
|
||||
v,
|
||||
)
|
||||
v = v.InnerConstructElement()?.textContent
|
||||
}
|
||||
|
@ -614,7 +614,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
if (!Array.isArray(targetV)) {
|
||||
throw new Error(
|
||||
"Cannot concatenate: value to add is not an array: " +
|
||||
JSON.stringify(targetV)
|
||||
JSON.stringify(targetV),
|
||||
)
|
||||
}
|
||||
if (Array.isArray(sourceV)) {
|
||||
|
@ -622,9 +622,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
} else {
|
||||
throw new Error(
|
||||
"Could not merge concatenate " +
|
||||
JSON.stringify(sourceV) +
|
||||
" and " +
|
||||
JSON.stringify(targetV)
|
||||
JSON.stringify(sourceV) +
|
||||
" and " +
|
||||
JSON.stringify(targetV),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
@ -660,16 +660,19 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
/**
|
||||
* Walks the specified path into the object till the end.
|
||||
*
|
||||
* If a list is encountered, this is transparently walked recursively on every object.
|
||||
* If a list is encountered, the behaviour depends on the next breadcrumb:
|
||||
* - if it is a string, the list is transparently walked recursively on every object.
|
||||
* - it is is a number, that index will be taken
|
||||
*
|
||||
* If 'null' or 'undefined' is encountered, this method stops
|
||||
*
|
||||
* The leaf objects are replaced in the object itself by the specified function.
|
||||
*/
|
||||
public static WalkPath(
|
||||
path: string[],
|
||||
path: (string | number)[],
|
||||
object: any,
|
||||
replaceLeaf: (leaf: any, travelledPath: string[]) => any,
|
||||
travelledPath: string[] = []
|
||||
travelledPath: (string)[] = [],
|
||||
): void {
|
||||
if (object == null) {
|
||||
return
|
||||
|
@ -699,12 +702,28 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
return
|
||||
}
|
||||
if (Array.isArray(sub)) {
|
||||
sub.forEach((el, i) =>
|
||||
Utils.WalkPath(path.slice(1), el, replaceLeaf, [...travelledPath, head, "" + i])
|
||||
)
|
||||
if (typeof path[1] === "number") {
|
||||
const i = path[1]
|
||||
if(path.length == 2){
|
||||
// We found the leaf
|
||||
const leaf = sub[i]
|
||||
if (leaf !== undefined) {
|
||||
sub[i] = replaceLeaf(leaf, [...travelledPath, ""+head,""+i])
|
||||
if (sub[i] === undefined) {
|
||||
delete sub[i]
|
||||
}
|
||||
}
|
||||
}else{
|
||||
Utils.WalkPath(path.slice(2), sub[i], replaceLeaf, [...travelledPath, "" + head, "" + i])
|
||||
}
|
||||
} else {
|
||||
sub.forEach((el, i) =>
|
||||
Utils.WalkPath(path.slice(1), el, replaceLeaf, [...travelledPath, "" + head, "" + i]),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
Utils.WalkPath(path.slice(1), sub, replaceLeaf, [...travelledPath, head])
|
||||
Utils.WalkPath(path.slice(1), sub, replaceLeaf, [...travelledPath, ""+head])
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -717,7 +736,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
path: string[],
|
||||
object: any,
|
||||
collectedList: { leaf: any; path: string[] }[] = [],
|
||||
travelledPath: string[] = []
|
||||
travelledPath: string[] = [],
|
||||
): { leaf: any; path: string[] }[] {
|
||||
if (object === undefined || object === null) {
|
||||
return collectedList
|
||||
|
@ -747,7 +766,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
|
||||
if (Array.isArray(sub)) {
|
||||
sub.forEach((el, i) =>
|
||||
Utils.CollectPath(path.slice(1), el, collectedList, [...travelledPath, "" + i])
|
||||
Utils.CollectPath(path.slice(1), el, collectedList, [...travelledPath, "" + i]),
|
||||
)
|
||||
return collectedList
|
||||
}
|
||||
|
@ -791,7 +810,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
json: any,
|
||||
f: (v: object | number | string | boolean | undefined, path: string[]) => any,
|
||||
isLeaf: (object) => boolean = undefined,
|
||||
path: string[] = []
|
||||
path: string[] = [],
|
||||
) {
|
||||
if (json === undefined || json === null) {
|
||||
return f(json, path)
|
||||
|
@ -830,7 +849,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
json: any,
|
||||
collect: (v: number | string | boolean | undefined, path: string[]) => any,
|
||||
isLeaf: (object) => boolean = undefined,
|
||||
path = []
|
||||
path = [],
|
||||
): void {
|
||||
if (json === undefined) {
|
||||
return
|
||||
|
@ -875,7 +894,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
|
||||
public static async download(
|
||||
url: string,
|
||||
headers?: Record<string, string>
|
||||
headers?: Record<string, string>,
|
||||
): Promise<string | undefined> {
|
||||
const result = await Utils.downloadAdvanced(url, headers)
|
||||
if (result["error"] !== undefined) {
|
||||
|
@ -889,7 +908,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
headers?: Record<string, string>,
|
||||
method: "POST" | "GET" | "PUT" | "UPDATE" | "DELETE" | "OPTIONS" = "GET",
|
||||
content?: string,
|
||||
maxAttempts: number = 3
|
||||
maxAttempts: number = 3,
|
||||
): Promise<
|
||||
| { content: string }
|
||||
| { redirect: string }
|
||||
|
@ -913,7 +932,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
console.log(
|
||||
`Request to ${url} failed, Trying again in a moment. Attempt ${
|
||||
i + 1
|
||||
}/${maxAttempts}`
|
||||
}/${maxAttempts}`,
|
||||
)
|
||||
await Utils.waitFor((i + 1) * 500)
|
||||
}
|
||||
|
@ -927,7 +946,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
url: string,
|
||||
headers?: Record<string, string>,
|
||||
method: "POST" | "GET" | "PUT" | "UPDATE" | "DELETE" | "OPTIONS" = "GET",
|
||||
content?: string
|
||||
content?: string,
|
||||
): Promise<
|
||||
| { content: string }
|
||||
| { redirect: string }
|
||||
|
@ -970,12 +989,12 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
xhr.onerror = (ev: ProgressEvent<EventTarget>) =>
|
||||
reject(
|
||||
"Could not get " +
|
||||
url +
|
||||
", xhr status code is " +
|
||||
xhr.status +
|
||||
" (" +
|
||||
xhr.statusText +
|
||||
")"
|
||||
url +
|
||||
", xhr status code is " +
|
||||
xhr.status +
|
||||
" (" +
|
||||
xhr.statusText +
|
||||
")",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -983,7 +1002,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
public static upload(
|
||||
url: string,
|
||||
data: string | Blob,
|
||||
headers?: Record<string, string>
|
||||
headers?: Record<string, string>,
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
|
@ -1012,13 +1031,13 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
url: string,
|
||||
maxCacheTimeMs: number,
|
||||
headers?: Record<string, string>,
|
||||
dontCacheErrors: boolean = false
|
||||
dontCacheErrors: boolean = false,
|
||||
): Promise<T> {
|
||||
const result = await Utils.downloadJsonCachedAdvanced(
|
||||
url,
|
||||
maxCacheTimeMs,
|
||||
headers,
|
||||
dontCacheErrors
|
||||
dontCacheErrors,
|
||||
)
|
||||
if (result["content"]) {
|
||||
return result["content"]
|
||||
|
@ -1031,7 +1050,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
maxCacheTimeMs: number,
|
||||
headers?: Record<string, string>,
|
||||
dontCacheErrors = false,
|
||||
maxAttempts = 3
|
||||
maxAttempts = 3,
|
||||
): Promise<
|
||||
{ content: T } | { error: string; url: string; statuscode?: number; errContent?: object }
|
||||
> {
|
||||
|
@ -1043,10 +1062,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
}
|
||||
const promise =
|
||||
/*NO AWAIT as we work with the promise directly */ Utils.downloadJsonAdvanced<T>(
|
||||
url,
|
||||
headers,
|
||||
maxAttempts
|
||||
)
|
||||
url,
|
||||
headers,
|
||||
maxAttempts,
|
||||
)
|
||||
Utils._download_cache.set(url, { promise, timestamp: new Date().getTime() })
|
||||
try {
|
||||
return await promise
|
||||
|
@ -1060,12 +1079,12 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
|
||||
public static async downloadJson<T = object | []>(
|
||||
url: string,
|
||||
headers?: Record<string, string>
|
||||
headers?: Record<string, string>,
|
||||
): Promise<T>
|
||||
public static async downloadJson<T>(url: string, headers?: Record<string, string>): Promise<T>
|
||||
public static async downloadJson(
|
||||
url: string,
|
||||
headers?: Record<string, string>
|
||||
headers?: Record<string, string>,
|
||||
): Promise<object | []> {
|
||||
const result = await Utils.downloadJsonAdvanced(url, headers)
|
||||
if (result["content"]) {
|
||||
|
@ -1085,7 +1104,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
public static async downloadJsonAdvanced<T = object | []>(
|
||||
url: string,
|
||||
headers?: Record<string, string>,
|
||||
maxAttempts = 3
|
||||
maxAttempts = 3,
|
||||
): Promise<
|
||||
{ content: T } | { error: string; url: string; statuscode?: number; errContent?: object }
|
||||
> {
|
||||
|
@ -1098,7 +1117,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
Utils.Merge({ accept: "application/json" }, headers ?? {}),
|
||||
"GET",
|
||||
undefined,
|
||||
maxAttempts
|
||||
maxAttempts,
|
||||
)
|
||||
if (result["error"] !== undefined) {
|
||||
return <{ error: string; url: string; statuscode?: number }>result
|
||||
|
@ -1121,7 +1140,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
"due to",
|
||||
e,
|
||||
"\n",
|
||||
e.stack
|
||||
e.stack,
|
||||
)
|
||||
return { error: "malformed", url }
|
||||
}
|
||||
|
@ -1142,7 +1161,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
| "{gpx=application/gpx+xml}"
|
||||
| "application/json"
|
||||
| "image/png"
|
||||
}
|
||||
},
|
||||
) {
|
||||
const element = document.createElement("a")
|
||||
let file
|
||||
|
@ -1263,19 +1282,19 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
|
||||
public static sortedByLevenshteinDistance(
|
||||
reference: string,
|
||||
ts: ReadonlyArray<string>
|
||||
ts: ReadonlyArray<string>,
|
||||
): string[]
|
||||
public static sortedByLevenshteinDistance<T>(
|
||||
reference: string,
|
||||
ts: ReadonlyArray<T>,
|
||||
getName: (t: T) => string
|
||||
getName: (t: T) => string,
|
||||
): T[]
|
||||
public static sortedByLevenshteinDistance<T>(
|
||||
reference: string,
|
||||
ts: ReadonlyArray<T>,
|
||||
getName?: (t: T) => string
|
||||
getName?: (t: T) => string,
|
||||
): T[] {
|
||||
getName ??= (str) => <string> str;
|
||||
getName ??= (str) => <string>str
|
||||
const withDistance: [T, number][] = ts.map((t) => [
|
||||
t,
|
||||
Utils.levenshteinDistance(getName(t), reference),
|
||||
|
@ -1300,7 +1319,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
track[j][i] = Math.min(
|
||||
track[j][i - 1] + 1, // deletion
|
||||
track[j - 1][i] + 1, // insertion
|
||||
track[j - 1][i - 1] + indicator // substitution
|
||||
track[j - 1][i - 1] + indicator, // substitution
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1310,11 +1329,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
public static MapToObj<V>(d: Map<string, V>): Record<string, V>
|
||||
public static MapToObj<V, T>(
|
||||
d: Map<string, V>,
|
||||
onValue: (t: V, key: string) => T
|
||||
onValue: (t: V, key: string) => T,
|
||||
): Record<string, T>
|
||||
public static MapToObj<V, T>(
|
||||
d: Map<string, V>,
|
||||
onValue: (t: V, key: string) => T = undefined
|
||||
onValue: (t: V, key: string) => T = undefined,
|
||||
): Record<string, T> {
|
||||
const o = {}
|
||||
const keys = Array.from(d.keys())
|
||||
|
@ -1332,7 +1351,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
* Utils.TransposeMap({"a" : ["b", "c"], "x" : ["b", "y"]}) // => {"b" : ["a", "x"], "c" : ["a"], "y" : ["x"]}
|
||||
*/
|
||||
public static TransposeMap<K extends string, V extends string>(
|
||||
d: Record<K, V[]>
|
||||
d: Record<K, V[]>,
|
||||
): Record<V, K[]> {
|
||||
const newD: Record<V, K[]> = <any>{}
|
||||
|
||||
|
@ -1355,7 +1374,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
* {"a": "b", "c":"d"} // => {"b":"a", "d":"c"}
|
||||
*/
|
||||
public static transposeMapSimple<K extends string, V extends string>(
|
||||
d: Record<K, V>
|
||||
d: Record<K, V>,
|
||||
): Record<V, K> {
|
||||
const inv = <Record<V, K>>{}
|
||||
for (const k in d) {
|
||||
|
@ -1438,7 +1457,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
}
|
||||
|
||||
public static asDict(
|
||||
tags: { key: string; value: string | number }[]
|
||||
tags: { key: string; value: string | number }[],
|
||||
): Map<string, string | number> {
|
||||
const d = new Map<string, string | number>()
|
||||
|
||||
|
@ -1451,7 +1470,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
|
||||
public static asRecord<K extends string | number | symbol, V>(
|
||||
keys: K[],
|
||||
f: (k: K) => V
|
||||
f: (k: K) => V,
|
||||
): Record<K, V> {
|
||||
const results = <Record<K, V>>{}
|
||||
for (const key of keys) {
|
||||
|
@ -1564,7 +1583,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
*
|
||||
*/
|
||||
public static splitIntoSubstitutionParts(
|
||||
template: string
|
||||
template: string,
|
||||
): ({ message: string } | { subs: string })[] {
|
||||
const preparts = template.split("{")
|
||||
const spec: ({ message: string } | { subs: string })[] = []
|
||||
|
@ -1738,7 +1757,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
}
|
||||
|
||||
private static findParentWithScrolling(
|
||||
element: HTMLBaseElement | HTMLDivElement
|
||||
element: HTMLBaseElement | HTMLDivElement,
|
||||
): HTMLBaseElement | HTMLDivElement {
|
||||
// Check if the element itself has scrolling
|
||||
if (element.scrollHeight > element.clientHeight) {
|
||||
|
@ -1820,6 +1839,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
href = href.replaceAll(/ /g, "%20")
|
||||
return href
|
||||
}
|
||||
|
||||
private static emojiRegex = /[\p{Extended_Pictographic}🛰️]/u
|
||||
|
||||
/**
|
||||
|
@ -1855,6 +1875,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
for (const key of allKeys) {
|
||||
copy[key] = object[key]
|
||||
}
|
||||
return <T> copy
|
||||
return <T>copy
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue