2020-11-06 01:58:26 +01:00
import { Translation } from "../../UI/i18n/Translation" ;
2021-08-07 23:11:34 +02:00
import { TagsFilter } from "../../Logic/Tags/TagsFilter" ;
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" ;
import Translations from "../../UI/i18n/Translations" ;
2021-03-29 00:41:53 +02:00
import { TagUtils } from "../../Logic/Tags/TagUtils" ;
import { And } from "../../Logic/Tags/And" ;
2021-08-07 23:11:34 +02:00
import ValidatedTextField from "../../UI/Input/ValidatedTextField" ;
import { Utils } from "../../Utils" ;
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-31 15:50:29 +02:00
readonly configuration_warnings : string [ ] = [ ]
2020-10-27 01:01:34 +01:00
2021-01-08 03:57:18 +01:00
readonly freeform ? : {
2021-03-31 15:50:29 +02:00
readonly key : string ,
readonly type : string ,
readonly addExtraTags : TagsFilter [ ] ;
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
} ;
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-31 15:50:29 +02: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-31 15:50:29 +02: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 ;
2021-08-07 23:11:34 +02:00
const condition = TagUtils . Tag ( json . condition ? ? { "and" : [ ] } , ` ${ context } .condition ` ) ;
2021-01-08 03:57:18 +01:00
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 ) {
2021-06-22 00:29:07 +02:00
2021-09-13 01:21:47 +02: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 } ) `
}
2020-10-27 01:01:34 +01:00
this . freeform = {
key : json.freeform.key ,
type : json . freeform . type ? ? "string" ,
addExtraTags : json.freeform.addExtraTags?.map ( ( tg , i ) = >
2021-08-07 23:11:34 +02:00
TagUtils . Tag ( 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
2021-06-27 19:21:31 +02:00
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-06-27 19:21:31 +02:00
2020-10-27 01:01:34 +01:00
if ( ValidatedTextField . AllTypes [ this . freeform . type ] === undefined ) {
2021-04-18 14:24:30 +02:00
const knownKeys = ValidatedTextField . tpList . map ( tp = > tp . name ) . join ( ", " ) ;
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-03-31 15:50:29 +02:00
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
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-06-22 00:29:07 +02:00
if ( mapping . if === undefined ) {
2021-05-17 17:10:54 +02:00
throw ` ${ context } .mapping[ ${ i } ]: Invalid mapping: "if" is not defined, but the tagrendering is not a multianswer `
}
2021-06-22 00:29:07 +02:00
if ( typeof mapping . if !== "string" && mapping . if [ "length" ] !== undefined ) {
2021-05-17 17:10:54 +02:00
throw ` ${ context } .mapping[ ${ i } ]: Invalid mapping: "if" is defined as an array. Use {"and": <your conditions>} or {"or": <your conditions>} instead `
}
2021-06-22 00:29:07 +02: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 ) {
2021-08-07 23:11:34 +02:00
hideInAnswer = TagUtils . 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-08-07 23:11:34 +02:00
if : TagUtils . Tag ( mapping . if , ` ${ mappingContext } .if ` ) ,
ifnot : ( mapping . ifnot !== undefined ? TagUtils . Tag ( mapping . ifnot , ` ${ mappingContext } .ifnot ` ) : undefined ) ,
2021-03-13 19:08:31 +01:00
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-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
}
}
}
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 ;
}
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 ) {
2021-03-13 17:25:44 +01:00
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-31 15:50:29 +02:00
public IsQuestionBoxElement ( ) : boolean {
2021-03-15 16:23:04 +01:00
return this . question === null && this . condition === null ;
}
2021-03-31 15:50:29 +02: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
* /
2021-06-22 00:29:07 +02:00
public GetRenderValues ( tags : any ) : Translation [ ] {
if ( ! this . multiAnswer ) {
2021-06-14 02:39:23 +02:00
return [ this . GetRenderValue ( tags ) ]
}
// 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
2021-06-22 00:29:07 +02:00
let freeformKeyUsed = this . freeform ? . key === undefined ;
2021-06-14 02:39:23 +02:00
// We run over all the mappings first, to check if the mapping matches
const applicableMappings : Translation [ ] = Utils . NoNull ( ( this . mappings ? ? [ ] ) ? . map ( mapping = > {
if ( mapping . if === undefined ) {
return mapping . then ;
}
if ( TagUtils . MatchesMultiAnswer ( mapping . if , tags ) ) {
2021-06-22 00:29:07 +02:00
if ( ! freeformKeyUsed ) {
if ( mapping . if . usedKeys ( ) . indexOf ( this . freeform . key ) >= 0 ) {
2021-06-14 02:39:23 +02:00
// This mapping matches the freeform key - we mark the freeform key to be ignored!
freeformKeyUsed = true ;
}
}
return mapping . then ;
}
return undefined ;
} ) )
2021-06-22 00:29:07 +02:00
2021-06-14 02:39:23 +02:00
if ( ! freeformKeyUsed
&& tags [ this . freeform . key ] !== undefined ) {
applicableMappings . push ( this . render )
}
return applicableMappings
}
2021-06-22 00:29:07 +02: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
* /
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 ;
}
2021-04-09 02:57:06 +02:00
public ExtractImages ( isIcon : boolean ) : Set < string > {
2021-06-22 00:29:07 +02:00
2021-04-09 02:57:06 +02:00
const usedIcons = new Set < string > ( )
this . render ? . ExtractImages ( isIcon ) ? . forEach ( usedIcons . add , usedIcons )
for ( const mapping of this . mappings ? ? [ ] ) {
mapping . then . ExtractImages ( isIcon ) . forEach ( usedIcons . add , usedIcons )
}
2021-06-22 00:29:07 +02:00
2021-04-09 02:57:06 +02:00
return usedIcons ;
}
2020-10-27 01:01:34 +01:00
2021-08-07 23:11:34 +02:00
2020-10-27 01:01:34 +01:00
}