2020-11-11 16:23:49 +01:00
import { Translation } from "../../UI/i18n/Translation"
2021-08-07 23:11:34 +02:00
import { LayoutConfigJson } from "./Json/LayoutConfigJson"
import LayerConfig from "./LayerConfig"
2021-09-07 00:23:00 +02:00
import { LayerConfigJson } from "./Json/LayerConfigJson"
2021-09-29 16:55:05 +02:00
import Constants from "../Constants"
2021-10-14 21:43:14 +02:00
import TilesourceConfig from "./TilesourceConfig"
2022-02-09 22:37:21 +01:00
import { ExtractImages } from "./Conversion/FixImages"
2022-02-14 04:48:33 +01:00
import ExtraLinkConfig from "./ExtraLinkConfig"
2023-01-13 02:48:48 +01:00
import { Utils } from "../../Utils"
2023-03-09 17:11:44 +01:00
import LanguageUtils from "../../Utils/LanguageUtils"
2023-02-11 15:04:20 +01:00
/ * *
* Minimal information about a theme
* * /
export class LayoutInformation {
id : string
icon : string
title : any
shortDescription : any
definition? : any
mustHaveLanguage? : boolean
hideFromOverview? : boolean
keywords? : any [ ]
}
export default class LayoutConfig implements LayoutInformation {
2022-03-08 04:09:03 +01:00
public static readonly defaultSocialImage = "assets/SocialImage.png"
2020-11-11 16:23:49 +01:00
public readonly id : string
2021-04-09 17:56:13 +02:00
public readonly credits? : string
2020-11-11 16:23:49 +01:00
public readonly language : string [ ]
public readonly title : Translation
2021-12-21 18:35:31 +01:00
public readonly shortDescription : Translation
2020-11-11 16:23:49 +01:00
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
2022-09-13 00:42:29 +02:00
public widenFactor : number
2022-09-14 12:18:51 +02:00
public defaultBackgroundId? : string
2021-05-17 00:18:21 +02:00
public layers : LayerConfig [ ]
2021-10-14 21:43:14 +02:00
public tileLayerSources : TilesourceConfig [ ]
2021-01-06 02:52:38 +01:00
public readonly clustering ? : {
2021-01-05 00:21:00 +01:00
maxZoom : number
2021-10-13 00:43:19 +02:00
minNeededElements : number
2021-01-04 20:09:07 +01:00
}
2020-11-11 16:23:49 +01:00
public readonly hideFromOverview : boolean
2021-05-27 18:56:02 +02:00
public 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
2021-05-17 00:18:21 +02:00
public readonly enableShowAllQuestions : boolean
2021-07-16 01:42:09 +02:00
public readonly enableExportButton : boolean
2021-07-28 02:51:07 +02:00
public readonly enablePdfDownload : boolean
2022-09-08 21:40:48 +02:00
2020-11-11 16:23:49 +01:00
public readonly customCss? : string
2022-09-08 21:40:48 +02:00
2021-09-29 16:55:05 +02:00
public readonly overpassUrl : string [ ]
2022-09-14 12:18:51 +02:00
public overpassTimeout : number
2021-10-15 05:20:02 +02:00
public readonly overpassMaxZoom : number
public readonly osmApiTileSize : number
2021-09-18 02:32:40 +02:00
public readonly official : boolean
2022-02-14 04:48:33 +01:00
public readonly usedImages : string [ ]
public readonly extraLink? : ExtraLinkConfig
2022-07-01 00:16:05 +02:00
public readonly definedAtUrl? : string
2022-04-18 02:39:30 +02:00
public readonly definitionRaw? : string
2022-07-01 00:16:05 +02:00
constructor (
json : LayoutConfigJson ,
official = true ,
options ? : {
2022-04-18 02:39:30 +02:00
definedAtUrl? : string
definitionRaw? : string
}
) {
2021-09-18 02:32:40 +02:00
this . official = official
2020-11-11 16:23:49 +01:00
this . id = json . id
2022-04-18 02:39:30 +02:00
this . definedAtUrl = options ? . definedAtUrl
this . definitionRaw = options ? . definitionRaw
2022-01-26 21:40:38 +01:00
if ( official ) {
if ( json . id . toLowerCase ( ) !== json . id ) {
throw "The id of a theme should be lowercase: " + json . id
2022-01-08 13:04:11 +01:00
}
2022-01-26 21:40:38 +01:00
if ( json . id . match ( /[a-z0-9-_]/ ) == null ) {
throw "The id of a theme should match [a-z0-9-_]*: " + json . id
2022-01-08 13:04:11 +01:00
}
2021-12-30 22:36:34 +01:00
}
2022-07-01 00:16:05 +02:00
const context = this . id
2021-04-09 17:56:13 +02:00
this . credits = json . credits
2022-10-04 14:23:19 +02:00
this . language = json . mustHaveLanguage ? ? Object . keys ( json . title )
2022-02-18 23:10:27 +01:00
this . usedImages = Array . from (
2023-02-03 03:57:30 +01:00
new ExtractImages ( official , undefined )
. convertStrict (
json ,
"while extracting the images of " + json . id + " " + context ? ? ""
)
. map ( ( i ) = > i . path )
2022-02-18 23:10:27 +01:00
) . sort ( )
2021-12-21 18:35:31 +01:00
{
2022-01-27 01:23:04 +01:00
if ( typeof json . title === "string" ) {
2022-01-31 20:52:56 +01:00
throw ` The title of a theme should always be a translation, as it sets the corresponding languages ( ${ context } .title). The themenID is ${
this . id
} ; the offending object is $ { JSON . stringify (
json . title
) } which is a $ { typeof json . title } ) `
2022-01-27 01:23:04 +01:00
}
2021-12-21 18:35:31 +01:00
if ( this . language . length == 0 ) {
throw ` No languages defined. Define at least one language. ( ${ context } .languages) `
}
if ( json . title === undefined ) {
throw "Title not defined in " + this . id
}
if ( json . description === undefined ) {
throw "Description not defined in " + this . id
}
if ( json . widenFactor <= 0 ) {
throw "Widenfactor too small, shoud be > 0"
}
if ( json . widenFactor > 20 ) {
throw (
"Widenfactor is very big, use a value between 1 and 5 (current value is " +
json . widenFactor +
") at " +
context
2022-09-08 21:40:48 +02:00
)
2021-12-21 18:35:31 +01:00
}
if ( json [ "hideInOverview" ] ) {
throw (
"The json for " +
this . id +
" contains a 'hideInOverview'. Did you mean hideFromOverview instead?"
2022-09-08 21:40:48 +02:00
)
2021-12-21 18:35:31 +01:00
}
if ( json . layers === undefined ) {
throw "Got undefined layers for " + json . id + " at " + context
}
2020-11-11 16:23:49 +01:00
}
2022-07-01 00:16:05 +02:00
this . title = new Translation ( json . title , "themes:" + context + ".title" )
this . description = new Translation ( json . description , "themes:" + context + ".description" )
this . shortDescription =
json . shortDescription === undefined
? this . description . FirstSentence ( )
: new Translation ( json . shortDescription , "themes:" + context + ".shortdescription" )
this . descriptionTail =
json . descriptionTail === undefined
? undefined
: new Translation ( json . descriptionTail , "themes:" + context + ".descriptionTail" )
2020-11-11 16:23:49 +01:00
this . icon = json . icon
2022-03-08 04:09:03 +01:00
this . socialImage = json . socialImage ? ? LayoutConfig . defaultSocialImage
if ( this . socialImage === "" ) {
2022-01-26 21:40:38 +01:00
if ( official ) {
2022-03-08 04:09:03 +01:00
throw "Theme " + json . id + " has empty string as social image"
2022-01-18 20:18:12 +01:00
}
}
2020-11-11 16:23:49 +01:00
this . startZoom = json . startZoom
this . startLat = json . startLat
this . startLon = json . startLon
2021-09-22 16:07:56 +02:00
this . widenFactor = json . widenFactor ? ? 1.5
2022-09-08 21:40:48 +02:00
2020-11-11 16:23:49 +01:00
this . defaultBackgroundId = json . defaultBackgroundId
2021-11-07 16:34:51 +01:00
this . tileLayerSources = ( json . tileLayerSources ? ? [ ] ) . map (
( config , i ) = > new TilesourceConfig ( config , ` ${ this . id } .tileLayerSources[ ${ i } ] ` )
2022-09-08 21:40:48 +02:00
)
2021-12-21 18:35:31 +01:00
// At this point, layers should be expanded and validated either by the generateScript or the LegacyJsonConvert
this . layers = json . layers . map (
( lyrJson ) = >
new LayerConfig (
< LayerConfigJson > lyrJson ,
json . id + ".layers." + lyrJson [ "id" ] ,
official
2022-09-08 21:40:48 +02:00
)
)
2022-07-01 00:16:05 +02:00
this . extraLink = new ExtraLinkConfig (
json . extraLink ? ? {
2022-02-14 04:48:33 +01:00
icon : "./assets/svg/pop-out.svg" ,
2022-07-01 00:16:05 +02:00
href : "https://{basepath}/{theme}.html?lat={lat}&lon={lon}&z={zoom}&language={language}" ,
2022-02-14 04:48:33 +01:00
newTab : true ,
2022-07-01 00:16:05 +02:00
requirements : [ "iframe" , "no-welcome-message" ] ,
} ,
context + ".extraLink"
)
2021-11-07 16:34:51 +01:00
2021-06-15 01:24:04 +02:00
this . clustering = {
2021-01-05 00:21:00 +01:00
maxZoom : 16 ,
2022-01-27 01:23:04 +01:00
minNeededElements : 250 ,
2021-01-05 00:21:00 +01:00
}
2021-11-07 16:34:51 +01:00
if ( json . clustering === false ) {
2021-10-13 01:28:46 +02:00
this . clustering = {
maxZoom : 0 ,
minNeededElements : 100000 ,
}
2021-11-07 16:34:51 +01:00
} else 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 ,
2022-01-27 01:23:04 +01:00
minNeededElements : json.clustering.minNeededElements ? ? 250 ,
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-11-07 16:34:51 +01:00
this . lockLocation = < [ [ number , number ] , [ number , number ] ] > json . lockLocation ? ? undefined
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
2021-05-17 00:18:21 +02:00
this . enableShowAllQuestions = json . enableShowAllQuestions ? ? false
2021-07-28 02:51:07 +02:00
this . enableExportButton = json . enableDownload ? ? false
this . enablePdfDownload = json . enablePdfDownload ? ? false
2020-11-11 16:23:49 +01:00
this . customCss = json . customCss
2021-09-29 16:55:05 +02:00
this . overpassUrl = Constants . defaultOverpassUrls
2021-11-07 16:34:51 +01:00
if ( json . overpassUrl !== undefined ) {
if ( typeof json . overpassUrl === "string" ) {
2021-09-29 16:55:05 +02:00
this . overpassUrl = [ json . overpassUrl ]
2021-11-07 16:34:51 +01:00
} else {
2021-09-29 16:55:05 +02:00
this . overpassUrl = json . overpassUrl
}
}
2021-08-23 15:48:42 +02:00
this . overpassTimeout = json . overpassTimeout ? ? 30
2022-01-15 02:44:11 +01:00
this . overpassMaxZoom = json . overpassMaxZoom ? ? 16
2021-10-15 05:20:02 +02:00
this . osmApiTileSize = json . osmApiTileSize ? ? this . overpassMaxZoom + 1
2021-06-22 03:16:45 +02:00
}
2021-03-24 01:25:57 +01:00
public CustomCodeSnippets ( ) : string [ ] {
2021-09-18 02:32:40 +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
2021-11-07 16:34:51 +01:00
public isLeftRightSensitive() {
2021-10-22 14:01:40 +02:00
return this . layers . some ( ( l ) = > l . isLeftRightSensitive ( ) )
}
2021-12-04 21:49:17 +01:00
2023-01-13 02:48:48 +01:00
public missingTranslations ( ) : {
untranslated : Map < string , string [ ] >
total : number
} {
const layout = this
let total = 0
const untranslated = new Map < string , string [ ] > ( )
Utils . WalkObject (
layout ,
( o ) = > {
const translation = < Translation > ( < any > o )
if ( translation . translations [ "*" ] !== undefined ) {
return
}
if ( translation . context === undefined || translation . context . indexOf ( ":" ) < 0 ) {
// no source given - lets ignore
return
}
total ++
2023-03-09 17:11:44 +01:00
LanguageUtils . usedLanguagesSorted . forEach ( ( ln ) = > {
2023-01-13 02:48:48 +01:00
const trans = translation . translations
if ( trans [ "*" ] !== undefined ) {
return
}
2023-02-09 02:45:19 +01:00
if ( translation . context . indexOf ( ":" ) < 0 ) {
return
}
2023-01-13 02:48:48 +01:00
if ( trans [ ln ] === undefined ) {
if ( ! untranslated . has ( ln ) ) {
untranslated . set ( ln , [ ] )
}
2023-02-09 02:45:19 +01:00
untranslated
. get ( ln )
. push (
translation . context . replace (
/^note_import_[a-zA-Z0-9_]*/ ,
"note_import"
)
)
2023-01-13 02:48:48 +01:00
}
} )
} ,
( o ) = > {
if ( o === undefined || o === null ) {
return false
}
return o instanceof Translation
}
)
2023-02-09 02:45:19 +01:00
return { untranslated , total }
2023-01-13 02:48:48 +01:00
}
2021-12-04 21:49:17 +01:00
public getMatchingLayer ( tags : any ) : LayerConfig | undefined {
if ( tags === undefined ) {
2021-11-08 20:49:51 +01:00
return undefined
}
for ( const layer of this . layers ) {
if ( layer . source . osmTags . matchesProperties ( tags ) ) {
return layer
}
}
return undefined
}
2020-11-11 16:23:49 +01:00
}