2020-10-27 01:01:34 +01:00
import { TagRenderingConfigJson } from "./TagRenderingConfigJson" ;
import Translations from "../../UI/i18n/Translations" ;
import { FromJSON } from "./FromJSON" ;
import ValidatedTextField from "../../UI/Input/ValidatedTextField" ;
2020-11-06 01:58:26 +01:00
import { Translation } from "../../UI/i18n/Translation" ;
2021-02-20 16:48:42 +01:00
import { Utils } from "../../Utils" ;
2021-03-22 03:05:08 +01:00
import { TagsFilter } from "../../Logic/TagsFilter" ;
import { And } from "../../Logic/And" ;
import { TagUtils } from "../../Logic/TagUtils" ;
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 {
2021-01-08 03:57:18 +01:00
readonly render? : Translation ;
readonly question? : Translation ;
readonly condition? : TagsFilter ;
2021-03-14 20:14:51 +01:00
readonly configuration_warnings : string [ ] = [ ]
2020-10-27 01:01:34 +01:00
2021-01-08 03:57:18 +01:00
readonly freeform ? : {
readonly key : string ,
readonly type : string ,
readonly addExtraTags : TagsFilter [ ] ;
2020-10-27 01:01:34 +01:00
} ;
2021-01-06 01:11:07 +01:00
readonly multiAnswer : boolean ;
2020-10-27 01:01:34 +01:00
2021-01-08 03:57:18 +01:00
readonly mappings ? : {
2021-02-20 16:48:42 +01:00
readonly if : TagsFilter ,
readonly ifnot? : TagsFilter ,
readonly then : Translation
readonly hideInAnswer : boolean | TagsFilter
2020-10-27 01:01:34 +01:00
} [ ]
2021-01-08 03:57:18 +01:00
readonly roaming : boolean ;
2020-10-27 01:01:34 +01:00
2021-01-08 03:57:18 +01:00
constructor ( json : string | TagRenderingConfigJson , conditionIfRoaming : TagsFilter , context? : string ) {
2020-10-27 01:01:34 +01:00
2020-12-08 23:44:34 +01:00
if ( json === "questions" ) {
// Very special value
this . render = null ;
this . question = null ;
this . condition = null ;
}
if ( json === undefined ) {
throw "Initing a TagRenderingConfig with undefined in " + context ;
2020-10-27 01:01:34 +01:00
}
if ( typeof json === "string" ) {
2021-03-13 19:08:31 +01:00
this . render = Translations . T ( json , context + ".render" ) ;
2020-10-27 01:01:34 +01:00
this . multiAnswer = false ;
return ;
}
2021-01-08 03:57:18 +01:00
2021-03-13 19:08:31 +01:00
this . render = Translations . T ( json . render , context + ".render" ) ;
this . question = Translations . T ( json . question , context + ".question" ) ;
2021-01-08 03:57:18 +01:00
this . roaming = json . roaming ? ? false ;
const condition = FromJSON . Tag ( json . condition ? ? { "and" : [ ] } , ` ${ context } .condition ` ) ;
if ( this . roaming && conditionIfRoaming !== undefined ) {
this . condition = new And ( [ condition , conditionIfRoaming ] ) ;
} else {
this . condition = condition ;
}
2020-10-27 01:01:34 +01:00
if ( json . freeform ) {
this . freeform = {
key : json.freeform.key ,
type : json . freeform . type ? ? "string" ,
addExtraTags : json.freeform.addExtraTags?.map ( ( tg , i ) = >
FromJSON . Tag ( tg , ` ${ context } .extratag[ ${ i } ] ` ) ) ? ? [ ]
}
2021-03-10 20:18:05 +01:00
if ( this . freeform . key === undefined || this . freeform . key === "" ) {
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 } `
}
2020-10-27 01:01:34 +01:00
if ( ValidatedTextField . AllTypes [ this . freeform . type ] === undefined ) {
throw ` Freeform.key ${ this . freeform . key } is an invalid type `
}
2021-03-10 20:18:05 +01:00
if ( this . freeform . addExtraTags ) {
const usedKeys = new And ( this . freeform . addExtraTags ) . usedKeys ( ) ;
2021-03-12 13:48:49 +01: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-03-14 01:40:35 +01:00
2020-10-27 01:01:34 +01:00
this . mappings = json . mappings . map ( ( mapping , i ) = > {
2021-03-14 01:40:35 +01:00
2020-10-27 01:01:34 +01:00
if ( mapping . then === undefined ) {
2021-02-20 16:48:42 +01:00
throw ` ${ context } .mapping[ ${ i } ]: Invalid mapping: if without body `
}
if ( mapping . ifnot !== undefined && ! this . multiAnswer ) {
throw ` ${ context } .mapping[ ${ i } ]: Invalid mapping: ifnot defined, but the tagrendering is not a multianswer `
2020-10-27 01:01:34 +01:00
}
2021-01-06 01:11:07 +01:00
let hideInAnswer : boolean | TagsFilter = false ;
2020-12-08 23:44:34 +01:00
if ( typeof mapping . hideInAnswer === "boolean" ) {
2020-12-07 03:02:50 +01:00
hideInAnswer = mapping . hideInAnswer ;
2020-12-08 23:44:34 +01:00
} else if ( mapping . hideInAnswer !== undefined ) {
hideInAnswer = FromJSON . Tag ( mapping . hideInAnswer , ` ${ context } .mapping[ ${ i } ].hideInAnswer ` ) ;
2020-12-07 03:02:50 +01:00
}
2021-03-13 19:08:31 +01:00
const mappingContext = ` ${ context } .mapping[ ${ i } ] `
2021-02-20 16:48:42 +01:00
const mp = {
2021-03-13 19:08:31 +01:00
if : FromJSON . Tag ( mapping . if , ` ${ mappingContext } .if ` ) ,
ifnot : ( mapping . ifnot !== undefined ? FromJSON . Tag ( mapping . ifnot , ` ${ mappingContext } .ifnot ` ) : undefined ) ,
then : Translations.T ( mapping . then , ` {mappingContext}.then ` ) ,
2020-12-07 03:02:50 +01:00
hideInAnswer : hideInAnswer
2020-10-27 01:01:34 +01:00
} ;
2021-02-20 16:48:42 +01:00
if ( this . question ) {
2021-03-14 01:40:35 +01:00
if ( hideInAnswer !== true && mp . if !== undefined && ! mp . if . isUsableAsAnswer ( ) ) {
2021-02-20 16:48:42 +01:00
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 ) ) {
throw ` ${ context } .mapping[ ${ i } ].ifnot: This value cannot be used to answer a question, probably because it contains a regex or an OR. Either change it or set 'hideInAnswer' `
}
}
return mp ;
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
if ( this . freeform && this . render === undefined ) {
throw ` ${ context } : Detected a freeform key without rendering... Key: ${ this . freeform . key } in ${ context } `
2021-01-12 20:46:01 +01:00
}
2021-03-10 12:55:39 +01: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-14 01:40:35 +01:00
if ( ! json . multiAnswer && this . mappings !== undefined && this . question !== undefined ) {
let keys = [ ]
for ( let i = 0 ; i < this . mappings . length ; i ++ ) {
const mapping = this . mappings [ i ] ;
if ( mapping . if === undefined ) {
throw ` ${ context } .mappings[ ${ i } ].if is undefined `
}
keys . push ( . . . mapping . if . usedKeys ( ) )
}
keys = Utils . Dedup ( keys )
for ( let i = 0 ; i < this . mappings . length ; i ++ ) {
const mapping = this . mappings [ i ] ;
if ( mapping . hideInAnswer ) {
continue
}
const usedKeys = mapping . if . usedKeys ( ) ;
for ( const expectedKey of keys ) {
if ( usedKeys . indexOf ( expectedKey ) < 0 ) {
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
}
}
}
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
* /
public IsKnown ( tags : any ) : boolean {
if ( this . condition &&
! this . condition . matchesProperties ( tags ) ) {
// Filtered away by the condition
return true ;
}
if ( this . multiAnswer ) {
for ( const m of this . mappings ) {
if ( TagUtils . MatchesMultiAnswer ( m . if , tags ) ) {
return true ;
}
}
const free = this . freeform ? . key
if ( free !== undefined ) {
return tags [ free ] !== undefined
}
return false
}
if ( this . GetRenderValue ( tags ) !== undefined ) {
// This value is known and can be rendered
return true ;
}
return false ;
}
2021-03-15 16:23:04 +01:00
public IsQuestionBoxElement ( ) : boolean {
return this . question === null && this . condition === null ;
}
2020-10-27 01:01:34 +01:00
/ * *
* Gets the correct rendering value ( or undefined if not known )
* @constructor
* /
public GetRenderValue ( tags : any ) : Translation {
if ( this . mappings !== undefined && ! this . multiAnswer ) {
for ( const mapping of this . mappings ) {
if ( mapping . if === undefined ) {
return mapping . then ;
}
if ( mapping . if . matchesProperties ( tags ) ) {
return mapping . then ;
}
}
}
2021-01-06 01:11:07 +01:00
if ( this . freeform ? . key === undefined ) {
2020-10-27 01:01:34 +01:00
return this . render ;
}
if ( tags [ this . freeform . key ] !== undefined ) {
return this . render ;
}
return undefined ;
}
}