2023-09-19 14:04:13 +02:00
import ScriptUtils from "./ScriptUtils"
import { existsSync , mkdirSync , readFileSync , statSync , writeFileSync } from "fs"
import licenses from "../src/assets/generated/license_info.json"
import { LayoutConfigJson } from "../src/Models/ThemeConfig/Json/LayoutConfigJson"
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
import Constants from "../src/Models/Constants"
2022-06-20 01:41:34 +02:00
import {
2022-09-24 03:33:09 +02:00
DetectDuplicateFilters ,
2022-07-06 11:14:19 +02:00
DoesImageExist ,
2022-06-20 01:41:34 +02:00
PrevalidateTheme ,
ValidateLayer ,
2023-09-19 14:04:13 +02:00
ValidateThemeAndLayers ,
2024-06-20 04:21:29 +02:00
ValidateThemeEnsemble ,
2023-09-19 14:04:13 +02:00
} from "../src/Models/ThemeConfig/Conversion/Validation"
import { Translation } from "../src/UI/i18n/Translation"
import { PrepareLayer } from "../src/Models/ThemeConfig/Conversion/PrepareLayer"
import { PrepareTheme } from "../src/Models/ThemeConfig/Conversion/PrepareTheme"
2024-06-20 04:21:29 +02:00
import {
Conversion ,
DesugaringContext ,
DesugaringStep ,
} from "../src/Models/ThemeConfig/Conversion/Conversion"
2023-09-19 14:04:13 +02:00
import { Utils } from "../src/Utils"
import Script from "./Script"
import { AllSharedLayers } from "../src/Customizations/AllSharedLayers"
import { parse as parse_html } from "node-html-parser"
2023-09-22 11:20:22 +02:00
import { ExtraFunctions } from "../src/Logic/ExtraFunctions"
2023-10-01 13:13:07 +02:00
import { QuestionableTagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
2023-10-11 04:16:52 +02:00
import LayerConfig from "../src/Models/ThemeConfig/LayerConfig"
import PointRenderingConfig from "../src/Models/ThemeConfig/PointRenderingConfig"
2023-11-02 04:35:32 +01:00
import { ConversionContext } from "../src/Models/ThemeConfig/Conversion/ConversionContext"
2023-11-30 00:39:55 +01:00
import { GenerateFavouritesLayer } from "./generateFavouritesLayer"
2024-01-23 22:03:22 +01:00
import LayoutConfig from "../src/Models/ThemeConfig/LayoutConfig"
2024-07-08 23:35:40 +02:00
import Translations from "../src/UI/i18n/Translations"
import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable"
2023-10-30 18:08:49 +01:00
2023-07-15 18:04:30 +02:00
// This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files.
2021-04-10 03:18:32 +02:00
// It spits out an overview of those to be used to load them
2023-10-12 16:55:26 +02:00
class ParseLayer extends Conversion <
string ,
{
parsed : LayerConfig
raw : LayerConfigJson
}
> {
private readonly _prepareLayer : PrepareLayer
private readonly _doesImageExist : DoesImageExist
constructor ( prepareLayer : PrepareLayer , doesImageExist : DoesImageExist ) {
super ( "Parsed a layer from file, validates it" , [ ] , "ParseLayer" )
this . _prepareLayer = prepareLayer
this . _doesImageExist = doesImageExist
}
convert (
path : string ,
2024-07-09 13:42:08 +02:00
context : ConversionContext
2023-10-12 16:55:26 +02:00
) : {
parsed : LayerConfig
raw : LayerConfigJson
} {
let parsed
let fileContents
try {
fileContents = readFileSync ( path , "utf8" )
} catch ( e ) {
context . err ( "Could not read file " + path + " due to " + e )
return undefined
}
try {
parsed = JSON . parse ( fileContents )
} catch ( e ) {
2024-01-30 18:33:30 +01:00
context . err ( "Could not parse file as JSON: " + e )
2023-10-12 16:55:26 +02:00
return undefined
}
if ( parsed === undefined ) {
context . err ( "yielded undefined" )
return undefined
}
const fixed = this . _prepareLayer . convert ( parsed , context . inOperation ( "PrepareLayer" ) )
if ( ! fixed . source ) {
context . enter ( "source" ) . err ( "No source is configured" )
return undefined
}
if (
typeof fixed . source !== "string" &&
fixed . source [ "osmTags" ] &&
fixed . source [ "osmTags" ] [ "and" ] === undefined
) {
fixed . source [ "osmTags" ] = { and : [ fixed . source [ "osmTags" ] ] }
}
const validator = new ValidateLayer ( path , true , this . _doesImageExist )
return validator . convert ( fixed , context . inOperation ( "ValidateLayer" ) )
}
}
class AddIconSummary extends DesugaringStep < { raw : LayerConfigJson ; parsed : LayerConfig } > {
static singleton = new AddIconSummary ( )
constructor ( ) {
super ( "Adds an icon summary for quick reference" , [ "_layerIcon" ] , "AddIconSummary" )
}
2024-06-18 03:33:11 +02:00
convert ( json : { raw : LayerConfigJson ; parsed : LayerConfig } ) {
2023-10-12 16:55:26 +02:00
// Add a summary of the icon
const fixed = json . raw
const layerConfig = json . parsed
const pointRendering : PointRenderingConfig = layerConfig . mapRendering . find ( ( pr ) = >
2024-07-09 13:42:08 +02:00
pr . location . has ( "point" )
2023-10-12 16:55:26 +02:00
)
const defaultTags = layerConfig . GetBaseTags ( )
fixed [ "_layerIcon" ] = Utils . NoNull (
( pointRendering ? . marker ? ? [ ] ) . map ( ( i ) = > {
const icon = i . icon ? . GetRenderValue ( defaultTags ) ? . txt
if ( ! icon ) {
return undefined
}
const result = { icon }
const c = i . color ? . GetRenderValue ( defaultTags ) ? . txt
if ( c ) {
result [ "color" ] = c
}
return result
2024-07-09 13:42:08 +02:00
} )
2023-10-12 16:55:26 +02:00
)
return { raw : fixed , parsed : layerConfig }
}
}
2023-03-15 13:53:53 +01:00
class LayerOverviewUtils extends Script {
2023-07-15 18:04:30 +02:00
public static readonly layerPath = "./src/assets/generated/layers/"
public static readonly themePath = "./src/assets/generated/themes/"
2022-07-06 13:58:56 +02:00
2023-03-15 13:53:53 +01:00
constructor ( ) {
super ( "Reviews and generates the compiled themes" )
}
2023-07-15 18:04:30 +02:00
2022-07-06 13:58:56 +02:00
private static publicLayerIdsFrom ( themefiles : LayoutConfigJson [ ] ) : Set < string > {
2022-09-08 21:40:48 +02:00
const publicThemes = [ ] . concat ( . . . themefiles . filter ( ( th ) = > ! th . hideFromOverview ) )
2022-07-06 13:58:56 +02:00
2022-09-08 21:40:48 +02:00
return new Set ( [ ] . concat ( . . . publicThemes . map ( ( th ) = > this . extractLayerIdsFrom ( th ) ) ) )
2022-07-06 13:58:56 +02:00
}
2022-09-08 21:40:48 +02:00
private static extractLayerIdsFrom (
themeFile : LayoutConfigJson ,
2024-07-09 13:42:08 +02:00
includeInlineLayers = true
2022-09-08 21:40:48 +02:00
) : string [ ] {
2024-06-18 03:33:11 +02:00
const publicLayerIds : string [ ] = [ ]
2023-10-30 14:32:31 +01:00
if ( ! Array . isArray ( themeFile . layers ) ) {
throw (
"Cannot iterate over 'layers' of " +
themeFile . id +
"; it is a " +
typeof themeFile . layers
)
}
2022-07-06 13:58:56 +02:00
for ( const publicLayer of themeFile . layers ) {
if ( typeof publicLayer === "string" ) {
publicLayerIds . push ( publicLayer )
continue
}
if ( publicLayer [ "builtin" ] !== undefined ) {
2024-06-20 04:21:29 +02:00
const bi : string | string [ ] = publicLayer [ "builtin" ]
2022-07-06 13:58:56 +02:00
if ( typeof bi === "string" ) {
publicLayerIds . push ( bi )
2024-06-18 03:33:11 +02:00
} else {
2024-06-20 04:21:29 +02:00
bi . forEach ( ( id ) = > publicLayerIds . push ( id ) )
2022-07-06 13:58:56 +02:00
}
continue
}
if ( includeInlineLayers ) {
publicLayerIds . push ( publicLayer [ "id" ] )
}
}
return publicLayerIds
}
2024-07-08 23:35:40 +02:00
public static cleanTranslation ( t : Record < string , string > | Translation ) : Translatable {
return Translations . T ( t ) . OnEveryLanguage ( ( s ) = > parse_html ( s ) . textContent ) . translations
}
2022-07-06 13:58:56 +02:00
shouldBeUpdated ( sourcefile : string | string [ ] , targetfile : string ) : boolean {
if ( ! existsSync ( targetfile ) ) {
2022-09-08 21:40:48 +02:00
return true
2022-07-06 13:58:56 +02:00
}
const targetModified = statSync ( targetfile ) . mtime
if ( typeof sourcefile === "string" ) {
sourcefile = [ sourcefile ]
}
2023-06-29 15:08:13 +02:00
for ( const path of sourcefile ) {
const hasChange = statSync ( path ) . mtime > targetModified
if ( hasChange ) {
console . log ( "File " , targetfile , " should be updated as " , path , "has been changed" )
return true
}
}
return false
2022-07-06 13:58:56 +02:00
}
2022-09-08 21:40:48 +02:00
writeSmallOverview (
themes : {
id : string
title : any
shortDescription : any
icon : string
hideFromOverview : boolean
mustHaveLanguage : boolean
2023-10-12 16:55:26 +02:00
layers : (
| LayerConfigJson
| string
| {
2024-07-09 13:42:08 +02:00
builtin
}
) [ ]
} [ ]
2022-09-08 21:40:48 +02:00
) {
const perId = new Map < string , any > ( )
2021-12-21 18:35:31 +01:00
for ( const theme of themes ) {
2022-06-20 01:41:34 +02:00
const keywords : { } [ ] = [ ]
2022-09-08 21:40:48 +02:00
for ( const layer of theme . layers ? ? [ ] ) {
const l = < LayerConfigJson > layer
keywords . push ( { "*" : l . id } )
2022-04-28 02:04:25 +02:00
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 ,
2024-07-08 23:35:40 +02:00
shortDescription : LayerOverviewUtils.cleanTranslation ( theme . shortDescription ) ,
2021-12-21 18:35:31 +01:00
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 ,
2024-06-20 04:21:29 +02:00
keywords : Utils.NoNull ( keywords ) ,
2021-12-21 18:35:31 +01:00
}
2022-09-08 21:40:48 +02:00
perId . set ( theme . id , data )
2021-12-21 18:35:31 +01:00
}
2022-09-08 21:40:48 +02:00
const sorted = Constants . themeOrder . map ( ( id ) = > {
2021-12-21 18:35:31 +01:00
if ( ! perId . has ( id ) ) {
throw "Ordered theme id " + id + " not found"
}
2022-09-08 21:40:48 +02:00
return perId . get ( id )
} )
2021-12-21 18:35:31 +01:00
perId . forEach ( ( value ) = > {
if ( Constants . themeOrder . indexOf ( value . id ) >= 0 ) {
2022-09-08 21:40:48 +02:00
return // actually a continue
2021-12-21 18:35:31 +01:00
}
sorted . push ( value )
} )
2022-09-08 21:40:48 +02:00
writeFileSync (
2023-07-15 18:04:30 +02:00
"./src/assets/generated/theme_overview.json" ,
2022-09-08 21:40:48 +02:00
JSON . stringify ( sorted , null , " " ) ,
2024-07-09 13:42:08 +02:00
{ encoding : "utf8" }
2022-09-08 21:40:48 +02:00
)
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 ) {
2022-07-06 13:58:56 +02:00
if ( ! existsSync ( LayerOverviewUtils . themePath ) ) {
2022-09-08 21:40:48 +02:00
mkdirSync ( LayerOverviewUtils . themePath )
2021-05-19 20:47:41 +02:00
}
2023-10-30 18:08:49 +01:00
2022-09-08 21:40:48 +02:00
writeFileSync (
` ${ LayerOverviewUtils . themePath } ${ theme . id } .json ` ,
JSON . stringify ( theme , null , " " ) ,
2024-07-09 13:42:08 +02:00
{ encoding : "utf8" }
2022-09-08 21:40:48 +02:00
)
2021-12-21 18:35:31 +01:00
}
writeLayer ( layer : LayerConfigJson ) {
2022-07-06 13:58:56 +02:00
if ( ! existsSync ( LayerOverviewUtils . layerPath ) ) {
2022-09-08 21:40:48 +02:00
mkdirSync ( LayerOverviewUtils . layerPath )
2021-11-07 21:20:05 +01:00
}
2022-09-08 21:40:48 +02:00
writeFileSync (
` ${ LayerOverviewUtils . layerPath } ${ layer . id } .json ` ,
JSON . stringify ( layer , null , " " ) ,
2024-07-09 13:42:08 +02:00
{ encoding : "utf8" }
2022-09-08 21:40:48 +02:00
)
2021-12-21 18:35:31 +01:00
}
2024-06-20 04:21:29 +02:00
static asDict (
2024-07-09 13:42:08 +02:00
trs : QuestionableTagRenderingConfigJson [ ]
2024-06-20 04:21:29 +02:00
) : Map < string , QuestionableTagRenderingConfigJson > {
2024-06-18 03:33:11 +02:00
const d = new Map < string , QuestionableTagRenderingConfigJson > ( )
for ( const tr of trs ) {
d . set ( tr . id , tr )
}
return d
}
2024-06-20 04:21:29 +02:00
getSharedTagRenderings ( doesImageExist : DoesImageExist ) : QuestionableTagRenderingConfigJson [ ]
2024-06-18 03:33:11 +02:00
getSharedTagRenderings (
doesImageExist : DoesImageExist ,
bootstrapTagRenderings : Map < string , QuestionableTagRenderingConfigJson > ,
2024-07-09 13:42:08 +02:00
bootstrapTagRenderingsOrder : string [ ]
2024-06-20 04:21:29 +02:00
) : QuestionableTagRenderingConfigJson [ ]
2023-07-15 18:04:30 +02:00
getSharedTagRenderings (
doesImageExist : DoesImageExist ,
2024-06-18 03:33:11 +02:00
bootstrapTagRenderings : Map < string , QuestionableTagRenderingConfigJson > = null ,
2024-07-09 13:42:08 +02:00
bootstrapTagRenderingsOrder : string [ ] = [ ]
2024-06-18 03:33:11 +02:00
) : QuestionableTagRenderingConfigJson [ ] {
2024-06-16 16:06:26 +02:00
const prepareLayer = new PrepareLayer (
{
tagRenderings : bootstrapTagRenderings ,
2024-06-18 03:33:11 +02:00
tagRenderingOrder : bootstrapTagRenderingsOrder ,
2024-06-16 16:06:26 +02:00
sharedLayers : null ,
2024-06-20 04:21:29 +02:00
publicLayers : null ,
2024-06-16 16:06:26 +02:00
} ,
{
2024-06-20 04:21:29 +02:00
addTagRenderingsToContext : true ,
2024-07-09 13:42:08 +02:00
}
2024-06-16 16:06:26 +02:00
)
2023-07-15 18:04:30 +02:00
2024-05-07 17:25:23 +02:00
const path = "assets/layers/questions/questions.json"
2023-10-12 16:55:26 +02:00
const sharedQuestions = this . parseLayer ( doesImageExist , prepareLayer , path ) . raw
2023-07-15 18:04:30 +02:00
2023-10-01 13:13:07 +02:00
const dict = new Map < string , QuestionableTagRenderingConfigJson > ( )
2022-07-06 13:58:56 +02:00
2023-07-15 18:04:30 +02:00
for ( const tr of sharedQuestions . tagRenderings ) {
2023-10-01 13:13:07 +02:00
const tagRendering = < QuestionableTagRenderingConfigJson > tr
2023-07-28 00:29:21 +02:00
dict . set ( tagRendering [ "id" ] , tagRendering )
2021-12-21 18:35:31 +01:00
}
2021-09-04 18:59:51 +02:00
2023-07-15 18:04:30 +02:00
if ( dict . size === bootstrapTagRenderings ? . size ) {
2024-06-18 03:33:11 +02:00
return < QuestionableTagRenderingConfigJson [ ] > sharedQuestions . tagRenderings
2023-07-15 18:04:30 +02:00
}
2021-05-19 20:47:41 +02:00
2024-06-20 04:21:29 +02:00
return this . getSharedTagRenderings (
doesImageExist ,
dict ,
2024-07-09 13:42:08 +02:00
sharedQuestions . tagRenderings . map ( ( tr ) = > tr [ "id" ] )
2024-06-20 04:21:29 +02:00
)
2021-12-21 18:35:31 +01:00
}
2021-05-19 20:47:41 +02:00
2022-02-10 23:10:39 +01:00
checkAllSvgs() {
2023-07-15 18:04:30 +02:00
const allSvgs = ScriptUtils . readDirRecSync ( "./src/assets" )
2022-09-08 21:40:48 +02:00
. filter ( ( path ) = > path . endsWith ( ".svg" ) )
2023-07-15 18:04:30 +02:00
. filter ( ( path ) = > ! path . startsWith ( "./src/assets/generated" ) )
2022-09-08 21:40:48 +02:00
let errCount = 0
const exempt = [
2023-07-15 18:04:30 +02:00
"src/assets/SocialImageTemplate.svg" ,
"src/assets/SocialImageTemplateWide.svg" ,
"src/assets/SocialImageBanner.svg" ,
"src/assets/SocialImageRepo.svg" ,
"src/assets/svg/osm-logo.svg" ,
2024-06-20 04:21:29 +02:00
"src/assets/templates/*" ,
2022-09-08 21:40:48 +02:00
]
2022-02-06 03:02:45 +01:00
for ( const path of allSvgs ) {
2022-10-27 01:50:01 +02:00
if (
exempt . some ( ( p ) = > {
if ( p . endsWith ( "*" ) && path . startsWith ( "./" + p . substring ( 0 , p . length - 1 ) ) ) {
return true
}
return "./" + p === path
} )
) {
2022-03-08 04:09:03 +01:00
continue
}
2022-06-04 02:28:53 +02:00
2023-01-15 23:28:02 +01:00
const contents = readFileSync ( path , { encoding : "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!" )
2022-09-08 21:40:48 +02:00
errCount ++
2023-07-15 18:04:30 +02:00
if ( path . startsWith ( "./src/assets/svg" ) ) {
2022-05-07 21:41:58 +02:00
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-09-08 21:40:48 +02:00
console . warn (
"The SVG at " +
2024-07-09 13:42:08 +02:00
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"
2022-09-08 21:40:48 +02:00
)
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
}
2023-03-15 13:53:53 +01:00
async main ( args : string [ ] ) {
2023-10-31 11:50:03 +01:00
console . log ( "Generating layer overview..." )
2024-01-30 18:33:30 +01:00
const themeWhitelist = new Set (
args
. find ( ( a ) = > a . startsWith ( "--themes=" ) )
? . substring ( "--themes=" . length )
2024-07-09 13:42:08 +02:00
? . split ( "," ) ? ? [ ]
2024-01-30 18:33:30 +01:00
)
2024-01-16 04:01:10 +01:00
2024-01-30 18:33:30 +01:00
const layerWhitelist = new Set (
args
. find ( ( a ) = > a . startsWith ( "--layers=" ) )
? . substring ( "--layers=" . length )
2024-07-09 13:42:08 +02:00
? . split ( "," ) ? ? [ ]
2024-01-30 18:33:30 +01:00
)
2024-01-16 04:01:10 +01:00
2022-09-08 21:40:48 +02:00
const forceReload = args . some ( ( a ) = > a == "--force" )
2022-01-16 01:59:06 +01:00
const licensePaths = new Set < string > ( )
for ( const i in licenses ) {
licensePaths . add ( licenses [ i ] . path )
}
2022-07-06 12:57:23 +02:00
const doesImageExist = new DoesImageExist ( licensePaths , existsSync )
2024-01-16 04:01:10 +01:00
const sharedLayers = this . buildLayerIndex ( doesImageExist , forceReload , layerWhitelist )
2023-03-29 18:54:00 +02:00
const priviliged = new Set < string > ( Constants . priviliged_layers )
sharedLayers . forEach ( ( _ , key ) = > {
priviliged . delete ( key )
} )
2024-08-08 22:54:49 +02:00
// These two get a free pass
priviliged . delete ( "summary" )
priviliged . delete ( "last_click" )
2023-03-29 18:54:00 +02:00
if ( priviliged . size > 0 ) {
throw (
"Priviliged layer " +
Array . from ( priviliged ) . join ( ", " ) +
2023-07-15 18:04:30 +02:00
" has no definition file, create it at `src/assets/layers/<layername>/<layername.json>"
2023-03-29 18:54:00 +02:00
)
}
2022-07-08 15:37:31 +02:00
const recompiledThemes : string [ ] = [ ]
2022-09-08 21:40:48 +02:00
const sharedThemes = this . buildThemeIndex (
2023-02-03 03:57:30 +01:00
licensePaths ,
2022-09-08 21:40:48 +02:00
sharedLayers ,
recompiledThemes ,
2024-01-16 04:01:10 +01:00
forceReload ,
2024-07-09 13:42:08 +02:00
themeWhitelist
2022-09-08 21:40:48 +02:00
)
2024-01-23 22:03:22 +01:00
new ValidateThemeEnsemble ( ) . convertStrict (
2024-07-09 13:42:08 +02:00
Array . from ( sharedThemes . values ( ) ) . map ( ( th ) = > new LayoutConfig ( th , true ) )
2024-02-15 17:48:26 +01:00
)
2024-01-23 22:03:22 +01:00
if ( recompiledThemes . length > 0 ) {
2024-01-16 04:01:10 +01:00
writeFileSync (
"./src/assets/generated/known_layers.json" ,
JSON . stringify ( {
2024-06-20 04:21:29 +02:00
layers : Array.from ( sharedLayers . values ( ) ) . filter ( ( l ) = > l . id !== "favourite" ) ,
2024-07-09 13:42:08 +02:00
} )
2024-01-16 04:01:10 +01:00
)
2024-01-23 22:03:22 +01:00
}
2022-09-08 21:40:48 +02:00
2023-11-13 04:33:25 +01:00
const mcChangesPath = "./assets/themes/mapcomplete-changes/mapcomplete-changes.json"
2022-09-08 21:40:48 +02:00
if (
2023-11-13 12:08:55 +01:00
( recompiledThemes . length > 0 &&
2023-12-06 03:07:36 +01:00
! (
recompiledThemes . length === 1 && recompiledThemes [ 0 ] === "mapcomplete-changes"
) ) ||
args . indexOf ( "--generate-change-map" ) >= 0 ||
2023-11-13 12:08:55 +01:00
! existsSync ( mcChangesPath )
2022-09-08 21:40:48 +02:00
) {
2022-01-16 02:45:07 +01:00
// mapcomplete-changes shows an icon for each corresponding mapcomplete-theme
2022-09-08 21:40:48 +02:00
const iconsPerTheme = Array . from ( sharedThemes . values ( ) ) . map ( ( th ) = > ( {
if : "theme=" + th . id ,
2024-06-20 04:21:29 +02:00
then : th.icon ,
2022-09-08 21:40:48 +02:00
} ) )
const proto : LayoutConfigJson = JSON . parse (
2023-01-17 01:00:43 +01:00
readFileSync ( "./assets/themes/mapcomplete-changes/mapcomplete-changes.proto.json" , {
2024-06-20 04:21:29 +02:00
encoding : "utf8" ,
2024-07-09 13:42:08 +02:00
} )
2022-09-08 21:40:48 +02:00
)
const protolayer = < LayerConfigJson > (
proto . layers . filter ( ( l ) = > l [ "id" ] === "mapcomplete-changes" ) [ 0 ]
)
2023-09-19 14:04:13 +02:00
const rendering = protolayer . pointRendering [ 0 ]
2023-10-06 23:56:50 +02:00
rendering . marker [ 0 ] . icon [ "mappings" ] = iconsPerTheme
2023-11-13 04:33:25 +01:00
writeFileSync ( mcChangesPath , JSON . stringify ( proto , null , " " ) )
2022-01-16 02:45:07 +01:00
}
2022-02-10 23:10:39 +01:00
2022-02-06 03:02:45 +01:00
this . checkAllSvgs ( )
2022-09-08 21:40:48 +02:00
2022-10-27 01:50:01 +02:00
new DetectDuplicateFilters ( ) . convertStrict (
{
layers : ScriptUtils.getLayerFiles ( ) . map ( ( f ) = > f . parsed ) ,
2024-06-20 04:21:29 +02:00
themes : ScriptUtils.getThemeFiles ( ) . map ( ( f ) = > f . parsed ) ,
2022-10-27 01:50:01 +02:00
} ,
2024-07-09 13:42:08 +02:00
ConversionContext . construct ( [ ] , [ ] )
2022-10-27 01:50:01 +02:00
)
2022-09-24 03:33:09 +02:00
2023-11-30 00:39:55 +01:00
for ( const [ _ , theme ] of sharedThemes ) {
theme . layers = theme . layers . filter (
2024-07-09 13:42:08 +02:00
( l ) = > Constants . added_by_default . indexOf ( l [ "id" ] ) < 0
2023-11-30 00:39:55 +01:00
)
}
2024-01-23 22:03:22 +01:00
if ( recompiledThemes . length > 0 ) {
2024-01-16 04:01:10 +01:00
writeFileSync (
"./src/assets/generated/known_themes.json" ,
JSON . stringify ( {
2024-06-20 04:21:29 +02:00
themes : Array.from ( sharedThemes . values ( ) ) ,
2024-07-09 13:42:08 +02:00
} )
2024-01-16 04:01:10 +01:00
)
}
2023-11-30 00:39:55 +01:00
2023-03-02 05:20:53 +01:00
if ( AllSharedLayers . getSharedLayersConfigs ( ) . size == 0 ) {
2024-02-15 17:48:26 +01:00
console . error ( "This was a bootstrapping-run. Run generate layeroverview again!" )
2022-07-11 09:14:26 +02:00
}
2022-01-16 01:59:06 +01:00
}
2021-12-21 18:35:31 +01:00
2023-07-15 18:04:30 +02:00
private parseLayer (
doesImageExist : DoesImageExist ,
prepLayer : PrepareLayer ,
2024-07-09 13:42:08 +02:00
sharedLayerPath : string
2023-10-12 16:55:26 +02:00
) : {
raw : LayerConfigJson
parsed : LayerConfig
2023-11-09 15:42:15 +01:00
context : ConversionContext
2023-10-12 16:55:26 +02:00
} {
const parser = new ParseLayer ( prepLayer , doesImageExist )
const context = ConversionContext . construct ( [ sharedLayerPath ] , [ "ParseLayer" ] )
const parsed = parser . convertStrict ( sharedLayerPath , context )
2023-11-09 15:42:15 +01:00
const result = AddIconSummary . singleton . convertStrict (
parsed ,
2024-07-09 13:42:08 +02:00
context . inOperation ( "AddIconSummary" )
2023-11-09 15:42:15 +01:00
)
return { . . . result , context }
2023-07-15 18:04:30 +02:00
}
2022-09-08 21:40:48 +02:00
private buildLayerIndex (
doesImageExist : DoesImageExist ,
2024-01-16 04:01:10 +01:00
forceReload : boolean ,
2024-07-09 13:42:08 +02:00
whitelist : Set < string >
2022-09-08 21:40:48 +02:00
) : Map < string , LayerConfigJson > {
2023-07-15 18:04:30 +02:00
// First, we expand and validate all builtin layers. These are written to src/assets/generated/layers
2021-12-21 18:35:31 +01:00
// At the same time, an index of available layers is built.
2023-07-15 18:04:30 +02:00
console . log ( "------------- VALIDATING THE BUILTIN QUESTIONS ---------------" )
2022-09-08 21:40:48 +02:00
const sharedTagRenderings = this . getSharedTagRenderings ( doesImageExist )
2023-07-15 18:04:30 +02:00
console . log ( " ---------- VALIDATING BUILTIN LAYERS ---------" )
2021-12-21 18:35:31 +01:00
const state : DesugaringContext = {
2024-06-18 03:33:11 +02:00
tagRenderings : LayerOverviewUtils.asDict ( sharedTagRenderings ) ,
2024-06-20 04:21:29 +02:00
tagRenderingOrder : sharedTagRenderings.map ( ( tr ) = > tr . id ) ,
sharedLayers : AllSharedLayers.getSharedLayersConfigs ( ) ,
2021-12-21 18:35:31 +01:00
}
2022-07-11 09:14:26 +02:00
const sharedLayers = new Map < string , LayerConfigJson > ( )
2022-09-08 21:40:48 +02:00
const prepLayer = new PrepareLayer ( state )
2022-07-06 13:58:56 +02:00
const skippedLayers : string [ ] = [ ]
const recompiledLayers : string [ ] = [ ]
2023-11-09 15:42:15 +01:00
let warningCount = 0
2022-07-06 13:58:56 +02:00
for ( const sharedLayerPath of ScriptUtils . getLayerPaths ( ) ) {
2024-01-23 22:03:22 +01:00
if ( whitelist . size > 0 ) {
const idByPath = sharedLayerPath . split ( "/" ) . at ( - 1 ) . split ( "." ) [ 0 ]
2024-01-30 18:33:30 +01:00
if (
Constants . priviliged_layers . indexOf ( < any > idByPath ) < 0 &&
! whitelist . has ( idByPath )
) {
2024-01-16 04:01:10 +01:00
continue
}
}
2022-07-06 13:58:56 +02:00
{
2022-09-08 21:40:48 +02:00
const targetPath =
LayerOverviewUtils . layerPath +
sharedLayerPath . substring ( sharedLayerPath . lastIndexOf ( "/" ) )
2022-07-06 13:58:56 +02:00
if ( ! forceReload && ! this . shouldBeUpdated ( sharedLayerPath , targetPath ) ) {
const sharedLayer = JSON . parse ( readFileSync ( targetPath , "utf8" ) )
sharedLayers . set ( sharedLayer . id , sharedLayer )
skippedLayers . push ( sharedLayer . id )
2023-10-12 16:55:26 +02:00
ScriptUtils . erasableLog ( "Loaded " + sharedLayer . id )
2022-09-08 21:40:48 +02:00
continue
2022-07-06 13:58:56 +02:00
}
2022-07-08 15:37:31 +02:00
}
2022-07-06 13:58:56 +02:00
2023-10-12 16:55:26 +02:00
const parsed = this . parseLayer ( doesImageExist , prepLayer , sharedLayerPath )
2023-11-09 15:42:15 +01:00
warningCount += parsed . context . getAll ( "warning" ) . length
2023-10-12 16:55:26 +02:00
const fixed = parsed . raw
2021-12-21 18:35:31 +01:00
if ( sharedLayers . has ( fixed . id ) ) {
2023-10-11 04:16:52 +02:00
throw "There are multiple layers with the id " + fixed . id + ", " + sharedLayerPath
2021-05-19 20:47:41 +02:00
}
2021-12-21 18:35:31 +01:00
sharedLayers . set ( fixed . id , fixed )
2022-07-06 13:58:56 +02:00
recompiledLayers . push ( fixed . id )
2021-12-21 18:35:31 +01:00
this . writeLayer ( fixed )
2021-04-23 13:56:16 +02:00
}
2022-06-20 01:41:34 +02:00
2022-09-08 21:40:48 +02:00
console . log (
"Recompiled layers " +
2024-07-09 13:42:08 +02:00
recompiledLayers . join ( ", " ) +
" and skipped " +
skippedLayers . length +
" layers. Detected " +
warningCount +
" warnings"
2022-09-08 21:40:48 +02:00
)
2023-09-22 11:20:22 +02:00
// We always need the calculated tags of 'usersettings', so we export them separately
this . extractJavascriptCodeForLayer (
state . sharedLayers . get ( "usersettings" ) ,
2024-07-09 13:42:08 +02:00
"./src/Logic/State/UserSettingsMetaTagging.ts"
2023-09-22 11:20:22 +02:00
)
2022-06-13 03:13:42 +02:00
2022-09-08 21:40:48 +02:00
return sharedLayers
2022-06-13 03:13:42 +02:00
}
2021-04-10 03:18:32 +02:00
2023-09-22 11:20:22 +02:00
/ * *
* Given : a fully expanded themeConfigJson
*
* Will extract a dictionary of the special code and write it into a javascript file which can be imported .
* This removes the need for _eval_ , allowing for a correct CSP
* @param themeFile
* @private
* /
private extractJavascriptCode ( themeFile : LayoutConfigJson ) {
const allCode = [
"import {Feature} from 'geojson'" ,
2024-07-09 13:42:08 +02:00
'import { ExtraFuncType } from "../../../Logic/ExtraFunctions";' ,
'import { Utils } from "../../../Utils"' ,
2023-09-22 11:20:22 +02:00
"export class ThemeMetaTagging {" ,
" public static readonly themeName = " + JSON . stringify ( themeFile . id ) ,
2024-06-20 04:21:29 +02:00
"" ,
2023-09-22 11:20:22 +02:00
]
for ( const layer of themeFile . layers ) {
const l = < LayerConfigJson > layer
2023-09-22 12:42:09 +02:00
const id = l . id . replace ( /[^a-zA-Z0-9_]/g , "_" )
2023-09-22 11:20:22 +02:00
const code = l . calculatedTags ? ? [ ]
allCode . push (
" public metaTaggging_for_" +
2024-07-09 13:42:08 +02:00
id +
"(feat: Feature, helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>) {"
2023-09-22 11:20:22 +02:00
)
allCode . push ( " const {" + ExtraFunctions . types . join ( ", " ) + "} = helperFunctions" )
for ( const line of code ) {
const firstEq = line . indexOf ( "=" )
let attributeName = line . substring ( 0 , firstEq ) . trim ( )
const expression = line . substring ( firstEq + 1 )
const isStrict = attributeName . endsWith ( ":" )
if ( ! isStrict ) {
allCode . push (
" Utils.AddLazyProperty(feat.properties, '" +
2024-07-09 13:42:08 +02:00
attributeName +
"', () => " +
expression +
" ) "
2023-09-22 11:20:22 +02:00
)
} else {
2023-09-22 12:42:09 +02:00
attributeName = attributeName . substring ( 0 , attributeName . length - 1 ) . trim ( )
2023-09-22 11:20:22 +02:00
allCode . push ( " feat.properties['" + attributeName + "'] = " + expression )
}
}
allCode . push ( " }" )
}
const targetDir = "./src/assets/generated/metatagging/"
if ( ! existsSync ( targetDir ) ) {
mkdirSync ( targetDir )
}
allCode . push ( "}" )
writeFileSync ( targetDir + themeFile . id + ".ts" , allCode . join ( "\n" ) )
}
private extractJavascriptCodeForLayer ( l : LayerConfigJson , targetPath? : string ) {
2023-09-22 12:42:09 +02:00
if ( ! l ) {
return // Probably a bootstrapping run
}
2023-09-22 11:20:22 +02:00
let importPath = "../../../"
if ( targetPath ) {
const l = targetPath . split ( "/" )
if ( l . length == 1 ) {
importPath = "./"
} else {
importPath = ""
for ( let i = 0 ; i < l . length - 3 ; i ++ ) {
importPath += "../"
}
}
}
const allCode = [
` import { Utils } from " ${ importPath } Utils" ` ,
2023-09-22 12:42:09 +02:00
` /** This code is autogenerated - do not edit. Edit ./assets/layers/ ${ l ? . id } / ${ l ? . id } .json instead */ ` ,
2023-09-22 11:20:22 +02:00
"export class ThemeMetaTagging {" ,
" public static readonly themeName = " + JSON . stringify ( l . id ) ,
2024-06-20 04:21:29 +02:00
"" ,
2023-09-22 11:20:22 +02:00
]
const code = l . calculatedTags ? ? [ ]
allCode . push (
2024-07-09 13:42:08 +02:00
" public metaTaggging_for_" + l . id + "(feat: {properties: Record<string, string>}) {"
2023-09-22 11:20:22 +02:00
)
for ( const line of code ) {
const firstEq = line . indexOf ( "=" )
let attributeName = line . substring ( 0 , firstEq ) . trim ( )
const expression = line . substring ( firstEq + 1 )
const isStrict = attributeName . endsWith ( ":" )
if ( ! isStrict ) {
allCode . push (
" Utils.AddLazyProperty(feat.properties, '" +
2024-07-09 13:42:08 +02:00
attributeName +
"', () => " +
expression +
" ) "
2023-09-22 11:20:22 +02:00
)
} else {
attributeName = attributeName . substring ( 0 , attributeName . length - 2 ) . trim ( )
allCode . push ( " feat.properties['" + attributeName + "'] = " + expression )
}
}
allCode . push ( " }" )
allCode . push ( "}" )
const targetDir = "./src/assets/generated/metatagging/"
if ( ! targetPath ) {
if ( ! existsSync ( targetDir ) ) {
mkdirSync ( targetDir )
}
}
writeFileSync ( targetPath ? ? targetDir + "layer_" + l . id + ".ts" , allCode . join ( "\n" ) )
}
2022-09-08 21:40:48 +02:00
private buildThemeIndex (
2023-02-03 03:57:30 +01:00
licensePaths : Set < string > ,
2022-09-08 21:40:48 +02:00
sharedLayers : Map < string , LayerConfigJson > ,
recompiledThemes : string [ ] ,
2024-01-16 04:01:10 +01:00
forceReload : boolean ,
2024-07-09 13:42:08 +02:00
whitelist : Set < string >
2022-09-08 21:40:48 +02:00
) : Map < string , LayoutConfigJson > {
2021-12-21 18:35:31 +01:00
console . log ( " ---------- VALIDATING BUILTIN THEMES ---------" )
2022-09-08 21:40:48 +02:00
const themeFiles = ScriptUtils . getThemeFiles ( )
const fixed = new Map < string , LayoutConfigJson > ( )
2021-04-23 13:56:16 +02:00
2022-09-08 21:40:48 +02:00
const publicLayers = LayerOverviewUtils . publicLayerIdsFrom (
2024-07-09 13:42:08 +02:00
themeFiles . map ( ( th ) = > th . parsed )
2022-09-08 21:40:48 +02:00
)
2022-06-13 03:13:42 +02:00
2024-06-20 04:21:29 +02:00
const trs = this . getSharedTagRenderings ( new DoesImageExist ( licensePaths , existsSync ) )
2024-06-18 03:33:11 +02:00
2021-12-21 18:35:31 +01:00
const convertState : DesugaringContext = {
sharedLayers ,
2024-06-18 03:33:11 +02:00
tagRenderings : LayerOverviewUtils.asDict ( trs ) ,
2024-06-20 04:21:29 +02:00
tagRenderingOrder : trs.map ( ( tr ) = > tr . id ) ,
publicLayers ,
2021-05-19 20:47:41 +02:00
}
2023-02-03 03:57:30 +01:00
const knownTagRenderings = new Set < string > ( )
convertState . tagRenderings . forEach ( ( _ , key ) = > knownTagRenderings . add ( key ) )
sharedLayers . forEach ( ( layer ) = > {
for ( const tagRendering of layer . tagRenderings ? ? [ ] ) {
if ( tagRendering [ "id" ] ) {
knownTagRenderings . add ( layer . id + "." + tagRendering [ "id" ] )
}
if ( tagRendering [ "labels" ] ) {
for ( const label of tagRendering [ "labels" ] ) {
knownTagRenderings . add ( layer . id + "." + label )
}
}
}
} )
2022-07-06 13:58:56 +02:00
const skippedThemes : string [ ] = [ ]
2023-09-22 11:20:22 +02:00
2023-06-14 20:39:36 +02:00
for ( let i = 0 ; i < themeFiles . length ; i ++ ) {
const themeInfo = themeFiles [ i ]
2022-09-08 21:40:48 +02:00
const themePath = themeInfo . path
2021-12-21 18:35:31 +01:00
let themeFile = themeInfo . parsed
2024-01-23 22:03:22 +01:00
if ( whitelist . size > 0 && ! whitelist . has ( themeFile . id ) ) {
2024-01-16 04:01:10 +01:00
continue
}
2023-06-29 01:43:26 +02:00
const targetPath =
LayerOverviewUtils . themePath + "/" + themePath . substring ( themePath . lastIndexOf ( "/" ) )
2023-09-22 11:20:22 +02:00
2023-06-29 01:43:26 +02:00
const usedLayers = Array . from (
2024-07-09 13:42:08 +02:00
LayerOverviewUtils . extractLayerIdsFrom ( themeFile , false )
2023-06-29 01:43:26 +02:00
) . map ( ( id ) = > LayerOverviewUtils . layerPath + id + ".json" )
if ( ! forceReload && ! this . shouldBeUpdated ( [ themePath , . . . usedLayers ] , targetPath ) ) {
fixed . set (
themeFile . id ,
JSON . parse (
2024-07-09 13:42:08 +02:00
readFileSync ( LayerOverviewUtils . themePath + themeFile . id + ".json" , "utf8" )
)
2023-06-29 01:43:26 +02:00
)
2023-10-12 16:55:26 +02:00
ScriptUtils . erasableLog ( "Skipping" , themeFile . id )
2023-06-29 01:43:26 +02:00
skippedThemes . push ( themeFile . id )
continue
2022-07-06 13:58:56 +02:00
}
2023-06-29 01:43:26 +02:00
recompiledThemes . push ( themeFile . id )
2022-01-16 01:59:06 +01:00
2023-10-11 04:16:52 +02:00
new PrevalidateTheme ( ) . convertStrict (
themeFile ,
2024-07-09 13:42:08 +02:00
ConversionContext . construct ( [ themePath ] , [ "PrepareLayer" ] )
2023-10-11 04:16:52 +02:00
)
2022-06-20 01:41:34 +02:00
try {
2023-12-06 12:12:53 +01:00
themeFile = new PrepareTheme ( convertState , {
2024-06-20 04:21:29 +02:00
skipDefaultLayers : true ,
2023-12-06 12:12:53 +01:00
} ) . convertStrict (
2023-10-11 04:16:52 +02:00
themeFile ,
2024-07-09 13:42:08 +02:00
ConversionContext . construct ( [ themePath ] , [ "PrepareLayer" ] )
2023-10-11 04:16:52 +02:00
)
2022-09-08 21:40:48 +02:00
new ValidateThemeAndLayers (
2023-02-03 03:57:30 +01:00
new DoesImageExist ( licensePaths , existsSync , knownTagRenderings ) ,
2022-09-08 21:40:48 +02:00
themePath ,
true ,
2024-07-09 13:42:08 +02:00
knownTagRenderings
2023-10-11 04:16:52 +02:00
) . convertStrict (
themeFile ,
2024-07-09 13:42:08 +02:00
ConversionContext . construct ( [ themePath ] , [ "PrepareLayer" ] )
2023-10-11 04:16:52 +02:00
)
2022-06-20 01:41:34 +02:00
2023-01-17 01:00:43 +01:00
if ( themeFile . icon . endsWith ( ".svg" ) ) {
try {
2023-01-17 01:53:50 +01:00
ScriptUtils . ReadSvgSync ( themeFile . icon , ( svg ) = > {
2024-06-18 03:33:11 +02:00
const width : string = svg [ "$" ] . width
2024-07-19 17:12:31 +02:00
if ( width === undefined ) {
2024-07-21 10:52:51 +02:00
throw (
"The logo at " +
themeFile . icon +
" does not have a defined width"
)
2024-07-19 17:12:31 +02:00
}
2024-06-18 03:33:11 +02:00
const height : string = svg [ "$" ] . height
2023-01-17 01:53:50 +01:00
const err = themeFile . hideFromOverview ? console.warn : console.error
2023-01-17 01:00:43 +01:00
if ( width !== height ) {
const e =
2023-01-17 01:53:50 +01:00
` the icon for theme ${ themeFile . id } is not square. Please square the icon at ${ themeFile . icon } ` +
2023-01-17 01:00:43 +01:00
` Width = ${ width } height = ${ height } `
2023-01-17 01:53:50 +01:00
err ( e )
2023-01-17 01:00:43 +01:00
}
2024-07-19 17:12:31 +02:00
if ( width ? . endsWith ( "%" ) ) {
2024-07-21 10:52:51 +02:00
throw (
"The logo at " +
themeFile . icon +
" has a relative width; this is not supported"
)
2024-07-19 17:12:31 +02:00
}
2023-01-17 01:00:43 +01:00
const w = parseInt ( width )
const h = parseInt ( height )
if ( w < 370 || h < 370 ) {
const e : string = [
2023-01-17 01:53:50 +01:00
` the icon for theme ${ themeFile . id } is too small. Please rescale the icon at ${ themeFile . icon } ` ,
2023-01-17 01:00:43 +01:00
` Even though an SVG is 'infinitely scaleable', the icon should be dimensioned bigger. One of the build steps of the theme does convert the image to a PNG (to serve as PWA-icon) and having a small dimension will cause blurry images. ` ,
2024-06-20 04:21:29 +02:00
` Width = ${ width } height = ${ height } ; we recommend a size of at least 500px * 500px and to use a square aspect ratio. ` ,
2023-01-17 01:00:43 +01:00
] . join ( "\n" )
2023-01-17 01:53:50 +01:00
err ( e )
2023-01-17 01:00:43 +01:00
}
} )
} catch ( e ) {
2023-01-17 01:53:50 +01:00
console . error ( "Could not read " + themeFile . icon + " due to " + e )
2023-01-17 01:00:43 +01:00
}
}
2022-04-22 03:17:40 +02:00
this . writeTheme ( themeFile )
fixed . set ( themeFile . id , themeFile )
2023-09-22 11:20:22 +02:00
this . extractJavascriptCode ( themeFile )
2022-06-20 01:41:34 +02:00
} catch ( e ) {
console . error ( "ERROR: could not prepare theme " + themePath + " due to " + e )
2022-09-08 21:40:48 +02:00
throw e
2022-02-18 23:10:27 +01:00
}
2021-04-23 13:56:16 +02:00
}
2024-01-23 22:03:22 +01:00
if ( whitelist . size == 0 ) {
this . writeSmallOverview (
Array . from ( fixed . values ( ) ) . map ( ( t ) = > {
return {
. . . t ,
hideFromOverview : t.hideFromOverview ? ? false ,
shortDescription :
2024-07-08 23:35:40 +02:00
t . shortDescription ? ? new Translation ( t . description ) . FirstSentence ( ) ,
2024-06-20 04:21:29 +02:00
mustHaveLanguage : t.mustHaveLanguage?.length > 0 ,
2024-01-23 22:03:22 +01:00
}
2024-07-09 13:42:08 +02:00
} )
2024-01-23 22:03:22 +01:00
)
2024-01-16 04:01:10 +01:00
}
2022-09-08 21:40:48 +02:00
console . log (
"Recompiled themes " +
2024-07-09 13:42:08 +02:00
recompiledThemes . join ( ", " ) +
" and skipped " +
skippedThemes . length +
" themes"
2022-09-08 21:40:48 +02:00
)
return fixed
2021-12-21 18:35:31 +01:00
}
2021-04-10 14:25:06 +02:00
}
2021-04-23 13:56:16 +02:00
2023-11-30 00:39:55 +01:00
new GenerateFavouritesLayer ( ) . run ( )
2023-03-15 13:53:53 +01:00
new LayerOverviewUtils ( ) . run ( )