2021-05-11 02:39:51 +02:00
import * as colors from "./assets/colors.json"
2021-05-18 19:48:20 +02:00
2020-07-24 01:12:57 +02:00
export class Utils {
2021-01-06 02:21:50 +01:00
/ * *
* In the 'deploy' - step , some code needs to be run by ts - node .
* However , ts - node crashes when it sees 'document' . When running from console , we flag this and disable all code where document is needed .
* This is a workaround and yet another hack
* /
2021-09-22 05:02:09 +02:00
public static runningFromConsole = typeof window === "undefined" ;
2020-11-17 02:22:48 +01:00
public static readonly assets_path = "./assets/svg/" ;
2021-09-26 17:36:39 +02:00
public static externalDownloadFunction : ( url : string , headers? : any ) = > Promise < any > ;
2021-11-07 16:34:51 +01:00
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 \` .
2021-10-31 02:08:39 +01:00
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.
If a value to substitute is undefined , empty string will be used instead .
This supports multiple values , e . g . \ ` ref= $ source:geometry:type/ $ source:geometry:ref \`
Remark that the syntax is slightly different then expected ; it uses '$' to note a value to copy , followed by a name ( matched with \ ` [a-zA-Z0-9_:]* \` ). Sadly, delimiting with \` {} \` as these already mark the boundaries of the special rendering...
Note that these values can be prepare with javascript in the theme by using a [ calculatedTag ] ( calculatedTags . md # calculating - tags - with - javascript )
`
2021-12-09 13:16:40 +01:00
public static readonly special_visualizations_importRequirementDocs = ` #### Importing a dataset into OpenStreetMap: requirements
If you want to import a dataset , make sure that :
1 . The dataset to import has a suitable license
2 . The community has been informed of the import
3 . All other requirements of the [ import guidelines ] ( https : //wiki.openstreetmap.org/wiki/Import/Guidelines) have been followed
There are also some technicalities in your theme to keep in mind :
1 . The new feature will be added and will flow through the program as any other new point as if it came from OSM .
This means that there should be a layer which will match the new tags and which will display it .
2 . The original feature from your geojson layer will gain the tag '_imported=yes' .
This should be used to change the appearance or even to hide it ( eg by changing the icon size to zero )
3 . There should be a way for the theme to detect previously imported points , even after reloading .
A reference number to the original dataset is an excellent way to do this
4 . When importing ways , the theme creator is also responsible of avoiding overlapping ways .
# # # # Disabled in unofficial themes
The import button can be tested in an unofficial theme by adding \ ` test=true \` or \` backend=osm-test \` as [URL-paramter](URL_Parameters.md).
The import button will show up then . If in testmode , you can read the changeset - XML directly in the web console .
In the case that MapComplete is pointed to the testing grounds , the edit will be made on https : //master.apis.dev.openstreetmap.org`
/ * *
* Parses the arguments for special visualisations
* /
public static ParseVisArgs ( specs : { name : string , defaultValue? : string } [ ] , args : string [ ] ) : any {
const parsed = { } ;
if ( args . length > specs . length ) {
throw "To much arguments for special visualization: got " + args . join ( "," ) + " but expected only " + args . length + " arguments"
}
for ( let i = 0 ; i < specs . length ; i ++ ) {
const spec = specs [ i ] ;
let arg = args [ i ] ? . trim ( ) ;
if ( arg === undefined || arg === "" ) {
arg = spec . defaultValue
}
parsed [ spec . name ] = arg
}
return parsed ;
}
2021-11-07 16:34:51 +01:00
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 } > ( )
2020-09-30 22:22:58 +02:00
static EncodeXmlValue ( str ) {
2021-05-18 19:48:20 +02:00
if ( typeof str !== "string" ) {
str = "" + str
2021-05-09 18:56:51 +02:00
}
2021-05-18 19:48:20 +02:00
2020-09-30 22:22:58 +02:00
return str . replace ( /&/g , '&' )
. replace ( /</g , '<' )
. replace ( />/g , '>' )
. replace ( /"/g , '"' )
. replace ( /'/g , ''' )
}
2020-07-24 01:12:57 +02:00
/ * *
* Gives a clean float , or undefined if parsing fails
* @param str
* /
static asFloat ( str ) : number {
if ( str ) {
const i = parseFloat ( str ) ;
if ( isNaN ( i ) ) {
return undefined ;
}
return i ;
}
return undefined ;
}
2020-09-30 22:22:58 +02:00
public static Upper ( str : string ) {
return str . substr ( 0 , 1 ) . toUpperCase ( ) + str . substr ( 1 ) ;
}
2020-10-04 01:04:46 +02:00
public static TwoDigits ( i : number ) {
if ( i < 10 ) {
return "0" + i ;
}
return "" + i ;
}
2020-10-30 00:56:46 +01:00
public static Round ( i : number ) {
2021-03-09 13:10:48 +01:00
if ( i < 0 ) {
2020-10-30 00:56:46 +01:00
return "-" + Utils . Round ( - i ) ;
}
const j = "" + Math . floor ( i * 10 ) ;
if ( j . length == 1 ) {
return "0." + j ;
}
return j . substr ( 0 , j . length - 1 ) + "." + j . substr ( j . length - 1 , j . length ) ;
}
2020-10-04 01:04:46 +02:00
public static Times ( f : ( ( i : number ) = > string ) , count : number ) : string {
2020-09-30 22:22:58 +02:00
let res = "" ;
for ( let i = 0 ; i < count ; i ++ ) {
2020-10-04 01:04:46 +02:00
res += f ( i ) ;
2020-09-30 22:22:58 +02:00
}
return res ;
2020-07-24 14:46:25 +02:00
}
2020-07-31 04:58:58 +02:00
2021-07-03 22:24:12 +02:00
public static TimesT < T > ( count : number , f : ( ( i : number ) = > T ) ) : T [ ] {
let res : T [ ] = [ ] ;
2021-06-16 14:23:53 +02:00
for ( let i = 0 ; i < count ; i ++ ) {
2021-07-03 22:24:12 +02:00
res . push ( f ( i ) ) ;
2021-06-16 14:23:53 +02:00
}
return res ;
}
2020-08-08 02:16:42 +02:00
public static NoNull < T > ( array : T [ ] ) : T [ ] {
const ls : T [ ] = [ ] ;
for ( const t of array ) {
if ( t === undefined || t === null ) {
continue ;
}
ls . push ( t ) ;
}
return ls ;
}
2021-03-09 13:10:48 +01:00
2021-10-18 22:17:15 +02:00
public static Hist ( array : string [ ] ) : Map < string , number > {
2021-10-04 03:12:42 +02:00
const hist = new Map < string , number > ( ) ;
for ( const s of array ) {
hist . set ( s , 1 + ( hist . get ( s ) ? ? 0 ) )
}
return hist ;
}
2021-10-18 22:17:15 +02:00
2021-03-09 13:10:48 +01:00
public static NoEmpty ( array : string [ ] ) : string [ ] {
2020-08-22 13:02:31 +02:00
const ls : string [ ] = [ ] ;
for ( const t of array ) {
if ( t === "" ) {
continue ;
}
ls . push ( t ) ;
}
return ls ;
}
2020-08-08 02:16:42 +02:00
2021-03-09 13:10:48 +01:00
public static EllipsesAfter ( str : string , l : number = 100 ) {
2021-06-15 16:18:58 +02:00
if ( str === undefined || str === null ) {
2020-09-03 03:16:43 +02:00
return undefined ;
}
2021-03-09 13:10:48 +01:00
if ( str . length <= l ) {
2020-08-26 00:21:34 +02:00
return str ;
}
2021-03-09 13:10:48 +01:00
return str . substr ( 0 , l - 3 ) + "..." ;
2020-08-26 00:21:34 +02:00
}
2021-03-09 13:10:48 +01:00
public static Dedup ( arr : string [ ] ) : string [ ] {
if ( arr === undefined ) {
2020-08-26 15:36:04 +02:00
return undefined ;
}
const newArr = [ ] ;
for ( const string of arr ) {
2020-10-02 19:00:24 +02:00
if ( newArr . indexOf ( string ) < 0 ) {
2020-08-26 15:36:04 +02:00
newArr . push ( string ) ;
}
}
return newArr ;
}
2021-09-09 00:05:51 +02:00
2021-12-21 18:35:31 +01:00
public static Dupiclates ( arr : string [ ] ) : string [ ] {
2021-11-08 03:00:58 +01:00
if ( arr === undefined ) {
return undefined ;
}
const newArr = [ ] ;
const seen = new Set < string > ( ) ;
for ( const string of arr ) {
if ( seen . has ( string ) ) {
newArr . push ( string )
}
seen . add ( string )
}
return newArr ;
}
2021-09-09 00:05:51 +02:00
public static Identical < T > ( t1 : T [ ] , t2 : T [ ] , eq ? : ( t : T , t0 : T ) = > boolean ) : boolean {
if ( t1 . length !== t2 . length ) {
2021-07-15 20:47:28 +02:00
return false
}
eq = ( a , b ) = > a === b
2021-09-09 00:05:51 +02:00
for ( let i = 0 ; i < t1 . length ; i ++ ) {
if ( ! eq ( t1 [ i ] , t2 [ i ] ) ) {
2021-07-15 20:47:28 +02:00
return false
}
}
return true ;
}
2021-09-09 00:05:51 +02:00
2020-10-02 19:00:24 +02:00
public static MergeTags ( a : any , b : any ) {
2020-08-27 00:08:00 +02:00
const t = { } ;
for ( const k in a ) {
t [ k ] = a [ k ] ;
}
for ( const k in b ) {
t [ k ] = b [ k ] ;
}
return t ;
}
2020-10-02 19:00:24 +02:00
public static SplitFirst ( a : string , sep : string ) : string [ ] {
2020-08-31 02:59:47 +02:00
const index = a . indexOf ( sep ) ;
2020-10-02 19:00:24 +02:00
if ( index < 0 ) {
2020-08-31 02:59:47 +02:00
return [ a ] ;
}
2020-10-02 19:00:24 +02:00
return [ a . substr ( 0 , index ) , a . substr ( index + sep . length ) ] ;
2020-08-31 02:59:47 +02:00
}
2020-07-31 04:58:58 +02:00
2021-12-14 17:29:21 +01:00
public static SubstituteKeys ( txt : string | undefined , tags : any , useLang? : string ) : string | undefined {
2021-10-22 01:07:32 +02:00
if ( txt === undefined ) {
return undefined
}
2021-10-18 22:17:15 +02:00
const regex = /.*{([^}]*)}.*/
let match = txt . match ( regex )
while ( match ) {
const key = match [ 1 ]
2021-11-07 18:37:42 +01:00
let v = tags [ key ]
if ( v !== undefined ) {
2021-12-06 03:24:33 +01:00
if ( v [ "toISOString" ] != undefined ) {
// This is a date, probably the timestamp of the object
// @ts-ignore
const date : Date = el ;
v = date . toISOString ( )
}
2021-12-14 17:29:21 +01:00
if ( useLang !== undefined && v ? . translations !== undefined ) {
v = v . translations [ useLang ] ? ? v . translations [ "*" ] ? ? ( v . textFor !== undefined ? v . textFor ( useLang ) : v ) ;
}
2021-12-06 03:24:33 +01:00
if ( v . InnerConstructElement !== undefined ) {
2021-12-07 17:46:57 +01:00
console . warn ( "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 )
2021-12-06 03:24:33 +01:00
v = ( < HTMLElement > v . InnerConstructElement ( ) ) ? . innerText
}
2021-11-07 18:37:42 +01:00
if ( typeof v !== "string" ) {
v = "" + v
}
v = v . replace ( /\n/g , "<br/>" )
}
txt = txt . replace ( "{" + key + "}" , v ? ? "" )
2021-10-18 22:17:15 +02:00
match = txt . match ( regex )
2021-06-14 02:39:23 +02:00
}
2021-10-22 01:07:32 +02:00
2021-06-14 02:39:23 +02:00
return txt ;
}
2021-03-09 13:10:48 +01:00
public static LoadCustomCss ( location : string ) {
2021-02-20 16:48:42 +01:00
const head = document . getElementsByTagName ( 'head' ) [ 0 ] ;
const link = document . createElement ( 'link' ) ;
2020-11-14 03:26:09 +01:00
link . id = "customCss" ;
link . rel = 'stylesheet' ;
link . type = 'text/css' ;
link . href = location ;
link . media = 'all' ;
head . appendChild ( link ) ;
2021-11-10 18:42:31 +01:00
console . log ( "Added custom css file " , location )
2020-11-14 03:26:09 +01:00
}
2021-03-09 13:10:48 +01:00
2021-06-20 03:06:00 +02:00
/ * *
2021-09-28 17:30:48 +02:00
* Copies all key - value pairs of the source into the target . This will change the target
2021-06-20 03:06:00 +02:00
* If the key starts with a '+' , the values of the list will be appended to the target instead of overwritten
* @param source
* @param target
* @constructor
2021-09-28 17:30:48 +02:00
* @return the second parameter as is
2021-06-20 03:06:00 +02:00
* /
2021-03-09 13:10:48 +01:00
static Merge ( source : any , target : any ) {
2021-01-06 02:52:38 +01:00
for ( const key in source ) {
2021-05-27 18:55:12 +02:00
if ( ! source . hasOwnProperty ( key ) ) {
2021-05-19 22:38:05 +02:00
continue
}
2021-06-20 03:06:00 +02:00
if ( key . startsWith ( "+" ) || key . endsWith ( "+" ) ) {
const trimmedKey = key . replace ( "+" , "" ) ;
const sourceV = source [ key ] ;
const targetV = ( target [ trimmedKey ] ? ? [ ] )
let newList : any [ ] ;
if ( key . startsWith ( "+" ) ) {
newList = sourceV . concat ( targetV )
} else {
newList = targetV . concat ( sourceV )
}
target [ trimmedKey ] = newList ;
continue ;
}
2021-01-06 02:52:38 +01:00
const sourceV = source [ key ] ;
2021-11-09 18:23:28 +01:00
if ( target === null ) {
return source
}
2021-01-06 02:52:38 +01:00
const targetV = target [ key ]
2021-09-07 00:23:00 +02:00
if ( typeof sourceV === "object" ) {
2021-07-27 19:35:43 +02:00
if ( sourceV === null ) {
target [ key ] = null
} else if ( targetV === undefined ) {
2021-01-06 02:52:38 +01:00
target [ key ] = sourceV ;
2021-03-09 13:10:48 +01:00
} else {
2021-01-06 02:52:38 +01:00
Utils . Merge ( sourceV , targetV ) ;
}
2021-03-09 13:10:48 +01:00
} else {
2021-01-06 02:52:38 +01:00
target [ key ] = sourceV ;
}
2021-03-09 13:10:48 +01:00
2021-01-06 02:52:38 +01:00
}
return target ;
}
2021-10-18 22:17:15 +02:00
2021-03-09 13:10:48 +01:00
static getOrSetDefault < K , V > ( dict : Map < K , V > , k : K , v : ( ) = > V ) {
let found = dict . get ( k ) ;
if ( found !== undefined ) {
return found ;
}
dict . set ( k , v ( ) ) ;
return dict . get ( k ) ;
}
2021-03-14 20:15:11 +01:00
2021-04-04 03:22:56 +02:00
public static MinifyJSON ( stringified : string ) : string {
stringified = stringified . replace ( /\|/g , "||" ) ;
const keys = Utils . knownKeys . concat ( Utils . extraKeys ) ;
for ( let i = 0 ; i < keys . length ; i ++ ) {
const knownKey = keys [ i ] ;
let code = i ;
if ( i >= 124 ) {
code += 1 ; // Character 127 is our 'escape' character |
}
let replacement = "|" + String . fromCharCode ( code )
stringified = stringified . replace ( new RegExp ( ` \ " ${ knownKey } \ ": ` , "g" ) , replacement ) ;
}
return stringified ;
}
public static UnMinify ( minified : string ) : string {
2021-07-03 22:24:12 +02:00
if ( minified === undefined || minified === null ) {
2021-06-14 02:39:23 +02:00
return undefined ;
}
2021-07-03 22:24:12 +02:00
2021-04-04 03:22:56 +02:00
const parts = minified . split ( "|" ) ;
let result = parts . shift ( ) ;
const keys = Utils . knownKeys . concat ( Utils . extraKeys ) ;
for ( const part of parts ) {
if ( part == "" ) {
// Empty string => this was a || originally
result += "|"
continue
}
const i = part . charCodeAt ( 0 ) ;
result += "\"" + keys [ i ] + "\":" + part . substring ( 1 )
}
return result ;
}
2021-09-22 16:07:56 +02:00
public static injectJsonDownloadForTests ( url : string , data ) {
Utils . injectedDownloads [ url ] = data
}
2021-09-28 17:30:48 +02:00
public static download ( url : string , headers? : any ) : Promise < string > {
2021-07-03 22:24:12 +02:00
if ( this . externalDownloadFunction !== undefined ) {
2021-09-26 17:36:39 +02:00
return this . externalDownloadFunction ( url , headers )
2021-06-22 14:21:32 +02:00
}
2021-09-22 16:07:56 +02:00
return new Promise ( ( resolve , reject ) = > {
const xhr = new XMLHttpRequest ( ) ;
xhr . onload = ( ) = > {
if ( xhr . status == 200 ) {
2021-09-28 17:30:48 +02:00
resolve ( xhr . response )
2021-09-28 18:40:45 +02:00
} else if ( xhr . status === 509 || xhr . status === 429 ) {
reject ( "rate limited" )
2021-09-22 16:07:56 +02:00
} else {
reject ( xhr . statusText )
}
} ;
xhr . open ( 'GET' , url ) ;
2021-09-26 17:36:39 +02:00
if ( headers !== undefined ) {
for ( const key in headers ) {
xhr . setRequestHeader ( key , headers [ key ] )
}
}
2021-09-22 16:07:56 +02:00
xhr . send ( ) ;
2021-09-29 16:55:05 +02:00
xhr . onerror = reject
2021-06-22 14:21:32 +02:00
}
)
}
2021-05-07 13:17:42 +02:00
2021-11-12 04:11:53 +01:00
public static upload ( url : string , data , headers? : any ) : Promise < string > {
return new Promise ( ( resolve , reject ) = > {
const xhr = new XMLHttpRequest ( ) ;
xhr . onload = ( ) = > {
if ( xhr . status == 200 ) {
resolve ( xhr . response )
} else if ( xhr . status === 509 || xhr . status === 429 ) {
reject ( "rate limited" )
} else {
reject ( xhr . statusText )
}
} ;
xhr . open ( 'POST' , url ) ;
if ( headers !== undefined ) {
for ( const key in headers ) {
xhr . setRequestHeader ( key , headers [ key ] )
}
}
xhr . send ( data ) ;
xhr . onerror = reject
}
)
}
2021-10-14 03:46:09 +02:00
public static async downloadJsonCached ( url : string , maxCacheTimeMs : number , headers? : any ) : Promise < any > {
const cached = Utils . _download_cache . get ( url )
2021-10-18 22:17:15 +02:00
if ( cached !== undefined ) {
if ( ( new Date ( ) . getTime ( ) - cached . timestamp ) <= maxCacheTimeMs ) {
2021-10-14 03:46:09 +02:00
return cached . promise
}
}
2021-10-20 00:19:33 +02:00
const promise = /*NO AWAIT as we work with the promise directly */ Utils . downloadJson ( url , headers )
2021-10-14 03:46:09 +02:00
Utils . _download_cache . set ( url , { promise , timestamp : new Date ( ) . getTime ( ) } )
return await promise
}
2021-09-28 17:30:48 +02:00
public static async downloadJson ( url : string , headers? : any ) : Promise < any > {
2021-09-28 18:40:45 +02:00
const injected = Utils . injectedDownloads [ url ]
if ( injected !== undefined ) {
console . log ( "Using injected resource for test for URL" , url )
return new Promise ( ( resolve , _ ) = > resolve ( injected ) )
}
2021-09-28 17:30:48 +02:00
const data = await Utils . download ( url , Utils . Merge ( { "accept" : "application/json" } , headers ? ? { } ) )
2021-09-28 18:40:45 +02:00
try {
return JSON . parse ( data )
} catch ( e ) {
console . error ( "Could not parse " , data , "due to" , e , "\n" , e . stack )
throw e ;
}
2021-09-28 17:30:48 +02:00
}
2021-05-10 23:46:19 +02:00
/ * *
* Triggers a 'download file' popup which will download the contents
* /
2021-07-27 19:59:41 +02:00
public static offerContentsAsDownloadableFile ( contents : string | Blob , fileName : string = "download.txt" ,
2021-09-09 00:05:51 +02:00
options ? : { mimetype : string } ) {
2021-05-07 13:17:42 +02:00
const element = document . createElement ( "a" ) ;
2021-07-22 14:33:49 +02:00
let file ;
2021-07-27 19:35:43 +02:00
if ( typeof ( contents ) === "string" ) {
2021-07-27 19:59:41 +02:00
file = new Blob ( [ contents ] , { type : options ? . mimetype ? ? 'text/plain' } ) ;
2021-07-27 19:35:43 +02:00
} else {
file = contents ;
}
2021-05-07 13:17:42 +02:00
element . href = URL . createObjectURL ( file ) ;
element . download = fileName ;
document . body . appendChild ( element ) ; // Required for this to work in FireFox
element . click ( ) ;
}
2021-05-18 19:48:20 +02:00
public static ColourNameToHex ( color : string ) : string {
2021-05-11 02:39:51 +02:00
return colors [ color . toLowerCase ( ) ] ? ? color ;
}
2021-05-18 19:48:20 +02:00
public static HexToColourName ( hex : string ) : string {
2021-05-11 02:39:51 +02:00
hex = hex . toLowerCase ( )
2021-05-18 19:48:20 +02:00
if ( ! hex . startsWith ( "#" ) ) {
2021-05-11 02:39:51 +02:00
return hex ;
}
const c = Utils . color ( hex ) ;
2021-05-18 19:48:20 +02:00
2021-05-11 02:39:51 +02:00
let smallestDiff = Number . MAX_VALUE ;
let bestColor = undefined ;
for ( const color in colors ) {
2021-05-18 19:48:20 +02:00
if ( ! colors . hasOwnProperty ( color ) ) {
2021-05-11 02:39:51 +02:00
continue ;
}
const foundhex = colors [ color ] ;
2021-05-18 19:48:20 +02:00
if ( typeof foundhex !== "string" ) {
2021-05-11 02:39:51 +02:00
continue
}
2021-05-18 19:48:20 +02:00
if ( foundhex === hex ) {
2021-05-11 02:39:51 +02:00
return color
}
const diff = this . colorDiff ( Utils . color ( foundhex ) , c )
2021-05-18 19:48:20 +02:00
if ( diff > 50 ) {
2021-05-11 02:39:51 +02:00
continue ;
}
2021-05-18 19:48:20 +02:00
if ( diff < smallestDiff ) {
2021-05-11 02:39:51 +02:00
smallestDiff = diff ;
bestColor = color ;
}
}
return bestColor ? ? hex ;
}
2021-05-19 22:38:05 +02:00
2021-09-14 18:20:25 +02:00
static sortKeys ( o : any ) {
2021-09-22 16:07:56 +02:00
const copy = { }
2021-09-14 18:20:25 +02:00
let keys = Object . keys ( o )
keys = keys . sort ( )
for ( const key of keys ) {
let v = o [ key ]
2021-09-22 16:07:56 +02:00
if ( typeof v === "object" ) {
2021-09-14 18:20:25 +02:00
v = Utils . sortKeys ( v )
}
copy [ key ] = v
}
return copy
}
2021-09-26 17:36:39 +02:00
public static async waitFor ( timeMillis : number ) : Promise < void > {
return new Promise ( ( resolve ) = > {
window . setTimeout ( resolve , timeMillis ) ;
} )
}
2021-10-18 22:17:15 +02:00
public static toHumanTime ( seconds ) : string {
2021-10-13 01:28:46 +02:00
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
2021-10-18 22:17:15 +02:00
if ( days > 0 ) {
return days + "days" + " " + hours + "h"
2021-10-13 01:28:46 +02:00
}
2021-10-18 22:17:15 +02:00
return hours + ":" + Utils . TwoDigits ( minutes ) + ":" + Utils . TwoDigits ( seconds )
2021-10-13 01:28:46 +02:00
}
2021-10-18 22:17:15 +02:00
public static DisableLongPresses() {
2021-10-15 05:20:02 +02:00
// 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 ) ;
}
2021-10-18 22:17:15 +02:00
public static OsmChaLinkFor ( daysInThePast , theme = undefined ) : string {
2021-10-16 18:30:24 +02:00
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 ( ) )
2021-10-30 01:59:17 +02:00
let osmcha_link = ` "date__gte":[{"label":" ${ date } ","value":" ${ date } "}],"editor":[{"label":"mapcomplete","value":"mapcomplete"}] `
2021-10-18 22:17:15 +02:00
if ( theme !== undefined ) {
2021-10-30 01:59:17 +02:00
osmcha_link = osmcha_link + "," + ` "comment":[{"label":"# ${ theme } ","value":"# ${ theme } "}] `
2021-10-16 18:30:24 +02:00
}
2021-11-07 16:34:51 +01:00
return "https://osmcha.org/?filters=" + encodeURIComponent ( "{" + osmcha_link + "}" )
2021-10-18 22:17:15 +02:00
}
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 ) ;
}
private static color ( hex : string ) : { r : number , g : number , b : number } {
if ( hex . startsWith == undefined ) {
console . trace ( "WUT?" , hex )
throw "wut?"
}
if ( ! hex . startsWith ( "#" ) ) {
return undefined ;
}
if ( hex . length === 4 ) {
return {
r : parseInt ( hex . substr ( 1 , 1 ) , 16 ) ,
g : parseInt ( hex . substr ( 2 , 1 ) , 16 ) ,
b : parseInt ( hex . substr ( 3 , 1 ) , 16 ) ,
}
}
return {
r : parseInt ( hex . substr ( 1 , 2 ) , 16 ) ,
g : parseInt ( hex . substr ( 3 , 2 ) , 16 ) ,
b : parseInt ( hex . substr ( 5 , 2 ) , 16 ) ,
}
2021-10-16 18:30:24 +02:00
}
2021-12-21 18:35:31 +01:00
/ * *
* Deepclone an object by serializing and deserializing it
* @param x
* @constructor
* /
static Clone < T > ( x : T ) : T {
if ( x === undefined ) {
return undefined ;
}
return JSON . parse ( JSON . stringify ( x ) ) ;
}
2021-05-06 03:03:54 +02:00
}
2021-05-18 19:48:20 +02:00