2020-11-11 16:23:49 +01:00
import { Translation } from "../../UI/i18n/Translation" ;
import TagRenderingConfig from "./TagRenderingConfig" ;
import LayerConfig from "./LayerConfig" ;
import { LayoutConfigJson } from "./LayoutConfigJson" ;
2021-02-26 17:22:24 +01:00
import AllKnownLayers from "../AllKnownLayers" ;
2020-11-11 16:23:49 +01:00
import SharedTagRenderings from "../SharedTagRenderings" ;
2021-01-06 02:52:38 +01:00
import { Utils } from "../../Utils" ;
2020-11-11 16:23:49 +01:00
export default class LayoutConfig {
public readonly id : string ;
public readonly maintainer : string ;
2021-04-09 17:56:13 +02:00
public readonly credits? : string ;
2020-11-11 16:23:49 +01:00
public readonly changesetmessage? : string ;
public readonly version : string ;
public readonly language : string [ ] ;
public readonly title : Translation ;
public readonly shortDescription? : Translation ;
public readonly description : Translation ;
public readonly descriptionTail? : Translation ;
public readonly icon : string ;
public readonly socialImage? : string ;
public readonly startZoom : number ;
public readonly startLat : number ;
public readonly startLon : number ;
public readonly widenFactor : number ;
public readonly roamingRenderings : TagRenderingConfig [ ] ;
public readonly defaultBackgroundId? : string ;
public readonly layers : LayerConfig [ ] ;
2021-01-06 02:52:38 +01:00
public readonly clustering ? : {
2021-01-05 00:21:00 +01:00
maxZoom : number ,
minNeededElements : number
2021-01-04 20:09:07 +01:00
} ;
2021-01-06 02:52:38 +01:00
2020-11-11 16:23:49 +01:00
public readonly hideFromOverview : boolean ;
2021-03-24 01:25:57 +01:00
public readonly lockLocation : boolean | [ [ number , number ] , [ number , number ] ] ;
2020-11-11 16:23:49 +01:00
public readonly enableUserBadge : boolean ;
public readonly enableShareScreen : boolean ;
public readonly enableMoreQuests : boolean ;
public readonly enableAddNewPoints : boolean ;
public readonly enableLayers : boolean ;
public readonly enableSearch : boolean ;
public readonly enableGeolocation : boolean ;
2021-01-04 18:55:10 +01:00
public readonly enableBackgroundLayerSelection : boolean ;
2020-11-11 16:23:49 +01:00
public readonly customCss? : string ;
2021-04-17 15:42:22 +02:00
/ *
How long is the cache valid , in seconds ?
* /
public readonly cacheTimeout? : number ;
2021-04-17 11:37:22 +02:00
private readonly _official : boolean ;
2020-11-11 16:23:49 +01:00
2021-04-17 11:37:22 +02:00
constructor ( json : LayoutConfigJson , official = true , context? : string ) {
2021-03-24 01:25:57 +01:00
this . _official = official ;
2020-11-11 16:23:49 +01:00
this . id = json . id ;
2021-01-04 18:55:10 +01:00
context = ( context ? ? "" ) + "." + this . id ;
2020-11-11 16:23:49 +01:00
this . maintainer = json . maintainer ;
2021-04-09 17:56:13 +02:00
this . credits = json . credits ;
2020-11-11 16:23:49 +01:00
this . changesetmessage = json . changesetmessage ;
this . version = json . version ;
this . language = [ ] ;
if ( typeof json . language === "string" ) {
this . language = [ json . language ] ;
} else {
this . language = json . language ;
}
2021-03-24 01:25:57 +01:00
if ( this . language . length == 0 ) {
2021-03-13 19:08:31 +01:00
throw "No languages defined. Define at least one language"
}
2021-01-04 18:55:10 +01:00
if ( json . title === undefined ) {
throw "Title not defined in " + this . id ;
2020-11-11 16:23:49 +01:00
}
2021-01-04 18:55:10 +01:00
if ( json . description === undefined ) {
throw "Description not defined in " + this . id ;
2020-11-11 16:23:49 +01:00
}
2021-01-04 18:55:10 +01:00
this . title = new Translation ( json . title , context + ".title" ) ;
this . description = new Translation ( json . description , context + ".description" ) ;
this . shortDescription = json . shortDescription === undefined ? this . description . FirstSentence ( ) : new Translation ( json . shortDescription , context + ".shortdescription" ) ;
2021-03-24 01:25:57 +01:00
this . descriptionTail = json . descriptionTail === undefined ? new Translation ( { "*" : "" } , context + ".descriptionTail" ) : new Translation ( json . descriptionTail , context + ".descriptionTail" ) ;
2020-11-11 16:23:49 +01:00
this . icon = json . icon ;
this . socialImage = json . socialImage ;
this . startZoom = json . startZoom ;
this . startLat = json . startLat ;
this . startLon = json . startLon ;
this . widenFactor = json . widenFactor ? ? 0.05 ;
this . roamingRenderings = ( json . roamingRenderings ? ? [ ] ) . map ( ( tr , i ) = > {
if ( typeof tr === "string" ) {
if ( SharedTagRenderings . SharedTagRendering [ tr ] !== undefined ) {
return SharedTagRenderings . SharedTagRendering [ tr ] ;
}
}
2021-03-24 01:25:57 +01:00
return new TagRenderingConfig ( tr , undefined , ` ${ this . id } .roaming_renderings[ ${ i } ] ` ) ;
2020-11-11 16:23:49 +01:00
}
) ;
this . defaultBackgroundId = json . defaultBackgroundId ;
this . layers = json . layers . map ( ( layer , i ) = > {
2021-01-06 02:52:38 +01:00
if ( typeof layer === "string" ) {
2021-02-26 17:22:24 +01:00
if ( AllKnownLayers . sharedLayers [ layer ] !== undefined ) {
return AllKnownLayers . sharedLayers [ layer ] ;
2020-11-11 16:23:49 +01:00
} else {
throw "Unkown fixed layer " + layer ;
}
2021-01-04 20:09:07 +01:00
}
2021-01-06 02:52:38 +01:00
// @ts-ignore
if ( layer . builtin !== undefined ) {
// @ts-ignore
const name = layer . builtin ;
2021-02-26 17:22:24 +01:00
const shared = AllKnownLayers . sharedLayersJson [ name ] ;
2021-01-06 02:52:38 +01:00
if ( shared === undefined ) {
throw "Unkown fixed layer " + name ;
}
// @ts-ignore
layer = Utils . Merge ( layer . override , shared ) ;
}
// @ts-ignore
2021-04-17 11:37:22 +02:00
return new LayerConfig ( layer , ` ${ this . id } .layers[ ${ i } ] ` , official )
2020-11-11 16:23:49 +01:00
} ) ;
2021-03-24 01:25:57 +01:00
2021-03-09 11:42:23 +01:00
// ALl the layers are constructed, let them share tags in now!
2021-03-24 01:25:57 +01:00
const roaming : { r , source : LayerConfig } [ ] = [ ]
2021-01-08 03:57:18 +01:00
for ( const layer of this . layers ) {
2021-03-24 01:25:57 +01:00
roaming . push ( { r : layer.GetRoamingRenderings ( ) , source : layer } ) ;
2021-01-08 03:57:18 +01:00
}
2021-01-04 18:55:10 +01:00
2021-01-08 03:57:18 +01:00
for ( const layer of this . layers ) {
for ( const r of roaming ) {
2021-03-24 01:25:57 +01:00
if ( r . source == layer ) {
2021-01-08 03:57:18 +01:00
continue ;
}
layer . AddRoamingRenderings ( r . r ) ;
}
}
2021-03-24 01:25:57 +01:00
for ( const layer of this . layers ) {
2021-03-09 11:42:23 +01:00
layer . AddRoamingRenderings (
{
2021-03-24 01:25:57 +01:00
titleIcons : [ ] ,
2021-03-09 11:42:23 +01:00
iconOverlays : [ ] ,
tagRenderings : this.roamingRenderings
2021-03-24 01:25:57 +01:00
}
2021-03-09 11:42:23 +01:00
) ;
}
2021-01-04 18:55:10 +01:00
2021-01-05 00:21:00 +01:00
this . clustering = {
maxZoom : 16 ,
minNeededElements : 250
} ;
2021-01-06 02:52:38 +01:00
if ( json . clustering ) {
2021-01-04 20:09:07 +01:00
this . clustering = {
2021-01-06 02:52:38 +01:00
maxZoom : json.clustering.maxZoom ? ? 18 ,
2021-01-05 00:21:00 +01:00
minNeededElements : json.clustering.minNeededElements ? ? 1
2021-01-04 20:09:07 +01:00
}
2021-01-04 18:55:10 +01:00
for ( const layer of this . layers ) {
2021-01-06 02:52:38 +01:00
if ( layer . wayHandling !== LayerConfig . WAYHANDLING_CENTER_ONLY ) {
console . error ( "WARNING: In order to allow clustering, every layer must be set to CENTER_ONLY. Layer" , layer . id , "does not respect this for layout" , this . id ) ;
2021-01-04 18:55:10 +01:00
}
}
}
2021-01-05 00:21:00 +01:00
2020-11-11 16:23:49 +01:00
this . hideFromOverview = json . hideFromOverview ? ? false ;
2021-03-15 16:35:00 +01:00
// @ts-ignore
2021-03-24 01:25:57 +01:00
if ( json . hideInOverview ) {
throw "The json for " + this . id + " contains a 'hideInOverview'. Did you mean hideFromOverview instead?"
2021-03-15 16:35:00 +01:00
}
2021-03-14 20:14:51 +01:00
this . lockLocation = json . lockLocation ? ? false ;
2020-11-11 16:23:49 +01:00
this . enableUserBadge = json . enableUserBadge ? ? true ;
this . enableShareScreen = json . enableShareScreen ? ? true ;
this . enableMoreQuests = json . enableMoreQuests ? ? true ;
this . enableLayers = json . enableLayers ? ? true ;
this . enableSearch = json . enableSearch ? ? true ;
this . enableGeolocation = json . enableGeolocation ? ? true ;
this . enableAddNewPoints = json . enableAddNewPoints ? ? true ;
this . enableBackgroundLayerSelection = json . enableBackgroundLayerSelection ? ? true ;
this . customCss = json . customCss ;
2021-04-17 15:42:22 +02:00
this . cacheTimeout = json . cacheTimout ? ? ( 60 * 24 * 60 * 60 )
2020-11-11 16:23:49 +01:00
}
2021-03-24 01:25:57 +01:00
public CustomCodeSnippets ( ) : string [ ] {
2021-04-17 11:37:22 +02:00
if ( this . _official ) {
2021-03-24 01:25:57 +01:00
return [ ] ;
}
const msg = "<br/><b>This layout uses <span class='alert'>custom javascript</span>, loaded for the wide internet. The code is printed below, please report suspicious code on the issue tracker of MapComplete:</b><br/>"
const custom = [ ] ;
for ( const layer of this . layers ) {
2021-04-17 11:37:22 +02:00
custom . push ( . . . layer . CustomCodeSnippets ( ) . map ( code = > code + "<br />" ) )
2021-03-24 01:25:57 +01:00
}
if ( custom . length === 0 ) {
return custom ;
}
custom . splice ( 0 , 0 , msg ) ;
return custom ;
}
2021-04-17 11:37:22 +02:00
public ExtractImages ( ) : Set < string > {
2021-04-09 02:57:06 +02:00
const icons = new Set < string > ( )
for ( const layer of this . layers ) {
layer . ExtractImages ( ) . forEach ( icons . add , icons )
}
icons . add ( this . icon )
icons . add ( this . socialImage )
return icons
}
2021-04-17 11:37:22 +02:00
/ * *
* Replaces all the relative image - urls with a fixed image url
* This is to fix loading from external sources
*
* It should be passed the location where the original theme file is hosted .
*
* If no images are rewritten , the same object is returned , otherwise a new copy is returned
* /
public patchImages ( originalURL : string , originalJson : string ) : LayoutConfig {
const allImages = Array . from ( this . ExtractImages ( ) )
const rewriting = new Map < string , string > ( )
// Needed for absolute urls: note that it doesn't contain a trailing slash
const origin = new URL ( originalURL ) . origin
let path = new URL ( originalURL ) . href
path = path . substring ( 0 , path . lastIndexOf ( "/" ) )
for ( const image of allImages ) {
if ( image == "" || image == undefined ) {
continue
}
if ( image . startsWith ( "http://" ) || image . startsWith ( "https://" ) ) {
continue
}
if ( image . startsWith ( "/" ) ) {
// This is an absolute path
rewriting . set ( image , origin + image )
} else if ( image . startsWith ( "./assets/themes" ) ) {
// Legacy workaround
rewriting . set ( image , path + image . substring ( image . lastIndexOf ( "/" ) ) )
} else if ( image . startsWith ( "./" ) ) {
// This is a relative url
rewriting . set ( image , path + image . substring ( 1 ) )
} else {
// This is a relative URL with only the path
rewriting . set ( image , path + image )
}
}
if ( rewriting . size == 0 ) {
return this ;
}
rewriting . forEach ( ( value , key ) = > {
console . log ( "Rewriting" , key , "==>" , value )
originalJson = originalJson . replace ( new RegExp ( key , "g" ) , value )
} )
return new LayoutConfig ( JSON . parse ( originalJson ) , false , "Layout rewriting" )
}
2020-11-11 16:23:49 +01:00
}