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-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-11-08 03:00:58 +01:00
public static Dupicates ( arr : string [ ] ) : string [ ] {
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-10-22 01:07:32 +02:00
public static SubstituteKeys ( txt : string | undefined , tags : any ) : string | undefined {
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 ) {
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-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-05-06 03:03:54 +02:00
}
2021-05-18 19:48:20 +02:00