2022-04-13 01:19:28 +02:00
import { Translation , TypedTranslation } from "../../UI/i18n/Translation"
2021-08-07 23:11:34 +02:00
import { TagsFilter } from "../../Logic/Tags/TagsFilter"
import Translations from "../../UI/i18n/Translations"
2022-09-02 12:40:06 +02:00
import { TagUtils , UploadableTag } from "../../Logic/Tags/TagUtils"
2021-03-29 00:41:53 +02:00
import { And } from "../../Logic/Tags/And"
2021-08-07 23:11:34 +02:00
import ValidatedTextField from "../../UI/Input/ValidatedTextField"
import { Utils } from "../../Utils"
2021-10-26 22:53:27 +02:00
import { Tag } from "../../Logic/Tags/Tag"
2022-01-14 19:34:00 +01:00
import BaseUIElement from "../../UI/BaseUIElement"
import Combine from "../../UI/Base/Combine"
import Title from "../../UI/Base/Title"
import Link from "../../UI/Base/Link"
import List from "../../UI/Base/List"
2022-07-03 13:18:05 +02:00
import {
MappingConfigJson ,
QuestionableTagRenderingConfigJson ,
} from "./Json/QuestionableTagRenderingConfigJson"
2022-04-08 01:55:42 +02:00
import { FixedUiElement } from "../../UI/Base/FixedUiElement"
2022-04-30 00:28:51 +02:00
import { Paragraph } from "../../UI/Base/Paragraph"
2020-10-27 01:01:34 +01:00
2022-07-03 13:18:05 +02:00
export interface Mapping {
2022-09-02 12:40:06 +02:00
readonly if : UploadableTag
readonly ifnot? : UploadableTag
2022-07-03 13:18:05 +02:00
readonly then : TypedTranslation < object >
readonly icon : string
2022-07-13 23:05:50 +02:00
readonly iconClass :
| string
| "small"
| "medium"
| "large"
| "small-height"
| "medium-height"
| "large-height"
2022-07-03 13:18:05 +02:00
readonly hideInAnswer : boolean | TagsFilter
readonly addExtraTags : Tag [ ]
2022-07-15 01:10:10 +02:00
readonly searchTerms? : Record < string , string [ ] >
readonly priorityIf? : TagsFilter
2022-07-03 13:18:05 +02:00
}
2020-10-27 01:01:34 +01:00
/ * * *
* The parsed version of TagRenderingConfigJSON
* Identical data , but with some methods and validation
* /
export default class TagRenderingConfig {
2022-02-11 04:28:11 +01:00
public readonly id : string
public readonly group : string
2022-04-13 01:19:28 +02:00
public readonly render? : TypedTranslation < object >
public readonly question? : TypedTranslation < object >
2022-02-11 04:28:11 +01:00
public readonly condition? : TagsFilter
2022-07-16 03:57:13 +02:00
public readonly description? : Translation
2021-03-31 15:50:29 +02:00
2022-02-11 04:28:11 +01:00
public readonly configuration_warnings : string [ ] = [ ]
2020-10-27 01:01:34 +01:00
2022-02-11 04:28:11 +01:00
public readonly freeform ? : {
2021-03-31 15:50:29 +02:00
readonly key : string
readonly type : string
2022-02-07 01:57:15 +01:00
readonly placeholder : Translation
2022-09-02 12:40:06 +02:00
readonly addExtraTags : UploadableTag [ ]
2021-07-11 15:44:17 +02:00
readonly inline : boolean
2021-07-20 01:33:58 +02:00
readonly default ? : string
readonly helperArgs ? : ( string | number | boolean ) [ ]
2020-10-27 01:01:34 +01:00
}
2022-02-08 02:23:38 +01:00
public readonly multiAnswer : boolean
2020-10-27 01:01:34 +01:00
2022-07-03 13:18:05 +02:00
public readonly mappings? : Mapping [ ]
2022-02-08 02:23:38 +01:00
public readonly labels : string [ ]
2022-01-26 21:40:38 +01:00
2022-02-28 17:17:38 +01:00
constructor ( json : string | QuestionableTagRenderingConfigJson , context? : string ) {
2021-11-14 18:01:48 +01:00
if ( json === undefined ) {
throw "Initing a TagRenderingConfig with undefined in " + context
}
2020-12-08 23:44:34 +01:00
if ( json === "questions" ) {
// Very special value
this . render = null
this . question = null
this . condition = null
2021-11-07 18:37:42 +01:00
this . id = "questions"
this . group = ""
return
2020-12-08 23:44:34 +01:00
}
2021-11-07 16:34:51 +01:00
if ( typeof json === "number" ) {
2021-12-05 02:06:14 +01:00
json = "" + json
2021-10-22 01:07:32 +02:00
}
2021-11-07 16:34:51 +01:00
2022-04-01 12:51:55 +02:00
let translationKey = context
2022-07-03 13:18:05 +02:00
if ( json [ "id" ] !== undefined ) {
2022-04-01 12:51:55 +02:00
const layerId = context . split ( "." ) [ 0 ]
2022-07-03 13:18:05 +02:00
if ( json [ "source" ] ) {
let src = json [ "source" ] + ":"
if ( json [ "source" ] === "shared-questions" ) {
2022-04-01 12:51:55 +02:00
src += "shared_questions."
}
translationKey = ` ${ src } ${ json [ "id" ] ? ? "" } `
2022-07-03 13:18:05 +02:00
} else {
2022-04-01 12:51:55 +02:00
translationKey = ` layers: ${ layerId } .tagRenderings. ${ json [ "id" ] ? ? "" } `
}
}
2020-10-27 01:01:34 +01:00
if ( typeof json === "string" ) {
2022-04-01 12:51:55 +02:00
this . render = Translations . T ( json , translationKey + ".render" )
2020-10-27 01:01:34 +01:00
this . multiAnswer = false
return
}
2021-01-08 03:57:18 +01:00
2021-12-21 18:35:31 +01:00
this . id = json . id ? ? "" // Some tagrenderings - especially for the map rendering - don't need an ID
2021-12-05 02:06:14 +01:00
if ( this . id . match ( /^[a-zA-Z0-9 ()?\/=:;,_-]*$/ ) === null ) {
throw (
"Invalid ID in " +
context +
": an id can only contain [a-zA-Z0-0_-] as characters. The offending id is: " +
this . id
2022-09-08 21:40:48 +02:00
)
2021-11-11 17:14:03 +01:00
}
2021-12-05 02:06:14 +01:00
2021-10-22 18:53:07 +02:00
this . group = json . group ? ? ""
2022-02-08 02:23:38 +01:00
this . labels = json . labels ? ? [ ]
2022-04-01 12:51:55 +02:00
this . render = Translations . T ( json . render , translationKey + ".render" )
this . question = Translations . T ( json . question , translationKey + ".question" )
2022-07-16 03:57:13 +02:00
this . description = Translations . T ( json . description , translationKey + ".description" )
2021-10-19 03:00:57 +02:00
this . condition = TagUtils . Tag ( json . condition ? ? { and : [ ] } , ` ${ context } .condition ` )
2020-10-27 01:01:34 +01:00
if ( json . freeform ) {
2021-11-07 16:34:51 +01:00
if (
json . freeform . addExtraTags !== undefined &&
json . freeform . addExtraTags . map === undefined
) {
2021-09-09 20:26:12 +02:00
throw ` Freeform.addExtraTags should be a list of strings - not a single string (at ${ context } ) `
}
2022-06-14 12:14:01 +02:00
const type = json . freeform . type ? ? "string"
2022-07-03 13:18:05 +02:00
if ( ValidatedTextField . AvailableTypes ( ) . indexOf ( type ) < 0 ) {
throw (
"At " +
context +
".freeform.type is an unknown type: " +
type +
"; try one of " +
ValidatedTextField . AvailableTypes ( ) . join ( ", " )
2022-09-08 21:40:48 +02:00
)
2022-06-14 12:14:01 +02:00
}
2022-02-11 04:28:11 +01:00
2022-04-13 01:19:28 +02:00
let placeholder : Translation = Translations . T ( json . freeform . placeholder )
2022-02-11 04:28:11 +01:00
if ( placeholder === undefined ) {
2022-07-03 13:18:05 +02:00
const typeDescription = < Translation > Translations . t . validation [ type ] ? . description
2022-06-30 03:07:54 +02:00
const key = json . freeform . key
2022-07-03 13:18:05 +02:00
if ( typeDescription !== undefined ) {
placeholder = typeDescription . OnEveryLanguage ( ( l ) = > key + " (" + l + ")" )
} else {
placeholder = Translations . T ( key + " (" + type + ")" )
2022-02-11 04:28:11 +01:00
}
}
2020-10-27 01:01:34 +01:00
this . freeform = {
key : json.freeform.key ,
2022-02-11 04:28:11 +01:00
type ,
placeholder ,
2020-10-27 01:01:34 +01:00
addExtraTags :
json . freeform . addExtraTags ? . map ( ( tg , i ) = >
2022-09-02 12:40:06 +02:00
TagUtils . ParseUploadableTag ( tg , ` ${ context } .extratag[ ${ i } ] ` )
) ? ? [ ] ,
2021-07-11 15:44:17 +02:00
inline : json.freeform.inline ? ? false ,
2021-07-20 01:33:58 +02:00
default : json . freeform . default ,
helperArgs : json.freeform.helperArgs ,
2020-10-27 01:01:34 +01:00
}
2021-04-09 02:57:06 +02:00
if ( json . freeform [ "extraTags" ] !== undefined ) {
2021-03-31 15:50:29 +02:00
throw ` Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${ context } ) `
}
if ( this . freeform . key === undefined || this . freeform . key === "" ) {
2021-03-10 20:18:05 +01:00
throw ` Freeform.key is undefined or the empty string - this is not allowed; either fill out something or remove the freeform block alltogether. Error in ${ context } `
}
2021-08-07 23:11:34 +02:00
if ( json . freeform [ "args" ] !== undefined ) {
2021-07-29 00:29:29 +02:00
throw ` Freeform.args is defined. This should probably be 'freeform.helperArgs' (at ${ context } ) `
}
2021-12-05 02:06:14 +01:00
if ( json . freeform . key === "questions" ) {
if ( this . id !== "questions" ) {
2021-11-12 01:44:13 +01:00
throw ` If you use a freeform key 'questions', the ID must be 'questions' too to trigger the special behaviour. The current id is ' ${ this . id } ' (at ${ context } ) `
}
}
2021-06-27 19:21:31 +02:00
2022-06-23 03:06:51 +02:00
if (
this . freeform . type !== undefined &&
ValidatedTextField . AvailableTypes ( ) . indexOf ( this . freeform . type ) < 0
) {
2022-02-11 20:56:54 +01:00
const knownKeys = ValidatedTextField . AvailableTypes ( ) . join ( ", " )
2021-04-18 14:24:30 +02:00
throw ` Freeform.key ${ this . freeform . key } is an invalid type. Known keys are ${ knownKeys } `
2020-10-27 01:01:34 +01:00
}
2021-03-31 15:50:29 +02:00
if ( this . freeform . addExtraTags ) {
2021-03-10 20:18:05 +01:00
const usedKeys = new And ( this . freeform . addExtraTags ) . usedKeys ( )
2021-03-31 15:50:29 +02:00
if ( usedKeys . indexOf ( this . freeform . key ) >= 0 ) {
2021-03-10 20:18:05 +01:00
throw ` The freeform key ${ this . freeform . key } will be overwritten by one of the extra tags, as they use the same key too. This is in ${ context } `
}
}
2020-10-27 01:01:34 +01:00
}
this . multiAnswer = json . multiAnswer ? ? false
if ( json . mappings ) {
2021-06-22 00:29:07 +02:00
if ( ! Array . isArray ( json . mappings ) ) {
throw "Tagrendering has a 'mappings'-object, but expected a list (" + context + ")"
2021-04-10 03:50:44 +02:00
}
2021-03-31 15:50:29 +02:00
2022-07-10 17:47:29 +02:00
const commonIconSize =
Utils . NoNull (
json . mappings . map ( ( m ) = > ( m . icon !== undefined ? m . icon [ "class" ] : undefined ) )
) [ 0 ] ? ? "small"
this . mappings = json . mappings . map ( ( m , i ) = >
TagRenderingConfig . ExtractMapping (
m ,
i ,
translationKey ,
context ,
this . multiAnswer ,
this . question !== undefined ,
commonIconSize
2022-09-08 21:40:48 +02:00
)
)
2020-10-27 01:01:34 +01:00
}
if ( this . question && this . freeform ? . key === undefined && this . mappings === undefined ) {
2021-02-20 16:48:42 +01:00
throw ` ${ context } : A question is defined, but no mappings nor freeform (key) are. The question is ${ this . question . txt } at ${ context } `
2020-10-27 01:01:34 +01:00
}
2021-02-20 16:48:42 +01:00
2021-11-11 17:14:03 +01:00
if ( this . id === "questions" && this . render !== undefined ) {
for ( const ln in this . render . translations ) {
2021-12-05 02:06:14 +01:00
const txt : string = this . render . translations [ ln ]
if ( txt . indexOf ( "{questions}" ) >= 0 ) {
2021-11-11 17:14:03 +01:00
continue
}
throw ` ${ context } : The rendering for language ${ ln } does not contain {questions}. This is a bug, as this rendering should include exactly this to trigger those questions to be shown! `
}
2021-12-05 02:06:14 +01:00
if ( this . freeform ? . key !== undefined && this . freeform ? . key !== "questions" ) {
2021-11-12 01:44:13 +01:00
throw ` ${ context } : If the ID is questions to trigger a question box, the only valid freeform value is 'questions' as well. Set freeform to questions or remove the freeform all together `
}
2021-11-11 17:14:03 +01:00
}
2021-11-10 18:42:31 +01:00
if ( this . freeform ) {
2021-12-05 02:06:14 +01:00
if ( this . render === undefined ) {
2021-11-10 18:42:31 +01:00
throw ` ${ context } : Detected a freeform key without rendering... Key: ${ this . freeform . key } in ${ context } `
}
for ( const ln in this . render . translations ) {
2022-11-02 14:44:06 +01:00
if ( ln . startsWith ( "_" ) ) {
2022-10-29 03:02:42 +02:00
continue
}
2021-12-05 02:06:14 +01:00
const txt : string = this . render . translations [ ln ]
if ( txt === "" ) {
throw context + " Rendering for language " + ln + " is empty"
2021-11-10 18:42:31 +01:00
}
2021-12-05 02:06:14 +01:00
if ( txt . indexOf ( "{" + this . freeform . key + "}" ) >= 0 ) {
2021-11-10 18:42:31 +01:00
continue
}
2021-12-05 02:06:14 +01:00
if ( txt . indexOf ( "{" + this . freeform . key + ":" ) >= 0 ) {
2021-11-12 01:44:13 +01:00
continue
}
2021-12-05 02:06:14 +01:00
if ( txt . indexOf ( "{canonical(" + this . freeform . key + ")" ) >= 0 ) {
2021-11-10 18:42:31 +01:00
continue
}
2021-12-05 02:06:14 +01:00
if (
this . freeform . type === "opening_hours" &&
txt . indexOf ( "{opening_hours_table(" ) >= 0
) {
2021-11-10 18:42:31 +01:00
continue
}
2021-12-05 02:06:14 +01:00
if (
this . freeform . type === "wikidata" &&
txt . indexOf ( "{wikipedia(" + this . freeform . key ) >= 0
) {
2021-11-10 18:42:31 +01:00
continue
}
2021-12-05 02:06:14 +01:00
if ( this . freeform . key === "wikidata" && txt . indexOf ( "{wikipedia()" ) >= 0 ) {
2021-11-10 18:42:31 +01:00
continue
}
2022-10-27 01:50:41 +02:00
if (
this . freeform . type === "wikidata" &&
txt . indexOf ( ` {wikidata_label( ${ this . freeform . key } ) ` ) >= 0
) {
2022-09-14 16:29:41 +02:00
continue
}
2021-11-10 18:42:31 +01:00
throw ` ${ context } : The rendering for language ${ ln } does not contain the freeform key { ${ this . freeform . key } }. This is a bug, as this rendering should show exactly this freeform key! \ nThe rendering is ${ txt } `
}
}
2021-03-31 15:50:29 +02:00
if ( this . render && this . question && this . freeform === undefined ) {
2021-03-26 00:14:17 +01:00
throw ` ${ context } : Detected a tagrendering which takes input without freeform key in ${ context } ; the question is ${ this . question . txt } `
2021-03-10 12:55:39 +01:00
}
2021-03-31 15:50:29 +02:00
if ( ! json . multiAnswer && this . mappings !== undefined && this . question !== undefined ) {
2021-03-14 01:40:35 +01:00
let keys = [ ]
2021-03-31 15:50:29 +02:00
for ( let i = 0 ; i < this . mappings . length ; i ++ ) {
2021-03-14 01:40:35 +01:00
const mapping = this . mappings [ i ]
2021-03-31 15:50:29 +02:00
if ( mapping . if === undefined ) {
2021-03-14 01:40:35 +01:00
throw ` ${ context } .mappings[ ${ i } ].if is undefined `
}
keys . push ( . . . mapping . if . usedKeys ( ) )
}
keys = Utils . Dedup ( keys )
2021-03-31 15:50:29 +02:00
for ( let i = 0 ; i < this . mappings . length ; i ++ ) {
2021-03-14 01:40:35 +01:00
const mapping = this . mappings [ i ]
2021-03-31 15:50:29 +02:00
if ( mapping . hideInAnswer ) {
2021-03-14 01:40:35 +01:00
continue
}
2021-03-31 15:50:29 +02:00
2021-03-14 01:40:35 +01:00
const usedKeys = mapping . if . usedKeys ( )
for ( const expectedKey of keys ) {
2021-03-31 15:50:29 +02:00
if ( usedKeys . indexOf ( expectedKey ) < 0 ) {
2021-03-14 01:40:35 +01:00
const msg = ` ${ context } .mappings[ ${ i } ]: This mapping only defines values for ${ usedKeys . join (
", "
) } , but it should also give a value for $ { expectedKey } `
2021-03-14 20:14:51 +01:00
this . configuration_warnings . push ( msg )
2021-03-14 01:40:35 +01:00
}
}
}
}
2020-10-27 01:01:34 +01:00
2021-02-20 16:48:42 +01:00
if ( this . question !== undefined && json . multiAnswer ) {
2020-10-27 01:01:34 +01:00
if ( ( this . mappings ? . length ? ? 0 ) === 0 ) {
2021-02-20 16:48:42 +01:00
throw ` ${ context } MultiAnswer is set, but no mappings are defined `
}
let allKeys = [ ]
let allHaveIfNot = true
for ( const mapping of this . mappings ) {
if ( mapping . hideInAnswer ) {
continue
}
if ( mapping . ifnot === undefined ) {
allHaveIfNot = false
}
allKeys = allKeys . concat ( mapping . if . usedKeys ( ) )
}
allKeys = Utils . Dedup ( allKeys )
if ( allKeys . length > 1 && ! allHaveIfNot ) {
throw ` ${ context } : A multi-answer is defined, which generates values over multiple keys. Please define ifnot-tags too on every mapping `
2020-10-27 01:01:34 +01:00
}
}
}
2022-07-15 01:10:10 +02:00
/ * *
* const tr = TagRenderingConfig . ExtractMapping ( { if : "a=b" , then : "x" , priorityIf : "_country=be" } , 0 , "test" , "test" , false , true )
* tr . if // => new Tag("a","b")
* tr . priorityIf // => new Tag("_country","be")
* /
2022-07-03 13:18:05 +02:00
public static ExtractMapping (
mapping : MappingConfigJson ,
i : number ,
translationKey : string ,
context : string ,
2022-07-10 17:47:29 +02:00
multiAnswer? : boolean ,
isQuestionable? : boolean ,
commonSize : string = "small"
) {
2022-07-03 13:18:05 +02:00
const ctx = ` ${ translationKey } .mappings. ${ i } `
if ( mapping . if === undefined ) {
throw ` ${ ctx } : Invalid mapping: "if" is not defined in ${ JSON . stringify ( mapping ) } `
}
if ( mapping . then === undefined ) {
if ( mapping [ "render" ] !== undefined ) {
throw ` ${ ctx } : Invalid mapping: no 'then'-clause found. You might have typed 'render' instead of 'then', change it in ${ JSON . stringify (
mapping
) } `
}
throw ` ${ ctx } : Invalid mapping: no 'then'-clause found in ${ JSON . stringify ( mapping ) } `
}
if ( mapping . ifnot !== undefined && ! multiAnswer ) {
throw ` ${ ctx } : Invalid mapping: 'ifnot' is defined, but the tagrendering is not a multianswer. Either remove ifnot or set 'multiAnswer:true' to enable checkboxes instead of radiobuttons `
}
if ( mapping [ "render" ] !== undefined ) {
throw ` ${ ctx } : Invalid mapping: a 'render'-key is present, this is probably a bug: ${ JSON . stringify (
mapping
) } `
}
if ( typeof mapping . if !== "string" && mapping . if [ "length" ] !== undefined ) {
throw ` ${ ctx } : Invalid mapping: "if" is defined as an array. Use {"and": <your conditions>} or {"or": <your conditions>} instead `
}
if ( mapping . addExtraTags !== undefined && multiAnswer ) {
throw ` ${ ctx } : Invalid mapping: got a multi-Answer with addExtraTags; this is not allowed `
}
let hideInAnswer : boolean | TagsFilter = false
if ( typeof mapping . hideInAnswer === "boolean" ) {
hideInAnswer = mapping . hideInAnswer
} else if ( mapping . hideInAnswer !== undefined ) {
hideInAnswer = TagUtils . Tag (
mapping . hideInAnswer ,
` ${ context } .mapping[ ${ i } ].hideInAnswer `
2022-09-08 21:40:48 +02:00
)
2022-07-03 13:18:05 +02:00
}
const addExtraTags = ( mapping . addExtraTags ? ? [ ] ) . map ( ( str , j ) = >
TagUtils . SimpleTag ( str , ` ${ ctx } .addExtraTags[ ${ j } ] ` )
2022-09-08 21:40:48 +02:00
)
2022-07-03 13:18:05 +02:00
if ( hideInAnswer === true && addExtraTags . length > 0 ) {
throw ` ${ ctx } : Invalid mapping: 'hideInAnswer' is set to 'true', but 'addExtraTags' is enabled as well. This means that extra tags will be applied if this mapping is chosen as answer, but it cannot be chosen as answer. This either indicates a thought error or obsolete code that must be removed. `
}
let icon = undefined
2022-07-10 17:47:29 +02:00
let iconClass = commonSize
2022-07-03 13:18:05 +02:00
if ( mapping . icon !== undefined ) {
if ( typeof mapping . icon === "string" && mapping . icon !== "" ) {
icon = mapping . icon
} else {
icon = mapping . icon [ "path" ]
iconClass = mapping . icon [ "class" ] ? ? iconClass
}
}
2022-07-15 01:10:10 +02:00
const prioritySearch =
mapping . priorityIf !== undefined ? TagUtils . Tag ( mapping . priorityIf ) : undefined
2022-07-03 13:18:05 +02:00
const mp = < Mapping > {
if : TagUtils . Tag ( mapping . if , ` ${ ctx } .if ` ) ,
ifnot :
mapping . ifnot !== undefined
? TagUtils . Tag ( mapping . ifnot , ` ${ ctx } .ifnot ` )
: undefined ,
then : Translations.T ( mapping . then , ` ${ ctx } .then ` ) ,
hideInAnswer ,
icon ,
iconClass ,
addExtraTags ,
2022-07-15 01:10:10 +02:00
searchTerms : mapping.searchTerms ,
priorityIf : prioritySearch ,
2022-07-03 13:18:05 +02:00
}
if ( isQuestionable ) {
if ( hideInAnswer !== true && mp . if !== undefined && ! mp . if . isUsableAsAnswer ( ) ) {
throw ` ${ context } .mapping[ ${ i } ].if: This value cannot be used to answer a question, probably because it contains a regex or an OR. Either change it or set 'hideInAnswer' `
}
if ( hideInAnswer !== true && ! ( mp . ifnot ? . isUsableAsAnswer ( ) ? ? true ) ) {
2022-07-06 12:26:37 +02:00
throw ` ${ context } .mapping[ ${ i } ].ifnot: This value cannot be used to answer a question, probably because it contains a regex or an OR. If a contributor were to pick this as an option, MapComplete wouldn't be able to determine which tags to add. \ n Either change it or set 'hideInAnswer' `
2022-07-03 13:18:05 +02:00
}
}
return mp
}
2021-03-13 17:25:44 +01:00
/ * *
* Returns true if it is known or not shown , false if the question should be asked
* @constructor
* /
2022-07-12 10:23:45 +02:00
public IsKnown ( tags : Record < string , string > ) : boolean {
2021-03-13 17:25:44 +01:00
if ( this . condition && ! this . condition . matchesProperties ( tags ) ) {
2021-11-10 18:42:31 +01:00
// Filtered away by the condition, so it is kindof known
2021-03-13 17:25:44 +01:00
return true
}
2021-03-31 15:50:29 +02:00
if ( this . multiAnswer ) {
2021-04-18 14:24:30 +02:00
for ( const m of this . mappings ? ? [ ] ) {
2021-03-31 15:50:29 +02:00
if ( TagUtils . MatchesMultiAnswer ( m . if , tags ) ) {
2021-03-13 17:25:44 +01:00
return true
}
}
const free = this . freeform ? . key
2021-03-31 15:50:29 +02:00
if ( free !== undefined ) {
2022-02-22 14:13:41 +01:00
const value = tags [ free ]
return value !== undefined && value !== ""
2021-03-13 17:25:44 +01:00
}
return false
}
if ( this . GetRenderValue ( tags ) !== undefined ) {
// This value is known and can be rendered
return true
}
return false
}
2021-11-07 16:34:51 +01:00
2021-06-14 02:39:23 +02:00
/ * *
* Gets all the render values . Will return multiple render values if 'multianswer' is enabled .
* The result will equal [ GetRenderValue ] if not 'multiAnswer'
* @param tags
* @constructor
* /
2022-07-12 10:23:45 +02:00
public GetRenderValues (
tags : Record < string , string >
) : { then : Translation ; icon? : string ; iconClass? : string } [ ] {
2021-06-22 00:29:07 +02:00
if ( ! this . multiAnswer ) {
2022-01-29 02:45:59 +01:00
return [ this . GetRenderValueWithImage ( tags ) ]
2021-06-14 02:39:23 +02:00
}
// A flag to check that the freeform key isn't matched multiple times
// If it is undefined, it is "used" already, or at least we don't have to check for it anymore
2022-03-14 20:45:17 +01:00
let freeformKeyDefined = this . freeform ? . key !== undefined
let usedFreeformValues = new Set < string > ( )
2021-06-14 02:39:23 +02:00
// We run over all the mappings first, to check if the mapping matches
2022-07-12 10:23:45 +02:00
const applicableMappings : {
then : TypedTranslation < Record < string , string > >
img? : string
} [ ] = Utils . NoNull (
( this . mappings ? ? [ ] ) ? . map ( ( mapping ) = > {
2021-06-14 02:39:23 +02:00
if ( mapping . if === undefined ) {
2022-01-29 02:45:59 +01:00
return mapping
2021-06-14 02:39:23 +02:00
}
if ( TagUtils . MatchesMultiAnswer ( mapping . if , tags ) ) {
2022-03-14 20:45:17 +01:00
if ( freeformKeyDefined && mapping . if . isUsableAsAnswer ( ) ) {
// THe freeform key is defined: what value does it use though?
// We mark the value to see if we have any leftovers
const value = mapping . if
2022-01-15 01:22:06 +01:00
. asChange ( { } )
2022-03-14 20:45:17 +01:00
. find ( ( kv ) = > kv . k === this . freeform . key ) . v
usedFreeformValues . add ( value )
2022-09-08 21:40:48 +02:00
}
2022-01-29 02:45:59 +01:00
return mapping
2021-06-14 02:39:23 +02:00
}
return undefined
} )
2022-09-08 21:40:48 +02:00
)
2021-06-22 00:29:07 +02:00
2022-07-03 13:18:05 +02:00
if ( freeformKeyDefined && tags [ this . freeform . key ] !== undefined ) {
2022-03-14 20:45:17 +01:00
const freeformValues = tags [ this . freeform . key ] . split ( ";" )
const leftovers = freeformValues . filter ( ( v ) = > ! usedFreeformValues . has ( v ) )
for ( const leftover of leftovers ) {
2022-07-03 13:18:05 +02:00
applicableMappings . push ( {
then : new TypedTranslation < object > (
this . render . replace ( "{" + this . freeform . key + "}" , leftover ) . translations
2022-09-08 21:40:48 +02:00
) ,
2022-03-15 01:42:38 +01:00
} )
2022-03-14 20:45:17 +01:00
}
2021-06-14 02:39:23 +02:00
}
2022-07-03 13:18:05 +02:00
2021-06-14 02:39:23 +02:00
return applicableMappings
}
2021-06-22 00:29:07 +02:00
2022-05-26 14:22:48 +02:00
public GetRenderValue (
tags : any ,
defltValue : any = undefined
) : TypedTranslation < any > | undefined {
return this . GetRenderValueWithImage ( tags , defltValue ) ? . then
2022-01-29 02:45:59 +01:00
}
2022-02-11 04:28:11 +01:00
2020-10-27 01:01:34 +01:00
/ * *
* Gets the correct rendering value ( or undefined if not known )
2021-06-22 00:29:07 +02:00
* Not compatible with multiAnswer - use GetRenderValueS instead in that case
2020-10-27 01:01:34 +01:00
* @constructor
* /
2022-05-26 14:22:48 +02:00
public GetRenderValueWithImage (
tags : any ,
defltValue : any = undefined
) : { then : TypedTranslation < any > ; icon? : string } | undefined {
2022-07-03 13:18:05 +02:00
if ( this . condition !== undefined ) {
if ( ! this . condition . matchesProperties ( tags ) ) {
2022-05-26 14:22:48 +02:00
return undefined
}
}
2022-07-03 13:18:05 +02:00
2020-10-27 01:01:34 +01:00
if ( this . mappings !== undefined && ! this . multiAnswer ) {
for ( const mapping of this . mappings ) {
if ( mapping . if === undefined ) {
2022-01-29 02:45:59 +01:00
return mapping
2020-10-27 01:01:34 +01:00
}
if ( mapping . if . matchesProperties ( tags ) ) {
2022-01-29 02:45:59 +01:00
return mapping
2020-10-27 01:01:34 +01:00
}
}
}
2022-01-29 02:45:59 +01:00
if (
this . id === "questions" ||
this . freeform ? . key === undefined ||
tags [ this . freeform . key ] !== undefined
) {
return { then : this.render }
2021-11-12 01:44:13 +01:00
}
2021-01-06 01:11:07 +01:00
2022-01-29 02:45:59 +01:00
return { then : defltValue }
2020-10-27 01:01:34 +01:00
}
2021-12-05 02:06:14 +01:00
/ * *
* Gets all translations that might be rendered in all languages
* USed for static analysis
* @constructor
* @private
* /
EnumerateTranslations ( ) : Translation [ ] {
const translations : Translation [ ] = [ ]
for ( const key in this ) {
2022-01-14 19:34:00 +01:00
if ( ! this . hasOwnProperty ( key ) ) {
2021-12-05 02:06:14 +01:00
continue
}
const o = this [ key ]
if ( o instanceof Translation ) {
translations . push ( o )
}
}
return translations
}
2022-01-14 19:34:00 +01:00
FreeformValues ( ) : { key : string ; type ? : string ; values? : string [ ] } {
try {
const key = this . freeform ? . key
2022-01-15 01:22:06 +01:00
const answerMappings = this . mappings ? . filter ( ( m ) = > m . hideInAnswer !== true )
2022-01-14 19:34:00 +01:00
if ( key === undefined ) {
2022-01-15 01:22:06 +01:00
let values : { k : string ; v : string } [ ] [ ] = Utils . NoNull (
answerMappings ? . map ( ( m ) = > m . if . asChange ( { } ) ) ? ? [ ]
2022-09-08 21:40:48 +02:00
)
2022-01-14 19:34:00 +01:00
if ( values . length === 0 ) {
return
}
const allKeys = values . map ( ( arr ) = > arr . map ( ( o ) = > o . k ) )
let common = allKeys [ 0 ]
for ( const keyset of allKeys ) {
common = common . filter ( ( item ) = > keyset . indexOf ( item ) >= 0 )
}
const commonKey = common [ 0 ]
if ( commonKey === undefined ) {
return undefined
}
return {
key : commonKey ,
values : Utils.NoNull (
values . map ( ( arr ) = > arr . filter ( ( item ) = > item . k === commonKey ) [ 0 ] ? . v )
2022-09-08 21:40:48 +02:00
) ,
2022-01-14 19:34:00 +01:00
}
}
2022-01-15 01:22:06 +01:00
let values = Utils . NoNull (
answerMappings ? . map (
( m ) = > m . if . asChange ( { } ) . filter ( ( item ) = > item . k === key ) [ 0 ] ? . v
) ? ? [ ]
2022-09-08 21:40:48 +02:00
)
2022-01-14 19:34:00 +01:00
if ( values . length === undefined ) {
values = undefined
}
return {
key ,
type : this . freeform . type ,
values ,
}
} catch ( e ) {
console . error ( "Could not create FreeformValues for tagrendering" , this . id )
return undefined
}
}
GenerateDocumentation ( ) : BaseUIElement {
let withRender : ( BaseUIElement | string ) [ ] = [ ]
if ( this . freeform ? . key !== undefined ) {
withRender = [
` This rendering asks information about the property ` ,
Link . OsmWiki ( this . freeform . key ) ,
2022-04-30 00:28:51 +02:00
new Paragraph (
new Combine ( [
2022-05-17 03:22:58 +02:00
"This is rendered with " ,
2022-04-30 00:28:51 +02:00
new FixedUiElement ( this . render . txt ) . SetClass ( "literalcode bold" ) ,
] )
2022-09-08 21:40:48 +02:00
) ,
2022-01-14 19:34:00 +01:00
]
}
let mappings : BaseUIElement = undefined
if ( this . mappings !== undefined ) {
mappings = new List (
2022-04-30 00:28:51 +02:00
[ ] . concat (
. . . this . mappings . map ( ( m ) = > {
2022-07-03 13:18:05 +02:00
const msgs : ( string | BaseUIElement ) [ ] = [
2022-04-30 00:28:51 +02:00
new Combine ( [
2022-07-03 13:18:05 +02:00
new FixedUiElement ( m . then . txt ) . SetClass ( "bold" ) ,
2022-07-16 03:57:13 +02:00
" corresponds with " ,
new FixedUiElement ( m . if . asHumanString ( true , false , { } ) ) . SetClass (
"code"
2022-04-30 00:28:51 +02:00
) ,
2022-09-08 21:40:48 +02:00
] ) ,
2022-04-30 00:28:51 +02:00
]
2022-01-26 21:40:38 +01:00
if ( m . hideInAnswer === true ) {
2022-04-30 00:28:51 +02:00
msgs . push (
new FixedUiElement (
"This option cannot be chosen as answer"
) . SetClass ( "italic" )
2022-09-08 21:40:48 +02:00
)
2022-01-15 01:22:06 +01:00
}
2022-01-26 21:40:38 +01:00
if ( m . ifnot !== undefined ) {
2022-07-03 13:18:05 +02:00
msgs . push (
"Unselecting this answer will add " +
m . ifnot . asHumanString ( true , false , { } )
2022-09-08 21:40:48 +02:00
)
2022-01-15 01:22:06 +01:00
}
2022-04-30 00:28:51 +02:00
return msgs
2022-01-15 01:22:06 +01:00
} )
2022-04-30 00:28:51 +02:00
)
2022-01-14 19:34:00 +01:00
)
}
2022-07-03 13:18:05 +02:00
let condition : BaseUIElement = undefined
if ( this . condition !== undefined && ! this . condition ? . matchesProperties ( { } ) ) {
2022-05-17 03:22:58 +02:00
condition = new Combine ( [
2022-12-05 23:14:07 +01:00
"This tagrendering is only visible in the popup if the following condition is met:" ,
new FixedUiElement ( this . condition . asHumanString ( false , false , { } ) ) . SetClass ( "code" )
2022-07-03 13:18:05 +02:00
] )
2022-04-08 01:55:42 +02:00
}
2022-07-03 13:18:05 +02:00
let group : BaseUIElement = undefined
if ( this . group !== undefined && this . group !== "" ) {
2022-04-08 22:13:10 +02:00
group = new Combine ( [
"This tagrendering is part of group " ,
new FixedUiElement ( this . group ) . SetClass ( "code" ) ,
] )
}
2022-07-03 13:18:05 +02:00
let labels : BaseUIElement = undefined
if ( this . labels ? . length > 0 ) {
2022-04-08 22:13:10 +02:00
labels = new Combine ( [
"This tagrendering has labels " ,
. . . this . labels . map ( ( label ) = > new FixedUiElement ( label ) . SetClass ( "code" ) ) ,
2022-07-16 03:57:13 +02:00
] ) . SetClass ( "flex" )
2022-04-08 22:13:10 +02:00
}
2022-09-08 21:40:48 +02:00
2022-01-14 19:34:00 +01:00
return new Combine ( [
new Title ( this . id , 3 ) ,
2022-07-16 03:57:13 +02:00
this . description ,
2022-04-30 00:28:51 +02:00
this . question !== undefined
2022-07-16 03:57:13 +02:00
? new Combine ( [
"The question is " ,
new FixedUiElement ( this . question . txt ) . SetClass ( "font-bold bold" ) ,
] )
2022-04-30 00:28:51 +02:00
: new FixedUiElement (
2022-07-03 13:18:05 +02:00
"This tagrendering has no question and is thus read-only"
2022-04-30 00:28:51 +02:00
) . SetClass ( "italic" ) ,
2022-01-14 19:34:00 +01:00
new Combine ( withRender ) ,
2022-04-08 01:55:42 +02:00
mappings ,
2022-04-08 22:13:10 +02:00
condition ,
group ,
labels ,
2022-07-16 03:57:13 +02:00
] ) . SetClass ( "flex flex-col" )
2022-01-14 19:34:00 +01:00
}
2020-10-27 01:01:34 +01:00
}