2022-10-27 01:50:41 +02:00
import {
Concat ,
Conversion ,
DesugaringContext ,
DesugaringStep ,
Each ,
FirstOf ,
Fuse ,
On ,
SetDefault ,
} from "./Conversion"
import { LayerConfigJson } from "../Json/LayerConfigJson"
import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
import { Utils } from "../../../Utils"
2022-03-29 00:20:10 +02:00
import RewritableConfigJson from "../Json/RewritableConfigJson"
import SpecialVisualizations from "../../../UI/SpecialVisualizations"
2022-01-29 02:45:59 +01:00
import Translations from "../../../UI/i18n/Translations"
2022-10-27 01:50:41 +02:00
import { Translation } from "../../../UI/i18n/Translation"
2022-03-29 00:20:10 +02:00
import * as tagrenderingconfigmeta from "../../../assets/tagrenderingconfigmeta.json"
2022-10-27 01:50:41 +02:00
import { AddContextToTranslations } from "./AddContextToTranslations"
import FilterConfigJson from "../Json/FilterConfigJson"
2022-09-14 16:29:41 +02:00
import * as predifined_filters from "../../../assets/layers/filters/filters.json"
2022-10-27 01:50:41 +02:00
class ExpandFilter extends DesugaringStep < LayerConfigJson > {
private static load_filters ( ) : Map < string , FilterConfigJson > {
let filters = new Map < string , FilterConfigJson > ( )
for ( const filter of < FilterConfigJson [ ] > predifined_filters . filter ) {
2022-09-14 16:29:41 +02:00
filters . set ( filter . id , filter )
}
2022-10-27 01:50:41 +02:00
return filters
2022-09-14 16:29:41 +02:00
}
2022-10-27 01:50:41 +02:00
private static readonly predefinedFilters = ExpandFilter . load_filters ( )
2022-09-14 16:29:41 +02:00
constructor ( ) {
2022-10-27 01:50:41 +02:00
super (
"Expands filters: replaces a shorthand by the value found in 'filters.json'" ,
[ "filter" ] ,
"ExpandFilter"
)
2022-09-14 16:29:41 +02:00
}
2022-10-27 01:50:41 +02:00
convert (
json : LayerConfigJson ,
context : string
) : { result : LayerConfigJson ; errors? : string [ ] ; warnings? : string [ ] ; information? : string [ ] } {
if ( json . filter === undefined || json . filter === null ) {
return { result : json } // Nothing to change here
2022-09-14 16:29:41 +02:00
}
2022-10-27 01:50:41 +02:00
if ( json . filter [ "sameAs" ] !== undefined ) {
return { result : json } // Nothing to change here
2022-09-14 16:29:41 +02:00
}
2022-10-27 01:50:41 +02:00
const newFilters : FilterConfigJson [ ] = [ ]
const errors : string [ ] = [ ]
for ( const filter of < ( FilterConfigJson | string ) [ ] > json . filter ) {
2022-09-14 16:29:41 +02:00
if ( typeof filter !== "string" ) {
newFilters . push ( filter )
continue
}
// Search for the filter:
const found = ExpandFilter . predefinedFilters . get ( filter )
2022-10-27 01:50:41 +02:00
if ( found === undefined ) {
const suggestions = Utils . sortedByLevenshteinDistance (
filter ,
Array . from ( ExpandFilter . predefinedFilters . keys ( ) ) ,
( t ) = > t
)
const err =
context +
".filter: while searching for predifined filter " +
filter +
": this filter is not found. Perhaps you meant one of: " +
suggestions
2022-09-14 16:29:41 +02:00
errors . push ( err )
}
newFilters . push ( found )
}
2022-10-27 01:50:41 +02:00
return {
result : {
. . . json ,
filter : newFilters ,
} ,
errors ,
}
2022-09-14 16:29:41 +02:00
}
}
2022-09-08 21:40:48 +02:00
2022-01-21 01:57:16 +01:00
class ExpandTagRendering extends Conversion <
string | TagRenderingConfigJson | { builtin : string | string [ ] ; override : any } ,
TagRenderingConfigJson [ ]
> {
2022-02-04 01:05:35 +01:00
private readonly _state : DesugaringContext
2022-07-11 09:14:26 +02:00
private readonly _self : LayerConfigJson
2022-08-18 14:39:40 +02:00
private readonly _options : {
/* If true, will copy the 'osmSource'-tags into the condition */
applyCondition? : true | boolean
2022-01-21 01:57:16 +01:00
}
2022-02-04 01:05:35 +01:00
constructor (
state : DesugaringContext ,
self : LayerConfigJson ,
options ? : { applyCondition? : true | boolean }
) {
2022-09-08 21:40:48 +02:00
super (
2022-02-04 01:05:35 +01:00
"Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question" ,
[ ] ,
2022-04-06 03:06:50 +02:00
"ExpandTagRendering"
2022-09-08 21:40:48 +02:00
)
2022-02-04 01:05:35 +01:00
this . _state = state
2022-07-11 09:14:26 +02:00
this . _self = self
2022-08-18 14:39:40 +02:00
this . _options = options
2022-09-08 21:40:48 +02:00
}
2022-02-04 01:05:35 +01:00
convert (
json : string | TagRenderingConfigJson | { builtin : string | string [ ] ; override : any } ,
context : string
) : { result : TagRenderingConfigJson [ ] ; errors : string [ ] ; warnings : string [ ] } {
2022-01-21 01:57:16 +01:00
const errors = [ ]
const warnings = [ ]
return {
2022-02-04 01:05:35 +01:00
result : this.convertUntilStable ( json , warnings , errors , context ) ,
2022-01-21 01:57:16 +01:00
errors ,
warnings ,
}
}
2022-02-04 01:05:35 +01:00
private lookup ( name : string ) : TagRenderingConfigJson [ ] {
const state = this . _state
2022-01-21 01:57:16 +01:00
if ( state . tagRenderings . has ( name ) ) {
return [ state . tagRenderings . get ( name ) ]
}
2022-07-11 09:14:26 +02:00
if ( name . indexOf ( "." ) < 0 ) {
return undefined
}
2022-07-27 23:59:04 +02:00
2022-07-11 09:14:26 +02:00
const spl = name . split ( "." )
let layer = state . sharedLayers . get ( spl [ 0 ] )
if ( spl [ 0 ] === this . _self . id ) {
layer = this . _self
}
2022-01-21 01:57:16 +01:00
2022-07-11 09:14:26 +02:00
if ( spl . length !== 2 || layer === undefined ) {
return undefined
}
2022-07-27 23:59:04 +02:00
2022-07-11 09:14:26 +02:00
const id = spl [ 1 ]
const layerTrs = < TagRenderingConfigJson [ ] > (
layer . tagRenderings . filter ( ( tr ) = > tr [ "id" ] !== undefined )
2022-09-08 21:40:48 +02:00
)
2022-07-11 09:14:26 +02:00
let matchingTrs : TagRenderingConfigJson [ ]
if ( id === "*" ) {
matchingTrs = layerTrs
} else if ( id . startsWith ( "*" ) ) {
const id_ = id . substring ( 1 )
matchingTrs = layerTrs . filter ( ( tr ) = > tr . group === id_ || tr . labels ? . indexOf ( id_ ) >= 0 )
} else {
matchingTrs = layerTrs . filter ( ( tr ) = > tr . id === id )
}
2022-01-21 01:57:16 +01:00
2022-07-11 09:14:26 +02:00
const contextWriter = new AddContextToTranslations < TagRenderingConfigJson > ( "layers:" )
for ( let i = 0 ; i < matchingTrs . length ; i ++ ) {
let found : TagRenderingConfigJson = Utils . Clone ( matchingTrs [ i ] )
2022-08-18 14:39:40 +02:00
if ( this . _options ? . applyCondition ) {
// The matched tagRenderings are 'stolen' from another layer. This means that they must match the layer condition before being shown
if ( found . condition === undefined ) {
found . condition = layer . source . osmTags
} else {
found . condition = { and : [ found . condition , layer . source . osmTags ] }
}
2022-01-21 01:57:16 +01:00
}
2022-07-11 09:14:26 +02:00
found = contextWriter . convertStrict ( found , layer . id + ".tagRenderings." + found [ "id" ] )
matchingTrs [ i ] = found
}
if ( matchingTrs . length !== 0 ) {
return matchingTrs
2022-01-21 01:57:16 +01:00
}
return undefined
}
2022-02-04 01:05:35 +01:00
private convertOnce (
tr : string | any ,
warnings : string [ ] ,
errors : string [ ] ,
ctx : string
) : TagRenderingConfigJson [ ] {
const state = this . _state
2022-01-21 01:57:16 +01:00
if ( tr === "questions" ) {
return [
{
id : "questions" ,
} ,
]
}
if ( typeof tr === "string" ) {
2022-02-04 01:05:35 +01:00
const lookup = this . lookup ( tr )
2022-02-18 23:10:27 +01:00
if ( lookup === undefined ) {
2022-07-03 13:18:05 +02:00
const isTagRendering = ctx . indexOf ( "On(mapRendering" ) < 0
2022-07-11 09:14:26 +02:00
if ( isTagRendering ) {
2022-07-03 13:18:05 +02:00
warnings . push ( ctx + "A literal rendering was detected: " + tr )
}
2022-02-18 23:10:27 +01:00
return [
{
render : tr ,
2022-07-03 13:18:05 +02:00
id : tr.replace ( /[^a-zA-Z0-9]/g , "" ) ,
2022-02-18 23:10:27 +01:00
} ,
]
2022-01-21 01:57:16 +01:00
}
2022-02-18 23:10:27 +01:00
return lookup
2022-01-21 01:57:16 +01:00
}
if ( tr [ "builtin" ] !== undefined ) {
2022-07-03 13:18:05 +02:00
let names : string | string [ ] = tr [ "builtin" ]
2022-01-21 01:57:16 +01:00
if ( typeof names === "string" ) {
names = [ names ]
}
for ( const key of Object . keys ( tr ) ) {
if (
key === "builtin" ||
key === "override" ||
key === "id" ||
key . startsWith ( "#" )
) {
continue
}
errors . push (
"At " +
ctx +
": an object calling a builtin can only have keys `builtin` or `override`, but a key with name `" +
key +
"` was found. This won't be picked up! The full object is: " +
JSON . stringify ( tr )
2022-09-08 21:40:48 +02:00
)
2022-01-21 01:57:16 +01:00
}
const trs : TagRenderingConfigJson [ ] = [ ]
for ( const name of names ) {
2022-02-04 01:05:35 +01:00
const lookup = this . lookup ( name )
2022-01-21 01:57:16 +01:00
if ( lookup === undefined ) {
2022-07-11 09:14:26 +02:00
let candidates = Array . from ( state . tagRenderings . keys ( ) )
if ( name . indexOf ( "." ) > 0 ) {
2022-09-14 16:29:41 +02:00
const [ layerName ] = name . split ( "." )
2022-07-11 09:14:26 +02:00
let layer = state . sharedLayers . get ( layerName )
if ( layerName === this . _self . id ) {
layer = this . _self
}
if ( layer === undefined ) {
const candidates = Utils . sortedByLevenshteinDistance (
layerName ,
Array . from ( state . sharedLayers . keys ( ) ) ,
( s ) = > s
2022-09-08 21:40:48 +02:00
)
2022-07-27 23:59:04 +02:00
if ( state . sharedLayers . size === 0 ) {
warnings . push (
ctx +
": BOOTSTRAPPING. Rerun generate layeroverview. While reusing tagrendering: " +
name +
": layer " +
layerName +
" not found. Maybe you meant on of " +
candidates . slice ( 0 , 3 ) . join ( ", " )
2022-09-08 21:40:48 +02:00
)
2022-07-27 23:59:04 +02:00
} else {
errors . push (
ctx +
": While reusing tagrendering: " +
name +
": layer " +
layerName +
" not found. Maybe you meant on of " +
candidates . slice ( 0 , 3 ) . join ( ", " )
2022-09-08 21:40:48 +02:00
)
2022-07-27 23:59:04 +02:00
}
2022-07-11 09:14:26 +02:00
continue
}
candidates = Utils . NoNull ( layer . tagRenderings . map ( ( tr ) = > tr [ "id" ] ) ) . map (
( id ) = > layerName + "." + id
2022-09-08 21:40:48 +02:00
)
2022-07-03 13:18:05 +02:00
}
candidates = Utils . sortedByLevenshteinDistance ( name , candidates , ( i ) = > i )
2022-07-11 09:14:26 +02:00
errors . push (
ctx +
": The tagRendering with identifier " +
name +
" was not found.\n\tDid you mean one of " +
candidates . join ( ", " ) +
2022-12-06 03:59:41 +01:00
"?\n(Hint: did you add a new label and are you trying to use this label at the same time? Run 'reset:layeroverview' first"
2022-07-11 09:14:26 +02:00
)
2022-01-21 01:57:16 +01:00
continue
}
for ( let foundTr of lookup ) {
foundTr = Utils . Clone < any > ( foundTr )
Utils . Merge ( tr [ "override" ] ? ? { } , foundTr )
trs . push ( foundTr )
}
}
return trs
}
return [ tr ]
}
2022-02-04 01:05:35 +01:00
private convertUntilStable (
spec : string | any ,
warnings : string [ ] ,
errors : string [ ] ,
ctx : string
) : TagRenderingConfigJson [ ] {
const trs = this . convertOnce ( spec , warnings , errors , ctx )
2022-01-21 01:57:16 +01:00
const result = [ ]
for ( const tr of trs ) {
2022-02-18 23:10:27 +01:00
if ( typeof tr === "string" || tr [ "builtin" ] !== undefined ) {
2022-02-04 01:05:35 +01:00
const stable = this . convertUntilStable (
tr ,
warnings ,
errors ,
ctx + "(RECURSIVE RESOLVE)"
)
2022-01-21 01:57:16 +01:00
result . push ( . . . stable )
} else {
result . push ( tr )
}
}
return result
}
}
2022-04-03 02:37:31 +02:00
export class ExpandRewrite < T > extends Conversion < T | RewritableConfigJson < T > , T [ ] > {
2022-02-28 18:52:28 +01:00
constructor ( ) {
super ( "Applies a rewrite" , [ ] , "ExpandRewrite" )
}
2022-04-03 02:37:31 +02:00
/ * *
* Used for left | right group creation and replacement .
* Every 'keyToRewrite' will be replaced with 'target' recursively . This substitution will happen in place in the object 'tr'
*
* // should substitute strings
* const spec = {
* "someKey" : "somevalue {xyz}"
* }
* ExpandRewrite . RewriteParts ( "{xyz}" , "rewritten" , spec ) // => {"someKey": "somevalue rewritten"}
2022-07-11 09:14:26 +02:00
*
2022-04-06 03:06:50 +02:00
* // should substitute all occurances in strings
* const spec = {
* "someKey" : "The left|right side has {key:left|right}"
* }
* ExpandRewrite . RewriteParts ( "left|right" , "left" , spec ) // => {"someKey": "The left side has {key:left}"}
2022-04-03 02:37:31 +02:00
*
* /
2022-02-28 18:52:28 +01:00
public static RewriteParts < T > ( keyToRewrite : string , target : string | any , tr : T ) : T {
2022-04-03 02:37:31 +02:00
const targetIsTranslation = Translations . isProbablyATranslation ( target )
2022-01-29 02:45:59 +01:00
2022-04-03 02:37:31 +02:00
function replaceRecursive ( obj : string | any , target ) {
if ( obj === keyToRewrite ) {
2022-03-08 01:05:54 +01:00
return target
}
2022-04-03 02:37:31 +02:00
if ( typeof obj === "string" ) {
2022-01-29 02:45:59 +01:00
// This is a simple string - we do a simple replace
2022-07-11 09:14:26 +02:00
while ( obj . indexOf ( keyToRewrite ) >= 0 ) {
obj = obj . replace ( keyToRewrite , target )
2022-04-06 03:06:50 +02:00
}
return obj
2022-01-21 01:57:16 +01:00
}
2022-04-03 02:37:31 +02:00
if ( Array . isArray ( obj ) ) {
2022-01-29 02:45:59 +01:00
// This is a list of items
2022-04-03 02:37:31 +02:00
return obj . map ( ( o ) = > replaceRecursive ( o , target ) )
2022-01-21 01:57:16 +01:00
}
2022-01-29 02:45:59 +01:00
2022-04-03 02:37:31 +02:00
if ( typeof obj === "object" ) {
obj = { . . . obj }
2022-07-11 09:14:26 +02:00
2022-04-03 02:37:31 +02:00
const isTr = targetIsTranslation && Translations . isProbablyATranslation ( obj )
2022-07-11 09:14:26 +02:00
2022-04-03 02:37:31 +02:00
for ( const key in obj ) {
let subtarget = target
2022-07-11 09:14:26 +02:00
if ( isTr && target [ key ] !== undefined ) {
2022-04-03 02:37:31 +02:00
// The target is a translation AND the current object is a translation
// This means we should recursively replace with the translated value
subtarget = target [ key ]
}
2022-07-11 09:14:26 +02:00
2022-04-03 02:37:31 +02:00
obj [ key ] = replaceRecursive ( obj [ key ] , subtarget )
2022-03-08 01:05:54 +01:00
}
2022-04-03 02:37:31 +02:00
return obj
2022-01-21 01:57:16 +01:00
}
2022-04-03 02:37:31 +02:00
return obj
2022-01-21 01:57:16 +01:00
}
2022-04-03 02:37:31 +02:00
return replaceRecursive ( tr , target )
2022-01-21 01:57:16 +01:00
}
2022-04-03 02:37:31 +02:00
/ * *
* // should convert simple strings
* const spec = < RewritableConfigJson < string > > {
* rewrite : {
* sourceString : [ "xyz" , "abc" ] ,
* into : [
* [ "X" , "A" ] ,
* [ "Y" , "B" ] ,
* [ "Z" , "C" ] ] ,
* } ,
* renderings : "The value of xyz is abc"
* }
* new ExpandRewrite ( ) . convertStrict ( spec , "test" ) // => ["The value of X is A", "The value of Y is B", "The value of Z is C"]
2022-07-11 09:14:26 +02:00
*
2022-04-03 02:37:31 +02:00
* // should rewrite with translations
* const spec = < RewritableConfigJson < any > > {
* rewrite : {
* sourceString : [ "xyz" , "abc" ] ,
* into : [
* [ "X" , { en : "value" , nl : "waarde" } ] ,
* [ "Y" , { en : "some other value" , nl : "een andere waarde" } ] ,
* } ,
* renderings : { en : "The value of xyz is abc" , nl : "De waarde van xyz is abc" }
* }
* const expected = [
* {
* en : "The value of X is value" ,
* nl : "De waarde van X is waarde"
* } ,
* {
* en : "The value of Y is some other value" ,
* nl : "De waarde van Y is een andere waarde"
* }
* ]
* new ExpandRewrite ( ) . convertStrict ( spec , "test" ) // => expected
* /
2022-02-28 18:52:28 +01:00
convert (
json : T | RewritableConfigJson < T > ,
context : string
) : { result : T [ ] ; errors? : string [ ] ; warnings? : string [ ] ; information? : string [ ] } {
2022-04-03 02:37:31 +02:00
if ( json === null || json === undefined ) {
2022-02-28 20:21:37 +01:00
return { result : [ ] }
}
2022-04-03 02:37:31 +02:00
2022-02-28 18:52:28 +01:00
if ( json [ "rewrite" ] === undefined ) {
// not a rewrite
return { result : [ < T > json ] }
}
const rewrite = < RewritableConfigJson < T > > json
2022-04-03 02:37:31 +02:00
const keysToRewrite = rewrite . rewrite
const ts : T [ ] = [ ]
{
// sanity check: rewrite: ["xyz", "longer_xyz"] is not allowed as "longer_xyz" will never be triggered
for ( let i = 0 ; i < keysToRewrite . sourceString . length ; i ++ ) {
const guard = keysToRewrite . sourceString [ i ]
for ( let j = i + 1 ; j < keysToRewrite . sourceString . length ; j ++ ) {
const toRewrite = keysToRewrite . sourceString [ j ]
if ( toRewrite . indexOf ( guard ) >= 0 ) {
throw ` ${ context } Error in rewrite: sourcestring[ ${ i } ] is a substring of sourcestring[ ${ j } ]: ${ guard } will be substituted away before ${ toRewrite } is reached. `
}
}
}
}
{
// sanity check: {rewrite: ["a", "b"] should have the right amount of 'intos' in every case
for ( let i = 0 ; i < rewrite . rewrite . into . length ; i ++ ) {
const into = keysToRewrite . into [ i ]
2022-07-11 09:14:26 +02:00
if ( into . length !== rewrite . rewrite . sourceString . length ) {
throw ` ${ context } .into. ${ i } Error in rewrite: there are ${ rewrite . rewrite . sourceString . length } keys to rewrite, but entry ${ i } has only ${ into . length } values `
2022-03-08 01:05:54 +01:00
}
}
}
2022-04-03 02:37:31 +02:00
for ( let i = 0 ; i < keysToRewrite . into . length ; i ++ ) {
2022-02-28 18:52:28 +01:00
let t = Utils . Clone ( rewrite . renderings )
2022-04-03 02:37:31 +02:00
for ( let j = 0 ; j < keysToRewrite . sourceString . length ; j ++ ) {
const key = keysToRewrite . sourceString [ j ]
const target = keysToRewrite . into [ i ] [ j ]
2022-02-28 18:52:28 +01:00
t = ExpandRewrite . RewriteParts ( key , target , t )
}
ts . push ( t )
}
return { result : ts }
}
}
2022-01-21 01:57:16 +01:00
2022-03-29 00:20:10 +02:00
/ * *
* Converts a 'special' translation into a regular translation which uses parameters
* /
export class RewriteSpecial extends DesugaringStep < TagRenderingConfigJson > {
constructor ( ) {
2022-07-11 09:14:26 +02:00
super (
"Converts a 'special' translation into a regular translation which uses parameters" ,
[ "special" ] ,
"RewriteSpecial"
2022-09-08 21:40:48 +02:00
)
2022-03-29 00:20:10 +02:00
}
2022-03-08 01:05:54 +01:00
2022-03-29 00:20:10 +02:00
/ * *
* Does the heavy lifting and conversion
2022-07-11 09:14:26 +02:00
*
2022-03-29 00:20:10 +02:00
* // should not do anything if no 'special'-key is present
* RewriteSpecial . convertIfNeeded ( { "en" : "xyz" , "nl" : "abc" } , [ ] , "test" ) // => {"en": "xyz", "nl": "abc"}
2022-07-11 09:14:26 +02:00
*
2022-03-29 00:20:10 +02:00
* // should handle a simple special case
* RewriteSpecial . convertIfNeeded ( { "special" : { "type" : "image_carousel" } } , [ ] , "test" ) // => {'*': "{image_carousel()}"}
2022-07-11 09:14:26 +02:00
*
2022-03-29 00:20:10 +02:00
* // should handle special case with a parameter
* RewriteSpecial . convertIfNeeded ( { "special" : { "type" : "image_carousel" , "image_key" : "some_image_key" } } , [ ] , "test" ) // => {'*': "{image_carousel(some_image_key)}"}
2022-07-11 09:14:26 +02:00
*
2022-03-29 00:20:10 +02:00
* // should handle special case with a translated parameter
* const spec = { "special" : { "type" : "image_upload" , "label" : { "en" : "Add a picture to this object" , "nl" : "Voeg een afbeelding toe" } } }
* const r = RewriteSpecial . convertIfNeeded ( spec , [ ] , "test" )
* r // => {"en": "{image_upload(,Add a picture to this object)}", "nl": "{image_upload(,Voeg een afbeelding toe)}" }
2022-07-11 09:14:26 +02:00
*
2022-05-06 12:41:24 +02:00
* // should handle special case with a prefix and postfix
* const spec = { "special" : { "type" : "image_upload" } , before : { "en" : "PREFIX " } , after : { "en" : " POSTFIX" , nl : " Achtervoegsel" } }
* const r = RewriteSpecial . convertIfNeeded ( spec , [ ] , "test" )
* r // => {"en": "PREFIX {image_upload(,)} POSTFIX", "nl": "PREFIX {image_upload(,)} Achtervoegsel" }
2022-07-11 09:14:26 +02:00
*
2022-03-29 00:20:10 +02:00
* // should warn for unexpected keys
* const errors = [ ]
* RewriteSpecial . convertIfNeeded ( { "special" : { type : "image_carousel" } , "en" : "xyz" } , errors , "test" ) // => {'*': "{image_carousel()}"}
2022-10-29 03:02:42 +02:00
* errors // => ["At test: The only keys allowed next to a 'special'-block are 'before' and 'after'. Perhaps you meant to put 'en' into the special block?"]
2022-07-11 09:14:26 +02:00
*
2022-03-29 00:20:10 +02:00
* // should give an error on unknown visualisations
* const errors = [ ]
* RewriteSpecial . convertIfNeeded ( { "special" : { type : "qsdf" } } , errors , "test" ) // => undefined
* errors . length // => 1
* errors [ 0 ] . indexOf ( "Special visualisation 'qsdf' not found" ) >= 0 // => true
2022-07-11 09:14:26 +02:00
*
2022-03-29 00:20:10 +02:00
* // should give an error is 'type' is missing
* const errors = [ ]
* RewriteSpecial . convertIfNeeded ( { "special" : { } } , errors , "test" ) // => undefined
* errors // => ["A 'special'-block should define 'type' to indicate which visualisation should be used"]
2022-07-29 21:09:58 +02:00
*
*
2022-07-29 20:04:36 +02:00
* // an actual test
2022-07-29 21:09:58 +02:00
* const special = {
* "before" : {
2022-07-29 20:04:36 +02:00
* "en" : "<h3>Entrances</h3>This building has {_entrances_count} entrances:"
* } ,
2022-07-29 21:09:58 +02:00
* "after" : {
2022-07-29 20:04:36 +02:00
* "en" : "{_entrances_count_without_width_count} entrances don't have width information yet"
* } ,
2022-07-29 21:09:58 +02:00
* "special" : {
* "type" : "multi" ,
2022-07-29 20:04:36 +02:00
* "key" : "_entrance_properties_with_width" ,
* "tagrendering" : {
* "en" : "An <a href='#{id}'>entrance</a> of {canonical(width)}"
* }
* } }
* const errors = [ ]
2022-07-29 21:09:58 +02:00
* RewriteSpecial . convertIfNeeded ( special , errors , "test" ) // => {"en": "<h3>Entrances</h3>This building has {_entrances_count} entrances:{multi(_entrance_properties_with_width,An <a href='#&LBRACEid&RBRACE'>entrance</a> of &LBRACEcanonical&LPARENSwidth&RPARENS&RBRACE)}{_entrances_count_without_width_count} entrances don't have width information yet"}
2022-07-29 20:04:36 +02:00
* errors // => []
2022-03-29 00:20:10 +02:00
* /
2022-07-11 09:14:26 +02:00
private static convertIfNeeded (
input : ( object & { special : { type : string } } ) | any ,
errors : string [ ] ,
context : string
) : any {
2022-03-29 00:20:10 +02:00
const special = input [ "special" ]
2022-07-11 09:14:26 +02:00
if ( special === undefined ) {
2022-03-29 00:20:10 +02:00
return input
}
2022-03-08 01:05:54 +01:00
2022-03-29 00:20:10 +02:00
const type = special [ "type" ]
2022-07-11 09:14:26 +02:00
if ( type === undefined ) {
2022-03-29 00:20:10 +02:00
errors . push (
"A 'special'-block should define 'type' to indicate which visualisation should be used"
)
return undefined
}
2022-07-29 21:09:58 +02:00
2022-03-29 00:20:10 +02:00
const vis = SpecialVisualizations . specialVisualizations . find ( ( sp ) = > sp . funcName === type )
2022-07-11 09:14:26 +02:00
if ( vis === undefined ) {
2022-03-29 00:20:10 +02:00
const options = Utils . sortedByLevenshteinDistance (
type ,
SpecialVisualizations . specialVisualizations ,
( sp ) = > sp . funcName
)
errors . push (
` Special visualisation ' ${ type } ' not found. Did you perhaps mean ${ options [ 0 ] . funcName } , ${ options [ 1 ] . funcName } or ${ options [ 2 ] . funcName } ? \ n \ tFor all known special visualisations, please see https://github.com/pietervdvn/MapComplete/blob/develop/Docs/SpecialRenderings.md `
2022-09-08 21:40:48 +02:00
)
2022-03-29 00:20:10 +02:00
return undefined
}
2022-07-29 21:09:58 +02:00
errors . push (
. . . Array . from ( Object . keys ( input ) )
. filter ( ( k ) = > k !== "special" && k !== "before" && k !== "after" )
. map ( ( k ) = > {
2022-10-29 03:02:42 +02:00
return ` At ${ context } : The only keys allowed next to a 'special'-block are 'before' and 'after'. Perhaps you meant to put ' ${ k } ' into the special block? `
2022-07-29 21:09:58 +02:00
} )
2022-09-08 21:40:48 +02:00
)
2022-03-29 00:20:10 +02:00
const argNamesList = vis . args . map ( ( a ) = > a . name )
const argNames = new Set < string > ( argNamesList )
// Check for obsolete and misspelled arguments
errors . push (
. . . Object . keys ( special )
. filter ( ( k ) = > ! argNames . has ( k ) )
2022-07-29 20:04:36 +02:00
. filter ( ( k ) = > k !== "type" && k !== "before" && k !== "after" )
2022-03-29 00:20:10 +02:00
. map ( ( wrongArg ) = > {
2022-07-11 09:14:26 +02:00
const byDistance = Utils . sortedByLevenshteinDistance (
wrongArg ,
argNamesList ,
( x ) = > x
2022-09-08 21:40:48 +02:00
)
2022-10-29 03:02:42 +02:00
return ` At ${ context } : Unexpected argument in special block at ${ context } with name ' ${ wrongArg } '. Did you mean ${
2022-07-29 20:04:36 +02:00
byDistance [ 0 ]
} ? \ n \ tAll known arguments are $ { argNamesList . join ( ", " ) } `
2022-07-11 09:14:26 +02:00
} )
2022-09-08 21:40:48 +02:00
)
2022-07-11 09:14:26 +02:00
2022-03-29 00:20:10 +02:00
// Check that all obligated arguments are present. They are obligated if they don't have a preset value
for ( const arg of vis . args ) {
if ( arg . required !== true ) {
continue
}
const param = special [ arg . name ]
2022-07-11 09:14:26 +02:00
if ( param === undefined ) {
2022-11-02 14:44:06 +01:00
errors . push (
` At ${ context } : Obligated parameter ' ${ arg . name } ' in special rendering of type ${ vis . funcName } not found. \ n ${ arg . doc } `
)
2022-03-29 00:20:10 +02:00
}
}
2022-07-11 09:14:26 +02:00
2022-03-29 00:20:10 +02:00
const foundLanguages = new Set < string > ( )
const translatedArgs = argNamesList
. map ( ( nm ) = > special [ nm ] )
. filter ( ( v ) = > v !== undefined )
. filter ( ( v ) = > Translations . isProbablyATranslation ( v ) )
for ( const translatedArg of translatedArgs ) {
for ( const ln of Object . keys ( translatedArg ) ) {
foundLanguages . add ( ln )
2022-07-11 09:14:26 +02:00
}
2022-03-29 00:20:10 +02:00
}
2022-07-11 09:14:26 +02:00
2022-05-06 12:41:24 +02:00
const before = Translations . T ( input . before )
const after = Translations . T ( input . after )
2022-07-11 09:14:26 +02:00
for ( const ln of Object . keys ( before ? . translations ? ? { } ) ) {
2022-05-06 12:41:24 +02:00
foundLanguages . add ( ln )
}
2022-07-11 09:14:26 +02:00
for ( const ln of Object . keys ( after ? . translations ? ? { } ) ) {
2022-05-06 12:41:24 +02:00
foundLanguages . add ( ln )
}
2022-07-11 09:14:26 +02:00
if ( foundLanguages . size === 0 ) {
const args = argNamesList . map ( ( nm ) = > special [ nm ] ? ? "" ) . join ( "," )
return {
"*" : ` { ${ type } ( ${ args } )} ` ,
}
2022-03-29 00:20:10 +02:00
}
2022-07-11 09:14:26 +02:00
2022-03-29 00:20:10 +02:00
const result = { }
const languages = Array . from ( foundLanguages )
languages . sort ( )
for ( const ln of languages ) {
const args = [ ]
for ( const argName of argNamesList ) {
2022-07-29 21:09:58 +02:00
let v = special [ argName ] ? ? ""
2022-07-11 09:14:26 +02:00
if ( Translations . isProbablyATranslation ( v ) ) {
2022-07-29 21:09:58 +02:00
v = new Translation ( v ) . textFor ( ln )
}
2022-09-08 21:40:48 +02:00
2022-07-29 21:09:58 +02:00
if ( typeof v === "string" ) {
2022-07-27 23:59:04 +02:00
const txt = v
. replace ( /,/g , "&COMMA" )
. replace ( /\{/g , "&LBRACE" )
. replace ( /}/g , "&RBRACE" )
2022-07-29 21:09:58 +02:00
. replace ( /\(/g , "&LPARENS" )
. replace ( /\)/g , "&RPARENS" )
2022-07-27 23:59:04 +02:00
args . push ( txt )
2022-07-29 21:09:58 +02:00
} else if ( typeof v === "object" ) {
2022-07-28 09:16:19 +02:00
args . push ( JSON . stringify ( v ) )
2022-07-11 09:14:26 +02:00
} else {
2022-03-29 00:20:10 +02:00
args . push ( v )
}
}
2022-05-06 12:41:24 +02:00
const beforeText = before ? . textFor ( ln ) ? ? ""
const afterText = after ? . textFor ( ln ) ? ? ""
2022-07-27 23:59:04 +02:00
result [ ln ] = ` ${ beforeText } { ${ type } ( ${ args . map ( ( a ) = > a ) . join ( "," ) } )} ${ afterText } `
2022-03-29 00:20:10 +02:00
}
return result
2022-03-08 01:05:54 +01:00
}
2022-03-29 00:20:10 +02:00
/ * *
* const tr = {
* render : { special : { type : "image_carousel" , image_key : "image" } } ,
* mappings : [
* {
* if : "other_image_key" ,
* then : { special : { type : "image_carousel" , image_key : "other_image_key" } }
* }
* ]
* }
* const result = new RewriteSpecial ( ) . convert ( tr , "test" ) . result
* const expected = { render : { '*' : "{image_carousel(image)}" } , mappings : [ { if : "other_image_key" , then : { '*' : "{image_carousel(other_image_key)}" } } ] }
* result // => expected
2022-07-11 09:14:26 +02:00
*
2022-07-29 20:04:36 +02:00
* // Should put text before if specified
2022-05-06 12:41:24 +02:00
* const tr = {
* render : { special : { type : "image_carousel" , image_key : "image" } , before : { en : "Some introduction" } } ,
* }
* const result = new RewriteSpecial ( ) . convert ( tr , "test" ) . result
* const expected = { render : { 'en' : "Some introduction{image_carousel(image)}" } }
* result // => expected
2022-07-29 21:09:58 +02:00
*
2022-07-29 20:04:36 +02:00
* // Should put text after if specified
* const tr = {
* render : { special : { type : "image_carousel" , image_key : "image" } , after : { en : "Some footer" } } ,
* }
* const result = new RewriteSpecial ( ) . convert ( tr , "test" ) . result
* const expected = { render : { 'en' : "{image_carousel(image)}Some footer" } }
* result // => expected
2022-03-29 00:20:10 +02:00
* /
convert (
json : TagRenderingConfigJson ,
context : string
) : {
result : TagRenderingConfigJson
errors? : string [ ]
warnings? : string [ ]
information? : string [ ]
} {
const errors = [ ]
json = Utils . Clone ( json )
2022-07-11 09:14:26 +02:00
const paths : { path : string [ ] ; type ? : any ; typeHint? : string } [ ] =
tagrenderingconfigmeta [ "default" ] ? ? tagrenderingconfigmeta
2022-03-29 00:20:10 +02:00
for ( const path of paths ) {
2022-07-11 09:14:26 +02:00
if ( path . typeHint !== "rendered" ) {
2022-03-29 00:20:10 +02:00
continue
}
Utils . WalkPath ( path . path , json , ( leaf , travelled ) = >
2022-10-29 03:02:42 +02:00
RewriteSpecial . convertIfNeeded ( leaf , errors , context + ":" + travelled . join ( "." ) )
2022-09-08 21:40:48 +02:00
)
2022-03-29 00:20:10 +02:00
}
2022-07-11 09:14:26 +02:00
2022-03-29 00:20:10 +02:00
return {
2022-07-11 09:14:26 +02:00
result : json ,
2022-03-29 00:20:10 +02:00
errors ,
}
2022-03-08 01:05:54 +01:00
}
}
2022-01-21 01:57:16 +01:00
export class PrepareLayer extends Fuse < LayerConfigJson > {
2022-02-04 01:05:35 +01:00
constructor ( state : DesugaringContext ) {
2022-01-21 01:57:16 +01:00
super (
"Fully prepares and expands a layer for the LayerConfig." ,
2022-04-06 03:06:50 +02:00
new On ( "tagRenderings" , new Each ( new RewriteSpecial ( ) ) ) ,
new On ( "tagRenderings" , new Concat ( new ExpandRewrite ( ) ) . andThenF ( Utils . Flatten ) ) ,
2022-07-11 09:14:26 +02:00
new On ( "tagRenderings" , ( layer ) = > new Concat ( new ExpandTagRendering ( state , layer ) ) ) ,
2022-04-06 03:06:50 +02:00
new On ( "mapRendering" , new Concat ( new ExpandRewrite ( ) ) . andThenF ( Utils . Flatten ) ) ,
2022-08-18 14:39:40 +02:00
new On (
"mapRendering" ,
( layer ) = >
new Each (
new On (
"icon" ,
new FirstOf (
new ExpandTagRendering ( state , layer , { applyCondition : false } )
2022-09-08 21:40:48 +02:00
)
)
)
2022-08-18 14:39:40 +02:00
) ,
2022-01-21 01:57:16 +01:00
new SetDefault ( "titleIcons" , [ "defaults" ] ) ,
2022-09-14 16:29:41 +02:00
new On ( "titleIcons" , ( layer ) = > new Concat ( new ExpandTagRendering ( state , layer ) ) ) ,
new ExpandFilter ( )
2022-01-21 01:57:16 +01:00
)
}
}