forked from MapComplete/MapComplete
		
	Fix in SubstituteKeys: implementation is now robust against non-iterable tags (because they are lazy)
This commit is contained in:
		
							parent
							
								
									be9d70d62b
								
							
						
					
					
						commit
						46e3fa84de
					
				
					 1 changed files with 74 additions and 78 deletions
				
			
		
							
								
								
									
										152
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										152
									
								
								Utils.ts
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -12,6 +12,8 @@ export class Utils {
 | 
			
		|||
    public static externalDownloadFunction: (url: string, headers?: any) => Promise<any>;
 | 
			
		||||
    private static knownKeys = ["addExtraTags", "and", "calculatedTags", "changesetmessage", "clustering", "color", "condition", "customCss", "dashArray", "defaultBackgroundId", "description", "descriptionTail", "doNotDownload", "enableAddNewPoints", "enableBackgroundLayerSelection", "enableGeolocation", "enableLayers", "enableMoreQuests", "enableSearch", "enableShareScreen", "enableUserBadge", "freeform", "hideFromOverview", "hideInAnswer", "icon", "iconOverlays", "iconSize", "id", "if", "ifnot", "isShown", "key", "language", "layers", "lockLocation", "maintainer", "mappings", "maxzoom", "maxZoom", "minNeededElements", "minzoom", "multiAnswer", "name", "or", "osmTags", "passAllFeatures", "presets", "question", "render", "roaming", "roamingRenderings", "rotation", "shortDescription", "socialImage", "source", "startLat", "startLon", "startZoom", "tagRenderings", "tags", "then", "title", "titleIcons", "type", "version", "wayHandling", "widenFactor", "width"]
 | 
			
		||||
    private static extraKeys = ["nl", "en", "fr", "de", "pt", "es", "name", "phone", "email", "amenity", "leisure", "highway", "building", "yes", "no", "true", "false"]
 | 
			
		||||
    private static injectedDownloads = {}
 | 
			
		||||
    private static _download_cache = new Map<string, { promise: Promise<any>, timestamp: number }>()
 | 
			
		||||
 | 
			
		||||
    static EncodeXmlValue(str) {
 | 
			
		||||
        if (typeof str !== "string") {
 | 
			
		||||
| 
						 | 
				
			
			@ -89,14 +91,14 @@ export class Utils {
 | 
			
		|||
        return ls;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Hist(array: string[]): Map<string, number>{
 | 
			
		||||
    public static Hist(array: string[]): Map<string, number> {
 | 
			
		||||
        const hist = new Map<string, number>();
 | 
			
		||||
        for (const s of array) {
 | 
			
		||||
            hist.set(s, 1 + (hist.get(s) ?? 0))
 | 
			
		||||
        }
 | 
			
		||||
        return hist;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public static NoEmpty(array: string[]): string[] {
 | 
			
		||||
        const ls: string[] = [];
 | 
			
		||||
        for (const t of array) {
 | 
			
		||||
| 
						 | 
				
			
			@ -164,18 +166,17 @@ export class Utils {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public static SubstituteKeys(txt: string, tags: any) {
 | 
			
		||||
        for (const key in tags) {
 | 
			
		||||
            if (!tags.hasOwnProperty(key)) {
 | 
			
		||||
                continue
 | 
			
		||||
            }
 | 
			
		||||
            try{
 | 
			
		||||
            txt = txt.replace(new RegExp("{" + key + "}", "g"), tags[key] ?? "")
 | 
			
		||||
            }catch(e){
 | 
			
		||||
                console.error("WEIRD" , e)
 | 
			
		||||
                throw e
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        const regex = /.*{([^}]*)}.*/
 | 
			
		||||
 | 
			
		||||
        let match = txt.match(regex)
 | 
			
		||||
 | 
			
		||||
        while (match) {
 | 
			
		||||
            const key = match[1]
 | 
			
		||||
            txt = txt.replace("{" + key + "}", tags[key] ?? "")
 | 
			
		||||
            match = txt.match(regex)
 | 
			
		||||
        }
 | 
			
		||||
        txt = txt.replace(new RegExp('{.*}', "g"), "")
 | 
			
		||||
      
 | 
			
		||||
        return txt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -238,7 +239,7 @@ export class Utils {
 | 
			
		|||
        }
 | 
			
		||||
        return target;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    static getOrSetDefault<K, V>(dict: Map<K, V>, k: K, v: () => V) {
 | 
			
		||||
        let found = dict.get(k);
 | 
			
		||||
        if (found !== undefined) {
 | 
			
		||||
| 
						 | 
				
			
			@ -288,9 +289,6 @@ export class Utils {
 | 
			
		|||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private static injectedDownloads = {}
 | 
			
		||||
 | 
			
		||||
    public static injectJsonDownloadForTests(url: string, data) {
 | 
			
		||||
        Utils.injectedDownloads[url] = data
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -325,11 +323,10 @@ export class Utils {
 | 
			
		|||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static _download_cache = new Map<string, {promise: Promise<any>, timestamp: number}>()
 | 
			
		||||
    public static async downloadJsonCached(url: string, maxCacheTimeMs: number, headers?: any): Promise<any> {
 | 
			
		||||
        const cached = Utils._download_cache.get(url)
 | 
			
		||||
        if(cached !== undefined){
 | 
			
		||||
            if((new Date().getTime() - cached.timestamp) <= maxCacheTimeMs){
 | 
			
		||||
        if (cached !== undefined) {
 | 
			
		||||
            if ((new Date().getTime() - cached.timestamp) <= maxCacheTimeMs) {
 | 
			
		||||
                return cached.promise
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -407,6 +404,63 @@ export class Utils {
 | 
			
		|||
        return bestColor ?? hex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static sortKeys(o: any) {
 | 
			
		||||
        const copy = {}
 | 
			
		||||
        let keys = Object.keys(o)
 | 
			
		||||
        keys = keys.sort()
 | 
			
		||||
        for (const key of keys) {
 | 
			
		||||
            let v = o[key]
 | 
			
		||||
            if (typeof v === "object") {
 | 
			
		||||
                v = Utils.sortKeys(v)
 | 
			
		||||
            }
 | 
			
		||||
            copy[key] = v
 | 
			
		||||
        }
 | 
			
		||||
        return copy
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async waitFor(timeMillis: number): Promise<void> {
 | 
			
		||||
        return new Promise((resolve) => {
 | 
			
		||||
            window.setTimeout(resolve, timeMillis);
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static toHumanTime(seconds): string {
 | 
			
		||||
        seconds = Math.floor(seconds)
 | 
			
		||||
        let minutes = Math.floor(seconds / 60)
 | 
			
		||||
        seconds = seconds % 60
 | 
			
		||||
        let hours = Math.floor(minutes / 60)
 | 
			
		||||
        minutes = minutes % 60
 | 
			
		||||
        let days = Math.floor(hours / 24)
 | 
			
		||||
        hours = hours % 24
 | 
			
		||||
        if (days > 0) {
 | 
			
		||||
            return days + "days" + " " + hours + "h"
 | 
			
		||||
        }
 | 
			
		||||
        return hours + ":" + Utils.TwoDigits(minutes) + ":" + Utils.TwoDigits(seconds)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static DisableLongPresses() {
 | 
			
		||||
        // Remove all context event listeners on mobile to prevent long presses
 | 
			
		||||
        window.addEventListener('contextmenu', (e) => { // Not compatible with IE < 9
 | 
			
		||||
 | 
			
		||||
            if (e.target["nodeName"] === "INPUT") {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            return false;
 | 
			
		||||
        }, false);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static OsmChaLinkFor(daysInThePast, theme = undefined): string {
 | 
			
		||||
        const now = new Date()
 | 
			
		||||
        const lastWeek = new Date(now.getTime() - daysInThePast * 24 * 60 * 60 * 1000)
 | 
			
		||||
        const date = lastWeek.getFullYear() + "-" + Utils.TwoDigits(lastWeek.getMonth() + 1) + "-" + Utils.TwoDigits(lastWeek.getDate())
 | 
			
		||||
        let osmcha_link = `{"date__gte":[{"label":"${date}","value":"${date}"}],"editor":[{"label":"mapcomplete","value":"mapcomplete"}]}`
 | 
			
		||||
        if (theme !== undefined) {
 | 
			
		||||
            osmcha_link = osmcha_link + "," + `{"comment":[{"label":"#${theme}","value":"#${theme}"}]`
 | 
			
		||||
        }
 | 
			
		||||
        return "https://osmcha.org/?filters=" + encodeURIComponent(osmcha_link)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static colorDiff(c0: { r: number, g: number, b: number }, c1: { r: number, g: number, b: number }) {
 | 
			
		||||
        return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b);
 | 
			
		||||
| 
						 | 
				
			
			@ -434,63 +488,5 @@ export class Utils {
 | 
			
		|||
            b: parseInt(hex.substr(5, 2), 16),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static sortKeys(o: any) {
 | 
			
		||||
        const copy = {}
 | 
			
		||||
        let keys = Object.keys(o)
 | 
			
		||||
        keys = keys.sort()
 | 
			
		||||
        for (const key of keys) {
 | 
			
		||||
            let v = o[key]
 | 
			
		||||
            if (typeof v === "object") {
 | 
			
		||||
                v = Utils.sortKeys(v)
 | 
			
		||||
            }
 | 
			
		||||
            copy[key] = v
 | 
			
		||||
        }
 | 
			
		||||
        return copy
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async waitFor(timeMillis: number): Promise<void> {
 | 
			
		||||
        return new Promise((resolve) => {
 | 
			
		||||
            window.setTimeout(resolve, timeMillis);
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static toHumanTime(seconds): string{
 | 
			
		||||
        seconds = Math.floor(seconds)
 | 
			
		||||
        let minutes = Math.floor(seconds / 60)
 | 
			
		||||
        seconds = seconds % 60
 | 
			
		||||
        let hours = Math.floor(minutes / 60)
 | 
			
		||||
        minutes = minutes % 60
 | 
			
		||||
        let days = Math.floor(hours / 24)
 | 
			
		||||
        hours = hours % 24
 | 
			
		||||
        if(days > 0){
 | 
			
		||||
            return days+"days"+" "+hours+"h"
 | 
			
		||||
        }
 | 
			
		||||
            return hours+":"+Utils.TwoDigits(minutes)+":"+Utils.TwoDigits(seconds)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static DisableLongPresses(){
 | 
			
		||||
        // Remove all context event listeners on mobile to prevent long presses
 | 
			
		||||
        window.addEventListener('contextmenu', (e) => { // Not compatible with IE < 9
 | 
			
		||||
 | 
			
		||||
            if (e.target["nodeName"] === "INPUT") {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            return false;
 | 
			
		||||
        }, false);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static OsmChaLinkFor(daysInThePast, theme = undefined) : string {
 | 
			
		||||
        const now = new Date()
 | 
			
		||||
        const lastWeek = new Date(now.getTime() - daysInThePast * 24 * 60 * 60 * 1000)
 | 
			
		||||
        const date = lastWeek.getFullYear() + "-" + Utils.TwoDigits(lastWeek.getMonth() + 1) + "-" + Utils.TwoDigits(lastWeek.getDate())
 | 
			
		||||
        let osmcha_link = `{"date__gte":[{"label":"${date}","value":"${date}"}],"editor":[{"label":"mapcomplete","value":"mapcomplete"}]}`
 | 
			
		||||
        if(theme !== undefined){
 | 
			
		||||
            osmcha_link = osmcha_link + "," + `{"comment":[{"label":"#${theme}","value":"#${theme}"}]`
 | 
			
		||||
        }
 | 
			
		||||
        return  "https://osmcha.org/?filters="+ encodeURIComponent(osmcha_link)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue