2021-04-10 03:18:32 +02:00
import ScriptUtils from "./ScriptUtils" ;
2022-01-16 02:45:07 +01:00
import { existsSync , mkdirSync , readFileSync , writeFileSync } from "fs" ;
2021-04-10 03:18:32 +02:00
import * as licenses from "../assets/generated/license_info.json"
2021-08-07 23:11:34 +02:00
import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson" ;
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" ;
2021-12-21 18:35:31 +01:00
import Constants from "../Models/Constants" ;
2022-06-20 01:41:34 +02:00
import {
PrevalidateTheme ,
ValidateLayer ,
ValidateTagRenderings ,
ValidateThemeAndLayers
} from "../Models/ThemeConfig/Conversion/Validation" ;
2021-10-01 04:49:19 +02:00
import { Translation } from "../UI/i18n/Translation" ;
2021-12-21 18:35:31 +01:00
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson" ;
import * as questions from "../assets/tagRenderings/questions.json" ;
import * as icons from "../assets/tagRenderings/icons.json" ;
2022-01-16 02:45:07 +01:00
import PointRenderingConfigJson from "../Models/ThemeConfig/Json/PointRenderingConfigJson" ;
2022-01-21 01:57:16 +01:00
import { PrepareLayer } from "../Models/ThemeConfig/Conversion/PrepareLayer" ;
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme" ;
import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion" ;
2022-04-28 02:04:25 +02:00
import { Utils } from "../Utils" ;
2021-06-23 02:41:30 +02:00
2021-04-10 03:18:32 +02:00
// This scripts scans 'assets/layers/*.json' for layer definition files and 'assets/themes/*.json' for theme definition files.
// It spits out an overview of those to be used to load them
2021-05-19 23:31:00 +02:00
class LayerOverviewUtils {
2021-05-19 20:47:41 +02:00
2022-06-20 01:41:34 +02:00
writeSmallOverview ( themes : { id : string , title : any , shortDescription : any , icon : string , hideFromOverview : boolean , mustHaveLanguage : boolean , layers : ( LayerConfigJson | string | { builtin } ) [ ] } [ ] ) {
2021-12-21 18:35:31 +01:00
const perId = new Map < string , any > ( ) ;
for ( const theme of themes ) {
2022-06-20 01:41:34 +02:00
const keywords : { } [ ] = [ ]
2022-04-28 02:04:25 +02:00
for ( const layer of ( theme . layers ? ? [ ] ) ) {
2022-06-20 01:41:34 +02:00
const l = < LayerConfigJson > layer ;
2022-04-28 02:04:25 +02:00
keywords . push ( { "*" : l . id } )
keywords . push ( l . title )
keywords . push ( l . description )
}
2022-06-20 01:41:34 +02:00
2021-12-21 18:35:31 +01:00
const data = {
id : theme.id ,
title : theme.title ,
shortDescription : theme.shortDescription ,
icon : theme.icon ,
2022-04-01 12:51:55 +02:00
hideFromOverview : theme.hideFromOverview ,
2022-04-28 02:04:25 +02:00
mustHaveLanguage : theme.mustHaveLanguage ,
keywords : Utils.NoNull ( keywords )
2021-12-21 18:35:31 +01:00
}
perId . set ( theme . id , data ) ;
}
const sorted = Constants . themeOrder . map ( id = > {
if ( ! perId . has ( id ) ) {
throw "Ordered theme id " + id + " not found"
}
return perId . get ( id ) ;
} ) ;
perId . forEach ( ( value ) = > {
if ( Constants . themeOrder . indexOf ( value . id ) >= 0 ) {
return ; // actually a continue
}
sorted . push ( value )
} )
writeFileSync ( "./assets/generated/theme_overview.json" , JSON . stringify ( sorted , null , " " ) , "UTF8" ) ;
2021-04-10 03:18:32 +02:00
}
2021-04-10 03:50:44 +02:00
2021-12-21 18:35:31 +01:00
writeTheme ( theme : LayoutConfigJson ) {
if ( ! existsSync ( "./assets/generated/themes" ) ) {
mkdirSync ( "./assets/generated/themes" ) ;
2021-05-19 20:47:41 +02:00
}
2021-12-21 18:35:31 +01:00
writeFileSync ( ` ./assets/generated/themes/ ${ theme . id } .json ` , JSON . stringify ( theme , null , " " ) , "UTF8" ) ;
}
writeLayer ( layer : LayerConfigJson ) {
if ( ! existsSync ( "./assets/generated/layers" ) ) {
mkdirSync ( "./assets/generated/layers" ) ;
2021-11-07 21:20:05 +01:00
}
2021-12-21 18:35:31 +01:00
writeFileSync ( ` ./assets/generated/layers/ ${ layer . id } .json ` , JSON . stringify ( layer , null , " " ) , "UTF8" ) ;
}
2022-06-20 01:41:34 +02:00
getSharedTagRenderings ( knownImagePaths : Set < string > ) : Map < string , TagRenderingConfigJson > {
2021-12-21 18:35:31 +01:00
const dict = new Map < string , TagRenderingConfigJson > ( ) ;
2022-06-20 01:41:34 +02:00
const validator = new ValidateTagRenderings ( undefined , knownImagePaths ) ;
2021-12-21 18:35:31 +01:00
for ( const key in questions [ "default" ] ) {
2022-01-26 21:40:38 +01:00
if ( key === "id" ) {
2022-01-22 02:56:35 +01:00
continue
}
2021-12-21 18:35:31 +01:00
questions [ key ] . id = key ;
2022-04-01 12:51:55 +02:00
questions [ key ] [ "source" ] = "shared-questions"
2022-06-20 01:41:34 +02:00
const config = < TagRenderingConfigJson > questions [ key ]
validator . convertStrict ( config , "generate-layer-overview:tagRenderings/questions.json:" + key )
dict . set ( key , config )
2021-12-21 18:35:31 +01:00
}
for ( const key in icons [ "default" ] ) {
2022-01-26 21:40:38 +01:00
if ( key === "id" ) {
2022-01-22 02:56:35 +01:00
continue
}
2022-01-16 01:59:06 +01:00
if ( typeof icons [ key ] !== "object" ) {
2021-12-21 18:35:31 +01:00
continue
2021-09-04 18:59:51 +02:00
}
2021-12-21 18:35:31 +01:00
icons [ key ] . id = key ;
2022-06-20 01:41:34 +02:00
const config = < TagRenderingConfigJson > icons [ key ]
validator . convertStrict ( config , "generate-layer-overview:tagRenderings/icons.json:" + key )
dict . set ( key , config )
2021-12-21 18:35:31 +01:00
}
2021-09-04 18:59:51 +02:00
2021-12-21 18:35:31 +01:00
dict . forEach ( ( value , key ) = > {
2022-01-26 21:40:38 +01:00
if ( key === "id" ) {
2022-01-24 00:24:51 +01:00
return
}
2021-12-21 18:35:31 +01:00
value . id = value . id ? ? key ;
} )
2021-05-19 20:47:41 +02:00
2021-12-21 18:35:31 +01:00
return dict ;
}
2021-05-19 20:47:41 +02:00
2022-02-10 23:10:39 +01:00
checkAllSvgs() {
2022-02-06 03:02:45 +01:00
const allSvgs = ScriptUtils . readDirRecSync ( "./assets" )
. filter ( path = > path . endsWith ( ".svg" ) )
. filter ( path = > ! path . startsWith ( "./assets/generated" ) )
let errCount = 0 ;
2022-06-20 01:41:34 +02:00
const exempt = [ "assets/SocialImageTemplate.svg" , "assets/SocialImageTemplateWide.svg" , "assets/SocialImageBanner.svg" , "assets/svg/osm-logo.svg" ] ;
2022-02-06 03:02:45 +01:00
for ( const path of allSvgs ) {
2022-06-20 01:41:34 +02:00
if ( exempt . some ( p = > "./" + p === path ) ) {
2022-03-08 04:09:03 +01:00
continue
}
2022-06-04 02:28:53 +02:00
2022-02-06 03:02:45 +01:00
const contents = readFileSync ( path , "UTF8" )
2022-05-07 21:41:58 +02:00
if ( contents . indexOf ( "data:image/png;" ) >= 0 ) {
console . warn ( "The SVG at " + path + " is a fake SVG: it contains PNG data!" )
errCount ++ ;
if ( path . startsWith ( "./assets/svg" ) ) {
throw "A core SVG is actually a PNG. Don't do this!"
}
2022-02-06 03:02:45 +01:00
}
2022-06-20 01:41:34 +02:00
if ( contents . indexOf ( "<text" ) > 0 ) {
2022-05-07 21:41:58 +02:00
console . warn ( "The SVG at " + path + " contains a `text`-tag. This is highly discouraged. Every machine viewing your theme has their own font libary, and the font you choose might not be present, resulting in a different font being rendered. Solution: open your .svg in inkscape (or another program), select the text and convert it to a path" )
errCount ++ ;
2022-02-06 03:02:45 +01:00
}
}
2022-02-10 23:10:39 +01:00
if ( errCount > 0 ) {
2022-05-07 21:41:58 +02:00
throw ` There are ${ errCount } invalid svgs `
2022-02-10 23:10:39 +01:00
}
2022-02-06 03:02:45 +01:00
}
2022-01-16 01:59:06 +01:00
main ( _ : string [ ] ) {
const licensePaths = new Set < string > ( )
for ( const i in licenses ) {
licensePaths . add ( licenses [ i ] . path )
}
const sharedLayers = this . buildLayerIndex ( licensePaths ) ;
const sharedThemes = this . buildThemeIndex ( licensePaths , sharedLayers )
writeFileSync ( "./assets/generated/known_layers_and_themes.json" , JSON . stringify ( {
"layers" : Array . from ( sharedLayers . values ( ) ) ,
"themes" : Array . from ( sharedThemes . values ( ) )
} ) )
2022-01-22 02:56:35 +01:00
writeFileSync ( "./assets/generated/known_layers.json" , JSON . stringify ( { layers : Array.from ( sharedLayers . values ( ) ) } ) )
2022-01-16 01:59:06 +01:00
2022-01-16 02:45:07 +01:00
{
// mapcomplete-changes shows an icon for each corresponding mapcomplete-theme
const iconsPerTheme =
Array . from ( sharedThemes . values ( ) ) . map ( th = > ( {
if : "theme=" + th . id ,
then : th.icon
} ) )
const proto : LayoutConfigJson = JSON . parse ( readFileSync ( "./assets/themes/mapcomplete-changes/mapcomplete-changes.proto.json" , "UTF8" ) ) ;
const protolayer = < LayerConfigJson > ( proto . layers . filter ( l = > l [ "id" ] === "mapcomplete-changes" ) [ 0 ] )
const rendering = ( < PointRenderingConfigJson > protolayer . mapRendering [ 0 ] )
rendering . icon [ "mappings" ] = iconsPerTheme
writeFileSync ( './assets/themes/mapcomplete-changes/mapcomplete-changes.json' , JSON . stringify ( proto , null , " " ) )
}
2022-02-10 23:10:39 +01:00
2022-02-06 03:02:45 +01:00
this . checkAllSvgs ( )
2022-04-23 15:20:54 +02:00
const green = s = > '\x1b[92m' + s + '\x1b[0m'
console . log ( green ( "All done!" ) )
2022-01-16 01:59:06 +01:00
}
2021-12-21 18:35:31 +01:00
private buildLayerIndex ( knownImagePaths : Set < string > ) : Map < string , LayerConfigJson > {
// First, we expand and validate all builtin layers. These are written to assets/generated/layers
// At the same time, an index of available layers is built.
console . log ( " ---------- VALIDATING BUILTIN LAYERS ---------" )
2022-06-20 01:41:34 +02:00
const sharedTagRenderings = this . getSharedTagRenderings ( knownImagePaths ) ;
2021-12-21 18:35:31 +01:00
const layerFiles = ScriptUtils . getLayerFiles ( ) ;
const sharedLayers = new Map < string , LayerConfigJson > ( )
const state : DesugaringContext = {
tagRenderings : sharedTagRenderings ,
2022-06-13 03:26:14 +02:00
sharedLayers
2021-12-21 18:35:31 +01:00
}
2022-02-04 01:05:35 +01:00
const prepLayer = new PrepareLayer ( state ) ;
2021-12-21 18:35:31 +01:00
for ( const sharedLayerJson of layerFiles ) {
const context = "While building builtin layer " + sharedLayerJson . path
2022-02-04 01:05:35 +01:00
const fixed = prepLayer . convertStrict ( sharedLayerJson . parsed , context )
2022-06-20 01:41:34 +02:00
const validator = new ValidateLayer ( sharedLayerJson . path , true , knownImagePaths ) ;
2022-02-04 01:05:35 +01:00
validator . convertStrict ( fixed , context )
2021-12-21 18:35:31 +01:00
if ( sharedLayers . has ( fixed . id ) ) {
throw "There are multiple layers with the id " + fixed . id
2021-05-19 20:47:41 +02:00
}
2021-12-21 18:35:31 +01:00
sharedLayers . set ( fixed . id , fixed )
this . writeLayer ( fixed )
2021-04-23 13:56:16 +02:00
}
2021-12-21 18:35:31 +01:00
return sharedLayers ;
2021-04-23 13:56:16 +02:00
}
2022-06-20 01:41:34 +02:00
private static publicLayerIdsFrom ( themefiles : LayoutConfigJson [ ] ) : Set < string > {
2022-06-13 03:13:42 +02:00
const publicLayers = [ ] . concat ( . . . themefiles
. filter ( th = > ! th . hideFromOverview )
. map ( th = > th . layers ) )
const publicLayerIds = new Set < string > ( )
for ( const publicLayer of publicLayers ) {
2022-06-20 01:41:34 +02:00
if ( typeof publicLayer === "string" ) {
2022-06-13 03:13:42 +02:00
publicLayerIds . add ( publicLayer )
continue
}
2022-06-20 01:41:34 +02:00
if ( publicLayer [ "builtin" ] !== undefined ) {
2022-06-13 03:13:42 +02:00
const bi = publicLayer [ "builtin" ]
2022-06-20 01:41:34 +02:00
if ( typeof bi === "string" ) {
2022-06-13 03:13:42 +02:00
publicLayerIds . add ( bi )
continue
}
2022-06-20 01:41:34 +02:00
bi . forEach ( id = > publicLayerIds . add ( id ) )
2022-06-13 03:13:42 +02:00
continue
}
publicLayerIds . add ( publicLayer . id )
}
return publicLayerIds
}
2021-04-10 03:18:32 +02:00
2021-12-21 18:35:31 +01:00
private buildThemeIndex ( knownImagePaths : Set < string > , sharedLayers : Map < string , LayerConfigJson > ) : Map < string , LayoutConfigJson > {
console . log ( " ---------- VALIDATING BUILTIN THEMES ---------" )
2021-07-26 10:13:50 +02:00
const themeFiles = ScriptUtils . getThemeFiles ( ) ;
2021-12-21 18:35:31 +01:00
const fixed = new Map < string , LayoutConfigJson > ( ) ;
2021-04-23 13:56:16 +02:00
2022-06-13 03:13:42 +02:00
const publicLayers = LayerOverviewUtils . publicLayerIdsFrom ( themeFiles . map ( th = > th . parsed ) )
2021-12-21 18:35:31 +01:00
const convertState : DesugaringContext = {
sharedLayers ,
2022-06-20 01:41:34 +02:00
tagRenderings : this.getSharedTagRenderings ( knownImagePaths ) ,
2022-06-13 03:13:42 +02:00
publicLayers
2021-05-19 20:47:41 +02:00
}
2021-12-21 18:35:31 +01:00
for ( const themeInfo of themeFiles ) {
let themeFile = themeInfo . parsed
const themePath = themeInfo . path
2022-01-16 01:59:06 +01:00
2022-02-04 01:05:35 +01:00
new PrevalidateTheme ( ) . convertStrict ( themeFile , themePath )
2022-06-20 01:41:34 +02:00
try {
2022-04-22 03:17:40 +02:00
themeFile = new PrepareTheme ( convertState ) . convertStrict ( themeFile , themePath )
2022-06-20 01:41:34 +02:00
if ( knownImagePaths === undefined ) {
2022-04-22 03:17:40 +02:00
throw "Could not load known images/licenses"
}
new ValidateThemeAndLayers ( knownImagePaths , themePath , true , convertState . tagRenderings )
. convertStrict ( themeFile , themePath )
2022-06-20 01:41:34 +02:00
2022-04-22 03:17:40 +02:00
this . writeTheme ( themeFile )
fixed . set ( themeFile . id , themeFile )
2022-06-20 01:41:34 +02:00
} catch ( e ) {
console . error ( "ERROR: could not prepare theme " + themePath + " due to " + e )
2022-04-22 03:17:40 +02:00
throw e ;
2022-02-18 23:10:27 +01:00
}
2021-04-23 13:56:16 +02:00
}
2022-04-28 02:04:25 +02:00
this . writeSmallOverview ( Array . from ( fixed . values ( ) ) . map ( t = > {
2021-12-21 18:35:31 +01:00
return {
. . . t ,
hideFromOverview : t.hideFromOverview ? ? false ,
2022-04-01 12:51:55 +02:00
shortDescription : t.shortDescription ? ? new Translation ( t . description ) . FirstSentence ( ) . translations ,
2022-04-28 02:04:25 +02:00
mustHaveLanguage : t.mustHaveLanguage?.length > 0 ,
2021-04-23 13:56:16 +02:00
}
2021-12-21 18:35:31 +01:00
} ) ) ;
return fixed ;
2021-10-31 02:08:39 +01:00
2021-12-21 18:35:31 +01:00
}
2021-04-10 14:25:06 +02:00
}
2021-04-23 13:56:16 +02:00
2021-10-19 02:13:50 +02:00
new LayerOverviewUtils ( ) . main ( process . argv )