2022-07-03 13:18:05 +02:00
import { Concat , Conversion , DesugaringContext , DesugaringStep , Each , FirstOf , Fuse , On , SetDefault } from "./Conversion" ;
2022-01-21 01:57:16 +01:00
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" ;
import { Translation } from "../../../UI/i18n/Translation" ;
2022-03-29 00:20:10 +02:00
import * as tagrenderingconfigmeta from "../../../assets/tagrenderingconfigmeta.json"
2022-04-08 21:53:08 +02:00
import { AddContextToTranslations } from "./AddContextToTranslations" ;
2022-07-03 13:18:05 +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-02-28 18:52:28 +01:00
2022-08-18 14:39:40 +02:00
constructor ( state : DesugaringContext , self : LayerConfigJson , options ? : { applyCondition? : true | boolean ; } ) {
2022-04-06 03:06:50 +02:00
super ( "Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question" , [ ] , "ExpandTagRendering" ) ;
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-01-21 01:57:16 +01: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 )
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 ) )
}
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 ) {
const [ layerName , search ] = name . split ( "." )
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-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 ( ", " ) )
} else {
errors . push ( ctx + ": While reusing tagrendering: " + name + ": layer " + layerName + " not found. Maybe you meant on of " + candidates . slice ( 0 , 3 ) . join ( ", " ) )
}
2022-07-11 09:14:26 +02:00
continue
}
candidates = Utils . NoNull ( layer . tagRenderings . map ( tr = > tr [ "id" ] ) ) . map ( id = > layerName + "." + id )
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-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 ) {
2022-01-29 02:45:59 +01:00
2022-04-03 02:37:31 +02:00
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 ) {
2022-04-03 02:37:31 +02:00
2022-02-28 18:52:28 +01:00
// 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-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-07-29 21:09:58 +02:00
* errors // => ["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 ` )
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 = > {
return ` 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-11 09:14:26 +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-07-29 20:04:36 +02:00
return ` Unexpected argument in special block at ${ context } with name ' ${ wrongArg } '. Did you mean ${ byDistance [ 0 ] } ? \ n \ tAll known arguments are ${ argNamesList . join ( ", " ) } ` ;
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-03-29 00:20:10 +02:00
errors . push ( ` Obligated parameter ' ${ arg . name } ' not found ` )
}
}
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 )
}
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 ) = > RewriteSpecial . convertIfNeeded ( leaf , errors , travelled . join ( "." ) ) ) )
}
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-07-11 09:14:26 +02:00
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-01-21 01:57:16 +01:00
new SetDefault ( "titleIcons" , [ "defaults" ] ) ,
2022-07-11 09:14:26 +02:00
new On ( "titleIcons" , layer = > new Concat ( new ExpandTagRendering ( state , layer ) ) )
2022-01-21 01:57:16 +01:00
) ;
}
}